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

How to make an optional dependency in AutoFac?

我有接口、实现和目标:

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 - "It's OK sweety, don't worry, just give me back a null, alright?"

我发现的一种方法是将默认参数添加到 Target:

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

可行,但我需要对所有需要 IPerson 的组件执行此操作。我也可以反过来做吗?以某种方式告诉 Autofac "If all else fails for resolving IPerson, return null"?

您可以使用以下语法:

  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 的空引用。另一种解决方案是创建 NullPerson 实现。

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

当您的真正实现只重新注册它时,它将覆盖原来的实现。

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

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

可以 只在你的构造函数中获取对 IPerson person = null 的依赖,这是一个可选的 IPerson 依赖的隐式声明(c.f.@YaserMoradi)。但是,这使您处于必须巩固这一点的位置,现在和以后永远:

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

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

best practice" pattern (which @CyrilDurand gives as a suffix on his answer) for this is to use a default implementation(link 结果:Autofac 将使用最后注册的组件作为该服务的默认提供者)。如果您的插件没有其他实现(在默认)将使用此默认值。

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

作为 , a default implementation should be considered in the first place. But there are situations where this might not be appropriate for some reason. If IService service = null is not enough in some situation, Adam Storr describes an alternative way 将构造函数参数注释为可选(使用 Autofac 5.x 引入的功能)。

结果可能如下所示:

public class Target { public Target([Optional] IPerson person) {} }

OptionalAttribute 可以动态决定返回什么默认值

请注意,每个受影响的构造函数都需要此属性(这实际上是原始发布者希望避免的事情)。