14

In my WCF service I have a custom message inspector for validating incoming messages as raw XML against an XML Schema. The message inspector has a few dependencies that it takes (such as a logger and the XML schema collection). My question is, can I use a Dependency Injection framework (I'm using Ninject at the moment) to instantiate these custom behaviours and automatically inject the dependencies?

I've made a simple example demonstrating the concept:

using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using Ninject.Extensions.Logging;

public class LogMessageInspector : IDispatchMessageInspector
{
    private readonly ILogger log;

    public LogMessageInspector(ILogger log)
    {
        this.log = log;
    }

    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        LogMessage(ref request);
        return null;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
        LogMessage(ref reply);
    }

    private void LogMessage(ref Message message)
    {
        //... copy the message and log using this.log ...
    }
}

public class LogMessageBehavior : IEndpointBehavior
{
    private readonly IDispatchMessageInspector inspector;

    public LogMessageBehavior(IDispatchMessageInspector inspector)
    {
        this.inspector = inspector;
    }

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this.inspector);
    }

    public void Validate(ServiceEndpoint endpoint) { }
}

How can I go about injecting an ILogger into LogMessageInspector and a LogMessageInspector into LogMessageBehavior?

Second question, is this overkill?

Edit: I can get this to work if I build my service in code because I create the behaviour using Ninject. However, when configuring the service via config, I need to add an additional class that extends BehaviorExtensionElement. This class is created by WCF and I can't seem to find a way to cause that to be created by Ninject instead. Configured in code:

static void Main(string[] args)
{
    using (IKernel kernel = new StandardKernel())
    {
        kernel.Bind<IEchoService>().To<EchoService>();
        kernel.Bind<LogMessageInspector>().ToSelf();
        kernel.Bind<LogMessageBehavior>().ToSelf();

        NinjectServiceHost<EchoService> host = kernel.Get<NinjectServiceHost<EchoService>>();
        ServiceEndpoint endpoint = host.AddServiceEndpoint(
            typeof(IEchoService),
            new NetNamedPipeBinding(),
            "net.pipe://localhost/EchoService"
        );
        endpoint.Behaviors.Add(kernel.Get<LogMessageBehavior>());

        host.Open();

        Console.WriteLine("Server started, press enter to exit");
        Console.ReadLine();
    }
}

This works fine, but I don't know how to create the behaviour when configured via my app.config:

<system.serviceModel>
    <services>
        <service name="Service.EchoService">
            <endpoint address="net.pipe://localhost/EchoService" 
                      binding="netNamedPipeBinding"
                      contract="Contracts.IEchoService" 
                      behaviorConfiguration="LogBehaviour"
            />
        </service>
    </services>
    <extensions>
        <behaviorExtensions>
            <add name="logMessages" type="Service.LogMessagesExtensionElement, Service" />
        </behaviorExtensions>
    </extensions>
    <behaviors>
        <endpointBehaviors>
            <behavior name="LogBehaviour">
                <logMessages />
            </behavior>
        </endpointBehaviors>
    </behaviors>
</system.serviceModel>
public class LogMessagesExtensionElement : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get { return typeof(LogMessageBehavior); }
    }

    protected override object CreateBehavior()
    {
        //how do I create an instance using the IoC container here?
    }
}
Adam Rodger
  • 3,472
  • 4
  • 33
  • 45

3 Answers3

4

How can I go about injecting an ILogger into LogMessageInspector and a LogMessageInspector into LogMessageBehavior?

The approach has been described here

UPDATE

Please correct me if I'm wrong but I guess the question boils down to how could you get an instance of Ninject kernel in BehaviorExtensionElement.CreateBehavior? The answer depends on your hosting scenario. If hosted under IIS you could add something like this to your NinjectWebCommon:

public static StandardKernel Kernel
{
    get { return (StandardKernel)bootstrapper.Kernel; }
}

Since you seem to be self-hosting, you might want to go for a static instance of the kernel too. In my view, however, this is not a terribly good idea.

I'd actually vote for your own approach and configure the behavior programmatically unless BehaviorExtensionElement is necessary because you need to be able to configure the behavior through the config file.

is this overkill?

It depends, but definitely not if you're going to unit test the implementation.

Community
  • 1
  • 1
