[Программирование, .NET, ASP, C#] Фильтры действий, или Как просто улучшить читаемость кода
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Введение
В свободное от работы время я, как и многие другие разработчики, занимаюсь созданием своих приложений, чтобы опробовать самые последние технологии, создать что-то полезное для повседневного использования или просто открыть для себя что-то новое. Одним из таких проектов было веб-приложение, которое обрабатывало данные, введённые пользователем, и планировало выполнение задач, основанных на введённых данных. Так как личные проекты не ограничены во времени, то мне хотелось по возможности избавить проект от всех раздражающих моментов. И одним из таких моментов были повторяющиеся строчки кода в методах контроллера. Я начал искать решение этой проблемы и наткнулся на фильтры. Идея использования фильтров для поддержания чистоты кода показалась мне не только интересной и эффективной, но в то же время простой, поэтому я решил поделиться этой информацией с вами.
Роль фильтров в процессе обработки запроса
Сначала обсудим сами фильтры: для чего же они нужны? Фильтры позволяют выполнять определённые действия на различных стадиях обработки запроса в ASP.NET Core. Существуют следующие встроенные фильтры:
- Фильтры авторизации (Authorization filters) выполняются самыми первыми и определяют, может ли пользователь выполнить текущий запрос.
- Фильтры ресурсов (Resource filters) вызываются после фильтров авторизации и необходимы, как следует из названия, для обработки ресурсов. В частности, данный тип фильтров применяют в качестве механизма кэширования.
- Фильтры действий (Action Filters) выполняют указанные в них операции до и после выполнения метода контроллера, обрабатывающего запрос.
- Фильтры исключений (Exception Filters) используются для перехвата необработанных исключений, произошедших при создании контроллера, привязке модели и выполнении кода фильтров действий и методов контроллера.
- И наконец, самыми последними вызываются фильтры результатов (Result Filters), если метод контроллера был выполнен успешно. Данный тип фильтров чаще всего используется, чтобы модифицировать конечные результаты, например, мы можем создать свой заголовок ответа, в котором добавим нужную нам информацию.
Ниже представлена схема, которая показывает, в каком порядке вызываются фильтры в процессе обработки запроса:
Из всех фильтров наиболее полезным в повседневном программировании я нахожу фильтры действий. С их помощью можно вынести повторяющиеся операции и хранить их в одном месте. Далее я приведу примеры, как можно «почистить» код, но сперва расскажу о самих фильтрах действий.
Внутреннее устройство фильтров действий
Фильтры действий в ASP.NET
Интерфейс IActionFilter, который нужно реализовать, чтобы создать фильтр действий, существовал ещё в ASP.NET MVC. Он определяет методы OnActionExecuting, который вызывается перед выполнением метода контроллера, и OnActionExecuted, который вызывается сразу после. Ниже представлен пример простейшего фильтра действий, который выводит информацию во время отладки приложения до и после выполнения метода контроллера:
public class CustomActionFilter:IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
Debug.WriteLine("Before Action Execution");
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
Debug.WriteLine("After Action Execution");
}
}
Чтобы использовать вышеуказанный фильтр, его нужно зарегистрировать. Для этого в файле FilterConfig.cs, который находится в папке App_Start, следует добавить следующую строку:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new CustomActionFilter());
}
Но гораздо удобнее использовать фильтры как атрибуты. Для этих целей существует абстрактный класс ActionFilterAttribute, который унаследован от класса FilterAttribute, а также реализует интерфейсы IActionFilter и IResultFilter. Таким образом, наш класс можно переписать следующим образом:
public class CustomActionFilterAttribute:ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Debug.WriteLine("Before Action Execution");
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
Debug.WriteLine("After Action Execution");
}
}
Теперь, чтобы применить наш фильтр, мы добавляем его к методу контроллера следующим образом:
public class HomeController : Controller
{
[CustomActionFilter]
public ActionResult Index()
{
return View();
}
}
Этот способ также удобен тем, что мы можем применять фильтры к определённому методу или к контроллеру целиком, а не регистрировать их глобально.
Фильтры действий в ASP.NET Core
С появлением ASP.NET Core в фильтрах действий произошёл ряд изменений. Кроме интерфейса IActionFilter, теперь имеется ещё и IAsyncActionFilter, который определяет единственный метод OnActionExecutionAsync. Ниже приведён пример класса, реализующего интерфейс IAsyncActionFilter:
public class AsyncCustomActionFilterAttribute:Attribute, IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
Debug.WriteLine("Before Action Execution");
await next();
Debug.WriteLine("After Action Execution");
}
}
В качестве второго параметра методу передаётся делегат ActionExecutionDelegate, с помощью которого вызываются либо следующие по порядку фильтры действий, либо сам метод контроллера.
Применяют такой фильтр так же, как и синхронный:
public class HomeController : Controller
{
[AsyncCustomActionFilter]
public ActionResult Index()
{
return View();
}
}
Также изменения затронули абстрактный класс ActionFilterAttribute: теперь он наследуется от класса Attribute и реализует синхронные и асинхронные интерфейсы для фильтров действий (IActionFilter и IAsyncActionFilter) и для фильтров результатов (IResultFilter и IAsyncResultFilter), а также интерфейс IOrderedFilter.
Фильтры действий в действии
Перейдём непосредственно к случаям, когда лучше использовать фильтры действий. Возьмём, например, ситуацию, когда мы создаём веб-приложение и нам нужно сохранить данные, которые приложение получает с помощью метода POST. Допустим, мы ведём сведения о сотрудниках организации. Чтобы представить данные на сервере, мы используем следующий класс:
public class Employee
{
[Required(ErrorMessage = "First name is required")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last name is required")]
public string LastName { get; set; }
[AgeRestriction(MinAge = 18, ErrorMessage = "Date of birth is incorrect")]
public DateTime DateOfBirth { get; set; }
[StringLength(50, MinimumLength = 2)]
public string Position { get; set; }
[Range(45000, 200000)]
public int Salary { get; set; }
}
С помощью атрибутов валидации мы можем контролировать корректность введённых данных. Следует заметить, что атрибуты со статическими параметрами не всегда удобны для валидации данных — как поля, указывающие на возраст и зарплату сотрудника, в примере выше. В таких случаях лучше создать отдельный сервис, который будет выполнять проверку таких полей, но в рамках этой статьи я решил воспользоваться исключительно атрибутами валидации.
После того как были реализованы методы POST и PUT, мы видим, что оба метода содержат повторяющиеся части кода:
[HttpPost]
public IActionResult Post([FromBody] Employee value)
{
if (value == null)
{
return BadRequest("Employee value cannot be null");
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// Perform save actions
return Ok();
}
[HttpPut]
public IActionResult Put([FromBody] Employee value)
{
if (value == null)
{
return BadRequest("Employee value cannot be null");
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// Perform update actions
return Ok();
}
И здесь нам на помощь приходят фильтры действий. Создадим новый фильтр действий и перенесём в него повторяющиеся строки следующим образом:
public class EmployeeValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var employeeObject = context.ActionArguments.SingleOrDefault(p => p.Value is Employee);
if (employeeObject.Value == null)
{
context.Result = new BadRequestObjectResult("Employee value cannot be null");
return;
}
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
Теперь удаляем ставшие ненужными части кода и применяем созданный нами атрибут-фильтр:
public class EmployeeController : ControllerBase
{
[EmployeeValidationFilter]
[HttpPost]
public IActionResult Post([FromBody] Employee value)
{
// Perform save actions
return Ok();
}
[EmployeeValidationFilter]
[HttpPut]
public IActionResult Put([FromBody] Employee value)
{
// Perform update actions
return Ok();
}
}
Теперь код выглядит гораздо компактнее и красивее, но в нашем случае его ещё можно упростить: т.к. в контроллере всего 2 метода и оба используют один и тот же фильтр, то можно применить атрибут непосредственно к контроллеру:
[EmployeeValidationFilter]
public class EmployeeController : ControllerBase
{
// Perform update actions
}
Таким образом, с помощью фильтров действий мы вынесли повторяющиеся отрывки кода. Выглядит это просто. Но что нужно сделать, если в фильтр действий нам необходимо передать зависимость?
Разработчики часто сталкиваются с задачей, когда требуется добавить логирование для определённых методов. Поэтому попробуем добавить в фильтры действий средство логирования, которое будет записывать информацию перед выполнением методов POST или PUT контроллера и сразу после. Наш фильтр будет выглядеть следующим образом:
public class LoggingFilter: IActionFilter
{
private readonly ILogger _logger;
public LoggingFilter(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<LoggingFilter>();
}
public void OnActionExecuted(ActionExecutedContext context)
{
_logger.LogInformation($"{context.ActionDescriptor.DisplayName} executed");
}
public void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation($"{context.ActionDescriptor.DisplayName} is executing");
}
}
Теперь мы можем применить этот фильтр либо глобально, либо к конкретной области. Сначала попробуем зарегистрировать его глобально. Для этого нам в Startup.cs следует добавить следующие строки:
services.AddControllers(options =>
{
options.Filters.Add<LoggingFilter>();
});
Если же нам нужно применить фильтр, например, к определённому методу контроллера, то следует его использовать вместе с ServiceFilterAttribute:
[HttpPost]
[ServiceFilter(typeof(LoggingFilter))]
public IActionResult Post([FromBody] Employee value)
ServiceFilterAttribute является фабрикой для других фильтров, реализующей интерфейс IFilterFactory и использующей IServiceProvider для получения нужного фильтра. Поэтому в Startup.cs нам необходимо зарегистрировать наш фильтр следующим образом:
services.AddSingleton<LoggingFilter>();
Запустив проект, мы сможем убедиться, что фильтр действий применился только к тем контроллерам и методам, у которых он указан в качестве атрибута.
Внедрение зависимостей в фильтры действий позволяет создавать удобные атрибуты-утилиты, которые можно легко переиспользовать. Ниже представлен пример фильтра, который проверяет наличие объекта в хранилище по Id и возвращает его, если он есть:
public class ProviderFilter : IActionFilter
{
private readonly IDataProvider _dataProvider;
public ProviderFilter(IDataProvider dataProvider)
{
_dataProvider = dataProvider;
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
object idValue;
if (!context.ActionArguments.TryGetValue("id", out idValue))
{
throw new ArgumentException("id");
}
var id = (int)idValue;
var result = _dataProvider.GetElement(id);
if (result == null)
{
context.Result = new NotFoundResult();
}
else
{
context.HttpContext.Items.Add("result", result);
}
}
}
Применить этот фильтр можно так же, как и фильтр из предыдущего примера, с помощью ServiceFilterAttribute.
Фильтры действий раньше очень часто применяли, чтобы заблокировать контент для определённых браузеров на основе информации о User-Agent. На ранних этапах становления веб-разработки многие сайты создавались исключительно для наиболее популярных браузеров, остальные же считались «запрещёнными». Сейчас данный подход является нежелательным, т.к. рекомендуется создавать такую HTML-разметку, которую смогло бы поддерживать большинство браузеров. Тем не менее, в некоторых случаях разработчику важно знать источник запроса. Ниже представлен пример получения User-Agent-информации в фильтре действий:
public class BrowserCheckFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
var userAgent = context.HttpContext.Request.Headers[HeaderNames.UserAgent].ToString().ToLower();
// Detect if a user uses IE
if (userAgent.Contains("msie") || userAgent.Contains("trident"))
{
// Do some actions
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
Стоит, однако, заметить, что вышеуказанный метод имеет ещё один недостаток. Многие браузеры умеют прятать или подделывать значения, указанные в User-Agent, поэтому данный способ не является однозначно достоверным в определении типа пользовательского браузера.
Другой пример применения фильтров действий — локализация. Создадим фильтр, который в зависимости от указанной культуры будет выводить дату в этой культуре. Ниже представлен код, который задаёт культуру текущего потока:
public class LocalizationActionFilterAttribute: ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var language = (string)filterContext.RouteData.Values["language"] ?? "en";
var culture = (string)filterContext.RouteData.Values["culture"] ?? "GB";
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo($"{language}-{culture}");
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo($"{language}-{culture}");
}
}
Следующим шагом следует добавить маршрутизацию, которая будет перенаправлять URL с данными культуры на наш контроллер:
endpoints.MapControllerRoute(name:"localizedRoute",
pattern: "{language}-{culture}/{controller}/{action}/{id}",
defaults: new
{
language = "en",
culture = "GB",
controller = "Date",
action = "Index",
id = "",
});
Код выше создаёт маршрут с именем localizedRoute, у которого в шаблоне имеется параметр, отвечающий за локализацию. Значение по умолчанию для этого параметра — “en-GB”.
Теперь создадим контроллер с именем DateController, который будет обрабатывать наш запрос, и представление, которое будет отображать локализованную дату. Код контроллера просто возвращает представлению текущую дату:
[LocalizationActionFilter]
public class DateController : Controller
{
public IActionResult Index()
{
ViewData["Date"] = DateTime.Now.ToShortDateString();
return View();
}
}
После того как пользователь перешёл по ссылке localhost:44338/Date, он увидит в браузере следующее:
На скриншоте выше текущая дата представлена с учётом локализации, заданной по умолчанию, т.е. с en-GB. Теперь, если пользователь перейдёт по ссылке, в которой будет явно указана культура, например, en-US, то он увидит следующее:
Таким образом, на этом примере мы можем увидеть, как сделать простую и быструю локализацию.
Заключение
В заключение следует заметить, что фильтры — это лишь один из множества механизмов, предоставляемых ASP.NET. Поэтому существует большая вероятность, что проблему можно решить другими, более привычными способами, например, через создание сервиса, в который можно вынести повторяющийся код. Большое же преимущество фильтров, как можно видеть выше, в том, что их просто реализовать и использовать. Поэтому о них стоит помнить как о несложных способах, которые можно всегда внедрить в проект, чтобы поддерживать код в чистоте.
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, Java, Scala] Scala 3: избавление от implicit. Тайпклассы (перевод)
- [Программирование, Анализ и проектирование систем, Проектирование и рефакторинг, ООП] Symfony и Гексагональная архитектура (перевод)
- [Программирование, Отладка, Go, DevOps] Monitoring your application with distributed tracing so you actually know what it's doing
- [Компьютерное железо, Старое железо, DIY или Сделай сам, Электроника для начинающих] Инженер купил 220 нерабочих плат Raspberry Pi Model B и начал их ремонтировать
- [Программирование, Алгоритмы, Go] Algorithms in Go: Merge Intervals
- [Разработка веб-сайтов, JavaScript, Программирование, Алгоритмы, Читальный зал] Библиотека Frontend-разработчика, часть 4: Алгоритмы
- [Программирование, Облачные сервисы, Микросервисы] Введение в паттерн распределенной трассировки (перевод)
- [Информационная безопасность, Программирование, Java, GitHub, Софт] Architectural approaches to authorization in server applications: Activity-Based Access Control Framework (перевод)
- [Программирование, Работа с 3D-графикой, 3D-принтеры, Звук] Флейты, программист и производство
- [Open source, Программирование, Go, Управление разработкой] Релиз ruleguard v0.3.0
Теги для поиска: #_programmirovanie (Программирование), #_.net, #_asp, #_c#, #_actionfilter, #_c#, #_refactoring, #_tips_and_tricks, #_blog_kompanii_arkadija (
Блог компании Аркадия
), #_programmirovanie (
Программирование
), #_.net, #_asp, #_c#
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 20:33
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Введение В свободное от работы время я, как и многие другие разработчики, занимаюсь созданием своих приложений, чтобы опробовать самые последние технологии, создать что-то полезное для повседневного использования или просто открыть для себя что-то новое. Одним из таких проектов было веб-приложение, которое обрабатывало данные, введённые пользователем, и планировало выполнение задач, основанных на введённых данных. Так как личные проекты не ограничены во времени, то мне хотелось по возможности избавить проект от всех раздражающих моментов. И одним из таких моментов были повторяющиеся строчки кода в методах контроллера. Я начал искать решение этой проблемы и наткнулся на фильтры. Идея использования фильтров для поддержания чистоты кода показалась мне не только интересной и эффективной, но в то же время простой, поэтому я решил поделиться этой информацией с вами. Роль фильтров в процессе обработки запроса Сначала обсудим сами фильтры: для чего же они нужны? Фильтры позволяют выполнять определённые действия на различных стадиях обработки запроса в ASP.NET Core. Существуют следующие встроенные фильтры:
Ниже представлена схема, которая показывает, в каком порядке вызываются фильтры в процессе обработки запроса: Из всех фильтров наиболее полезным в повседневном программировании я нахожу фильтры действий. С их помощью можно вынести повторяющиеся операции и хранить их в одном месте. Далее я приведу примеры, как можно «почистить» код, но сперва расскажу о самих фильтрах действий. Внутреннее устройство фильтров действий Фильтры действий в ASP.NET Интерфейс IActionFilter, который нужно реализовать, чтобы создать фильтр действий, существовал ещё в ASP.NET MVC. Он определяет методы OnActionExecuting, который вызывается перед выполнением метода контроллера, и OnActionExecuted, который вызывается сразу после. Ниже представлен пример простейшего фильтра действий, который выводит информацию во время отладки приложения до и после выполнения метода контроллера: public class CustomActionFilter:IActionFilter
{ public void OnActionExecuting(ActionExecutingContext filterContext) { Debug.WriteLine("Before Action Execution"); } public void OnActionExecuted(ActionExecutedContext filterContext) { Debug.WriteLine("After Action Execution"); } } Чтобы использовать вышеуказанный фильтр, его нужно зарегистрировать. Для этого в файле FilterConfig.cs, который находится в папке App_Start, следует добавить следующую строку: public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{ filters.Add(new HandleErrorAttribute()); filters.Add(new CustomActionFilter()); } Но гораздо удобнее использовать фильтры как атрибуты. Для этих целей существует абстрактный класс ActionFilterAttribute, который унаследован от класса FilterAttribute, а также реализует интерфейсы IActionFilter и IResultFilter. Таким образом, наш класс можно переписать следующим образом: public class CustomActionFilterAttribute:ActionFilterAttribute
{ public override void OnActionExecuting(ActionExecutingContext filterContext) { Debug.WriteLine("Before Action Execution"); } public override void OnActionExecuted(ActionExecutedContext filterContext) { Debug.WriteLine("After Action Execution"); } } Теперь, чтобы применить наш фильтр, мы добавляем его к методу контроллера следующим образом: public class HomeController : Controller
{ [CustomActionFilter] public ActionResult Index() { return View(); } } Этот способ также удобен тем, что мы можем применять фильтры к определённому методу или к контроллеру целиком, а не регистрировать их глобально. Фильтры действий в ASP.NET Core С появлением ASP.NET Core в фильтрах действий произошёл ряд изменений. Кроме интерфейса IActionFilter, теперь имеется ещё и IAsyncActionFilter, который определяет единственный метод OnActionExecutionAsync. Ниже приведён пример класса, реализующего интерфейс IAsyncActionFilter: public class AsyncCustomActionFilterAttribute:Attribute, IAsyncActionFilter
{ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { Debug.WriteLine("Before Action Execution"); await next(); Debug.WriteLine("After Action Execution"); } } В качестве второго параметра методу передаётся делегат ActionExecutionDelegate, с помощью которого вызываются либо следующие по порядку фильтры действий, либо сам метод контроллера. Применяют такой фильтр так же, как и синхронный: public class HomeController : Controller
{ [AsyncCustomActionFilter] public ActionResult Index() { return View(); } } Также изменения затронули абстрактный класс ActionFilterAttribute: теперь он наследуется от класса Attribute и реализует синхронные и асинхронные интерфейсы для фильтров действий (IActionFilter и IAsyncActionFilter) и для фильтров результатов (IResultFilter и IAsyncResultFilter), а также интерфейс IOrderedFilter. Фильтры действий в действии Перейдём непосредственно к случаям, когда лучше использовать фильтры действий. Возьмём, например, ситуацию, когда мы создаём веб-приложение и нам нужно сохранить данные, которые приложение получает с помощью метода POST. Допустим, мы ведём сведения о сотрудниках организации. Чтобы представить данные на сервере, мы используем следующий класс: public class Employee
{ [Required(ErrorMessage = "First name is required")] public string FirstName { get; set; } [Required(ErrorMessage = "Last name is required")] public string LastName { get; set; } [AgeRestriction(MinAge = 18, ErrorMessage = "Date of birth is incorrect")] public DateTime DateOfBirth { get; set; } [StringLength(50, MinimumLength = 2)] public string Position { get; set; } [Range(45000, 200000)] public int Salary { get; set; } } С помощью атрибутов валидации мы можем контролировать корректность введённых данных. Следует заметить, что атрибуты со статическими параметрами не всегда удобны для валидации данных — как поля, указывающие на возраст и зарплату сотрудника, в примере выше. В таких случаях лучше создать отдельный сервис, который будет выполнять проверку таких полей, но в рамках этой статьи я решил воспользоваться исключительно атрибутами валидации. После того как были реализованы методы POST и PUT, мы видим, что оба метода содержат повторяющиеся части кода: [HttpPost]
public IActionResult Post([FromBody] Employee value) { if (value == null) { return BadRequest("Employee value cannot be null"); } if (!ModelState.IsValid) { return BadRequest(ModelState); } // Perform save actions return Ok(); } [HttpPut] public IActionResult Put([FromBody] Employee value) { if (value == null) { return BadRequest("Employee value cannot be null"); } if (!ModelState.IsValid) { return BadRequest(ModelState); } // Perform update actions return Ok(); } И здесь нам на помощь приходят фильтры действий. Создадим новый фильтр действий и перенесём в него повторяющиеся строки следующим образом: public class EmployeeValidationFilterAttribute : ActionFilterAttribute
{ public override void OnActionExecuting(ActionExecutingContext context) { var employeeObject = context.ActionArguments.SingleOrDefault(p => p.Value is Employee); if (employeeObject.Value == null) { context.Result = new BadRequestObjectResult("Employee value cannot be null"); return; } if (!context.ModelState.IsValid) { context.Result = new BadRequestObjectResult(context.ModelState); } } } Теперь удаляем ставшие ненужными части кода и применяем созданный нами атрибут-фильтр: public class EmployeeController : ControllerBase
{ [EmployeeValidationFilter] [HttpPost] public IActionResult Post([FromBody] Employee value) { // Perform save actions return Ok(); } [EmployeeValidationFilter] [HttpPut] public IActionResult Put([FromBody] Employee value) { // Perform update actions return Ok(); } } Теперь код выглядит гораздо компактнее и красивее, но в нашем случае его ещё можно упростить: т.к. в контроллере всего 2 метода и оба используют один и тот же фильтр, то можно применить атрибут непосредственно к контроллеру: [EmployeeValidationFilter]
public class EmployeeController : ControllerBase { // Perform update actions } Таким образом, с помощью фильтров действий мы вынесли повторяющиеся отрывки кода. Выглядит это просто. Но что нужно сделать, если в фильтр действий нам необходимо передать зависимость? Разработчики часто сталкиваются с задачей, когда требуется добавить логирование для определённых методов. Поэтому попробуем добавить в фильтры действий средство логирования, которое будет записывать информацию перед выполнением методов POST или PUT контроллера и сразу после. Наш фильтр будет выглядеть следующим образом: public class LoggingFilter: IActionFilter
{ private readonly ILogger _logger; public LoggingFilter(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger<LoggingFilter>(); } public void OnActionExecuted(ActionExecutedContext context) { _logger.LogInformation($"{context.ActionDescriptor.DisplayName} executed"); } public void OnActionExecuting(ActionExecutingContext context) { _logger.LogInformation($"{context.ActionDescriptor.DisplayName} is executing"); } } Теперь мы можем применить этот фильтр либо глобально, либо к конкретной области. Сначала попробуем зарегистрировать его глобально. Для этого нам в Startup.cs следует добавить следующие строки: services.AddControllers(options =>
{ options.Filters.Add<LoggingFilter>(); }); Если же нам нужно применить фильтр, например, к определённому методу контроллера, то следует его использовать вместе с ServiceFilterAttribute: [HttpPost]
[ServiceFilter(typeof(LoggingFilter))] public IActionResult Post([FromBody] Employee value) ServiceFilterAttribute является фабрикой для других фильтров, реализующей интерфейс IFilterFactory и использующей IServiceProvider для получения нужного фильтра. Поэтому в Startup.cs нам необходимо зарегистрировать наш фильтр следующим образом: services.AddSingleton<LoggingFilter>();
Запустив проект, мы сможем убедиться, что фильтр действий применился только к тем контроллерам и методам, у которых он указан в качестве атрибута. Внедрение зависимостей в фильтры действий позволяет создавать удобные атрибуты-утилиты, которые можно легко переиспользовать. Ниже представлен пример фильтра, который проверяет наличие объекта в хранилище по Id и возвращает его, если он есть: public class ProviderFilter : IActionFilter
{ private readonly IDataProvider _dataProvider; public ProviderFilter(IDataProvider dataProvider) { _dataProvider = dataProvider; } public void OnActionExecuted(ActionExecutedContext context) { } public void OnActionExecuting(ActionExecutingContext context) { object idValue; if (!context.ActionArguments.TryGetValue("id", out idValue)) { throw new ArgumentException("id"); } var id = (int)idValue; var result = _dataProvider.GetElement(id); if (result == null) { context.Result = new NotFoundResult(); } else { context.HttpContext.Items.Add("result", result); } } } Применить этот фильтр можно так же, как и фильтр из предыдущего примера, с помощью ServiceFilterAttribute. Фильтры действий раньше очень часто применяли, чтобы заблокировать контент для определённых браузеров на основе информации о User-Agent. На ранних этапах становления веб-разработки многие сайты создавались исключительно для наиболее популярных браузеров, остальные же считались «запрещёнными». Сейчас данный подход является нежелательным, т.к. рекомендуется создавать такую HTML-разметку, которую смогло бы поддерживать большинство браузеров. Тем не менее, в некоторых случаях разработчику важно знать источник запроса. Ниже представлен пример получения User-Agent-информации в фильтре действий: public class BrowserCheckFilter : IActionFilter
{ public void OnActionExecuting(ActionExecutingContext context) { var userAgent = context.HttpContext.Request.Headers[HeaderNames.UserAgent].ToString().ToLower(); // Detect if a user uses IE if (userAgent.Contains("msie") || userAgent.Contains("trident")) { // Do some actions } } public void OnActionExecuted(ActionExecutedContext context) { } } Стоит, однако, заметить, что вышеуказанный метод имеет ещё один недостаток. Многие браузеры умеют прятать или подделывать значения, указанные в User-Agent, поэтому данный способ не является однозначно достоверным в определении типа пользовательского браузера. Другой пример применения фильтров действий — локализация. Создадим фильтр, который в зависимости от указанной культуры будет выводить дату в этой культуре. Ниже представлен код, который задаёт культуру текущего потока: public class LocalizationActionFilterAttribute: ActionFilterAttribute
{ public override void OnActionExecuting(ActionExecutingContext filterContext) { var language = (string)filterContext.RouteData.Values["language"] ?? "en"; var culture = (string)filterContext.RouteData.Values["culture"] ?? "GB"; Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo($"{language}-{culture}"); Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo($"{language}-{culture}"); } } Следующим шагом следует добавить маршрутизацию, которая будет перенаправлять URL с данными культуры на наш контроллер: endpoints.MapControllerRoute(name:"localizedRoute",
pattern: "{language}-{culture}/{controller}/{action}/{id}", defaults: new { language = "en", culture = "GB", controller = "Date", action = "Index", id = "", }); Код выше создаёт маршрут с именем localizedRoute, у которого в шаблоне имеется параметр, отвечающий за локализацию. Значение по умолчанию для этого параметра — “en-GB”. Теперь создадим контроллер с именем DateController, который будет обрабатывать наш запрос, и представление, которое будет отображать локализованную дату. Код контроллера просто возвращает представлению текущую дату: [LocalizationActionFilter]
public class DateController : Controller { public IActionResult Index() { ViewData["Date"] = DateTime.Now.ToShortDateString(); return View(); } } После того как пользователь перешёл по ссылке localhost:44338/Date, он увидит в браузере следующее: На скриншоте выше текущая дата представлена с учётом локализации, заданной по умолчанию, т.е. с en-GB. Теперь, если пользователь перейдёт по ссылке, в которой будет явно указана культура, например, en-US, то он увидит следующее: Таким образом, на этом примере мы можем увидеть, как сделать простую и быструю локализацию. Заключение В заключение следует заметить, что фильтры — это лишь один из множества механизмов, предоставляемых ASP.NET. Поэтому существует большая вероятность, что проблему можно решить другими, более привычными способами, например, через создание сервиса, в который можно вынести повторяющийся код. Большое же преимущество фильтров, как можно видеть выше, в том, что их просто реализовать и использовать. Поэтому о них стоит помнить как о несложных способах, которые можно всегда внедрить в проект, чтобы поддерживать код в чистоте. =========== Источник: habr.com =========== Похожие новости:
Блог компании Аркадия ), #_programmirovanie ( Программирование ), #_.net, #_asp, #_c# |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 20:33
Часовой пояс: UTC + 5