Search code examples
.netautofacdefault-value

How to make an optional dependency in AutoFac?


I've got an interface, implementation, and target:

public interface IPerson { public string Name { get; } }
public class Person: IPerson { public string Name { get { return "John"; } } }
public class Target { public Target(IPerson person) {} }

I'm using Autofac to tie things together:

builder.RegisterType<Person>().As<IPerson>().SingleInstance();

The problem is that IPerson lives in a shared assembly, Person lives in a plugin (which may or may not be there), and Target lives in the main application that loads plugins. If there are no plugins loaded that implement IPerson, Autofac goes ballistic about not being able to resolve Target's dependencies. And I cannot really blame it for that.

However I know that Target is able to handle the lack of an IPerson and would be more than happy to get a null instead. In fact, I'm pretty sure that all the components which rely on an IPerson are prepared to take a null it its stead. So how can I tell Autofac - "It's OK sweety, don't worry, just give me back a null, alright?"

One way I found is to add a default parameter to Target:

public class Target { public Target(IPerson person = null) {} }

That works, but then I need to do this for all the components that require an IPerson. Can I also do it the other way round? Somehow tell Autofac "If all else fails for resolving IPerson, return null"?


Solution

  • You can use this syntax :

      builder.RegisterType<Target>().WithParameter(TypedParameter.From<IPerson>(null));
    

    Unfortunately

      builder.Register(c => (IPerson)null).As<IPerson>();
      // will throw : Autofac.Core.DependencyResolutionException: A delegate registered to create instances of 'ConsoleApplication17.Program+IPerson' returned null.
    

    and

      builder.RegisterInstance<IPerson>(null).As<IPerson>();
      // will throw : Unhandled Exception: System.ArgumentNullException: Value cannot be null.
    

    If you don't want to add a WithParameter for each registration, you can add a module that will do it for you

    public class OptionalAutowiringModule : Autofac.Module
    {
        public OptionalAutowiringModule(IEnumerable<Type> optionalTypes)
        {
            this._optionalTypes = optionalTypes;
        }
        public OptionalAutowiringModule(params Type[] optionalTypes)
        {
            this._optionalTypes = optionalTypes;
        }
    
    
        private readonly IEnumerable<Type> _optionalTypes;
    
    
        protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
        {
            base.AttachToComponentRegistration(componentRegistry, registration);
    
            registration.Preparing += (sender, e) =>
            {
                e.Parameters = e.Parameters.Concat(new Parameter[] { new OptionalAutowiringParameter(this._optionalTypes) });
            };
        }
    }
    public class OptionalAutowiringParameter : Parameter
    {
        public OptionalAutowiringParameter(IEnumerable<Type> optionalTypes)
        {
            this._optionalTypes = optionalTypes.ToList();
        }
    
    
        private readonly List<Type> _optionalTypes;
    
    
        public override Boolean CanSupplyValue(ParameterInfo pi, IComponentContext context, out Func<Object> valueProvider)
        {
            if (this._optionalTypes.Contains(pi.ParameterType) && !context.IsRegistered(pi.ParameterType))
            {
                valueProvider = () => null;
                return true;
            }
            else
            {
                valueProvider = null;
                return false;
            }
        }
    }
    

    Then, all you have to do is to register your module with your optional dependencies

    builder.RegisterModule(new OptionalAutowiringModule(typeof(IPerson)));
    

    But instead of injecting a null reference which may cause a nullReferenceException. An another solution would be to create NullPerson implementation.

      builder.RegisterType<NullPerson>().As<IPerson>();
      builder.RegisterType<Target>();
    

    When you have your real implementation only registered it again, it will overwrite the original implementation.