เข้าถึง HttpContext ปัจจุบันใน ASP.NET Core


132

ฉันต้องการเข้าถึงกระแสHttpContextในวิธีคงที่หรือบริการยูทิลิตี้

ด้วย ASP.NET MVC แบบคลาสสิกและSystem.Webฉันจะใช้HttpContext.Currentเพื่อเข้าถึงบริบทแบบคงที่ แต่ฉันจะทำสิ่งนี้ใน ASP.NET Core ได้อย่างไร?

คำตอบ:


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

6
@tugberk ดีในทฤษฎีคุณยังสามารถใช้CallContextServiceLocatorในการแก้ปัญหาการให้บริการแม้จะมาจากอินสแตนซ์ที่ไม่ใช่ CallContextServiceLocator.Locator.ServiceProvider.GetService<IHttpContextAccessor>()DI-ฉีด: ในทางปฏิบัติมันเป็นสิ่งที่ดีถ้าคุณสามารถหลีกเลี่ยงได้ :)
Kévin Chalet

17
อย่าใช้ CallContextServiceLocator
davidfowl

9
@davidfowl เว้นแต่คุณจะมีเหตุผลทางเทคนิคที่ถูกต้อง (แน่นอนว่านอกจาก 'สถิตยศาสตร์จะชั่วร้าย') ฉันพนันได้เลยว่าผู้คนจะใช้มันหากพวกเขาไม่มีทางเลือกอื่น
Kévin Chalet

7
แน่นอนว่าผู้คนไม่ค่อยมีเหตุผลทางเทคนิคที่ถูกต้อง มันเหมือนกับว่ามันง่ายกว่าที่จะใช้แบบคงที่และใคร
ก็ตาม

35

Necromancing
ใช่คุณสามารถ
เคล็ดลับลับสำหรับผู้ที่ย้ายข้อมูลขนาดใหญ่เรือสำเภาชิ้นส่วน (ถอนหายใจสลิปฟรอยด์) ของรหัส
วิธีต่อไปนี้เป็น carbuncle ชั่วร้ายของการแฮ็กที่มีส่วนร่วมอย่างแข็งขันในการดำเนินการด่วนของซาตาน (ในสายตาของผู้พัฒนา. NET Core framework) แต่มันใช้ได้ผล :

ใน public class Startup

เพิ่มคุณสมบัติ

public IConfigurationRoot Configuration { get; }

จากนั้นเพิ่ม Singleton 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>();

จากนั้นในกำหนดค่า

    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;

นี่คือสิ่งที่ SystemWeb ทำโดยที่คุณไม่เคยเห็นมัน (ฉันเดาว่าตัวแปรถูกประกาศเป็นภายในแทนที่จะเป็นสาธารณะ)

// 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 Web-Forms คุณจะได้รับ NullReference เมื่อคุณพยายามเข้าถึง HttpContext เมื่อไม่มีเช่นเคยอยู่Application_Startใน global.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
THIS IS PURE EVIL
Art

2
เวอร์ชันที่มีวิธีการช่วยเหลือทำงานอย่างถูกต้องในแต่ละสถานการณ์หรือไม่ คิดถึงมัลติเธรด async และบริการในคอนเทนเนอร์ IoC ที่มีอายุการใช้งานแตกต่างกันหรือไม่?
Tamas Molnar

7
ฉันรู้ว่าเราทุกคนต้องออกนอกเส้นทางเพื่อชี้ให้เห็นว่านี่มันโหดร้ายแค่ไหน ... แต่ถ้าคุณย้ายโปรเจ็กต์ขนาดใหญ่ไปยัง Core ที่ HttpContext.Current ถูกใช้ในคลาสคงที่ยากต่อการเข้าถึง .. นี้คงจะมีประโยชน์มากทีเดียว ที่นั่นฉันพูดมัน
Brian MacKay

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>());

ฉันเดาว่าคุณควรจะต้องลงทะเบียนบริการ singleton:

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

สวย. ตามที่หมอสั่ง!
ShrapNull

23

เพียงเพื่อเพิ่มคำตอบอื่น ๆ ...

ใน ASP.NET 2.1 หลักมีวิธีขยายที่จะลงทะเบียนกับอายุการใช้งานที่ถูกต้อง:AddHttpContextAccessorIHttpContextAccessor

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

    // Other code...
}

2
ดีใจที่ได้เห็นทางเลือกที่เป็นทางการมากขึ้นสำหรับซาตาน carbuncle!
Ken Lyon

@ Ken Lyon :) khellang: Singleton คืออายุการใช้งานที่ถูกต้อง ขอบเขตจะผิด หรืออย่างน้อยที่สุดในขณะที่เขียนก็เป็นเช่นนั้น แต่จะดีกว่าถ้า AddHttpContextAccessor ทำอย่างถูกต้องโดยที่เราไม่จำเป็นต้องมีการอ้างอิงสำหรับเวอร์ชันเฟรมเวิร์กเฉพาะ
Stefan Steiger

คุณช่วยแบ่งปันตัวอย่างได้ไหม
Toolkit

@Toolkit เพิ่มโค้ดตัวอย่าง ไม่แน่ใจว่ามันให้ค่าอะไรจากข้อความด้านบน
khellang

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();
        }
   }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.