4

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.

Community
  • 1
  • 1
Sai
  • 629
  • 1
  • 8
  • 26
  • Do your WCF services receive both an ILog and an IErrorHandler via constructor injection, and then the IErrorHandler receives an ILog through property injection? And the ILog via property to IErrorHandler is the one that is failing? – greyalien007 Oct 28 '13 at 14:57
  • I get ILog via constructor injection inside all my hosted services But not inside the IErrorHandler. I just figured out IErrorHandler is not getting injected through castle, this is due to the fact that ServiceBehaviour itself is not injected by Castle. Am self hosting my wcf services so am not using .svc file. Usually in the .svc file we specify the ServiceHostFactory as windor's defaultServiceHostFactory. So I think in my case as there is no .svc file so its not injecting the servicebehaviour. What is the correct process to do this? – Sai Oct 28 '13 at 15:18

1 Answers1

3

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>
greyalien007
  • 510
  • 4
  • 13
  • thanks , this puts me in right direction. But still getting same error while using fluentApi with all binding info in config file. Could you please give example with fluentapi as well. – Sai Oct 29 '13 at 09:57
  • Also if am using above approach, how can I host multiple service endpoints. Do I need to call factory.createHost for every endpoint? on doing that I get an error saying "The value could not be added to the collection, as the collection already contains an item of the same type: CastleCreatedLoggingServiceBehavior" – Sai Oct 29 '13 at 10:03