پیشنیازها
- فیلترها در MVC
- ASP.NET MVC #15
فیلترها در ASP.NET MVC، امکان اجرای کدهایی را پیش و یا پس از مرحلهی خاصی از طول اجرای pipeline آن فراهم میکنند. کلیات فیلترها در ASP.NET Core با نگارشهای قبلی ASP.NET MVC (پیشنیازهای فوق) تفاوت چندانی را ندارد و بیشتر تغییراتی مانند نحوهی معرفی سراسری آنها، اکشن فیلترهای Async و یا تزریق وابستگیها در آنها، جدید هستند.
امکان تعریف فیلترهای Async در ASP.NET Core
حالت کلی تعریف یک فیلتر در ASP.NET MVC که در ASP.NET Core نیز همچنان معتبر است، پیاده سازی اینترفیس کلی IActionFilter میباشد که توسط آن میتوان به مراحل پیش و پس از اجرای قطعهای از کدهای برنامه دسترسی پیدا کرد:
در اینجا اینترفیس IAsyncActionFilter نیز معرفی شدهاست که توسط آن میتوان فراخوانیهای غیرهمزمان و async را نیز مدیریت کرد:
به کامنتهای نوشته شدهی در بدنهی متد OnActionExecutionAsync دقت کنید. در اینجا کدهای پیش از await next معادل OnActionExecuting و کدهای پس از await next معادل OnActionExecuted حالت همزمان و یا همان حالت متداول هستند. بنابراین جایی که اکشن متد اجرا میشود، همان await next است.
یک نکته:توصیه شدهاست که تنها یکی از حالتهای همزمان و یا غیرهمزمان را پیاده سازی کنید و نه هر دوی آنها را. اگر هر دوی اینها را در طی یک کلاس پیاده سازی کنید (تک کلاسی که هر دوی اینترفیسهای IActionFilter و IAsyncActionFilter را با هم پیاده سازی میکند)، تنها نگارش Async آن توسط ASP.NET Core فراخوانی و استفاده خواهد شد. همچنین مهم نیست که اکشن متد شما Async هست یا خیر؛ برای هر دو حالت میتوان از فیلترهای async نیز استفاده کرد.
ساده سازی تعریف فیلترها
اگر مدتی با ASP.NET MVC کار کرده باشید، میدانید که عموما کسی از این اینترفیسهای کلی برای پیاده سازی فیلترها استفاده نمیکند. روش کار با ارث بری از یکی از فیلترهای از پیش تعریف شدهی ASP.NET MVC صورت میگیرد؛ از این جهت که این فیلترها که در اصل همین اینترفیسها را پیاده سازی کردهاند، یک سری جزئیات توکار protected را نیز به همراه دارند که با ارث بری از آنها میتوان به امکانات بیشتری دسترسی پیدا کرد و کدهای سادهتر و کم حجمتری را تولید نمود:
ActionFilterAttribute
ExceptionFilterAttribute
ResultFilterAttribute
FormatFilterAttribute
ServiceFilterAttribute
TypeFilterAttribute
برای مثال در اینجا فیلتری را مشاهده میکنید که با ارث بری از فیلتر توکار ResultFilterAttribute، سعی در تغییر Response برنامه و افزودن هدری به آن کردهاست:
و برای استفادهی از این فیلتر جدید خواهیم داشت:
نحوهی تعریف میدان دید فیلترها
نحوهی دید فیلترها در اینجا نیز همانند سابق، سه حالت را میتواند داشته باشد:
الف) اعمال شدهی به یک اکشن متد.
ب) اعمال شدهی به یک کنترلر که به تمام اکشن متدهای آن کنترلر اعمال خواهد شد.
ج) حالت تعریف سراسری و این مورد محل تعریف آن به کلاس آغازین برنامه منتقل شدهاست:
در اینجا دو روش معرفی فیلترهای سراسری را در متد ConfigureServices کلاس آغازین برنامه مشاهده میکنید:
الف) اگر توسط ارائهی new ClassName معرفی شوند، یعنی وهله سازی را خودتان قرار است مدیریت کنید و در این حالت تزریق وابستگیهایی صورت نخواهند گرفت.
ب) اگر توسط typeof معرفی شوند، یعنی این وهله سازی توسط IoC Container توکار ASP.NET Core انجام خواهد شد و طول عمر آن Transient است. یعنی به ازای هربار نیاز به آن، یکبار وهله سازی خواهد شد.
ترتیب اجرای فیلترها
توسط خاصیت Order میتوان ترتیب اجرای چندین فیلتر اجرا شدهی به یک اکشن متد را مشخص کرد. اگر این مقدار منفی وارد شود:
این فیلتر پیش از فیلترهای سراسری و همچنین فیلترهای اعمال شدهی در سطح کلاس اجرا میشود.
تزریق وابستگیها در فیلترها
فیلترهایی که به صورت ویژگیها یا Attributes تعریف میشوند و قرار است به کنترلرها و یا اکشن متدها به صورت مستقیم اعمال شوند، نمیتوانند دارای وابستگیهای تزریق شدهی در سازندهی خود باشند. این محدودیتی است که توسط زبانهای برنامه نویسی اعمال میشود و نه ASP.NET Core. اگر ویژگی قرار است پارامتری در سازندهی خود داشته باشد، هنگام تعریف و اعمال آن، این پارامترها باید مشخص بوده و تعریف شوند. به همین جهت آنچنان با تزریق وابستگیهای از طریق سازندهی کلاس قابل مدیریت نیستند. برای رفع این نقصیه، راهحلهای متفاوتی در ASP.NET Core پیشنهاد و طراحی شدهاند:
الف) استفادهی از ServiceFilterAttribute
ویژگی جدید ServiceFilter، نوع کلاس فیلتر را دریافت میکند و سپس هر زمانیکه نیاز به اجرای این فیلتر خاص بود، کار وهله سازیهای وابستگیهای آن، در پشت صحنه توسط IoC Container توکار ASP.NET Core انجام خواهد شد.
همچنین باید دقت داشت که در این حالت ثبت کلاس فیلتر در متد ConfigureServices کلاس آغازین برنامه الزامی است.
در غیراینصورت استثنای ذیل را دریافت خواهید کرد:
ب) استفاده از TypeFilterAttribute
فیلتر و ویژگی TypeFilter بسیار شبیه است به عملکرد ServiceFilter، با این تفاوت که:
- نیازی نیست تا وابستگی آنرا در متد ConfigureServices ثبت کرد (هرچند وابستگیهای خود را از DI Container دریافت میکنند).
- امکان دریافت پارامترهای اضافی سازندهی کلاس مدنظر را نیز دارند.
یک مثال تکمیلی: لاگ کردن تمام استثناءهای مدیریت نشدهی یک برنامهی ASP.NET Core 1.0
میتوان با سفارشی سازی فیلتر توکار ExceptionFilterAttribute، امکان ثبت وقایع را توسط فریم ورک توکار Loggingاضافه کرد:
و برای ثبت سراسری آن در کلاس آغازین برنامه خواهیم داشت:
در اینجا از typeof استفاده شدهاست تا کار تزریق وابستگیهای این فیلتر به صورت خودکار انجام شود.
در ادامه با این فرض که پیشتر تنظیمات ثبت وقایع صورت گرفتهاست:
اکنون اگر یک چنین اکشن متدی فراخوانی شود:
در پنجرهی دیباگ ویژوال استودیو، این استثناء قابل مشاهده خواهد بود:
- فیلترها در MVC
- ASP.NET MVC #15
فیلترها در ASP.NET MVC، امکان اجرای کدهایی را پیش و یا پس از مرحلهی خاصی از طول اجرای pipeline آن فراهم میکنند. کلیات فیلترها در ASP.NET Core با نگارشهای قبلی ASP.NET MVC (پیشنیازهای فوق) تفاوت چندانی را ندارد و بیشتر تغییراتی مانند نحوهی معرفی سراسری آنها، اکشن فیلترهای Async و یا تزریق وابستگیها در آنها، جدید هستند.
امکان تعریف فیلترهای Async در ASP.NET Core
حالت کلی تعریف یک فیلتر در ASP.NET MVC که در ASP.NET Core نیز همچنان معتبر است، پیاده سازی اینترفیس کلی IActionFilter میباشد که توسط آن میتوان به مراحل پیش و پس از اجرای قطعهای از کدهای برنامه دسترسی پیدا کرد:
namespace FiltersSample.Filters { public class SampleActionFilter : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { // انجام کاری پیش از اجرای اکشن متد } public void OnActionExecuted(ActionExecutedContext context) { // انجام کاری پس از اجرای اکشن متد } } }
namespace FiltersSample.Filters { public class SampleAsyncActionFilter : IAsyncActionFilter { public async Task OnActionExecutionAsync( ActionExecutingContext context, ActionExecutionDelegate next) { // انجام کاری پیش از اجرای اکشن متد await next(); // انجام کاری پس از اجرای اکشن متد } } }
یک نکته:توصیه شدهاست که تنها یکی از حالتهای همزمان و یا غیرهمزمان را پیاده سازی کنید و نه هر دوی آنها را. اگر هر دوی اینها را در طی یک کلاس پیاده سازی کنید (تک کلاسی که هر دوی اینترفیسهای IActionFilter و IAsyncActionFilter را با هم پیاده سازی میکند)، تنها نگارش Async آن توسط ASP.NET Core فراخوانی و استفاده خواهد شد. همچنین مهم نیست که اکشن متد شما Async هست یا خیر؛ برای هر دو حالت میتوان از فیلترهای async نیز استفاده کرد.
ساده سازی تعریف فیلترها
اگر مدتی با ASP.NET MVC کار کرده باشید، میدانید که عموما کسی از این اینترفیسهای کلی برای پیاده سازی فیلترها استفاده نمیکند. روش کار با ارث بری از یکی از فیلترهای از پیش تعریف شدهی ASP.NET MVC صورت میگیرد؛ از این جهت که این فیلترها که در اصل همین اینترفیسها را پیاده سازی کردهاند، یک سری جزئیات توکار protected را نیز به همراه دارند که با ارث بری از آنها میتوان به امکانات بیشتری دسترسی پیدا کرد و کدهای سادهتر و کم حجمتری را تولید نمود:
ActionFilterAttribute
ExceptionFilterAttribute
ResultFilterAttribute
FormatFilterAttribute
ServiceFilterAttribute
TypeFilterAttribute
برای مثال در اینجا فیلتری را مشاهده میکنید که با ارث بری از فیلتر توکار ResultFilterAttribute، سعی در تغییر Response برنامه و افزودن هدری به آن کردهاست:
using Microsoft.AspNetCore.Mvc.Filters; namespace FiltersSample.Filters { public class AddHeaderAttribute : ResultFilterAttribute { private readonly string _name; private readonly string _value; public AddHeaderAttribute(string name, string value) { _name = name; _value = value; } public override void OnResultExecuting(ResultExecutingContext context) { context.HttpContext.Response.Headers.Add( _name, new string[] { _value }); base.OnResultExecuting(context); } } }
[AddHeader("Author", "DNT")] public class SampleController : Controller { public IActionResult Index() { return Content("با فایرباگ هدر خروجی را بررسی کنید"); } }
نحوهی تعریف میدان دید فیلترها
نحوهی دید فیلترها در اینجا نیز همانند سابق، سه حالت را میتواند داشته باشد:
الف) اعمال شدهی به یک اکشن متد.
ب) اعمال شدهی به یک کنترلر که به تمام اکشن متدهای آن کنترلر اعمال خواهد شد.
ج) حالت تعریف سراسری و این مورد محل تعریف آن به کلاس آغازین برنامه منتقل شدهاست:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(options => { options.Filters.Add(typeof(SampleActionFilter)); // by type options.Filters.Add(new SampleGlobalActionFilter()); // an instance }); }
الف) اگر توسط ارائهی new ClassName معرفی شوند، یعنی وهله سازی را خودتان قرار است مدیریت کنید و در این حالت تزریق وابستگیهایی صورت نخواهند گرفت.
ب) اگر توسط typeof معرفی شوند، یعنی این وهله سازی توسط IoC Container توکار ASP.NET Core انجام خواهد شد و طول عمر آن Transient است. یعنی به ازای هربار نیاز به آن، یکبار وهله سازی خواهد شد.
ترتیب اجرای فیلترها
توسط خاصیت Order میتوان ترتیب اجرای چندین فیلتر اجرا شدهی به یک اکشن متد را مشخص کرد. اگر این مقدار منفی وارد شود:
[MyFilter(Name = "Method Level Attribute", Order=-1)]
تزریق وابستگیها در فیلترها
فیلترهایی که به صورت ویژگیها یا Attributes تعریف میشوند و قرار است به کنترلرها و یا اکشن متدها به صورت مستقیم اعمال شوند، نمیتوانند دارای وابستگیهای تزریق شدهی در سازندهی خود باشند. این محدودیتی است که توسط زبانهای برنامه نویسی اعمال میشود و نه ASP.NET Core. اگر ویژگی قرار است پارامتری در سازندهی خود داشته باشد، هنگام تعریف و اعمال آن، این پارامترها باید مشخص بوده و تعریف شوند. به همین جهت آنچنان با تزریق وابستگیهای از طریق سازندهی کلاس قابل مدیریت نیستند. برای رفع این نقصیه، راهحلهای متفاوتی در ASP.NET Core پیشنهاد و طراحی شدهاند:
الف) استفادهی از ServiceFilterAttribute
[ServiceFilter(typeof(AddHeaderFilterWithDi))] public IActionResult Index() { return View(); }
همچنین باید دقت داشت که در این حالت ثبت کلاس فیلتر در متد ConfigureServices کلاس آغازین برنامه الزامی است.
services.AddScoped<AddHeaderFilterWithDi>();
System.InvalidOperationException: No service for type 'FiltersSample.Filters.AddHeaderFilterWithDI' has been registered.
ب) استفاده از TypeFilterAttribute
[TypeFilter(typeof(AddHeaderAttribute), Arguments = new object[] { "Author", "DNT" })] public IActionResult Hi(string name) { return Content($"Hi {name}"); }
- نیازی نیست تا وابستگی آنرا در متد ConfigureServices ثبت کرد (هرچند وابستگیهای خود را از DI Container دریافت میکنند).
- امکان دریافت پارامترهای اضافی سازندهی کلاس مدنظر را نیز دارند.
یک مثال تکمیلی: لاگ کردن تمام استثناءهای مدیریت نشدهی یک برنامهی ASP.NET Core 1.0
میتوان با سفارشی سازی فیلتر توکار ExceptionFilterAttribute، امکان ثبت وقایع را توسط فریم ورک توکار Loggingاضافه کرد:
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; namespace Core1RtmEmptyTest.StartupCustomizations { public class CustomExceptionLoggingFilterAttribute : ExceptionFilterAttribute { private readonly ILogger<CustomExceptionLoggingFilterAttribute> _logger; public CustomExceptionLoggingFilterAttribute(ILogger<CustomExceptionLoggingFilterAttribute> logger) { _logger = logger; } public override void OnException(ExceptionContext context) { _logger.LogInformation($"OnException: {context.Exception}"); base.OnException(context); } } }
public void ConfigureServices(IServiceCollection services) { services.AddMvc(options => { options.Filters.Add(typeof(CustomExceptionLoggingFilterAttribute));
در ادامه با این فرض که پیشتر تنظیمات ثبت وقایع صورت گرفتهاست:
public void Configure(ILoggerFactory loggerFactory) { loggerFactory.AddDebug(minLevel: LogLevel.Debug);
public IActionResult GetData() { throw new Exception("throwing an exception!"); }