Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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"?

like image 958
Vilx- Avatar asked Feb 09 '15 17:02

Vilx-


People also ask

How do I add Autofac references?

Open the solution that want to use Autofac in, then select Manager NuGet Packages for Solution... by going to: Tools -> NuGet Package Manager -> Manager NuGet Packages for Solution... Installing through NuGet will automatically add Autofac in the References of the projects which were selected during installation.

Does Autofac use reflection?

Reflection Components RegisterType(typeof(ConfigReader)); When using reflection-based components, Autofac automatically uses the constructor for your class with the most parameters that are able to be obtained from the container.

Is Autofac A IoC?

Autofac is an IoC container for Microsoft . NET. It manages the dependencies between classes so that applications stay easy to change as they grow in size and complexity.

What is Autofac dependency injection?

Autofac is an open-source dependency injection (DI) or inversion of control (IoC) container developed on Google Code. Autofac differs from many related technologies in that it sticks as close to bare-metal C# programming as possible.


3 Answers

You could just take your dependency on IPerson person = null in your constructor, which is an implicit declaration of an optional IPerson dependency (c.f. @YaserMoradi). However, that puts you in the position of having to solidify this, now and forever after:

"... I'm pretty sure that all the components which rely on an IPerson are prepared to take a null it its stead."

Better that this doesn't have to be a question at all.

The "best practice" pattern (which @CyrilDurand gives as a suffix on his answer) for this is to use a default implementation (link upshot: Autofac will use the last registered component as the default provider of that service). If you have no other implementation coming from your plugin (registered after the default) this default will be used.

In your case, the default component should be some kind of no-op or base implementation of the IPerson service, where any method called will have whatever constitutes the default behavior for your application. This gives a better reuse story as well, since you can define the default behavior once and for all.

like image 106
Marc L. Avatar answered Oct 23 '22 14:10

Marc L.


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.

like image 24
Cyril Durand Avatar answered Oct 23 '22 12:10

Cyril Durand


Just use optional parameters, see the following sample:

public class SomeClass
{
     public SomeClass(ISomeDependency someDependency = null)
     {
           // someDependency will be null in case you've not registered that before, and will be filled whenever you register that.
     }
}
like image 34
Yaser Moradi Avatar answered Oct 23 '22 14:10

Yaser Moradi