Доступ к текущему HttpContext в ASP.NET Core

132

Мне нужно получить доступ к току HttpContextстатическим методом или служебной службой.

С классическим ASP.NET MVC и System.Webя бы просто использовал HttpContext.Currentдля статического доступа к контексту. Но как мне это сделать в ASP.NET Core?

maxswitcher
источник

Ответы:

150

HttpContext.Currentбольше не существует в ASP.NET Core, но есть новое, IHttpContextAccessorкоторое вы можете вставить в свои зависимости и использовать для получения текущего HttpContext:

public class MyComponent : IMyComponent
{
    private readonly IHttpContextAccessor _contextAccessor;

    public MyComponent(IHttpContextAccessor contextAccessor)
    {
        _contextAccessor = contextAccessor;
    }

    public string GetDataFromSession()
    {
        return _contextAccessor.HttpContext.Session.GetString(*KEY*);
    }
}
Кевин Шале
источник
3
Хорошая точка зрения! Также стоит упомянуть, что IHttpContextAccessorэто будет доступно только в тех местах, где контейнер DI разрешает экземпляр.
tugberk 06
6
@tugberk хорошо, в теории, можно также использовать CallContextServiceLocatorдля разрешения службы, даже из не-DI-закачиваемой например: CallContextServiceLocator.Locator.ServiceProvider.GetService<IHttpContextAccessor>(). На практике это отличная вещь, если вы можете этого избежать :)
Кевин Шале
17
Не используйте CallContextServiceLocator
davidfowl
9
@davidfowl, если у вас нет веской технической причины (кроме, конечно, «статика - зло»), держу пари, люди будут использовать ее, если у них не будет другого выбора.
Kévin Chalet
7
Конечно, у людей редко бывает веская техническая причина. Это больше похоже на то, что проще использовать статику и кого волнует тестируемость :)
davidfowl 07
35

Necromancing.
ДА МОЖНО
Секретный совет для тех, кто мигрируетджонкикуски (вздох, оговорка по Фрейду) кода.
Следующий метод является злым карбункулом хака, который активно выполняет экспресс-работу сатаны (в глазах разработчиков .NET Core), но он работает :

В public class Startup

добавить недвижимость

public IConfigurationRoot Configuration { get; }

А затем добавьте одноэлементный IHttpContextAccessor в DI в ConfigureServices.

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<Microsoft.AspNetCore.Http.IHttpContextAccessor, Microsoft.AspNetCore.Http.HttpContextAccessor>();

Затем в Configure

    public void Configure(
              IApplicationBuilder app
             ,IHostingEnvironment env
             ,ILoggerFactory loggerFactory
    )
    {

добавьте параметр DI IServiceProvider svp, чтобы метод выглядел так:

    public void Configure(
           IApplicationBuilder app
          ,IHostingEnvironment env
          ,ILoggerFactory loggerFactory
          ,IServiceProvider svp)
    {

Затем создайте класс замены для System.Web:

namespace System.Web
{

    namespace Hosting
    {
        public static class HostingEnvironment 
        {
            public static bool m_IsHosted;

            static HostingEnvironment()
            {
                m_IsHosted = false;
            }

            public static bool IsHosted
            {
                get
                {
                    return m_IsHosted;
                }
            }
        }
    }


    public static class HttpContext
    {
        public static IServiceProvider ServiceProvider;

        static HttpContext()
        { }


        public static Microsoft.AspNetCore.Http.HttpContext Current
        {
            get
            {
                // var factory2 = ServiceProvider.GetService<Microsoft.AspNetCore.Http.IHttpContextAccessor>();
                object factory = ServiceProvider.GetService(typeof(Microsoft.AspNetCore.Http.IHttpContextAccessor));

                // Microsoft.AspNetCore.Http.HttpContextAccessor fac =(Microsoft.AspNetCore.Http.HttpContextAccessor)factory;
                Microsoft.AspNetCore.Http.HttpContext context = ((Microsoft.AspNetCore.Http.HttpContextAccessor)factory).HttpContext;
                // context.Response.WriteAsync("Test");

                return context;
            }
        }


    } // End Class HttpContext 


}

Теперь в Configure, куда вы добавили IServiceProvider svp, сохраните этого поставщика услуг в статической переменной «ServiceProvider» в только что созданном фиктивном классе System.Web.HttpContext (System.Web.HttpContext.ServiceProvider)

и установите для HostingEnvironment.IsHosted значение true

System.Web.Hosting.HostingEnvironment.m_IsHosted = true;

По сути, это то, что сделал System.Web, только вы никогда этого не видели (я предполагаю, что переменная была объявлена ​​как внутренняя, а не общедоступная).

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider svp)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    ServiceProvider = svp;
    System.Web.HttpContext.ServiceProvider = svp;
    System.Web.Hosting.HostingEnvironment.m_IsHosted = true;


    app.UseCookieAuthentication(new CookieAuthenticationOptions()
    {
        AuthenticationScheme = "MyCookieMiddlewareInstance",
        LoginPath = new Microsoft.AspNetCore.Http.PathString("/Account/Unauthorized/"),
        AccessDeniedPath = new Microsoft.AspNetCore.Http.PathString("/Account/Forbidden/"),
        AutomaticAuthenticate = true,
        AutomaticChallenge = true,
        CookieSecure = Microsoft.AspNetCore.Http.CookieSecurePolicy.SameAsRequest

       , CookieHttpOnly=false

    });

Как и в веб-формах ASP.NET, вы получите NullReference, когда пытаетесь получить доступ к HttpContext, когда его нет, например, как раньше в Application_Startglobal.asax.

Еще раз подчеркиваю, это работает, только если вы действительно добавили

services.AddSingleton<Microsoft.AspNetCore.Http.IHttpContextAccessor, Microsoft.AspNetCore.Http.HttpContextAccessor>();

как я написал, вам следует.
Добро пожаловать в шаблон ServiceLocator в шаблоне DI;)
Чтобы узнать о рисках и побочных эффектах, обратитесь к своему лечащему врачу или фармацевту - или изучите источники .NET Core на github.com/aspnet и проведите небольшое тестирование.


