Не System.Web альтернатива HttpContext.Current - PullRequest
0 голосов
/ 14 октября 2019

Я работаю над устаревшим проектом ASP.NET Web API 2, который загрязнен с использованием HttpContext.Current. Я пытаюсь переместить приложение в OWIN (на данном этапе ASP.NET Core не вариант) для использования Microsoft.Owin.Testing.TestServer, где HttpContext.Current не будет работать, поскольку оно тесно связано с IIS через сборку System.Web.

Итак, я пытаюсь реализовать альтернативу, используя System.Net.Http.HttpRequestMessage (я понимаю, что это другой тип, но я верю, что смогу изменить свой выход из него). Основная идея заключается в добавлении System.Net.Http.DelegatingHandler к веб-API, который будет перехватывать входящий запрос и назначать его статическому свойству. Это свойство заменит использование HttpContext.Current. Теперь, прежде чем предлагать использовать свойство ApiController.Request - статическое поле используется глубоко в графе зависимостей, поэтому его передача в качестве параметра полностью из контроллера будет затруднена, если не невозможна.

Поскольку существуютнесколько запросов, обрабатываемых параллельно, простое статическое свойство с неявным полем не будет работать. Это будет постоянно перезаписываться различными потоками, обрабатывающими запросы. Я мог бы использовать ThreadStaticAttribute, но с async все еще есть риск - если запрос обрабатывается несколькими потоками, мое поле может оказаться пустым (или, что еще хуже, другим запросом).

Оригинальный вопрос

Чтобы смягчить это, я думаю связать каждый запрос с SynchronizationContext.Current в словаре, который будет возвращать статическое свойство. Примерно так:

class CurrentRequestProvider
{
    private static readonly ConcurrentDictionary<SynchronizationContext, HttpRequestMessage> Requests =
      new ConcurrentDictionary<SynchronizationContext, HttpRequestMessage>();

    static HttpRequestMessage CurrentRequest
    {
      get
      {
        if (SynchronizationContext.Current is object &&
            Requests.TryGetValue(SynchronizationContext.Current, out var request))
        {
          return request;
        }
        return null;
      }
    }
}

Тогда DelegatingHandler вкладывается в CurrentRequestProvider, поэтому у него есть доступ к словарю Requests:

class SetCurrentRequestHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        try
        {
            Requests.AddOrUpdate(SynchronizationContext.Current, request, (sc, r) => request);
            /* Execute controller's actions and everything */
            return await base.SendAsync(request, cancellationToken);
        }
        finally
        {
            /* Clean up the dictionary of requests */
            Requests.TryRemove(SynchronizationContext.Current, out _);
        }
    }
}

Теперь, на мой вопросмногопоточным гуру :). Достаточно ли использовать ConcurrentDictionary, как я сделал, чтобы обеспечить безопасность потоков в этом сценарии, или я должен добавить дополнительную блокировку в геттер CurrentRequest или где-то еще? Вы замечаете какие-либо другие потенциальные проблемы с этим обходным решением?

Заранее спасибо.

Обновление с решением

Комментарий acelent ниже направил меня к лучшему подходу, основанному на AsyncLocal<T>:

class CurrentRequestProvider
{
    private static readonly AsyncLocal<HttpRequestMessage> AsyncLocalRequest =
      new AsyncLocal<HttpRequestMessage>();

    static HttpRequestMessage CurrentRequest => AsyncLocalRequest.Value;

    class SetCurrentRequestHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            try
            {
                AsyncLocalRequest.Value = request;
                /* Execute controller's actions and everything */
                return await base.SendAsync(request, cancellationToken);
            }
            finally
            {
                AsyncLocalRequest.Value = null
            }
        }
    }
}

Я больше не вижу необходимости обеспечивать дополнительную безопасность потоков при таком подходе (поправьте меня, если я ошибаюсь).

...