Search code examples
c#dependency-injectionautomapperautofac

Caching dependencies, or copy them at injection?


I have some configuration objects initialized by Autofac. They are quite heavy to load (read the configuration file, deserialize it, verify some things, map the matching POCO object...).
But performances are fine because the registered object is declared as .SingleInstance(), so it's loaded only once, the first time it is injected somewhere, and after that the same instance is always used.

        builder.Register(c => _settingsReader.LoadSection(thing)) // Read and map object
            .OnActivated(t =>
            {
                // Do some checks on it to see if settings appear to be correct
            })
            .As(settingInterface)
            .SingleInstance();

But, these objects can be modified later (deliberately, or by accident), and as they are singletons the whole application will be affected if it happens.

The ability to be both able to update the initial singleton itself (to globally override defined settings) AND to update the local copy (to change the behavior of the object where the setting class have been injected, but not of the whole application) would also be interesting.

I thought about two possibilities :

  • Ask Autofac to inject "copy" of objects, instead of the same instance, but I don't think there is a such "caching / copy" feature in it and I would lose the abilility to manipulate the singleton, or
  • Use Automapper to deep-copy the object when it's injected, and store the copy in the dependant object instead of the original object. I'm fine with automapper (and the objects are very light so performance would not be an issue), but adding the copy for each injected property will burden either my Autofac configuration, or my objects themselves depending of where I place the "copy this object" code.

To use the Automapper idea, I'd have to do something like :

        builder.RegisterType<MyConsumingClass>()
            .WithParameter(
                new ResolvedParameter(
                    (pi, ctx) => pi.ParameterType == typeof(IMySettingClass),
                    (pi, ctx) =>
                    {
                        var mapper = ctx.Resolve<Mapper>();
                        return mapper.Map<ConcreteSettingClass>(ctx.Resolve<IMySettingClass>());
                    }))
            .As<IMyConsumingClass>();

Instead of just

        builder.RegisterType<MyConsumingClass>().As<IMyConsumingClass>();

(the previous code have not been tested, it's just here for example purpose)

I would really appreciate to have suggestions on best ways to handle this.


Solution

  • As said in comments, due to the lack of other proposals, I finally retained my own initial approach and used Automapper to clone my setting object when a new instance of the consuming class is created. Here's an exemple for a Service and its setting object. If your "setting" object have classes in its properties, remember to clone them also or you will only have a reference copy of the initial singleton ! To avoid this trap you could also use a true "clone library" more suited for this cases than Automapper.

    public class ServiceFacadeModule
        : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            // Modifying the settings on the service instance itself should NOT affect global setting object. To achieve this, settings classes are copied when initializing new service instances
            var config = new MapperConfiguration(cfg =>
            {
                cfg.CreateMap<ServiceSettings, ServiceSettings>();
            });
    
            var mapper = new Mapper(config);
            builder.RegisterType<ServiceDAO>()
                .WithParameter(
                    new ResolvedParameter(
                        (pi, _) => pi.ParameterType == typeof(ISettings),
                        (_, ctx) => mapper.Map<ServiceSettings>(ctx.Resolve<ISettings>())))
                .As<IServiceDAO>()
                .InstancePerLifetimeScope();
        }
    }