10

有没有办法处理 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 不能被实例化

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

记录实际的异常

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

我四处寻找,找不到有关此类问题的任何信息。

4

2 回答 2

11

以 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 的装饰器支持来注册它,如下所示:

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

builder.RegisterDecorator<IInformationRetriever>(
    (c, inner) => new ValidatingInformationRetriever(inner),
    fromKey: "base");
于 2012-07-16T15:13:56.230 回答
9

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

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

...FailoverInformationRetreiver在成员访问中引发异常的地方。另一个想法可能是:

public InformationService(Lazy<IInformationRetriever> informationRetriever)
{
    _informationRetriever = informationRetriever;
}

以及try/catch里面的用法InformationServiceInformationRetriever如果在应用程序启动时知道可用性,您可以选择另一个选项:

// 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;
}

这些想法有帮助吗?

于 2012-07-14T19:25:38.230 回答