【问题标题】:How to make an optional dependency in AutoFac?如何在 AutoFac 中创建可选依赖项?
【发布时间】:2015-04-09 12:21:06
【问题描述】:

我有一个接口、实现和目标:

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

【问题讨论】:

  • 另见ResolveOptional()
  • 如果Person 存在于插件中,如何注册?这个电话去哪儿了? builder.RegisterType&lt;Person&gt;().As&lt;IPerson&gt;().SingleInstance();
  • @SriramSakthivel - 每个插件都有一个由主应用程序调用的Initialize(ContainerBuilder) 方法。插件在那里注册它们的组件。

标签: .net autofac default-value


【解决方案1】:

你可以使用这个语法:

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

当您的真正实现只再次注册它时,它将覆盖原始实现。

【讨论】:

  • 嗯,第一个选项并不比在参数声明中指定= null 好多少。事实上,它甚至更长。 NullPerson 是一种解决方案,但很尴尬。
  • 我更新了我的示例以避免使用模块为每个注册注册一个参数
  • OK,自定义注册源。好吧,我想那是最好的方法。谢谢! :)
【解决方案2】:

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

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

【讨论】:

  • 这不起作用。 (尝试使用我需要在我的项目中使用的builder.Register(c =&gt; (IPerson)null).As&lt;IPerson&gt;(); 之类的方法。)-1
  • @AlKepp,缺少的依赖项是一个根本没有注册的依赖项。您正在做的是试图强制 Autofac 返回一个空值作为依赖项,但它不会这样做。这种注册形式总是会抛出异常。这不是 Yaser 的错。
【解决方案3】:

可以只在构造函数中获取对IPerson person = null 的依赖,这是可选IPerson 依赖的隐式声明(参见@YaserMoradi)。然而,这让你不得不在现在和以后巩固这一点:

“...我很确定所有依赖 IPerson 的组件都准备好用 null 代替它。”

最好这根本不是问题。

best practice”模式(@CyrilDurand 在他的回答中作为后缀给出)是使用default implementation(链接结果:Autofac 将使用最后注册的组件作为该服务的默认提供者) .如果您的插件没有其他实现(在默认值之后注册),则将使用此默认值。

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

【讨论】:

  • 我尝试了= null 方法,但它不起作用。无论= null 是否存在,它仍然会引发相同的异常。
  • 如果确实是 Autofac 在解决时引发了异常,那么还有其他事情正在发生。但即便如此,更好的解决方案是使用“特殊情况”默认实现,如答案的后半部分所述。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多