بررسی روش دسترسی به HttpContext در ASP.NET Core
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: دو دقیقه

در نگارش‌های دیگر ASP.NET، برای دسترسی به اطلاعات درخواست وب جاری، می‌توان از خاصیت استاتیک System.Web.HttpContext.Current استفاده کرد. با حذف شدن System.Web از ASP.NET Core و همچنین بهبود طراحی آن جهت سازگاری کامل با مفاهیم تزریق وابستگی‌ها، دیگر روش استفاده‌ی مستقیم از خواص استاتیک توصیه نشده و بجای آن تزریق اینترفیس ویژه‌ی IHttpContextAccessor توصیه می‌شود.


دسترسی به اطلاعات درخواست وب جاری در ASP.NET Core

برای دسترسی به اطلاعات درخواست جاری در ASP.NET Core، می‌توان از طریق تزریق سرویس جدید IHttpContextAccessor اقدام کرد. این اینترفیس دارای تک خاصیت HttpContext است که به صورت پیش فرض جزو سرویس‌های از پیش ثبت شده‌ی ASP.NET Core نیست و برای اینکه تزریق وابستگی‌ها در اینجا به درستی صورت گیرد، طول عمر این سرویس باید به صورت singleton تنظیم شود:
public void ConfigureServices(IServiceCollection services)
{
   services.AddMvc();
   services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
روش کارکرد این سرویس نیز به صورت ذیل است:
- هر زمانیکه درخواست جدیدی برای پردازش فرا می‌رسد، IHttpContextFactory کار ایجاد یک HttpContext جدید را آغاز می‌کند.
- اگر سرویس IHttpContextAccessor پیشتر ثبت شده باشد، IHttpContextFactory کار مقدار دهی HttpContext آن‌را نیز انجام می‌دهد.
- اینجا شاید این سؤال مطرح شود که طول عمر IHttpContextAccessor «باید» به صورت singleton ثبت شود. پس این سرویس چگونه می‌تواند HttpContextهای مختلفی را شامل شود؟ کلاس HttpContextAccessor که پیاده سازی کننده‌ی IHttpContextAccessor است، دارای یک خاصیت AsyncLocal است که از این خاصیت جهت ذخیره سازی اطلاعات Contextهای مختلف استفاده می‌شود. بنابراین کلاس HttpContextAccessor دارای طول عمر singleton است، اما خاصیت AsyncLocal آن دارای طول عمری محدود به یک درخواست (request scoped) می‌باشد.


بنابراین به صورت خلاصه:
- هرجایی که نیاز به اطلاعات HTTP context وجود داشت، از تزریق اینترفیس IHttpContextAccessor استفاده کنید.
- ثبت سرویس IHttpContextAccessor را در ابتدای برنامه فراموش نکنید.
- طول عمر سرویس ثبت شده‌ی IHttpContextAccessor باید singleton باشد.

یک نکته: اگر از ASP.NET Core Identity استفاده می‌کنید، متد services.AddIdentity کار ثبت سرویس IHttpContextAccessor را نیز انجام می‌دهد.



یک مثال: ذخیره سازی اطلاعاتی با طول عمر کوتاه در HttpContext و سپس دسترسی به آن‌ها در کلاس‌های دیگر برنامه

استفاده‌ی از مجموعه‌ی Items شیء HttpContext، یکی از روش‌هایی است که از آن می‌توان جهت ذخیره سازی اطلاعات موقتی و محدود به طول عمر درخواست جاری استفاده کرد. برای مثال در یک کنترلر و اکشن متدی خاص، دو key/value جدید را به آن اضافه می‌کنیم:
public IActionResult ProcessForm()
{
   HttpContext.Items["firstname"] = "Vahid";
   HttpContext.Items["lastname"] = "N.";
   return View();
}
سپس جهت دسترسی به این اطلاعات در یک کلاس دیگر می‌توان به صورت ذیل عمل کرد:
public class MyHelperClass
{
    private readonly IHttpContextAccessor _contextAccessor;
    public MyHelperClass(IHttpContextAccessor  contextAccessor)
    {
        _contextAccessor = contextAccessor;
    }

    public string DoWork()
    {
        string firstName = _contextAccessor.HttpContext.Items["firstname"].ToString();
        string lastName = _contextAccessor.HttpContext.Items["lastname"].ToString();
        return $"Hello {firstName} {lastName}!";
    }
}
در اینجا در کلاسی قرار داریم که مستقیما ارتباطی به کنترلر جاری نداشته و دسترسی مستقیمی به خاصیت HttpContext آن ندارد. بنابراین برای دسترسی به اطلاعات موجود در HttpContext جاری می‌توان سرویس IHttpContextAccessor را به سازنده‌ی این کلاس تزریق کرد و سپس با کمک خاصیت contextAccessor.HttpContext آن، به اطلاعات مدنظر دسترسی یافت.
  • #
    ‫۷ سال و ۹ ماه قبل، سه‌شنبه ۱۶ آذر ۱۳۹۵، ساعت ۱۵:۲۷
    برای دسترسی به رویدادهای Session_Start و Session_End و دسترسی به متغیر Application که قبلا در Global.asax قرار داشتند .چه روشی وجود دارد؟ برای مثال: بدست آوردن Session‌های Active و قرار دادن آن در متغیری که در کل برنامه در دسترس باشد.برای شمارش تعداد ویزیتورها و ...
    • #
      ‫۷ سال و ۹ ماه قبل، سه‌شنبه ۱۶ آذر ۱۳۹۵، ساعت ۱۵:۴۵
      - توسط httpContextAccessor امکان دسترسی به سشن هم وجود دارد: httpContextAccessor.HttpContext.Session.GetInt32("count").Value 
      - متغیر Application مربوط هست به دوران Classics ASP دهه‌ی نود میلادی (حتی پیش از معرفی ASP.NET Web Forms). این متغیر این روزها با یک ConcurrentDictionary که بدون نیاز به قفل گذاری، امکان تهیه یک دیکشنری thread-safe را میسر می‌کند، قابلیت جایگزینی را دارد. یک مثال از کاربرد ConcurrentDictionary (OnlineVisitorsModule.zip برای ASP.NET 4.x و MVC 5.x)
      - رویدادهای Session_Start و Session_End و کلا مباحث Global.asax در اصل بهتر است به HTTP Modules تبدیل و refactor شوند. HTTP Modules هم در ASP.NET Core به صورت کامل حذف و با مفهوم جدیدی به نام Middlewares جایگزین شده‌اند. امکان نوشتن Middlewareهای سفارشی هم وجود دارد.
      • #
        ‫۷ سال و ۶ ماه قبل، دوشنبه ۷ فروردین ۱۳۹۶، ساعت ۱۴:۳۰
        سلام؛ نحوه کار با ConcurrentDictionary  در Asp.Net Core دقیقا چطوری هستش؟ لطف کنید نحوه پیاده سازی همین ماژول Online Visitors رو با ConcurrentDictionary داخل Asp.Net Core بگید.
        • #
          ‫۷ سال و ۶ ماه قبل، دوشنبه ۷ فروردین ۱۳۹۶، ساعت ۱۴:۴۵
          یکی هست. تنها تفاوت آن یافتن معادل End Request در ASP.NET Core است که در اینجا توضیح دادم.
  • #
    ‫۶ سال و ۶ ماه قبل، سه‌شنبه ۸ اسفند ۱۳۹۶، ساعت ۱۲:۳۵
    یک نکته تکمیلی
    در نسخه‌ی 2.1 بسته نیوگت  Microsoft.AspNetCore.All هلپری جهت افزودن این سرویس ارائه شده:
    public void ConfigureServices(IServiceCollection services)
    {
    services.AddMvc();
    services.AddHttpContextAccessor();
    }

    public static IServiceCollection AddHttpContextAccessor(this IServiceCollection services)
            {
                if (services == null)
                {
                    throw new ArgumentNullException(nameof(services));
                }
    
                services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
                return services;
            }

    توضیحات تکمیلی:
  • #
    ‫۶ سال و ۵ ماه قبل، شنبه ۱۱ فروردین ۱۳۹۷، ساعت ۰۸:۳۹
    با سلام،
    اگر کلاس ما فقط دارای سازنده‌ای،constructor، باشد که هیچ پارامتری دریافت نکند، آن وقت چطور می‌توان از IHttpContextAccessor  استفاده کرد؟
    به طور مثال:
    public class BaseController : Controller
        {
            public CurrentUser CurrentUser;
            private readonly IHttpContextAccessor _httpContextAccessor;
            public BaseController() : base()
            {
                // در اینجا می‌خواهیم به مقدار HttpContext  دسترسی پیداکنیم
            }
       }

    • #
      ‫۶ سال و ۵ ماه قبل، شنبه ۱۱ فروردین ۱۳۹۷، ساعت ۱۲:۳۵
      - هدف اصلی مطلب جاری، تزریق HttpContext در سرویس‌های برنامه است و نه در کنترلرها. از این جهت که در کنترلرها همیشه توسط this.HttpContext دسترسی به آن وجود دارد و نیازی به تزریق سرویس آن در اینجا نیست.
      - در جائیکه دسترسی به this.HttpContext وجود دارد، روش دیگر دسترسی به سرویس‌ها به صورت زیر است (البته همیشه تزریق در سازنده‌ها مقدم هستند؛ مگر در موارد ضروری):
      var serviceT = this.HttpContext.RequestServices.GetService<T>();
  • #
    ‫۴ سال و ۱۰ ماه قبل، چهارشنبه ۲۴ مهر ۱۳۹۸، ساعت ۱۸:۳۹
    یک نکته‌ی تکمیلی: IHttpContextAccessor.HttpContext را در یک فیلد ذخیره نکنید

    مطابق توصیه‌ی رسمی تیم ASP.NET Core، از نوشتن چنین قطعه کدی پرهیز کنید:
    public class MyBadType
    {
        private readonly HttpContext _context;
        public MyBadType(IHttpContextAccessor accessor)
        {
            _context = accessor.HttpContext;
        }
    
        public void CheckAdmin()
        {
            if (!_context.User.IsInRole("admin"))
            {
                throw new UnauthorizedAccessException("The current user isn't an admin");
            }
        }
    }

    در اینجا HttpContext کش شده و سپس در زمانی دیگر، مورد استفاده قرار گرفته‌است؛ در یک چنین حالتی ممکن است نال و یا Context نادرستی ذخیره شود. درست آن به صورت زیر است:
    public class MyGoodType
    {
        private readonly IHttpContextAccessor _accessor;
        public MyGoodType(IHttpContextAccessor accessor)
        {
            _accessor = accessor;
        }
    
        public void CheckAdmin()
        {
            var context = _accessor.HttpContext;
            if (context != null && !context.User.IsInRole("admin"))
            {
                throw new UnauthorizedAccessException("The current user isn't an admin");
            }
        }
    }
    یعنی اگر نیاز به HttpContext داشتید، فقط در آن لحظه باید accessor.HttpContext را فراخوانی کنید و نه پیش از آن، در سازنده‌ی کلاس. همچنین همیشه نال نبودن آن‌را هم باید بررسی کنید.
  • #
    ‫۴ سال و ۶ ماه قبل، پنجشنبه ۱۵ اسفند ۱۳۹۸، ساعت ۱۷:۰۵
    سلام؛ من از پروژه DNTIdentity استفاده میکنم و میخوام یه سری پیغام‌های مشخصی رو از لایه سرویس به لایه UI ارسال کنم، چه روشی رو پیشنهاد می‌کنید؟ استفاده از HttpContext برای اینکار مناسب هست؟ روش بهتری؟ پیغام‌ها میتونه اعلان‌های خاص یا خطاهای صورت گرفته در لایه سرویس باشه که میخوایم متن مشخصی رو به ازای اونا به کاربر نشون بدیم.
    • #
      ‫۴ سال و ۶ ماه قبل، پنجشنبه ۱۵ اسفند ۱۳۹۸، ساعت ۱۷:۲۵
      لایه سرویس، متن مدنظر را به صورت یک رشته، به کنترلر و اکشن متدی که آن‌را به صورت تزریق شده دریافت می‌کند، باز می‌گرداند. سپس این کنترلر می‌تواند از روش‌هایی مانند «نمایش اخطارها و پیام‌های بوت استرپ به کمک TempData در ASP.NET MVC» استفاده کند.
      • #
        ‫۴ سال و ۶ ماه قبل، پنجشنبه ۱۵ اسفند ۱۳۹۸، ساعت ۱۷:۵۰
        سلام، ممنونم،
        دقیقا تا بخش کنترلر که فرمودین : ""لایه سرویس، متن مدنظر را به صورت یک رشته، به کنترلر و اکشن متدی که آن‌را به صورت تزریق شده دریافت می‌کند، باز می‌گرداند "" برام سواله.
         کاری که برای ارسال این رشته از سرویس به کنترلر انجام دادم ، یک پراپرتی توی کلاس سرویس تعریف کردم و موقع خوندن هر متد از اون کلاس سرویس ، کل خطاهای رخ داده در اون سرویس رو از پراپرتی دریافت می‌کنم. روش بهتری هست؟ من هدفم اینه که بدون اینکه امضای متدهای سرویس رو عوض کنم بتونم پیغام‌ها رو به کنترلر ارسال کنم، وگرنه میشد به یک پارامتر رو به صورت out به متد ارسال کرد که بتونی در داخل اون متد مقداردهی کنی و در کنترلر داشته باشی ولی اینجوری کل متدها باید این پارامتر رو داشته باشن و ظاهر متدها جالب نمیشه!