Использование HttpContext.Current в WebApi опасно из-за async

Мой вопрос немного связан с этим: эквивалент WebApi для HttpContext.Items с Injection Dependency.

Мы хотим ввести класс с использованием HttpContext.Current в области WebApi с помощью Ninject.

Меня беспокоит, что это может быть очень опасно, так как в WebApi (все?) есть async.

Пожалуйста, исправьте меня, если я ошибаюсь в этих точках, это то, что я исследовал до сих пор:

  • HttpContext.Current получает текущий контекст по потоку (я напрямую смотрел на реализацию).

  • Использование HttpContext.Current внутри async Задача невозможна, поскольку она может работать в другом потоке.

  • WebApi использует IHttpController с методом Task<HttpResponseMessage> ExecuteAsync = > каждый запрос isync = > вы не можете использовать HttpContext.Current внутри метода действия. Это может даже произойти, больше запросов выполняются в одном потоке с помощью проверки соответствия.

  • Для создания контроллеров с инъецируемым материалом в конструкторы IHttpControllerActivator используется с методом синхронизации IHttpController Create. Это, где ninject создает контроллер со всеми его зависимостями.


  • Если я прав во всех этих четырех точках, использование HttpContext.Current внутри метода действия или любого нижележащего слоя очень опасно и может иметь неожиданные результаты. Я видел на SO много принятых ответов, предлагая именно это. IMHO это может работать некоторое время, но будет работать под нагрузкой.

  • Но при использовании DI для создания контроллера и его зависимостей, это нормально, потому что это выполняется на одном отдельном потоке. Я мог бы получить значение из HttpContext в конструкторе, и это было бы безопасно?. Интересно, создается ли каждый контроллер в одном потоке для каждого запроса, так как это может вызвать проблему при тяжелых нагрузках, где могут потребляться все потоки из IIS.

Просто чтобы объяснить, почему я хочу вставлять материал HttpContext:

  • одним из решений было бы получить запрос в методе действия контроллера и передать необходимое значение всем слоям как param, пока оно не будет использоваться где-то глубоко в коде.
  • наше желаемое решение: все слои между ними не зависят от этого, и мы можем использовать введенный запрос где-то глубоко в коде (например, в некоторых ConfigurationProvider, который зависит от URL-адреса).

    Пожалуйста, дайте мне свое мнение, если я ошибаюсь или мои предложения верны, поскольку эта тема кажется очень сложной. спасибо заранее!

Ответ 1

HttpContext.Current получает текущий контекст по потоку (я напрямую посмотрел на реализацию).

Было бы правильнее сказать, что HttpContext применяется к потоку; или поток "вводит" HttpContext.

Использование HttpContext.Current внутри async Задача невозможна, поскольку она может работать в другом потоке.

Совсем нет; поведение по умолчанию async/await будет возобновлено на произвольном потоке, но этот поток войдет в контекст запроса, прежде чем возобновит ваш метод async.


Ключом к этому является SynchronizationContext. У меня есть статья MSDN по этому вопросу, если вы не знакомы с ней. A SynchronizationContext определяет "контекст" для платформы, а общие - это контексты пользовательского интерфейса (WPF, WinPhone, WinForms и т.д.), Контекст пула потоков и контекст запроса ASP.NET.

Контекст запроса ASP.NET управляет HttpContext.Current, а также несколькими другими вещами, такими как культура и безопасность. Контексты пользовательского интерфейса тесно связаны с потоком одного потока (), но контекст запроса ASP.NET не привязан к определенному потоку. Однако он будет разрешать только один поток в контексте запроса.

Другая часть решения - это то, как работают async и await. У меня есть async intro в моем блоге, который описывает их поведение. Таким образом, await по умолчанию будет захватывать текущий контекст (который SynchronizationContext.Current, если он не является null), и использовать этот контекст для возобновления метода async. Таким образом, await автоматически захватывает ASP.NET SynchronizationContext и возобновляет метод async в этом контексте запроса (таким образом сохраняя культуру, безопасность и HttpContext.Current).

