14

在我的 WCF 服务中,我有一个自定义消息检查器,用于根据 XML 架构将传入消息验证为原始 XML。消息检查器有一些依赖项(例如记录器和 XML 模式集合)。我的问题是,我可以使用依赖注入框架(我目前正在使用 Ninject)来实例化这些自定义行为并自动注入依赖项吗?

我做了一个简单的例子来展示这个概念:

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) { }
}

我该如何去注入一个ILoggerintoLogMessageInspector和一个LogMessageInspectorinto LogMessageBehavior

第二个问题,这是不是矫枉过正?

编辑:如果我在代码中构建我的服务,我可以让它工作,因为我使用 Ninject 创建行为。但是,当通过 config 配置服务时,我需要添加一个扩展类的附加类BehaviorExtensionElement。这个类是由 WCF 创建的,我似乎无法找到一种方法来让 Ninject 创建它。在代码中配置:

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();
    }
}

这很好用,但我不知道如何在通过 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?
    }
}
4

3 回答 3

4

如何将 ILogger 注入 LogMessageInspector 并将 LogMessageInspector 注入 LogMessageBehavior?

该方法已在此处描述

更新

如果我错了,请纠正我,但我想问题归结为你如何获得 Ninject 内核的实例BehaviorExtensionElement.CreateBehavior?答案取决于您的托管方案。如果托管在 IIS 下,您可以将这样的内容添加到您的NinjectWebCommon

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

由于您似乎是自托管的,因此您可能也想使用内核的静态实例。然而,在我看来,这并不是一个非常好的主意。

我实际上会投票支持您自己的方法并以编程方式配置行为,除非BehaviorExtensionElement有必要,因为您需要能够通过配置文件配置行为。

这是矫枉过正吗?

这取决于,但如果您要对实现进行单元测试,则绝对不是。

于 2012-11-24T13:34:51.057 回答
1

为什么不采用更面向对象的方法,而不是针对 XML 模式验证原始 XML?例如,您可以将每个操作建模为单个消息(DTO)并将实际逻辑隐藏在通用接口后面。因此,不是有一个包含MoveCustomer(int customerId, Address address)方法的 WCF 服务,而是有一个MoveCustomerCommand { CustomerId, Address }类,实际逻辑将由一个ICommandHandler<MoveCustomerCommand>用单个Handle(TCommand)方法实现接口的类来实现。

这种设计具有以下优点:

  • 系统中的每个操作都有自己的类(SRP
  • 那些消息/命令对象将获得 WCF 合同
  • WCF 服务将只包含一个服务类和一个方法。这导致了一个高度可维护的 WCF 服务。
  • 允许通过为接口(OCP)实现装饰器来添加横切关注点ICommandHandler<T>
  • 允许将验证放在消息/命令对象上(例如使用属性),并允许通过使用装饰器再次添加此验证。

当您应用基于单个通用ICommandHandler<TCommand>接口的设计时,创建可应用于所有实现的通用装饰器非常容易。一些装饰器可能只需要在 WCF 服务中运行时应用,其他应用程序类型也可能需要其他装饰器(如验证)。

消息可以定义如下:

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

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

此消息定义了将客户移动CustomerId到提供的NewAddress. 属性定义什么状态是有效的。有了这个,我们可以使用 .NET DataAnnotations 或 Enterprise Library Validation Application Block 简单地进行基于对象的验证。这比编写基于 XSD 的 XML 验证要好得多,后者是相当难以维护的。这比您当前尝试解决的复杂 WCF 配置要好得多。而不是在 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);
    }
}

这个ValidationCommandHandlerDecorator<T>装饰器可以被任何类型的应用程序使用;不仅是 WCF。由于默认情况下 WCF 不会处理任何 throwed 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);
        }
    }
}

在不使用 DI 容器的情况下,可以创建一个新的命令处理程序,如下所示:

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

handler.Handle(command);

如果您想了解有关此类设计的更多信息,请阅读以下文章:

于 2012-11-25T18:17:12.710 回答
-1

尝试让您的 LogMessageBehavior 也使用 BehaviorExtensionElement 作为其基类,那么您应该能够执行以下操作:

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

protected override object CreateBehavior()
{
    return this;
}
于 2012-11-25T00:04:43.887 回答