Accéder au HttpContext actuel dans ASP.NET Core

132

J'ai besoin d'accéder au courant HttpContextdans une méthode statique ou un service utilitaire.

Avec ASP.NET MVC classique et System.Web, je voudrais simplement utiliser HttpContext.Currentpour accéder au contexte de manière statique. Mais comment faire cela dans ASP.NET Core?

maxswitcher
la source

Réponses:

149

HttpContext.Currentn'existe plus dans ASP.NET Core mais il y a un nouveau IHttpContextAccessorque vous pouvez injecter dans vos dépendances et utiliser pour récupérer le courant HttpContext:

public class MyComponent : IMyComponent
{
    private readonly IHttpContextAccessor _contextAccessor;

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

    public string GetDataFromSession()
    {
        return _contextAccessor.HttpContext.Session.GetString(*KEY*);
    }
}
Chalet Kévin
la source
3
Bon point! Il convient également de mentionner que ce IHttpContextAccessorne serait disponible que dans les endroits où le conteneur DI résout l'instance.
tugberk
6
@tugberk bien, en théorie, vous pouvez également utiliser le CallContextServiceLocatorpour résoudre un service, même d'une instance à injection non-DI: CallContextServiceLocator.Locator.ServiceProvider.GetService<IHttpContextAccessor>(). En pratique, c'est une bonne chose si vous pouvez l'éviter :)
Chalet Kévin
17
Ne pas utiliser CallContextServiceLocator
davidfowl
9
@davidfowl à moins que vous n'ayez une raison technique valable (à part «la statique est mauvaise», bien sûr), je parie que les gens l'utiliseront s'ils n'ont pas d'autre choix.
Chalet Kévin du
7
Bien sûr, les gens ont rarement une raison technique valable. C'est plus comme s'il était plus facile d'utiliser un statique et qui se soucie de la testabilité :)
davidfowl
35

Nécromancie.
OUI VOUS POUVEZ Astuce
secrète pour les grands migrantsjonquesmorceaux (soupir, glissement freudien) de code.
La méthode suivante est un mauvais anthrax d'un hack qui est activement engagé dans la réalisation du travail express de satan (aux yeux des développeurs du framework .NET Core), mais cela fonctionne :

Dans public class Startup

ajouter une propriété

public IConfigurationRoot Configuration { get; }

Et puis ajoutez un singleton IHttpContextAccessor à DI dans 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>();

Puis dans Configure

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

ajoutez le paramètre DI IServiceProvider svp, de sorte que la méthode ressemble à:

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

Ensuite, créez une classe de remplacement pour 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 


}

Maintenant, dans Configure, où vous avez ajouté le IServiceProvider svp, enregistrez ce fournisseur de services dans la variable statique «ServiceProvider» dans la classe factice qui vient d'être créée System.Web.HttpContext (System.Web.HttpContext.ServiceProvider)

et définissez HostingEnvironment.IsHosted sur true

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

c'est essentiellement ce que System.Web a fait, juste que vous ne l'avez jamais vu (je suppose que la variable a été déclarée comme interne au lieu de publique).

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

    });

Comme dans les formulaires Web ASP.NET, vous obtiendrez un NullReference lorsque vous essayez d'accéder à un HttpContext alors qu'il n'y en a pas, comme c'était le cas Application_Startdans global.asax.

Je souligne encore une fois, cela ne fonctionne que si vous avez réellement ajouté

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

comme je l'ai écrit tu devrais.
Bienvenue dans le modèle ServiceLocator dans le modèle DI;)
Pour les risques et les effets secondaires, demandez à votre médecin ou pharmacien résident - ou étudiez les sources de .NET Core sur github.com/aspnet , et effectuez des tests.


Peut-être qu'une méthode plus maintenable consisterait à ajouter cette classe d'assistance

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


    }


}

Et puis en appelant HttpContext.Configure dans 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
la source
37
THIS IS PURE EVIL
Art
2
La version avec la méthode d'assistance fonctionne-t-elle correctement dans chaque scénario. Vous envisagez de multithreading, asynchrone et avec des services dans un conteneur IoC avec une durée de vie différente?
Tamas Molnar le
7
Je sais que nous devons tous faire tout notre possible pour souligner à quel point c'est diabolique ... Mais si vous portiez un énorme projet sur Core, où HttpContext.Current était utilisé dans certaines classes statiques difficiles à atteindre. Cela serait probablement très utile. Là, je l'ai dit.
Brian MacKay
2
C'est du mal pur ... et il convient que je le mette en œuvre à Halloween. J'adore DI et IoC ... mais j'ai affaire à une application héritée avec des classes statiques diaboliques avec des variables statiques diaboliques, que nous devons pousser en utilisant Kestrel et essayer d'injecter HttpContext serait simplement annulable pour nous, sans tout casser.
House of Dexter
2
Oui, c'est la bonne réponse pour les MIGRATIONS. ;)
Tom Stickel
23

Juste pour ajouter aux autres réponses ...

Dans ASP.NET Core 2.1, il existe la AddHttpContextAccessorméthode d'extension , qui enregistrera le IHttpContextAccessoravec la durée de vie correcte:

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

    // Other code...
}
Khellang
la source
2
Heureux de voir une alternative plus officielle à l'anthrax satanique!
Ken Lyon
@Ken Lyon:;) khellang: Singleton est la durée de vie correcte. Scoped serait faux. Ou du moins au moment de la rédaction de cet article, c'était le cas. Mais tant mieux si AddHttpContextAccessor le fait correctement sans que nous ayons besoin d'une référence pour la version spécifique du framework.
Stefan Steiger
Pouvez-vous s'il vous plaît partager un exemple?
Boîte à outils du
@Toolkit Ajout d'un exemple de code. Je ne sais pas quelle valeur il fournit sur le texte ci-dessus, cependant.
khellang
22

La manière la plus légitime que j'ai trouvée était d'injecter IHttpContextAccessor dans votre implémentation statique comme suit:

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

     public static HttpContext HttpContext => _accessor.HttpContext;
}

Ensuite, l'affectation de IHttpContextAccessor dans le Startup Configure devrait faire le travail.

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

Je suppose que vous devriez également avoir besoin d'enregistrer le service singleton:

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Jan
la source
Belle. Juste ce que le docteur à prescrit!
ShrapNull
5

Selon cet article: Accéder à HttpContext en dehors des composants du framework dans 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;
        }
    }
}

Ensuite:

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

Ensuite:

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

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

Vous pouvez l'utiliser comme ceci:

using System.Web;

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

Au démarrage

services.AddHttpContextAccessor();

Dans le contrôleur

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
la source