Search code examples
c#restwcfautofac

WCF Generic Error Handler with Autofac Injection


I am trying to implement a simple generic error handler in my wcf project but the catch is that i want to inject a service (using autofac) in order to save all exceptions. I ve looked everywhere and I didn't find anything. I am using Autofac with Autofac.Integration.Wcf.

public class GlobalErrorHandler : IErrorHandler
{

    private IErrorService ErrorService;

    public GlobalErrorHandler(IErrorService errorService)
    {
        this.ErrorService = errorService;
    }

    public bool HandleError(Exception error)
    {
        return true;
    }

    public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault)
    {
        //log the error using the service
    }
}

public class ErrorHandlerExtension : BehaviorExtensionElement, IServiceBehavior
{

    public override Type BehaviorType
    {
        get { return GetType(); }
    }

    protected override object CreateBehavior()
    {
        return this;
    }

    private IErrorHandler GetInstance()
    {
        return new GlobalErrorHandler();
    }

    void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
    {
    }

    void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        IErrorHandler errorHandlerInstance = GetInstance();

        foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
        {
            dispatcher.ErrorHandlers.Add(errorHandlerInstance);
        }
    }

    void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (ServiceEndpoint endpoint in serviceDescription.Endpoints)
        {
            if (endpoint.Contract.Name.Equals("IMetadataExchange") &&
                endpoint.Contract.Namespace.Equals("http://schemas.microsoft.com/2006/04/mex"))
                continue;

            foreach (OperationDescription description in endpoint.Contract.Operations)
            {
                if (description.Faults.Count == 0)
                {
                    throw new InvalidOperationException("FaultContractAttribute not found on this method");
                }
            }
        }
    }
}

web.config

<extensions>
  <behaviorExtensions>
    <add name="errorHandler"
          type="Base.WCF.Helpers.Error_Handler.ErrorHandlerExtension, Base.WCF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  </behaviorExtensions>
</extensions>

global.asax

var builder = new ContainerBuilder();
builder.RegisterType<Base.WCF.BaseService>();
builder.RegisterType<ErrorService>().As<IErrorService>();
var IOCContainer = builder.Build();
AutofacHostFactory.Container = IOCContainer;

I cannot find any way which will allow me to inject the service in my IErrorHandler as i cannot resolve its dependencies. The thing is that i have to register the custom IErrorHandler through the ApplyDispatchBehavior. I have also tried constructor injection in my ErrorHandlerExtension but it didn't work either. Also all my other injections in my wcf service methods work fine.

Is there any way i can inject the IErrorService in my IErrorHandler?

EDIT

Based on Travis' answer I also had to resolve my other repository injections so I used the following

        using (var scope = AutofacHostFactory.Container.BeginLifetimeScope())
        {
            var svc = scope.Resolve<IErrorHistoryService>();
            scope.Resolve<IErrorHistoryRepository>();
            scope.Resolve<IUnitOfWork>();
            svc.AddError(new ErrorLog_BO());
            svc.SaveError();
        }

Solution

  • The short version answer here is that there's really no good way to do this.

    WCF has a notoriously bad story around dependency injection. The only way you can even do DI into service instances is because there's a specific IInstanceProvider interface that is used to handle creating a new instance of your service.

    You'll notice in implementing your extension that you end up having to attach an instance of your error handler to the channel rather than having some sort of factory/function available.

    One not-so-great option is to use service location in your IErrorHandler.

    public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault)
    {
      //log the error using the service
      var svc = AutofacHostFactory.Container.Resolve<IErrorService>();
      svc.Log(error); 
    }
    

    There are several reasons this isn't great.

    • It'll come out of the root container, so if anything in the IErrorService is IDisposable you'll get a memory leak because it won't get disposed until the container is disposed.
    • You won't be in the same lifetime scope as your services, so you won't have shared dependencies unless they're singletons.

    You can sort of fix the first issue like this:

    public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault)
    {
      using(var scope = AutofacHostFactory.Container.BeginLifetimeScope())
      {
        var svc = scope.Resolve<IErrorService>();
        svc.Log(error);
      } 
    }
    

    That should at least avoid the memory leak with IDisposable instances... but you'll still have the shared dependency problem. Maybe that doesn't matter. Sort of up to you.

    But... no, most stuff in WCF doesn't run through DI so you're going to be super stuck on this and other similar things.