如何在AutoFac中创建可选的依赖项?

时间:2015-02-09 17:05:31

标签: .net autofac default-value

我有一个界面,实现和目标:

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

我正在使用Autofac将事物联系在一起:

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

问题是IPerson存在于共享程序集中,Person存在于插件中(可能存在也可能不存在),Target存在于加载的主应用程序中插件。如果没有加载实现IPerson的插件,Autofac就无法解决Target的依赖关系而犹豫不决。我不能真的责怪它。

但我知道Target能够处理IPerson的缺失,并且非常乐意获得null。事实上,我很确定依赖于IPerson的所有组件都准备采用null代替它。那么我怎么能告诉Autofac-“这很好,不要担心,只要给我一个null,好吗?”

我找到的一种方法是向Target添加默认参数:

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

这样可行,但我需要为需要IPerson的所有组件执行此操作。我可以反过来做吗?以某种方式告诉Autofac“如果所有其他方法都无法解析IPerson,则返回null”?

4 个答案:

答案 0 :(得分:5)

您可以使用以下语法:

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

不幸的是

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

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

如果您不想为每次注册添加WithParameter,您可以添加一个可以为您执行此操作的模块

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;
        }
    }
}

然后,您所要做的就是使用可选的依赖项注册模块

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

但不是注入可能导致nullReferenceException的null引用。另一种解决方案是创建NullPerson实现。

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

如果您的实际实施仅重新注册,它将覆盖原始实现。

答案 1 :(得分:5)

只需使用可选参数,请参阅以下示例:

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.
     }
}

答案 2 :(得分:1)

只能在构造函数中依赖IPerson person = null,这是可选IPerson依赖项的隐式声明(c.f。@YaserMoradi)。然而,这使你处于必须巩固这一点的地位,现在和将来永远:

  

&#34; ...我非常确定依赖IPerson的所有组件都准备采用null代替它。&#34;

更好的是,这根本不是一个问题。

&#34; best practice&#34;模式(@CyrilDurand作为其答案的后缀)为此使用default implementation(链接结果:Autofac将使用最后注册的组件作为该服务的默认提供者)。如果您没有来自插件的其他实现(在默认情况下注册),则将使用此默认值。

在您的情况下,默认组件应该是IPerson服务的某种无操作或基本实现,其中任何调用的方法都将构成应用程序的默认行为。这也提供了更好的重用故事,因为您可以一劳永逸地定义默认行为。

答案 3 :(得分:-1)

作为一种解决方法,您可以注入类似于Lazy<IPerson>的外观,它将在调用时尝试从容器中解析它。

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>());