简体   繁体   English

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

[英]How to handle constructor exception when using Autofac WcfIntegration

Is there a way to handle an exception thrown by the constructor of a WCF service, when that constructor takes in a dependency, and it is the instantiation of the dependency by the IoC container (AutoFac in this case) that causes the exception ? 有没有办法处理WCF服务的构造函数抛出的异常,当该构造函数接受依赖时, 它是IoC容器(在本例中为AutoFac)导致异常的依赖实例化

Consider a WCF service with the following constructor: 考虑具有以下构造函数的WCF服务:

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

The service uses AutoFac WcfIntegration and the AutofacWebServiceHostFactory (this happens to be a RESTful service). 该服务使用AutoFac WcfIntegration和AutofacWebServiceHostFactory (这恰好是一个RESTful服务)。

Dependencies are registered in the global.asax.cs of the service, ie: 依赖关系在服务的global.asax.cs中注册,即:

builder.RegisterType<InformationRetriever>()
                .As<IInformationRetriever>()

Now the InformationRetriever implementation performs some checks in its constructor to ensure everything is in place for it to be able to do its job. 现在, InformationRetriever实现在其构造函数中执行一些检查,以确保一切就绪,以便能够完成其工作。 When it discovers a problem in this phase, it throws an exception. 当它在此阶段发现问题时,它会抛出异常。

However, I do not want the caller of the service to receive the AutoFac exception: 但是,我不希望服务的调用者接收AutoFac异常:

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

Effectively I am trying to test: 我有效地试图测试:

Given the InformationService is running 鉴于 InformationService正在运行

When I call the GetSomeInformation() method 我调用GetSomeInformation()方法时

And The InformationRetriever cannot be instantiated 并且无法实例化InformationRetriever

Then I want to return a friendly error message 然后我想返回一个友好的错误消息

And Log the actual exception 记录实际的异常

Is this a problem with my design, or is there a known pattern to overcome or prevent this problem? 这是我的设计问题,还是有一个已知的模式来克服或防止这个问题?

I have hunted around and could not find any information on this type of problem. 我一直在寻找,无法找到有关此类问题的任何信息。

Objects written in the DI style generally pass through two separate phases: composition and execution. 以DI风格编写的对象通常通过两个独立的阶段:组合和执行。 The composition phase is where you wire up dependencies and do things like throw argument exceptions. 组合阶段是连接依赖项并执行抛出参数异常等操作的地方。 You generally want to keep this phase free of meaningful behavior, as that allows you to surface errors in the configuration of your system. 您通常希望保持此阶段没有有意义的行为,因为这样可以在系统配置中显示错误。 The second phase, execution, is where you use the output of the first phase (dependencies) to do your work. 第二阶段是执行,您可以使用第一阶段(依赖项)的输出来完成工作。

Separating these two phases removes a lot of ambiguity and complexity. 分离这两个阶段消除了很多模糊性和复杂性。 As an example, you don't try to mow your lawn while gassing up your lawnmower; 举个例子,你不要试图在给割草机放气的同时割草坪; that causes both activities to become more complex (and dangerous!) 这导致两个活动变得更加复杂(并且危险!)

In this case, InformationRetriever is conflating the composition and execution phases by performing meaningful work in its constructor. 在这种情况下, InformationRetriever通过在其构造函数中执行有意义的工作来混合组合和执行阶段。 This mixing is causing exactly the issue you are trying to avoid: a meaningful business exception being wrapped in a composition exception. 这种混合正是导致您试图避免的问题:一个有意义的业务异常被包装在一个组合异常中。 It is also unclear how to handle the exception, since the top-level invoker is Autofac and not the component which is actually asking InformationRetriever to do work. 还不清楚如何处理异常,因为顶级调用程序是Autofac而不是实际要求InformationRetriever执行工作的组件。

I suggest striving to do the validation when calling on InformationRetriever ; 我建议在调用InformationRetriever时努力进行验证; this removes the Autofac exception and allows InformationService to handle the exceptional situation without any trickery. 这将删除Autofac异常,并允许InformationService在没有任何欺骗的情况下处理异常情况。

One potential downside of this approach is that the validation will happen on every call to InformationRetriever , rather than once in the constructor. 这种方法的一个潜在缺点是验证将在每次调用InformationRetriever时发生,而不是在构造函数中发生一次。 You have two choices: 1) Let it happen every time, to be absolutely sure the work is valid to do, or 2) Keep track of whether you've done the check and only do it if you haven't before. 你有两个选择:1)让它每次都发生,绝对确定工作是否有效,或者2)跟踪你是否已经完成检查,只有你以前没有做过。

If you choose #2, you can keep InformationRetriever clean by using a decorator to wrap it in a validating version of the same interface: 如果选择#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()
    {
        // ...
    }
}

You can register it using Autofac's decorator support like so: 您可以使用Autofac的装饰器支持注册它,如下所示:

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

builder.RegisterDecorator<IInformationRetriever>(
    (c, inner) => new ValidatingInformationRetriever(inner),
    fromKey: "base");

I'm not a big fan of constructors throwing exceptions for reasons other than bad arguments. 我不是构造函数的忠实粉丝,因为除了错误的参数之外的其他原因抛出异常。 I'd probably model my types a different way. 我可能会以不同的方式为我的类型建模。 But here's some ideas. 但这里有一些想法。 At first I thought about doing something like this: 起初我想过做这样的事情:

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

... where FailoverInformationRetreiver throws exceptions on member access. ...其中FailoverInformationRetreiver在成员访问时抛出异常。 Another idea might be to do: 另一个想法可能是:

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

and try/catch around usages inside InformationService . try/catch InformationService用法。 Another option you could go with if the availability of InformationRetriever is known at app startup: 如果在应用启动时已知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;
}

Do any of those ideas help? 这些想法有帮助吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM