wcfc#-4.0castle-windsorierrorhandler

Castle Windsor - Injecting logger into IErrorHandler


Am using Castle windsor 3.0 in my Wcf services project. I'm hosting it as windows service project using Topshelf. All my wcf services configuration is on app.config file. I'm using castle wcffacility and registering services like this -

Container.AddFacility<WcfFacility>();
Container.AddFacility<LoggingFacility>(f => f.UseLog4Net());
container.Register(
            Classes
                .FromThisAssembly()
                .Pick()
                .If(x => x.IsClass 
                    && HasServiceContract(x))
                .WithServiceDefaultInterfaces()
                .Configure(c => c.AsWcfService().LifeStyle.HybridPerWcfOperationTransient()));

This injects ILog (from log4net) into my services without any problem but it fails to inject into IErrorHandler.

I have added ServiceBehaviour with IErrorHandler so that I can catch user unhandled exceptions and log the errors using the below code.

 #region IErrorHandler Members
    public ILog Logger { get; set; }
    public bool HandleError(Exception error)
    {
        if (error is FaultException)
            return false; // Let WCF do normal processing

        return true; // Fault message is already generated
    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {

        var uniqueKey = UniqueKeyGenerator.Generate();

        //create the custom wcfexception before passing that to client
        var wcfException = new CustomServerException("Unknown Error has occured. Please contact your administrator!", uniqueKey);

        //log the exception
        Logger.Error(uniqueKey, error);


        MessageFault messageFault = MessageFault.CreateFault(
            new FaultCode("Sender"),
            new FaultReason(wcfException.Message),
            wcfException,
            new NetDataContractSerializer());
        fault = Message.CreateMessage(version, messageFault, null);

    }

I looked at this stackoverflow post but it was too old and no answer was posted so am posting this new question.

Update

I got this resolved (partially for now) with the answer provided by greyAlien. All I had to do was

  1. register custom servicebehavior class to castle windsor.

    Container.Register(
             Component.For<IServiceBehavior>()
             .ImplementedBy<PassThroughExceptionHandlingBehaviour>()
                    .Named("IServiceBehavior")
    
  2. Remove the serviceBehaviour extension from the app.config file. When I add behavior extension in config file, for some reason, castle is not able to inject the dependencies instead I think Wcf is creating new instances and logger public property is turning out to be null.

It works for me now but need to understand (in future) on how to inject dependencies using behaviourextensions as well.


Solution

  • Here's a self hosted sample that does the things I believe you are trying to do. It's a console app. You will need to start visual studio as an administrator in order to get netsh to register localhost:55001

    I'm using castle 3.1.

    The source code file:

    namespace WcfSelfHost
    {
    using System;
    using Castle.Windsor;
    using Castle.Facilities.WcfIntegration;
    using System.ServiceModel;
    using Castle.MicroKernel.Registration;
    using System.ServiceModel.Configuration;
    using System.ServiceModel.Description;
    using Castle.MicroKernel;
    
    public interface ILog
    {
        void LogMessage(string message);
    }
    
    public class ConsoleLogger : ILog
    {
        public void LogMessage(string message)
        {
            Console.WriteLine(message);
        }
    }
    
    [ServiceBehavior]
    public class CastleCreatedLoggingServiceBehavior : IServiceBehavior
    {
        private readonly ILog logger;
    
        public CastleCreatedLoggingServiceBehavior(ILog logger)
        {
            this.logger = logger;
        }
    
        public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
            this.logger.LogMessage("in AddBindingParameters");
        }
    
        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            this.logger.LogMessage("in ApplyDispatchBehavior");
        }
    
        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            this.logger.LogMessage("in Validate");
        }
    }
    
    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        void TheOneOperation(string data);
    }
    
    public class ServiceImplementation : IService
    {
        private readonly ILog logger;
    
        public ServiceImplementation(ILog logger)
        {
            this.logger = logger;
        }
    
        public void TheOneOperation(string data)
        {
            this.logger.LogMessage("service received message:");
            this.logger.LogMessage(data);
        }
    }
    
    public class ConsoleApplication
    {
        public static void Main()
        {
            //making this a variable to show the requirement that the names match
            string serviceName = "TheService";
    
            //configure the container with all the items we need
            IWindsorContainer container = new WindsorContainer()
                .AddFacility<WcfFacility>(f => f.CloseTimeout = TimeSpan.Zero)
                .Register(
                    Component.For<CastleCreatedLoggingServiceBehavior>(),
                    Component.For<ILog>()
                        .ImplementedBy<ConsoleLogger>()
                        .LifestyleSingleton(),
                    Component.For<IService>()
                        .ImplementedBy<ServiceImplementation>()
                        .LifestyleSingleton()
                        .Named(serviceName)
                );
    
            //setup our factory with that has knowledge of our kernel.
            DefaultServiceHostFactory factory = new DefaultServiceHostFactory(container.Kernel);
    
            //create a host for our service matching the name of the castle component.  Not adding any additional base addresses.
            using (ServiceHostBase host = factory.CreateServiceHost(serviceName, new Uri[0]))
            {
                host.Open();
                Console.WriteLine("server listening for messages");
    
    
                //and here's the client..
                IWindsorContainer clientContainer = new WindsorContainer()
                    .AddFacility<WcfFacility>(f => f.CloseTimeout = TimeSpan.Zero)
                    .Register(
                        Component.For<IService>()
                            .AsWcfClient(WcfEndpoint.FromConfiguration("serviceEndpointDefinition"))
                    );
    
                IService client = clientContainer.Resolve<IService>();
                client.TheOneOperation("data from client");
                Console.ReadLine();
    
            }
        }
    }
    }
    

    Here's the app.config file for the console application. We could have used the fluent API to configure all of this in the source code, but separating out the services & client config is pretty normal, so I chose to go config file route. Let me know if you want a c# fluent API version.

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
    
    <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="overrideMessageSize_forBasicHttpBinding" maxBufferPoolSize="2147483647"
                 maxReceivedMessageSize="2147483647"/>
      </basicHttpBinding>
    </bindings>
    
    <services>
      <service name="WcfSelfHost.ServiceImplementation">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:55001/baseaddress"/>
          </baseAddresses>
        </host>
    
        <endpoint 
            contract="WcfSelfHost.IService"
            binding="basicHttpBinding"
            bindingConfiguration="overrideMessageSize_forBasicHttpBinding" 
            address="http://localhost:55001/baseaddress/serviceimplementation"
            />
      </service>
    </services>
    
        <client>
            <endpoint 
        name="serviceEndpointDefinition"
        contract="WcfSelfHost.IService" 
        binding="basicHttpBinding" 
        bindingConfiguration="overrideMessageSize_forBasicHttpBinding"
        address="http://localhost:55001/baseaddress/serviceimplementation"
        />
        </client>
    
    </system.serviceModel>
    </configuration>