Injecting dependencies into an IErrorHandler implementation

726 views Asked by At

I am implementing IErrorHandler in order to centralize all of the error handling for my WCF service in one place. This works fairly well:

public class ServiceErrorHandler : IErrorHandler
{

    public bool HandleError(Exception error)
    {
        // ..Log..
    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {
        // ..Provide fault..
    }

}

Now, we're using Ninject to inject dependencies in the rest of the service, and I'd like to do the same here. Since WCF is constructing the objects based on my configuration, and I don't think I have any hooks into this process, I need to use property injection:

    [Inject]
    public ILoggingService Logger { get; set; }

However, this never seems to get injected. I tried using Ninject's MVC extensions to set ServiceErrorHandler to allow injection like a filter, but that didn't seem to do the trick. Is there a way to make this happen?

1

There are 1 answers

0
voytek On

Late answer, but you can inject dependencies into IErrorHandler by creating your custom ServiceHost, let's say TestServiceHost.

In your TestServiceHost you need to do:

  1. Implement constructor with IErrorHandler parameter.
  2. Inside, create a private nested class named ErrorHandlerBehaviour*, which needs to implement both IServiceBehavior and IErrorHandler. It also must have constructor with IErrorHandler parameter.
  3. Override OnStarting() method, where you will add ErrorHandlerBehaviour to service behaviours. All behaviours must be added before base.OnStarting().

*the idea came from Juval Lowy's example in book - "Programming WCF Services". More information about Faults and Error extensions you can find there.

Here is the working host console application. I don't use IoC there, just Pure DI, but you can easily resolve logger with any IoC you want:

using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace ConsoleHost
{
    class Program
    {
        static void Main(string[] args)
        {
            var logger = new DummyLogger();
            var errorHandler = new TestErrorHandler(logger);

            ServiceHost host = new TestServiceHost(errorHandler, typeof(TestService), new Uri("net.tcp://localhost:8002"));
            host.Open();

            Console.WriteLine("Press enter to exit");
            Console.ReadKey();
        }
    }

    [ServiceContract]
    public interface ITestService
    {
        [OperationContract]
        string Test(int input);
    }

    public class TestService : ITestService
    {
        public string Test(int input)
        {
            throw new Exception("Test exception!");
        }
    }

    public class TestErrorHandler : IErrorHandler
    {
        private ILogger Logger { get; }

        public TestErrorHandler(ILogger logger)
        {
            Logger = logger;
        }

        public bool HandleError(Exception error)
        {
            Logger.Log(error.Message);
            return true;
        }

        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            FaultException fe = new FaultException();
            MessageFault message = fe.CreateMessageFault();
            fault = Message.CreateMessage(version, message, null);
        }
    }

    public class TestServiceHost : ServiceHost
    {
        private readonly IErrorHandler errorHandler;

        public TestServiceHost(IErrorHandler errorHandler, Type serviceType, params Uri[] baseAddresses)
            : base(serviceType, baseAddresses)
        {
            this.errorHandler = errorHandler;
        }

        protected override void OnOpening()
        {
            Description.Behaviors.Add(new ErrorHandlerBehaviour(errorHandler));
            base.OnOpening();
        }

        class ErrorHandlerBehaviour : IServiceBehavior, IErrorHandler
        {
            private readonly IErrorHandler errorHandler;

            public ErrorHandlerBehaviour(IErrorHandler errorHandler)
            {
                this.errorHandler = errorHandler;
            }

            bool IErrorHandler.HandleError(Exception error)
            {
                return errorHandler.HandleError(error);
            }

            void IErrorHandler.ProvideFault(Exception error, MessageVersion version, ref Message fault)
            {
                errorHandler.ProvideFault(error, version, ref fault);
            }

            void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
            {
                foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
                {
                    channelDispatcher.ErrorHandlers.Add(this);
                }
            }

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

            void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
            {
            }
        }
    }

    // Dummy logger
    public interface ILogger
    {
        void Log(string input);
    }

    public class DummyLogger : ILogger
    {
        public void Log(string input) => Console.WriteLine(input);
    }
}

And configuration:

<system.serviceModel>
  <services>
    <service name="ConsoleHost.TestService">
      <endpoint address="net.tcp://localhost:8002/TestService"
                binding="netTcpBinding"
                contract="ConsoleHost.ITestService" />
    </service>
  </services>
</system.serviceModel>

Btw. Make sure you added System.Runtime.Serialization to your references