Я работаю над устаревшим проектом 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
}
}
}
}
Я больше не вижу необходимости обеспечивать дополнительную безопасность потоков при таком подходе (поправьте меня, если я ошибаюсь).