Acesse o HttpContext atual no ASP.NET Core

132

Preciso acessar a corrente HttpContextem um método estático ou em um serviço utilitário.

Com o clássico ASP.NET MVC e System.Web, eu usaria apenas HttpContext.Currentpara acessar o contexto estaticamente. Mas como faço isso no ASP.NET Core?

maxswitcher
fonte

Respostas:

149

HttpContext.Currentnão existe mais no ASP.NET Core, mas há um novo IHttpContextAccessorque você pode injetar em suas dependências e usar para recuperar a corrente 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
fonte
3
Bom ponto! Também vale a pena mencionar que IHttpContextAccessorestaria disponível apenas em locais onde o contêiner de DI está resolvendo a instância.
tugberk
6
@tugberk bem, em teoria, você poderia também usar a CallContextServiceLocatorresolver um serviço, mesmo a partir de uma instância não-DI-injetada: CallContextServiceLocator.Locator.ServiceProvider.GetService<IHttpContextAccessor>(). Na prática, é uma grande coisa se você pode evitá-lo :)
Kévin Chalet
17
Não use CallContextServiceLocator
davidfowl
9
@davidfowl, a menos que você tenha um motivo técnico válido (além de 'estática é ruim', é claro), aposto que as pessoas o usarão se não tiverem outra opção.
Kévin Chalet
7
Claro, as pessoas raramente têm uma razão técnica válida. É mais como é mais fácil de usar um estático e que se preocupa com a capacidade de teste :)
davidfowl
35

Necromante.
SIM PODE
Dica secreta para quem está migrando grandesjuncospedaços (suspiro, deslize freudiano) de código.
O método a seguir é um carbúnculo maligno de um hack que está envolvido ativamente na execução do trabalho expresso de satanás (aos olhos dos desenvolvedores de estrutura do .NET Core), mas funciona :

No public class Startup

adicionar uma propriedade

public IConfigurationRoot Configuration { get; }

E, em seguida, adicione um singleton IHttpContextAccessor à DI no 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>();

Em seguida, no Configure

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

adicione o parâmetro DI IServiceProvider svp, para que o método se pareça com:

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

Em seguida, crie uma classe de substituição 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 


}

Agora no Configure, onde você adicionou IServiceProvider svp, salve este provedor de serviços na variável estática "ServiceProvider" na classe fictícia System.Web.HttpContext (System.Web.HttpContext.ServiceProvider)

e defina HostingEnvironment.IsHosted como true

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

isso é essencialmente o que System.Web fez, só que você nunca viu (acho que a variável foi declarada como interna em vez 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

    });

Como nos formulários da Web do ASP.NET, você recebe um NullReference ao tentar acessar um HttpContext quando não houver nenhum, como costumava ser Application_Startno global.asax.

Sublinho novamente, isso só funciona se você realmente adicionou

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

como eu escrevi você deveria.
Bem-vindo ao padrão ServiceLocator no padrão DI;)
Para riscos e efeitos colaterais, pergunte ao seu médico ou farmacêutico residente - ou estude as fontes do .NET Core em github.com/aspnet e faça alguns testes.


Talvez um método mais sustentável possa adicionar essa classe 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;
            }
        }


    }


}

E, em seguida, chamando HttpContext.Configure em Inicialização-> Configurar

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
fonte
37
ESTE É pura maldade
Art
2
A versão com o método auxiliar funciona corretamente em cada cenário. Pensando em multithreading, assíncrono e com serviços em contêiner IoC com vida útil diferente?
Tamas Molnar
7
Eu sei que todos temos que fazer o possível para mostrar como isso é diabolicamente diabólico ... Mas se você estivesse portando um projeto enorme para o Core, onde o HttpContext.Current foi usado em algumas classes estáticas difíceis de alcançar. Isso provavelmente seria bastante útil. Lá, eu disse.
Brian MacKay
2
Isso é puro mal ... e apropriado que eu o implemente no Halloween. Eu amo DI e IoC ... mas estou lidando com um aplicativo herdado com Classes estáticas ruins com variáveis ​​estáticas ruins, que precisamos empurrar usando o Kestrel e tentando injetar o HttpContext seria apenas desfavorável para nós, sem quebrar tudo.
House of Dexter
2
Sim, esta é a resposta correta para MIGRAÇÕES. ;)
Tom Stickel
23

Apenas para adicionar às outras respostas ...

No ASP.NET Core 2.1, existe o AddHttpContextAccessormétodo de extensão , que registrará o IHttpContextAccessortempo de vida correto:

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

    // Other code...
}
Khellang
fonte
2
Fico feliz em ver uma alternativa mais oficial ao carbúnculo satânico!
Ken Lyon
@Ken Lyon:;) khellang: Singleton é a vida correta. O escopo estaria errado. Ou, pelo menos no momento da redação, era assim. Mas melhor ainda, se AddHttpContextAccessor fizer isso corretamente, sem precisar de uma referência para a versão específica da estrutura.
Stefan Steiger
Você pode compartilhar um exemplo?
Toolkit
@Toolkit Adicionado um código de exemplo. Porém, não tenho certeza de qual valor ele fornece sobre o texto acima.
khellang 9/03
22

A maneira mais legítima que eu criei foi injetar IHttpContextAccessor na sua implementação estática da seguinte maneira:

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

     public static HttpContext HttpContext => _accessor.HttpContext;
}

Em seguida, atribuir o IHttpContextAccessor no Startup Configure deve fazer o trabalho.

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

Eu acho que você também deve registrar o serviço singleton:

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Jan
fonte
Lindo. Exatamente o que o médico pediu!
ShrapNull
5

De acordo com este artigo: Acessando o HttpContext fora dos componentes da estrutura no 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;
        }
    }
}

Então:

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

Então:

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

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

Você pode usá-lo assim:

using System.Web;

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

Na inicialização

services.AddHttpContextAccessor();

No 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
fonte