Возможно, более удобный метод добавит этот вспомогательный класс

namespace System.Web
{

    public static class HttpContext
    {
        private static Microsoft.AspNetCore.Http.IHttpContextAccessor m_httpContextAccessor;


        public static void Configure(Microsoft.AspNetCore.Http.IHttpContextAccessor httpContextAccessor)
        {
            m_httpContextAccessor = httpContextAccessor;
        }


        public static Microsoft.AspNetCore.Http.HttpContext Current
        {
            get
            {
                return m_httpContextAccessor.HttpContext;
            }
        }


    }


}

А затем вызов HttpContext.Configure в Startup-> Configure

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider svp)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();


    System.Web.HttpContext.Configure(app.ApplicationServices.
        GetRequiredService<Microsoft.AspNetCore.Http.IHttpContextAccessor>()
    );
Стефан Штайгер
источник
37
ЭТО ЧИСТЫЕ ЗЛО
Art
2
Правильно ли работает версия с вспомогательным методом в каждом сценарии. Думаете о многопоточности, асинхронности и сервисах в контейнере IoC с разным временем жизни?
Tamas Molnar
7
Я знаю, что мы все должны изо всех сил показывать, насколько это чертовски дьявольски ... Но если бы вы переносили огромный проект на Core, где HttpContext.Current использовался в некоторых труднодоступных статических классах ... Это, вероятно, было бы весьма полезно. Вот, я это сказал.
Брайан Маккей
2
Это чистое зло ... и уместно, что я собираюсь реализовать его на Хэллоуин. Я люблю DI и IoC ... но я имею дело с устаревшим приложением со злыми статическими классами со злыми статическими переменными, которые нам нужно протолкнуть с помощью Kestrel, и попытка внедрить HttpContext будет для нас просто отменой, без нарушения всего.
House of Dexter
2
Да, это правильный ответ для МИГРАЦИИ. ;)
Tom Stickel
23

Самый законный способ, который я придумал, - это вставить IHttpContextAccessor в вашу статическую реализацию следующим образом:

public static class HttpHelper
{
     private static IHttpContextAccessor _accessor;
     public static void Configure(IHttpContextAccessor httpContextAccessor)
     {
          _accessor = httpContextAccessor;
     }

     public static HttpContext HttpContext => _accessor.HttpContext;
}

Затем назначение IHttpContextAccessor в Startup Configure должно выполнить свою работу.

HttpHelper.Configure(app.ApplicationServices.GetRequiredService<IHttpContextAccessor>());

Думаю, вам также необходимо зарегистрировать синглтон службы:

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
январь
источник
Прекрасный. Только то, что доктор прописал!
ShrapNull
23

Просто чтобы добавить к другим ответам ...

В ASP.NET 2.1 Ядра, есть метод расширения , который будет зарегистрировать с правильной жизнью:AddHttpContextAccessorIHttpContextAccessor

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpContextAccessor();

    // Other code...
}
khellang
источник
2
Рад видеть более официальную альтернативу сатанинскому карбункулу!
Кен Лайон
@Ken Lyon:;) khellang: Синглтон - это правильный срок жизни. Scoped было бы неправильно. Или, по крайней мере, на момент написания, так оно и было. Но тем лучше, если AddHttpContextAccessor сделает это правильно, и нам не понадобится ссылка на конкретную версию фреймворка.
Stefan Steiger
Не могли бы вы привести пример?
Инструментарий
@Toolkit Добавлен пример кода. Однако не уверен, какое значение он дает по сравнению с текстом выше.
Хелланг,
6

Согласно этой статье: Доступ к HttpContext вне компонентов фреймворка в ASP.NET Core

namespace System.Web
{
    public static class HttpContext
    {
        private static IHttpContextAccessor _contextAccessor;

        public static Microsoft.AspNetCore.Http.HttpContext Current => _contextAccessor.HttpContext;

        internal static void Configure(IHttpContextAccessor contextAccessor)
        {
            _contextAccessor = contextAccessor;
        }
    }
}

Затем:

public static class StaticHttpContextExtensions
{
    public static void AddHttpContextAccessor(this IServiceCollection services)
    {
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    }

    public static IApplicationBuilder UseStaticHttpContext(this IApplicationBuilder app)
    {
        var httpContextAccessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
        System.Web.HttpContext.Configure(httpContextAccessor);
        return app;
    }
}

Затем:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpContextAccessor();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseStaticHttpContext();
        app.UseMvc();
    }
}

Вы можете использовать это так:

using System.Web;

public class MyService
{
   public void DoWork()
   {
    var context = HttpContext.Current;
    // continue with context instance
   }
}
Саид Рухулла Аллем
источник
2

При запуске

services.AddHttpContextAccessor();

В контроллере

public class HomeController : Controller
    {
        private readonly IHttpContextAccessor _context;

        public HomeController(IHttpContextAccessor context)
        {
            _context = context; 
        }
        public IActionResult Index()
        {
           var context = _context.HttpContext.Request.Headers.ToList();
           return View();
        }
   }
Диана Терешко
источник