我有一个界面,实现和目标:
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”?
答案 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>());