是否有办法处理WCF服务的构造函数抛出的异常,当该构造函数接受依赖项时,它是IoC容器(在本例中为AutoFac)的依赖实例化导致异常?
考虑使用以下构造函数的WCF服务:
public InformationService(IInformationRetriever informationRetriever)
{
_informationRetriever = informationRetriever;
}
//... the service later makes use of the injected InformationRetriever
该服务使用AutoFac WcfIntegration和AutofacWebServiceHostFactory
(这恰好是RESTful服务)。
依赖关系在服务的global.asax.cs中注册,即:
builder.RegisterType<InformationRetriever>()
.As<IInformationRetriever>()
现在InformationRetriever
实现在其构造函数中执行一些检查,以确保一切就绪,以便能够完成其工作。当它在此阶段发现问题时,会引发异常。
但是,我不希望服务的调用者接收AutoFac异常:
An exception was thrown while invoking the constructor ... on type InformationRetriever
实际上我正在尝试测试:
鉴于,InformationService正在运行
何时我调用了GetSomeInformation()方法
无法实例化InformationRetriever
然后我想返回友好的错误消息
和记录实际异常
这是我设计的问题,还是有一种已知的模式可以克服或阻止这个问题?
我一直在寻找,但无法找到有关此类问题的任何信息。
答案 0 :(得分:9)
以DI风格编写的对象通常会通过两个独立的阶段:组合和执行。组合阶段是连接依赖项并执行抛出参数异常等操作的地方。您通常希望保持此阶段没有有意义的行为,因为这样可以在系统的配置中显示错误。第二阶段是执行,您可以使用第一阶段(依赖项)的输出来完成工作。
分离这两个阶段消除了很多模糊性和复杂性。举个例子,你不要试图在给割草机放气的同时割草坪;这导致两个活动变得更加复杂(并且危险!)
在这种情况下,InformationRetriever
通过在构造函数中执行有意义的工作来混淆组合和执行阶段。这种混合正是导致您试图避免的问题:一个有意义的业务异常被包装在一个组合异常中。还不清楚如何处理异常,因为顶级调用程序是Autofac而不是实际要求InformationRetriever
执行工作的组件。
我建议在致电InformationRetriever
时努力进行验证;这将删除Autofac异常并允许InformationService
在没有任何欺骗的情况下处理异常情况。
这种方法的一个潜在缺点是验证将在每次调用InformationRetriever
时发生,而不是在构造函数中调用一次。你有两个选择:1)让它每次都发生,绝对确定工作是否有效,或者2)跟踪你是否已经完成了检查,只有你以前没有做过。
如果选择#2,可以使用装饰器将其包装在同一界面的验证版本中,以保持InformationRetriever
清洁:
public class ValidatingInformationRetriever : IInformationRetriever
{
private readonly IInformationRetriever _baseRetriever;
private bool _validated;
public ValidatingInformationRetriever(IInformationRetriever baseRetriever)
{
_baseRetriever = baseRetriever;
}
public void Foo()
{
if(!_validated)
{
Validate();
_validated = true;
}
_baseRetriever.Foo();
}
private void Validate()
{
// ...
}
}
您可以使用Autofac's decorator support注册,如下所示:
builder
.RegisterType<InformationRetriever>()
.Named<IInformationRetriever>("base");
builder.RegisterDecorator<IInformationRetriever>(
(c, inner) => new ValidatingInformationRetriever(inner),
fromKey: "base");
答案 1 :(得分:5)
我不是构造函数的忠实粉丝,因为错误的参数以外的原因抛出异常。我可能会以不同的方式为我的类型建模。但这里有一些想法。起初我想过做这样的事情:
builder
.Register(c => {
try
{
return new InformationRetriever();
}
catch (Exception)
{
return new FailoverInformationRetreiver();
}})
.As<IInformationRetriever>();
...其中FailoverInformationRetreiver
抛出成员访问的异常。另一个想法可能是:
public InformationService(Lazy<IInformationRetriever> informationRetriever)
{
_informationRetriever = informationRetriever;
}
和try/catch
围绕InformationService
内的用法。如果在app启动时知道InformationRetriever
的可用性,您可以选择另一个选项:
// inside your container builder:
if (InformationRetreiver.IsAvailable())
builder.RegisterType<InformationRetriever>()
.As<IInformationRetriever>()
// inside InformationService, make the dependency optional
public InformationService(IInformationRetriever informationRetriever = null)
{
_informationRetriever = informationRetriever;
}
这些想法有帮助吗?