Serge Belov
  • 5,633
  • 1
  • 31
  • 40
  • The linked question certainly demonstrates the concept of using DI with WCF, which Ninject takes care of for me. I probably wasn't specific enough in my question, so I've added an edit. What I'm trying to do is work out a way how I can get Ninject to create the custom endpoint behaviours on the fly when configured via config. – Adam Rodger Nov 24 '12 at 19:21
  • @AdamRodger Sorry, I mis-read your question. I've updated the answer but essentially I think creating the behavior in code is a better alternative. – Serge Belov Nov 25 '12 at 08:56
  • That's OK. I'm favouring adding this in code as well, but in the real service I won't be self-hosting so I'll look into adding behaviours in code when hosting in IIS and report back. – Adam Rodger Nov 26 '12 at 10:38
  • 1
    I've had to add a static reference to my kernel and only use that in the `BehaviorExtensionElement` so that I can configure via config. I know this is the Common Service Locator pattern, but I don't think I can avoid it. I looked into overriding `NinjectServiceHostFactory.CreateServiceHost()` so that I could add the behaviour in code but even that doesn't give me access to the kernel because it's private, so I was a bit stuck. – Adam Rodger Nov 26 '12 at 13:47
1

Instead of validation raw XML against an XML schema, why not take a more object oriented approach? You could for instance model each operation as a single message (a DTO) and hide the actual logic behind a generic interface. So instead of having a WCF service which contains a MoveCustomer(int customerId, Address address) method, there would be a MoveCustomerCommand { CustomerId, Address } class and the actual logic would be implemented by a class that implements the ICommandHandler<MoveCustomerCommand> interface with a single Handle(TCommand) method.

This design gives the following advantages:

  • Every operation in the system gets its own class (SRP)
  • Those message/command objects will get the WCF contract
  • The WCF service will contain just a single service class with a single method. This leads to a WCF service that is highly maintainable.
  • Allows adding cross-cutting concerns by implementing decorators for the ICommandHandler<T> interface (OCP)
  • Allows validation to be placed on the message/command objects (using attributes for instance) and allows this validation to be added again by using decorators.

When you apply a design based around a single generic ICommandHandler<TCommand> interface, its very easy to create generic decorators that can be applied to all implementations. Some decorators might only be needed to be applied when running inside a WCF service, others (like validation) might be needed for other application types as well.

A message could be defined as follows:

public class MoveCustomerCommand
{
    [Range(1, Int32.MaxValue)]
    public int CustomerId { get; set; }

    [Required]
    [ObjectValidator]
    public Address NewAddress { get; set; }
}

This message defines an operation that will move the customer with CustomerId to the supplied NewAddress. The attributes define what state is valid. With this we can simply do object based validation using .NET DataAnnotations or Enterprise Library Validation Application Block. This is much nicer than having to write XSD based XML validations which are quite unmaintainable. And this is much nicer than having to do complex WCF configurations as you are currently trying to solve. And instead of baking this validation inside the WCF service, we can simply define a decorator class that ensures every command is validated as follows:

public class ValidationCommandHandlerDecorator<TCommand>
    : ICommandHandler<TCommand>
{
    private ICommandHandler<TCommand> decoratedHandler;

    public ValidationCommandHandlerDecorator(
        ICommandHandler<TCommand> decoratedHandler)
    {
        this.decoratedHandler = decoratedHandler;
    }

    public void Handle(TCommand command)
    {
        // Throws a ValidationException if invalid.
        Validator.Validate(command);

        this.decoratedHandler.Handle(command);
    }
}

This ValidationCommandHandlerDecorator<T> decorator can be used by any type of application; not only WCF. Since WCF will by default not handle any thrown ValidationException, you might define a special decorator for WCF:

public class WcfFaultsCommandHandlerDecorator<TCommand>
    : ICommandHandler<TCommand>
{
    private ICommandHandler<TCommand> decoratedHandler;

    public WcfFaultsCommandHandlerDecorator(
        ICommandHandler<TCommand> decoratedHandler)
    {
        this.decoratedHandler = decoratedHandler;
    }

    public void Handle(TCommand command)
    {
        try
        {
            this.decoratedHandler.Handle(command);
        }
        catch (ValidationException ex)
        {
            // Allows WCF to communicate the validation 
            // exception back to the client.
            throw new FaultException<ValidationResults>(
                ex.ValidationResults);
        }
    }
}

Without using a DI container, a new command handler could be created as follows:

ICommandHandler<MoveCustomerCommand> handler = 
    new WcfFaultsCommandHandlerDecorator<MoveCustomerCommand>(
        new ValidationCommandHandlerDecorator<MoveCustomerCommand>(
            // the real thing
            new MoveCustomerCommandHandler()
        )
    );

handler.Handle(command);

If you want to know more about this type of design, read the following articles:

Steven
  • 166,672
  • 24
  • 332
  • 435
  • This is certainly a very interesting approach, but unfortunately this is an existing service with thousands of clients that I can't change and the schema was defined by a third party. I'm refactoring the service, including adding dependency injection, and this is the only bit I'm not sure how to refactor. – Adam Rodger Nov 26 '12 at 10:36
-1

Try having your LogMessageBehavior also use BehaviorExtensionElement as its base class, then you should be able to do the following:

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

protected override object CreateBehavior()
{
    return this;
}
Kwal
  • 1,531
  • 7
  • 11