Инъекция зависимостей для пользовательских поведений WCF
В моей службе WCF у меня есть специальный инспектор сообщений для проверки входящих сообщений как необработанный XML-код в XML-схеме. Инспектор сообщений имеет несколько зависимостей, которые он принимает (например, журнал и сборка XML-схемы). Мой вопрос в том, могу ли я использовать инфраструктуру Injection Dependency (я использую 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
и a 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?
Этот подход описан здесь
UPDATE
Пожалуйста, исправьте меня, если я ошибаюсь, но, думаю, вопрос сводится к тому, как вы можете получить экземпляр ядра Ninject в BehaviorExtensionElement.CreateBehavior
? Ответ зависит от вашего хостинга. Если вы размещаете в IIS, вы можете добавить что-то вроде этого к NinjectWebCommon
:
public static StandardKernel Kernel
{
get { return (StandardKernel)bootstrapper.Kernel; }
}
Поскольку вы, кажется, являетесь самостоятельным хостингом, вам может понадобиться статический экземпляр ядра. На мой взгляд, однако, это не очень хорошая идея.
Я бы голосовал за свой подход и программировал поведение программно, если только BehaviorExtensionElement
не требуется, потому что вам нужно настроить поведение через файл конфигурации.
- это избыток?
Это зависит, но определенно нет, если вы перейдете к unit test реализации.
Ответ 2
Вместо проверки необработанного XML-кода на схему XML, почему бы не использовать более объектно-ориентированный подход? Например, вы можете моделировать каждую операцию как одно сообщение (DTO) и скрыть фактическую логику общего интерфейса. Таким образом, вместо службы WCF, которая содержит метод MoveCustomer(int customerId, Address address)
, будет класс MoveCustomerCommand { CustomerId, Address }
, и фактическая логика будет реализована классом, который реализует интерфейс ICommandHandler<MoveCustomerCommand>
с помощью одного метода Handle(TCommand)
.
Эта конструкция дает следующие преимущества:
- Каждая операция в системе получает свой собственный класс (SRP)
- Эти объекты message/command получат контракт WCF
- Служба WCF будет содержать только один класс обслуживания с единственным методом. Это приводит к сервису WCF, который поддерживается с высокой степенью поддержки.
- Позволяет добавить сквозные проблемы, реализовав декораторы для интерфейса
ICommandHandler<T>
(OCP)
- Позволяет проверять правильность размещения в объектах message/command (используя атрибуты для примера) и позволяет снова добавить эту проверку, используя декораторы.
Когда вы применяете дизайн, основанный на одном общем интерфейсе 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 или Block Application Validation Application. Это гораздо приятнее, чем писать XML-проверки на основе XSD, которые совершенно недостижимы. И это гораздо приятнее, чем выполнение сложных конфигураций 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);
Если вы хотите узнать больше об этом типе конструкции, прочитайте следующие статьи:
Ответ 3
Попробуйте, чтобы ваш LogMessageBehavior также использовал BehaviorExtensionElement в качестве базового класса, тогда вы должны иметь возможность сделать следующее:
public override Type BehaviorType
{
get { return this.GetType(); }
}
protected override object CreateBehavior()
{
return this;
}