فیلترهای موجود در ASP.NET Core MVC این امکان را به ما میدهند تا اقدامات ویژه ای را قبل یا بعد از مراحل خاص در pipeline پردازش request اجرا کنیم. تعدادی فیلترهای توکار در ASP.NET Core وجود دارد. ما همچنین میتوانیم فیلترهای سفارشی خودمان را برای اجرای اقدامات در مراحل مختلفی از request pipeline بنویسیم. فیلترها همچنین به ما کمک می کنند تا نگرانی های مقطعی (cross-cutting concerns) را مدیریت کنیم و از تکرار کدها جلوگیری کنیم.
در این مقاله، ما انواع فیلترهای موجود در ASP.NET Core MVC و نحوه پیاده سازی هریک از آنها را توضیح خواهیم داد.
اگر برخی از مقالات قبلی این آموزشی سریالی را ندیده اید، توصیه می کنیم صفحه مربوط به این سری آموزشی را مشاهده کنید: آموزش سریالی ASP.NET Core MVC.
برای دانلود سورس کد این مقاله، روی این لینک کلیک کنید: فیلترها در ASP.NET Core MVC.
اول نگاهی به انواع فیلترهای موجود در ASP.NET Core MVC بیندازیم.
انواع فیلترها
هریک از این انواع فیلترها در یک مرحله متفاوت از request pipeline اجرا میشوند.
فیلترهای Authorization اول از همه اجرا میشوند و جهت تعیین اینکه آیا کاربر کنونی مجوز درخواست فعلی را دارد یا خیر مورد استفاده قرار میگیرند. این فیلترها می توانند در صورت غیرمجاز بودن درخواست، pipeline را short-circuit (کوتاه کردن جریان pipeline و نادیده گرفتن مابقی پردازش درخواست در pipeline ) کنند.
فیلترهای Resource درست بعد از authorization قرار میگیرند. ما میتوانیم از این فیلترها برای پیاده سازی caching یا short-circuit کردن pipeline فیلتر، به دلایل performance استفاده کنیم.
فیلترهای action میتوانند کد را بلافاصله قبل و بعد از فراخوانی action method مورد نظر اجرا کنند. ما میتوانیم از آن برای دستکاری آرگومانهای ارسال شده به action و نتیجه بازگشتی از action مورد نظر استفاده کنیم. صفحات Razor از فیلترهای Action پشتیبانی نمیکنند.
فیلترهای Exception در حالت عموم، برای handle کردن تمام exception های کنترل نشده در application مورد استفاده قرار میگیرند.
فیلترهای Result، میتوانند کد را بلافاصله قبل و بعد از اجرای نتایج action مورد نظر اجرا کنند. این فیلترها فقط زمانی اجرا میشوند که action method مورد نظر با موفقیت اجرا شده باشد.
فیلتر Authorization
فیلترهای Authorization دسترسی به action method ها را کنترل میکنند. این فیلترها، اولین فیلتری هستند که داخل pipeline فیلتر اجرا میشوند. این فیلترها یک متد قبل از فراخوانی اکشن متد به نام ()OnAuthorization دارند. اما متد بعد از فراخوانی اکشن متد ندارند.
پیاده سازی
اجازه دهید یک فیلتر Authorization را پیاده سازی کنیم. برای این منظور، باید اینترفیس IAuthorizationFilter
و متد ()OnAuthorization را درون آن پیاده سازی کنیم.
در این فیلتر، یک فیلد به نام _permission
که از طریق سازنده تامین میشود را تعریف میکنیم. همچنین میتوانیم یک متد ()CheckUserPermission را ایجاد کنیم که کار logic مربوط به مجوز دسترسی را انجام دهد.
جهت تست اهداف، فرض کنیم که کاربر فرضی همیشه فقط مجوز خواندن دارد و مجوز نوشتن ندارد. وقتی کاربر مورد نظر مجوز مورد نظر را جهت دسترسی نداشته باشد، میتوانیم ویژگی Result
در HTTP Context را با UnauthorizedResult
تنظیم کنیم که باعث میشود جریان اجرای pipeline کوتاه شده و مابقی درخواست اجرا نشود و یا به اصطلاح اجرای short-circuit ،pipeline شود.
یک کلاس AuthorizeActionFilter
ایجاد میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class AuthorizeActionFilter : IAuthorizationFilter { private readonly string _permission; public AuthorizeActionFilter(string permission) { _permission = permission; } public void OnAuthorization(AuthorizationFilterContext context) { bool isAuthorized = CheckUserPermission(context.HttpContext.User, _permission); if (!isAuthorized) { context.Result = new UnauthorizedResult(); } } private bool CheckUserPermission(ClaimsPrincipal user, string permission) { // Logic for checking the user permission goes here. // Let's assume this user has only read permission. return permission == "Read"; } } |
حالا با استفاده از TypeFilterAttribute، یک Attribute برای فیلتری که در حال حاضر ایجاد کردیم میسازیم:
1 2 3 4 5 6 7 8 |
public class AuthorizeAttribute : TypeFilterAttribute { public AuthorizeAttribute(string permission) : base(typeof(AuthorizeActionFilter)) { Arguments = new object[] { permission }; } } |
حالا Authorize attribute خود را آماده جهت استفاده در اختیار داریم. حالا از آن در برنامه خود استفاده میکنیم.
جهت آزمایش attribute، سه اکشن در کنترلر به نامهای Index()
, Read()
و Edit()
میسازیم.
متد ()Index را بدون مزین کردن Authorize
attribute در بالای آن رها میکنیم. برای متد ()Read، ویژگی Authorize
را با مجوز Read
تعیین میکنیم. همچنین ویژگی Authorize
برای متد ()Edit را با مجوز Write
مشخص میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public IActionResult Index() { return View(); } [Authorize("Read")] public IActionResult Read() { return View(); } [Authorize("Write")] public IActionResult Edit() { return View(); } |
آزمایش فیلتر
به یاد داشته باشید که جهت تست هدف خود، فیلتر را طوری که فقط مجوز Read
برای تمام کاربران در دسترس است پیاده سازی کرده ایم.
برنامه را اجرا میکنیم و به \home\index
میرویم:

از آنجایی که این متد نیازی به مجوز دسترسی ندارد، بدون هیچ گونه مشکلی اجرا میشود.
حالا به \home\read
میرویم:

این اکشن متد به مجوز Read
نیاز دارد که برای کاربر مورد نظر در دسترس است.
اگر یک breakpoint در متد ()OnAuthorized در AuthorizationFilter قرار دهیم، میتوانیم ببینیم که این متد برای متد ()Index اجرا نمیشود ولی برای متد ()Read اجرا میشود.
حالا به \home\edit
میرویم:

این action به مجوز Write
نیاز دارد که برای کاربر مورد نظر در دسترس نیست. از این رو، یک حطای HTTP 401 را throw میکند که نشان دهنده یک درخواست غیرمجاز است.
چیزی که ما اینجا میبینیم صفحه خطای استاندارد مرورگر برای HTTP 401
Response است. ما همچنین میتوانیم جهت تجربه کاربری بهتر، صفحات خطای سفازشی مربوط به کدهای وضعیتهای مختلف را در اپلیکیشن خود بسازیم.
در این قسمت، ما نحوه پیاده سازی یک فیلتر Authorization را یاد گرفتیم.
فیلترهای Resource
همانطور که ما از نام این فیلتر متوجه میشویم، فیلترهای Resource جهت handle کردن منابع و کمک به کوتاه کردن جریان اجرای request pipeline در صورت نیاز مورد استفاده قرار میگیرد. یک استفاده رایج از این نوع فیلتر، پیاده سازی Caching است. این نوع فیلتر، میتواند زمانیکه یک Cache نتیجه مورد انتظار را دربر دارد از مابقی کد pipeline اجتناب کند.
پیاده سازی
حالا ببینیم که چطور با استفاده از فیلتر resource میتوانیم caching را پیاده سازی کنیم.
اول یک کلاس CacheResourceFilter
که اینترفیس IResourceFilter
را پیاده سازی میکند را تعریف میکنیم. فیلتر Resource یک متد قبل از فراخوانی اکشن متد به نام ()OnResourceExecuting و یک متد بعد از فراخوانی اکشن متد به نام ()OnResourceExecuted دارد.
یک آبجکت dictionary به نام cache_ برای نگهداری مقدار cache شده و یک متغیر رشته ای به نام _cacheKey
برای ذخیره کلید Cache تعریف میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public class CacheResourceFilter : IResourceFilter { private static readonly Dictionary<string, object> _cache = new Dictionary<string, object>(); private string _cacheKey; public void OnResourceExecuting(ResourceExecutingContext context) { _cacheKey = context.HttpContext.Request.Path.ToString(); if (_cache.ContainsKey(_cacheKey)) { var cachedValue = _cache[_cacheKey] as string; if (cachedValue != null) { context.Result = new ContentResult() { Content = cachedValue }; } } } public void OnResourceExecuted(ResourceExecutedContext context) { if (!String.IsNullOrEmpty(_cacheKey) && !_cache.ContainsKey(_cacheKey)) { var result = context.Result as ContentResult; if (result != null) { _cache.Add(_cacheKey, result.Content); } } } } |
در متد ()OnResourceExecuting میتوانیم بررسی کنیم که آیا _cacheKey
در دیکشنری _cache
موجود است یا خیر. اگر موجود باشد، context.Result را با مقدار cache شده تنظیم میکنیم. میتوانیم با تنظیم کردن ویژگی context.Result، جریان pipeline فیلتر را short-circuit کنیم.
در متد ()OnResourceExecuted بررسی میکنیم که آیا _cacheKey
هم اکنون در cache_ موجود است یا خیر. اگر نیست، مقدار context.Result را به درون cache_ درج میکنیم.
حال یک attribute برای این فیلتر ایجاد میکنیم:
1 2 3 4 5 6 7 |
public class CacheResourceAttribute : TypeFilterAttribute { public CacheResourceAttribute() : base(typeof(CacheResourceFilter)) { } } |
در نهایت، اجازه دهید یک کنترلر و یک اکشن متد ایجاد کنیم تا متنی را برگردانیم که زمان تولید محتوا را نشان میدهد. همچنین اجازه دهید کنترلر را با ویژگی CacheResource
که به تازگی ایجاد کردیم مزین کنیم:
1 2 3 4 5 6 7 8 |
[CacheResource] public class CachedController : Controller { public IActionResult Index() { return Content("This content was generated at " + DateTime.Now); } } |
آزمایش فیلتر
حالا برنامه را اجرا میکنیم و به cached/ میرویم:

وقتی این URL را برای دفعه اول مورد دسترسی قرار میدهیم، میتوانیم ببینیم که content با timestamp فعلی تولید میشود. سپس برای تمام دسترسی های متوالی بعدی به همین URL، یک ورژن کش شده از resource را دریافت میکنیم. ما میتوانیم این را با بررسی کردن timestamp در عمل ببینیم. همچنین اگر یک breakpoint در اکشن متد درون کنترلر قرار دهیم، میتوانیم ببینیم که این متد فقط برای اولین درخواست اجرا میشود. در درخواستهای بعدی، میتوانیم ببینیم که با استفاده از فیلتر resource، اجرای pipeline را short-circuited کرده ایم.
عالی! ما نحوه پیاده سازی یک فیلتر Resource را یاد گرفتیم.
فیلترهای Action
فیلترهای Action درست قبل و بعد از هر اکشن متد اجرا میشوند.
پیاده سازی
اجازه دهید یک فیلتر action ایجاد کنیم که model های نامعتبر را handle کند. اگر model نامعتبر باشد، میخواهیم یک BadRequest
response استاندارد به همراه یک پیغام سفارشی برگردانیم.
کلاس اعتبارسنجی سفارشی ما باید از کلاس abstract ActionFilterAttribute
ارث بری کند و متدهای OnActionExecuting()
و OnActionExecuted()
را override کند:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class ValidateModelAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { var param = context.ActionArguments.SingleOrDefault(); if (param.Value == null) { context.Result = new BadRequestObjectResult("Model is null"); return; } if (!context.ModelState.IsValid) { context.Result = new BadRequestObjectResult(context.ModelState); } } public override void OnActionExecuted(ActionExecutedContext context) { } } |
()OnActionExecuting قبل از action method و ()OnActionExecuted بعد از action method اجرا میشود.
در متد ()OnActionExecuting ما بررسی میکنیم که آیا Model برابر با null است یا ModelState
نامعتبر است!! در صورتی که یکی از این دو موارد صحیح بود ما میتوانیم یک BadRequest
response برگردانیم. به این ترتیب، با این کار، ما می توانیم به جای نوشتن این کد در هر یک از action method ها، این کار را در سراسر برنامه handle کنیم.
حالا این فیلتر را در یک action method استفاده کنیم:
1 2 3 4 5 6 |
[ValidateModel] [HttpPost] public IActionResult Create([FromForm]Book book) { return View(); } |
تست فیلتر
اگر این action method را بدون ارائه یک Book
model معتبر فراخوانی کنیم، یک BadRequest
response استاندارد را به همراه یک پیغام سفارشی دریافت خواهیم کرد:

تمام! در این مرحله، ما نحوه پیاده سازی یک فیلتر action را بررسی کردیم.
فیلترهای Exception
فیلترهای Exception جهت handle کردن هر نوع Exception کنترل نشده ای که در برنامه ما اتفاق می افتد استفاده میشوند. این فیلترها، متدهای قبل یا بعد ندارند. این فیلترها فقط متد ()OnException را پیاده سازی میکنند. این متد هرزمان که یک exception کنترل نشده در برنامه ما اتفاق بیفتد فراخوانی خواهد شد.
پیاده سازی
برای مشاهده نحوه کارکرد فیلتر exception، یک action method میسازیم که یک exeption مدیریت نشده را تولید میکند:
1 2 3 4 |
public IActionResult GenerateError() { throw new NotImplementedException(); } |
حالا با پیاده سازی اینترفیس IExceptionFilter
، یک فیلتر exception سفارشی ایجاد میکنیم. ما میتوانیم منطق مدیریت exeption مورد نیاز را در متد ()OnException بنویسیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class CustomExceptionFilter : IExceptionFilter { private readonly IModelMetadataProvider _modelMetadataProvider; public CustomExceptionFilter( IModelMetadataProvider modelMetadataProvider) { _modelMetadataProvider = modelMetadataProvider; } public void OnException(ExceptionContext context) { var result = new ViewResult { ViewName = "CustomError" }; result.ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState); result.ViewData.Add("Exception", context.Exception); // Here we can pass additional detailed data via ViewData context.ExceptionHandled = true; // mark exception as handled context.Result = result; } } |
در متد ()OnException، میتوانیم ViewResult
را با CustomError
تنظیم کنیم. سپس میتوانیم جزییات exception را به عنوان ViewData به داخل view پاس دهیم.
بعد از آن، CustomError
view را ایجاد کرده و پیغام خطا را به یک شیوه کاربرپسندانه نمایش میدهیم:
1 2 3 4 5 6 7 8 |
@{ ViewData["Title"] = "CustomError"; var exception = ViewData["Exception"] as Exception; } <h1>An Error has Occurred</h1> <p>@exception.Message</p> |
سپس باید فیلتر exception را در startup.cs تنظیم کنیم:
1 2 3 4 5 |
public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(config => config.Filters.Add(typeof(CustomExceptionFilter))); } |
تست فیلتر
حالا برنامه را اجرا میکنیم و به home\generateerror\ میرویم:

می بینیم که اینجا یک صفحه خطای سفارشی با جزئیات exception نمایش داده می شود که می توانیم آن را سفارشی کنیم تا فقط اطلاعات مورد نیاز را به کاربر نشان دهیم. به این ترتیب، میتوانیم تجربه بسیار بهتری به کاربر ارائه دهیم و exception ها را در سراسر برنامه به روشی یکپارچه مدیریت کنیم.
Handle کردن Exception – میان افزارها در مقابل فیلترها
علاوه بر این، ما میتوانیم از میانافزار برای مدیریت Exception های کنترل نشده نیز استفاده کنیم. بنابراین، چه زمانی باید از یک میان افزار مدیریت Exception استفاده کنیم و چه زمانی باید به دنبال فیلتر Exception باشیم؟
اگر نگران خطاهایی هستیم که ممکن است خارج از بستر MVC یا کد ما رخ دهد، برای مثال، ممکن است بخواهیم خطایی را که در داخل یک میان افزار یا فیلتر رخ دهد را ثبت کنیم، باید به سراغ یک میان افزار مدیریت Exception برویم. از طرف دیگر، اگر بخواهیم بستر MVC را در حین رسیدگی به Exception ها داشته باشیم و بر اساس آن اقداماتی را انجام دهیم، باید از یک فیلتر Exception استفاده کنیم.
در این قسمت یاد گرفتیم که چگونه فیلتر Exception را در اپلیکیشن خود پیاده سازی کنیم.
فیلترهای Result
ما میتوانیم از فیلترهای Result برای اجرای کد، قبل یا بعد از اجرای نتیجه action در controller استفاده کنیم. این فیلترها فقط زمانی اجرا میشوند که action method مورد نظر با موفقیت اجرا شود. ما میتوانیم logic مورد نظر خود را حول و محور view یا جهت اعمال برخی سفارشیسازیها برای تمام نتایج action های موجود در برنامه خود بنویسیم.
فرض کنید میخواهیم یک مقدار خاص را به header تمام action result ها در برنامه خود اضافه کنیم.
ما قصد داریم نحوه پیاده سازی این کار را با استفاده از فیلتر result بررسی کنیم.
اول از همه یک کلاس AddHeaderFilter
که اینترفیس IResultFilter
را پیاده سازی میکند را ایجاد میکنیم. فیلتر Result یک متد قبل از فراخوانی به نام ()OnResultExecuting و یک متد بعد از فراخوانی به نام ()OnResultExecuted دارد.
در متد ()OnResultExecuting، یک response header سفارشی اضافه میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class AddHeaderFilter : IResultFilter { public void OnResultExecuting(ResultExecutingContext context) { context.HttpContext.Response.Headers.Add( "OnResultExecuting", "This header was added by result filter."); } public void OnResultExecuted(ResultExecutedContext context) { } } |
بعد از این، یک attribute برای فیلتر Result که در حال حاضر ایجاد کردیم میسازیم:
1 2 3 4 5 6 |
public class AddHeaderAttribute : TypeFilterAttribute { public AddHeaderAttribute() : base(typeof(AddHeaderFilter)) { } } |
در مرحله آخر، یک action method در HomeController
میسازیم و attribute فیلتر AddHeader
result را در آن اعمال میکنیم:
1 2 3 4 5 |
[AddHeader] public IActionResult TestResultFilter() { return View(); } |
حال برنامه را اجرا کنیم و به home\testresultfilter\ برویم.
با inspect کردن headers، میتوانیم ببینیم که header سفارشی ما در response آمده است:

به این ترتیب، ما میتوانیم از فیلتر Result برای سفارشی سازی action result های برنامه استفاده کنیم.
عالی! در این قسمت، ما نحوه پیاده سازی یک فیلتر result را یاد گرفتیم.
نتیجه گیری
در این مقاله، ما عناوین زیر را یاد گرفتیم:
- انواع مختلف فیلترهای موجود در ASP.NET Core MVC
- ایجاد یک فیلتر Authorization سفارشی
- پیاده سازی Caching با استفاده از فیلتر Resource
- استفاده از فیلتر Exception برای handle کردن exception های کنترل نشده
- افزودن یک response header سفارشی به تمام action method های خود با استفاده از فیلتر Resource
با این مقاله، به پایان سری آموزش سریالی ASP.NET Core MVC رسیدیم. امیدواریم از خواندن این مطلب لذت برده باشید و فرآیند یادگیری خوبی را سپری کرده باشید.