Как разрешить обработчик сообщений API Web/DelegatingHandler из контейнера IoC по каждому запросу

В этой статье MSDN описывается, как обработчики HTTP-сообщений могут эффективно использоваться в ASP.NET Web API для "украшения" запросов. Кроме того, в статье показан следующий код для регистрации ваших пользовательских обработчиков в конвейере веб-API:

config.MessageHandlers.Add(new MessageHandler1());

Проблема с этим подходом заключается в том, что он эффективно регистрирует MessageHandler1 как одноэлементный. Это нормально, когда сам обработчик не имеет состояния и никаких зависимостей, но в системе, основанной на принципах дизайна SOLID, очень вероятно, что эти обработчики будут иметь собственные зависимости и очень вероятно, что некоторые из этих зависимостей нуждаются в время жизни, которое короче, чем одиночный.

Если в этом случае такой обработчик сообщения не должен быть создан как singleton, так как в общем случае компонент никогда не должен иметь продолжительность жизни, которая больше, чем время жизни его зависимостей.

Итак, мой вопрос в том, какие альтернативные способы мы должны регистрировать обработчиков пользовательских сообщений таким образом, чтобы их можно было решить из нашего контейнера IoC по каждому запросу?

Ответ 1

Мне не известно о другом API регистрации для обработчиков сообщений, но вы можете ввести Factory в свой обработчик сообщений, чтобы заставить его разрешать зависимости для вызова метода:

public class LifetimeProxyMessageHandler : DelegatingHandler
{
    private readonly IHttpMessageHandlerFactory factory;

    public LifetimeProxyMessageHandler(IHttpMessageHandlerFactory factory)
    {
        if (factory == null)
            throw new ArgumentNullException("factory");
        this.factory = factory;
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpMessageHandler ephemeralHandler = factory.Create();

        ephemeralHandler.InnerHandler = this.InnerHandler;

        var invoker = new HttpMessageInvoker(ephemeralHandler);

        return invoker.SendAsync(request, cancellationToken);
    }
}

Возможно, вы захотите пропустить эфемерный HttpMessageHandler, а вместо этого попросите Factory создать экземпляр любой службы, которую вы хотите вызвать в этой точке.

IHttpMessageHandlerFactory - это настраиваемый интерфейс, который я только что придумал. Это может выглядеть так:

public interface IHttpMessageHandlerFactory
{
    HttpMessageHandler Create();
}

Ответ 2

Взяв cue из в этой статье, я решил ту же проблему следующим образом:

Я создал службу, в которой будет размещен мой глобальный variabile

public interface IPerRequestScopedService : IDisposable
{
    string ClientId { get; set; }
}

public class PerRequestScopedService : IPerRequestScopedService
{
    public string ClientId { get; set; }

    public void Dispose()
    {
        Trace.TraceInformation("Object {0} disposed", this.GetType().Name);
    }
}

Затем я зарегистрировал его в своем контейнере IoC (в моем случае SimpleInjector), используя время выполнения запроса

container.RegisterWebApiRequest<IPerRequestScopedService, PerRequestScopedService>();

Тогда я использовал службу внутри DelegatingHandler таким образом

public class HeaderReaderHandler : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Trace.TraceInformation("start HeaderReaderHandler");

        IEnumerable<string> headerValues;
        if (request.Headers.TryGetValues("x-client-id", out headerValues))
        {
            string token = headerValues.First();
            var requestScopedService = request.GetDependencyScope().GetService(typeof(IPerRequestScopedService)) as IPerRequestScopedService;
            requestScopedService.ClientId = token;
        }

        // Call the inner handler.
        var response = await base.SendAsync(request, cancellationToken);
        return response;
    }
}

Я выбрал эту реализацию по сравнению с Factory, потому что, я думаю, более ориентирован на IoC, даже если использует шаблон Service Locator вместо полной инъекции зависимостей.