如何在使用Autofac WcfIntegration时处理构造函数异常

Dav*_*tte 7 c# inversion-of-control autofac

有没有办法处理WCF服务的构造函数抛出的异常,当该构造函数接受依赖时,它是IoC容器(在本例中为AutoFac)导致异常的依赖实例化

考虑具有以下构造函数的WCF服务:

public InformationService(IInformationRetriever informationRetriever)
{
    _informationRetriever = informationRetriever;
}
//... the service later makes use of the injected InformationRetriever
Run Code Online (Sandbox Code Playgroud)

该服务使用AutoFac WcfIntegration和AutofacWebServiceHostFactory(这恰好是一个RESTful服务).

依赖关系在服务的global.asax.cs中注册,即:

builder.RegisterType<InformationRetriever>()
                .As<IInformationRetriever>()
Run Code Online (Sandbox Code Playgroud)

现在,InformationRetriever实现在其构造函数中执行一些检查,以确保一切就绪,以便能够完成其工作.当它在此阶段发现问题时,它会抛出异常.

但是,我不希望服务的调用者接收AutoFac异常:

An exception was thrown while invoking the constructor ... on type InformationRetriever

我有效地试图测试:

鉴于 InformationService正在运行

我调用GetSomeInformation()方法时

并且无法实例化InformationRetriever

然后我想返回一个友好的错误消息

记录实际的异常

这是我的设计问题,还是有一个已知的模式来克服或防止这个问题?

我一直在寻找,无法找到有关此类问题的任何信息.

Bry*_*tts 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()
    {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以使用Autofac的装饰器支持注册它,如下所示:

builder
    .RegisterType<InformationRetriever>()
    .Named<IInformationRetriever>("base");

builder.RegisterDecorator<IInformationRetriever>(
    (c, inner) => new ValidatingInformationRetriever(inner),
    fromKey: "base");
Run Code Online (Sandbox Code Playgroud)


Jim*_*lla 5

我不是构造函数的忠实粉丝,因为除了错误的参数之外的其他原因抛出异常.我可能会以不同的方式为我的类型建模.但这里有一些想法.起初我想过做这样的事情:

builder
    .Register(c => {
        try
        {
            return new InformationRetriever();
        }
        catch (Exception)
        {
            return new FailoverInformationRetreiver();
        }})
    .As<IInformationRetriever>();
Run Code Online (Sandbox Code Playgroud)

... FailoverInformationRetreiver在成员访问时抛出异常.另一个想法可能是:

public InformationService(Lazy<IInformationRetriever> informationRetriever)
{
    _informationRetriever = informationRetriever;
}
Run Code Online (Sandbox Code Playgroud)

try/catch周围的用法内InformationService.如果InformationRetriever在app启动时已知可用性,您可以选择另一个选项:

// 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;
}
Run Code Online (Sandbox Code Playgroud)

这些想法有帮助吗?