Инъекция зависимостей для пользовательских поведений 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;
}