11

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?

Consider a WCF service with the following constructor:

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).

Dependencies are registered in the global.asax.cs of the service, i.e.:

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. 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:

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

Effectively I am trying to test:

Given the InformationService is running

When I call the GetSomeInformation() method

And The InformationRetriever cannot be instantiated

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.

David Sette
  • 733
  • 1
  • 7
  • 13

2 Answers2

11

Objects written in the DI style generally pass through two separate phases: composition and execution. 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. 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.

I suggest striving to do the validation when calling on InformationRetriever; this removes the Autofac exception and allows InformationService to handle the exceptional situation without any trickery.

One potential downside of this approach is that the validation will happen on every call to InformationRetriever, rather than once in the constructor. 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.

If you choose #2, you can keep InformationRetriever clean by using a decorator to wrap it in a validating version of the same interface:

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:

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

builder.RegisterDecorator<IInformationRetriever>(
    (c, inner) => new ValidatingInformationRetriever(inner),
    fromKey: "base");
Bryan Watts
  • 44,911
  • 16
  • 83
  • 88
  • Bryan, thanks for your well-thought out response. I completely understand the point your are making about segregating the composition and execution phases. I believe that in this particular case though it is actually the composition phase that is failiing. I look at it like the InformationRetriever needs something else to be there in order to be able to do its job. If that is not there then it can't do anything. However, I will take a closer look at the options you mention. – David Sette Jul 19 '12 at 09:07
9

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. Another idea might be to do:

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

and try/catch around usages inside InformationService. Another option you could go with if the availability of InformationRetriever is known at app startup:

// 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?

Jim Bolla
  • 8,265
  • 36
  • 54
  • Jim, thanks for your suggestions and sorry for the slow reponse (I've been away a couple of days). I will try them out (favouring the Lazy option at the moment) and will report back here with my findings – David Sette Jul 16 '12 at 19:56
  • I can approve that "optional dependency" approach worked for me. Thanks! – Sam Sep 04 '20 at 14:34