How to make an optional dependency in AutoFac?

I have an interface, implementation, and purpose:

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 use Autofac to bundle things:

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

The problem is that IPerson lives in the general assembly, Person lives in the plugin (which may or may not be there), and Target lives in the main application that loads the plugins. If there are no plugins loaded that implement IPerson , Autofac ballistic about the inability to resolve Target dependencies. And I can't blame him for that.

However, I know that Target is able to handle the absence of IPerson and will be more than happy to get null . In fact, I'm sure all the components that rely on IPerson are ready to take null its place. So, how can I say Autofac: "It's alright, don’t worry, just give me null , okay?"

One way to find the following parameter: Target :

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

This works, but then I need to do this for all components that require IPerson . Can I also do it the other way around? Somehow say Autofac "If all else fails to resolve IPerson , return null"?

+5
source share
5 answers

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 do not want to add WithParameter for each registration, you can add a module that will do this 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 register your module with your additional dependencies

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

Instead of entering an empty reference, which may throw a nullReferenceException. Another solution is to create a NullPerson implementation.

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

If your real implementation only registered it again, it will overwrite the original implementation.

+3
source

Just use the optional parameters, see the following example:

 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. } } 
+3
source

You can simply take your IPerson person = null dependency in your constructor, which is an implicit declaration of an optional IPerson dependency (cf @YaserMoradi). However, this puts you in a position to consolidate it, now and forever after:

"... I'm pretty sure that all the components that rely on IPerson are ready to take null its place."

It is better that this is not a question at all.

The " best practice " pattern (which @CyrilDurand gives as a suffix in his answer), for this you need to use the default implementation (link above: Autofac will use the last registered component as the default provider of this service). If you do not have another implementation coming from your plugin (registered by default), this value will be used by default.

In your case, the default component should be some kind of non-working or basic version of the IPerson service, where any method called will have everything that makes up the default behavior for your application. This gives a better reuse history, since you can define default behavior once and for all.

+1
source

As a workaround, you can enter a facade similar to Lazy<IPerson> , which will try to resolve it from the container when called.

 class PersonFacade { public PersonFacade(Func<IPerson> func) { _func = func; } public IPerson Value { // will Autofac throw exception if plugin is missing? try { return _func(); } catch { return null; } } } // register me new PersonFacade(() => container.Resolve<IPerson>()); 
0
source

The usual mechanism for declaring optional dependencies is to put them in properties instead of constructor arguments.

0
source

Source: https://habr.com/ru/post/1212983/


All Articles