简体   繁体   English

WCF自定义行为的依赖注入

[英]Dependency Injection for WCF Custom Behaviors

In my WCF service I have a custom message inspector for validating incoming messages as raw XML against an XML Schema. 在我的WCF服务中,我有一个自定义消息检查器,用于将传入消息验证为XML Schema的原始XML。 The message inspector has a few dependencies that it takes (such as a logger and the XML schema collection). 消息检查器具有一些依赖性(例如记录器和XML架构集合)。 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? 我的问题是,我可以使用依赖注入框架(我目前正在使用Ninject)来实例化这些自定义行为并自动注入依赖项吗?

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 ? 我如何将注册ILogger注入LogMessageInspector并将LogMessageInspector注入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. 编辑 :如果我在代码中构建我的服务,我可以使用它,因为我使用Ninject创建行为。 However, when configuring the service via config, I need to add an additional class that extends BehaviorExtensionElement . 但是,在通过config配置服务时,我需要添加一个扩展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. 这个类是由WCF创建的,我似乎无法找到一种方法来导致由Ninject创建它。 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 : 这工作正常,但我不知道如何通过我的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?
    }
}

How can I go about injecting an ILogger into LogMessageInspector and a LogMessageInspector into LogMessageBehavior? 我如何将注册ILogger注入LogMessageInspector并将LogMessageInspector注入LogMessageBehavior?

The approach has been described here 这里已经描述这种方法

UPDATE 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 ? 如果我错了,请纠正我,但我想这个问题归结为如何在BehaviorExtensionElement.CreateBehavior获得Ninject内核的实例? The answer depends on your hosting scenario. 答案取决于您的托管方案。 If hosted under IIS you could add something like this to your NinjectWebCommon : 如果在IIS下托管,您可以向NinjectWebCommon添加类似这样的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. 我实际上会投票选择您自己的方法并以编程方式配置行为,除非有必要使用BehaviorExtensionElement ,因为您需要能够通过配置文件配置行为。

is this overkill? 这有点过分吗?

It depends, but definitely not if you're going to unit test the implementation. 这取决于,但绝对不是,如果您要对实施进行单元测试。

Instead of validation raw XML against an XML schema, why not take a more object oriented approach? 为什么不采用更面向对象的方法,而不是针对XML模式验证原始XML? You could for instance model each operation as a single message (a DTO ) and hide the actual logic behind a generic interface. 例如,您可以将每个操作建模为单个消息( DTO )并隐藏通用接口背后的实际逻辑。 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. 因此,不是拥有包含MoveCustomer(int customerId, Address address)方法的WCF服务,而是存在MoveCustomerCommand { CustomerId, Address }类,实际逻辑将由实现ICommandHandler<MoveCustomerCommand>接口的类实现。单个Handle(TCommand)方法。

This design gives the following advantages: 这种设计具有以下优点:

  • Every operation in the system gets its own class ( SRP ) 系统中的每个操作都有自己的类( SRP
  • Those message/command objects will get the WCF contract 那些消息/命令对象将获得WCF合同
  • The WCF service will contain just a single service class with a single method. WCF服务将只包含一个具有单个方法的服务类。 This leads to a WCF service that is highly maintainable. 这导致高度可维护的WCF服务。
  • Allows adding cross-cutting concerns by implementing decorators for the ICommandHandler<T> interface ( OCP ) 允许通过为ICommandHandler<T>接口( 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. 当您应用基于单个通用ICommandHandler<TCommand>接口的设计时,很容易创建可应用于所有实现的通用装饰器。 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. 在WCF服务内部运行时可能只需要应用一些装饰器,其他应用程序类型也可能需要其他装饰器(如验证)。

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 . 此消息定义了一个操作,该操作将CustomerId的客户移动到提供的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. 有了这个,我们可以使用.NET DataAnnotations或Enterprise Library Validation Application Block进行基于对象的验证。 This is much nicer than having to write XSD based XML validations which are quite unmaintainable. 这比编写基于XSD的XML验证要好得多,而这些验证是非常难以维护的。 And this is much nicer than having to do complex WCF configurations as you are currently trying to solve. 这比你当前试图解决的复杂WCF配置要好得多。 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: 而不是在WCF服务中烘焙此验证,我们可以简单地定义一个装饰器类,确保每个命令的验证如下:

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; 这个ValidationCommandHandlerDecorator<T>装饰器可以被任何类型的应用程序使用; not only WCF. 不仅是WCF。 Since WCF will by default not handle any thrown ValidationException , you might define a special decorator for WCF: 由于WCF默认不处理任何抛出的ValidationException ,因此您可以为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: 如果不使用DI容器,可以按如下方式创建新的命令处理程序:

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: 如果您想了解有关此类设计的更多信息,请阅读以下文章:

Try having your LogMessageBehavior also use BehaviorExtensionElement as its base class, then you should be able to do the following: 尝试让LogMessageBehavior也使用BehaviorExtensionElement作为其基类,然后您应该能够执行以下操作:

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

protected override object CreateBehavior()
{
    return this;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM