WCF 自定义行为的依赖注入
Posted
技术标签:
【中文标题】WCF 自定义行为的依赖注入【英文标题】:Dependency Injection for WCF Custom Behaviors 【发布时间】:2012-11-12 12:49:54 【问题描述】:在我的 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)
如何将ILogger
注入LogMessageInspector
并将LogMessageInspector
注入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();
这很好用,但我不知道如何在通过我的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?
【问题讨论】:
【参考方案1】:如何将 ILogger 注入 LogMessageInspector 和 将 LogMessageInspector 转换为 LogMessageBehavior?
方法已经描述here
更新
如果我错了,请纠正我,但我想问题归结为你如何在BehaviorExtensionElement.CreateBehavior
中获得 Ninject 内核的实例?答案取决于您的托管方案。如果托管在 IIS 下,您可以将这样的内容添加到您的 NinjectWebCommon
:
public static StandardKernel Kernel
get return (StandardKernel)bootstrapper.Kernel;
由于您似乎是自托管的,因此您可能也想使用内核的静态实例。然而,在我看来,这并不是一个非常好的主意。
我实际上会投票支持您自己的方法并以编程方式配置行为,除非BehaviorExtensionElement
是必要的,因为您需要能够通过配置文件配置行为。
这是不是矫枉过正?
这取决于,但如果您要对实现进行单元测试,则绝对不是。
【讨论】:
链接的问题肯定演示了将 DI 与 WCF 结合使用的概念,Ninject 为我处理了这个概念。我的问题可能不够具体,所以我添加了一个编辑。我想做的是找出一种方法,让 Ninject 在通过 config 配置时动态创建自定义端点行为。 @AdamRodger 对不起,我看错了你的问题。我已经更新了答案,但基本上我认为在代码中创建行为是一个更好的选择。 没关系。我也倾向于在代码中添加它,但在实际服务中我不会自托管,所以我会考虑在 IIS 中托管时在代码中添加行为并报告。 我必须为我的内核添加一个静态引用,并且只在BehaviorExtensionElement
中使用它,以便我可以通过 config.xml 进行配置。我知道这是通用服务定位器模式,但我认为我无法避免它。我研究了覆盖 NinjectServiceHostFactory.CreateServiceHost()
以便可以在代码中添加行为,但即使这样我也无法访问内核,因为它是私有的,所以我有点卡住了。【参考方案2】:
为什么不采用更面向对象的方法,而不是针对 XML 模式验证原始 XML?例如,您可以将每个操作建模为单个消息(DTO)并将实际逻辑隐藏在通用接口后面。因此,与其拥有一个包含 MoveCustomer(int customerId, Address address)
方法的 WCF 服务,不如使用一个 MoveCustomerCommand CustomerId, Address
类,并且实际逻辑将由一个使用单个 Handle(TCommand)
方法实现 ICommandHandler<MoveCustomerCommand>
接口的类来实现。
这种设计具有以下优点:
系统中的每个操作都有自己的类 (SRP) 那些消息/命令对象将获得 WCF 合同 WCF 服务将只包含一个服务类和一个方法。这导致 WCF 服务具有高度可维护性。 允许通过为ICommandHandler<T>
接口 (OCP) 实现 decorators 添加横切关注点 (OCP)
允许对消息/命令对象进行验证(例如使用属性),并允许使用装饰器再次添加此验证。
当您应用基于单个通用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 不会处理任何抛出的 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);
如果您想了解更多关于此类设计的信息,请阅读以下文章:
Meanwhile... on the command side of my architecture Writing Highly Maintainable WCF Services【讨论】:
这当然是一种非常有趣的方法,但不幸的是,这是一项现有服务,拥有数千个我无法更改的客户端,并且架构是由第三方定义的。我正在重构服务,包括添加依赖注入,这是我唯一不确定如何重构的地方。【参考方案3】:尝试让您的 LogMessageBehavior 也使用 BehaviorExtensionElement 作为其基类,那么您应该能够执行以下操作:
public override Type BehaviorType
get return this.GetType();
protected override object CreateBehavior()
return this;
【讨论】:
以上是关于WCF 自定义行为的依赖注入的主要内容,如果未能解决你的问题,请参考以下文章
使用 Castle Windsor 进行 WCF 依赖注入 - 请帮忙?