Если вы await ConfigureAwait(false), то вы явно говорите await, чтобы не отображать контекст.

Обратите внимание, что ASP.NET пришлось изменить свой SynchronizationContext, чтобы работать с помощью async/await. Вы должны убедиться, что приложение скомпилировано против .NET 4.5 и также явно нацелено на 4.5 в своем web.config; это значение по умолчанию для новых проектов ASP.NET 4.5, но должно быть явно задано, если вы обновили существующий проект из ASP.NET 4.0 или ранее.

Вы можете убедиться, что эти настройки верны, выполнив приложение против .NET 4.5 и соблюдая SynchronizationContext.Current. Если это AspNetSynchronizationContext, тогда вы хороши; если он LegacyAspNetSynchronizationContext, то настройки неправильные.

Пока настройки правильные (и вы используете ASP.NET 4.5 AspNetSynchronizationContext), вы можете безопасно использовать HttpContext.Current после await, не беспокоясь об этом.

Ответ 2

Я нашел очень хорошую статью, описывающую именно эту проблему: http://byterot.blogspot.cz/2012/04/aspnet-web-api-series-part-3-async-deep.html?m=1

автор глубоко исследовал, как метод ExecuteAsync вызывается в рамках WebApi и пришел к такому выводу:

Действия веб-API ASP.NET(и все методы конвейера) будут вызваны асинхронно, только если вы вернете задачу или задачу <T> . Это может показаться очевидным, но ни один из методов конвейера с суффиксом Async не будет работать в своих потоках. Использование одеяла Async может быть неправильным. [UPDATE: команда ASP.NET действительно подтвердила, что Async используется для обозначения методов, возвращающих Task, и может выполняться асинхронно, но не обязательно]

Из статьи я понял, что методы Action называются синхронно, но это решение вызывающего.

Я создал небольшое тестовое приложение для этой цели, примерно так:

public class ValuesController : ApiController
{
    public object Get(string clientId, string specialValue)
    {
        HttpRequest staticContext = HttpContext.Current.Request;
        string staticUrl = staticContext.Url.ToString();

        HttpRequestMessage dynamicContext = Request;
        string dynamicUrl = dynamicContext.RequestUri.ToString();

        return new {one = staticUrl, two = dynamicUrl};
    }
}

и одна версия Async, возвращающая async Task<object>

Я попытался сделать небольшую атаку DOS на нем с помощью jquery и не смог определить какую-либо проблему до тех пор, пока не использовал await Task.Delay(1).ConfigureAwait(false);, что, очевидно, не получится.

То, что я взял из статьи, заключается в том, что проблема очень сложная, и при использовании метода асинхронного действия может произойти переключение Thread, поэтому определенно NOT хорошая идея использовать HttpContext.Current в любом месте код, вызванный методами действия. Но поскольку контроллер создается синхронно, использование HttpContext.Current в конструкторе, а также в инъекции зависимостей - это нормально.

Когда у кого-то есть другое объяснение этой проблемы, пожалуйста, исправьте меня, поскольку эта проблема очень сложная, и я все еще не уверен на 100%.

письменный отказ от ответственности:

  • Я сейчас игнорирую проблему самообслуживания Web-Api с IIS, где HttpContext.Current не будет работать, вероятно, в любом случае. Теперь мы полагаемся на IIS.

Ответ 3

Я использую web api, который использует методологию async/await.

также используя

1) HttpContext.Current.Server.MapPath
2) System.Web.HttpContext.Current.Request.ServerVariables

Это работало нормально в течение большого количества времени, которое внезапно разразилось без изменения кода.

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

< httpRuntime targetFramework="4.5.2"  /> under system.web

Я не эксперт технически. Но я предлагаю добавить ключ в свой веб-конфигуратор и дать ему GO.