Acceda al HttpContext actual en ASP.NET Core

132

Necesito acceder a current HttpContexten un método estático o un servicio de utilidad.

Con ASP.NET MVC clásico y System.Web, solo lo usaría HttpContext.Currentpara acceder al contexto de forma estática. Pero, ¿cómo hago esto en ASP.NET Core?

maxswitcher
fuente

Respuestas:

149

HttpContext.Currentya no existe en ASP.NET Core, pero hay una nueva IHttpContextAccessorque puede inyectar en sus dependencias y usar para recuperar la actual 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
fuente
3
¡Buen punto! También vale la pena mencionar que IHttpContextAccessorsolo estaría disponible en lugares donde el contenedor DI está resolviendo la instancia.
tugberk
66
@tugberk bien, en teoría, se podría también utilizar la CallContextServiceLocatorde resolver un servicio, incluso desde una instancia inyecta la no-DI: CallContextServiceLocator.Locator.ServiceProvider.GetService<IHttpContextAccessor>(). En la práctica, es una gran cosa si puedes evitarlo :)
Kévin Chalet
17
No use CallContextServiceLocator
davidfowl
9
@davidfowl a menos que tenga una razón técnica válida (aparte de 'las estadísticas son malas', por supuesto), apuesto a que las personas lo usarán si no tienen otra opción.
Kévin Chalet
77
Claro, la gente rara vez tiene una razón técnica válida. Es más fácil usar una estática y a quién le importa la
comprobabilidad
35

Nigromancia
SÍ PUEDES CONSEJO
secreto para aquellos que migran grandeschatarratrozos (suspiro, deslizamiento freudiano) de código.
El siguiente método es un malvado truco de un hack que se dedica activamente a llevar a cabo el trabajo expreso de satanás (a los ojos de los desarrolladores de .NET Core Framework), pero funciona :

En public class Startup

agregar una propiedad

public IConfigurationRoot Configuration { get; }

Y luego agregue un IHttpContextAccessor singleton a DI en 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>();

Luego en Configurar

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

agregue el parámetro DI IServiceProvider svp, para que el método se vea así:

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

A continuación, cree una clase de reemplazo para 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 


}

Ahora en Configurar, donde agregó el IServiceProvider svp, guarde este proveedor de servicios en la variable estática "ServiceProvider" en la clase ficticia recién creada System.Web.HttpContext (System.Web.HttpContext.ServiceProvider)

y establezca HostingEnvironment.IsHosted en true

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

esto es esencialmente lo que hizo System.Web, solo que nunca lo viste (supongo que la variable se declaró como interna en lugar de pública).

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

    });

Al igual que en los formularios web ASP.NET, obtendrá una NullReference cuando intente acceder a un HttpContext cuando no haya ninguno, como solía estar Application_Starten global.asax.

Insisto nuevamente, esto solo funciona si realmente agregaste

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

como te escribí deberías.
Bienvenido al patrón ServiceLocator dentro del patrón DI;)
Para conocer los riesgos y los efectos secundarios, consulte a su médico o farmacéutico residente, o estudie las fuentes de .NET Core en github.com/aspnet y realice algunas pruebas.


Quizás un método más fácil de mantener sería agregar esta clase auxiliar

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


    }


}

Y luego llamando a HttpContext.Configure en 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
fuente
37
ESTO ES MAL PURO
Art
2
Es la versión con el método auxiliar funciona correctamente en cada escenario. ¿Pensando en multihilo, asíncrono y con servicios en contenedor IoC con diferente duración?
Tamas Molnar
77
Sé que todos tenemos que salir de nuestro camino para señalar cuán diabólicamente diabólico es esto ... Pero si estuvieras portando un gran proyecto a Core, donde HttpContext.Current se usaba en algunas clases estáticas difíciles de alcanzar ... Esto probablemente sería bastante útil. Ahí lo dije.
Brian MacKay
2
Esto es pura maldad ... y es apropiado que lo implemente en Halloween. Me encanta DI y IoC ... pero estoy lidiando con una aplicación heredada con clases estáticas malvadas con variables estáticas malvadas, que necesitamos presionar usando Kestrel e intentar inyectar HttpContext sería simplemente imposible de deshacer, sin romper todo.
Casa de Dexter
2
Sí, esta es la respuesta correcta para MIGRACIONES. ;)
Tom Stickel
23

Solo para agregar a las otras respuestas ...

En ASP.NET Core 2.1, existe el AddHttpContextAccessormétodo de extensión , que registrará IHttpContextAccessorcon la vida útil correcta:

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

    // Other code...
}
khellang
fuente
2
¡Me alegra ver una alternativa más oficial al carbunco satánico!
Ken Lyon
@Ken Lyon:;) khellang: Singleton es la vida correcta. Scoped estaría mal. O al menos al momento de escribir, eso era así. Pero mucho mejor si AddHttpContextAccessor lo hace correctamente sin que necesitemos una referencia para la versión de marco específica.
Stefan Steiger
¿Puedes por favor compartir un ejemplo?
Juego de herramientas
@Toolkit Se agregó un código de ejemplo. Sin embargo, no estoy seguro de qué valor proporciona sobre el texto anterior.
khellang
22

La forma más legítima que se me ocurrió fue inyectando IHttpContextAccessor en su implementación estática de la siguiente manera:

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

     public static HttpContext HttpContext => _accessor.HttpContext;
}

Luego asignar el IHttpContextAccessor en la Configuración de inicio debería hacer el trabajo.

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

Supongo que también deberías necesitar registrar el servicio singleton:

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
ene
fuente
Hermoso. ¡Justo lo que recetó el doctor!
ShrapNull
5

De acuerdo con este artículo: Acceso a HttpContext fuera de los componentes del marco en 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;
        }
    }
}

Luego:

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

Luego:

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

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

Puedes usarlo así:

using System.Web;

public class MyService
{
   public void DoWork()
   {
    var context = HttpContext.Current;
    // continue with context instance
   }
}
Dijo Roohullah Allem
fuente
2

En el inicio

services.AddHttpContextAccessor();

En el controlador

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
fuente