Uzyskaj dostęp do bieżącego HttpContext w ASP.NET Core

132

Potrzebuję dostępu do prądu HttpContextw metodzie statycznej lub usłudze narzędziowej.

W przypadku klasycznego ASP.NET MVC i System.Webpo prostu używałbym HttpContext.Currentstatycznego dostępu do kontekstu. Ale jak to zrobić w ASP.NET Core?

maxswitcher
źródło

Odpowiedzi:

149

HttpContext.Currentnie istnieje już w ASP.NET Core, ale jest nowy IHttpContextAccessor, który można wstrzyknąć w zależnościach i użyć do pobrania bieżącego HttpContext:

public class MyComponent : IMyComponent
{
    private readonly IHttpContextAccessor _contextAccessor;

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

    public string GetDataFromSession()
    {
        return _contextAccessor.HttpContext.Session.GetString(*KEY*);
    }
}
Kévin Chalet
źródło
3
Słuszna uwaga! Warto również wspomnieć, że IHttpContextAccessorbyłoby to dostępne tylko w miejscach, w których kontener DI rozwiązuje instancję.
tugberk
6
@tugberk dobrze w teorii, można również użyć CallContextServiceLocator, aby rozwiązać usługę, nawet z nie-DI-wtryskiwanego przykład: CallContextServiceLocator.Locator.ServiceProvider.GetService<IHttpContextAccessor>(). W praktyce to super, jeśli można tego uniknąć :)
Kévin Chalet
17
Nie używaj CallContextServiceLocator
davidfowl
9
@davidfowl, chyba że masz ważny powód techniczny (oprócz oczywiście „statyka jest zła”), założę się, że ludzie będą go używać, jeśli nie będą mieli innego wyboru.
Kévin Chalet
7
Jasne, ludzie rzadko mają jednak uzasadniony powód techniczny. To bardziej tak, jakby łatwiej było używać statycznego i kogo obchodzi testowalność :)
davidfowl
35

Nekromancja.
TAK, MOŻESZ
Tajna wskazówka dla tych, którzy migrują w dużych ilościachdżonkifragmenty (westchnienie, wpadka Freuda) kodu.
Poniższa metoda jest złym hackiem, który aktywnie angażuje się w wykonywanie ekspresowej pracy szatana (w oczach twórców frameworka .NET Core), ale działa :

W public class Startup

dodać właściwość

public IConfigurationRoot Configuration { get; }

Następnie dodaj singleton IHttpContextAccessor do DI w 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>();

Następnie w Konfiguruj

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

dodaj parametr DI IServiceProvider svp, więc metoda wygląda następująco:

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

Następnie utwórz klasę zastępczą dla 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 


}

Teraz w programie Configure, gdzie dodano IServiceProvider svp, zapisz tego dostawcę usług w zmiennej statycznej „ServiceProvider” w właśnie utworzonej klasie fikcyjnej System.Web.HttpContext (System.Web.HttpContext.ServiceProvider)

i ustaw HostingEnvironment.IsHosted na true

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

to jest w zasadzie to, co zrobił System.Web, tyle że nigdy go nie widziałeś (myślę, że zmienna została zadeklarowana jako wewnętrzna, a nie publiczna).

// 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

    });

Podobnie jak w ASP.NET Web-Forms, otrzymasz NullReference, gdy próbujesz uzyskać dostęp do HttpContext, gdy nie ma go, na przykład Application_Startw global.asax.

Podkreślam jeszcze raz, działa to tylko wtedy, gdy faktycznie dodałeś

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

tak jak napisałem, powinieneś.
Witamy we wzorcu ServiceLocator we wzorcu DI;)
W przypadku zagrożeń i skutków ubocznych zapytaj swojego lekarza lub farmaceutę - lub zapoznaj się z źródłami .NET Core na github.com/aspnet i przeprowadź testy.


Być może łatwiejszą w utrzymaniu metodą byłoby dodanie tej klasy pomocniczej

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;
            }
        }


    }


}

A następnie wywołanie HttpContext.Configure w 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>()
    );
Stefan Steiger
źródło
37
TO JEST CZYSTE ZŁO
Sztuka
2
Czy wersja z metodą pomocniczą działa poprawnie w każdym scenariuszu. Myślisz o wielowątkowości, asynchronizacji i usługach w kontenerze IoC o różnym okresie istnienia?
Tamas Molnar
7
Wiem, że wszyscy musimy zrobić wszystko, co w naszej mocy, aby wskazać, jak diabelnie diaboliczne jest to ... Ale jeśli przenosisz ogromny projekt na Core, gdzie HttpContext.Current był używany w niektórych trudno dostępnych statycznych klasach. , Prawdopodobnie byłoby to całkiem przydatne. Tam, powiedziałem to.
Brian MacKay
2
To jest czyste zło ... i dobrze, że zaimplementuję to w Halloween. Uwielbiam DI i IoC ... ale mam do czynienia ze starszą aplikacją ze złymi klasami statycznymi ze złymi zmiennymi statycznymi, które musimy wypchnąć za pomocą Kestrel i próba wstrzyknięcia HttpContext byłaby dla nas po prostu cofnięta, bez zepsucia wszystkiego.
House of Dexter
2
Tak, to jest poprawna odpowiedź na MIGRACJE. ;)
Tom Stickel
23

Aby dodać do innych odpowiedzi ...

W ASP.NET 2.1 Rdzeń, tam metodę rozszerzenia , które zarejestruje z prawidłowym życia:AddHttpContextAccessorIHttpContextAccessor

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

    // Other code...
}
khellang
źródło
2
Cieszę się, że widzę bardziej oficjalną alternatywę dla satanistycznego karbunkuła!
Ken Lyon
@Ken Lyon:;) khellang: Singleton to poprawna długość życia. Scoped byłby zły. A przynajmniej tak było w momencie pisania. Ale tym lepiej, jeśli AddHttpContextAccessor robi to poprawnie, bez konieczności odwoływania się do określonej wersji platformy.
Stefan Steiger,
Czy możesz podać przykład?
Zestaw narzędzi
@Toolkit Dodano przykładowy kod. Nie jestem jednak pewien, jaką wartość zapewnia powyżej powyższego tekstu.
khellang
22

Najbardziej legalnym sposobem, jaki wymyśliłem, było wstrzyknięcie IHttpContextAccessor do Twojej statycznej implementacji w następujący sposób:

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

     public static HttpContext HttpContext => _accessor.HttpContext;
}

Następnie przypisanie IHttpContextAccessor w Startup Configure powinno wykonać zadanie.

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

Chyba należy również zarejestrować singleton usługi:

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Jan
źródło
Piękny. Dokładnie to, co zalecił lekarz!
ShrapNull
5

Zgodnie z tym artykułem: Dostęp do HttpContext poza składnikami platformy w 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;
        }
    }
}

Następnie:

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;
    }
}

Następnie:

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

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

Możesz go używać w ten sposób:

using System.Web;

public class MyService
{
   public void DoWork()
   {
    var context = HttpContext.Current;
    // continue with context instance
   }
}
Powiedział Roohullah Allem
źródło
2

W Startup

services.AddHttpContextAccessor();

W kontrolerze

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();
        }
   }
Diana Tereshko
źródło