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