вторник, 21 сентября 2010 г.

ActionFilterAttribute в действии

Простой способ избавления от всевозможного дублирования кода в контроллерах:
Предположим, что в нашей власти оказался достаточно крупный программный проект, в котором есть очень много полезных функций, выводящих очень много разной табличной информации из большой-большой базы. Напрашивается желание выборку по базе производить (и выводить на экран) в виде, удобном пользователю. То есть, постранично.
Общая организация интуитивно ясна - контроллеру в качестве параметра номер страницы, которую мы хотим получить, он выбирает информацию из базы для этой страницы и вызывает соответствующее представление. Имеем url навроде
http://localhost/Reports/ImportantInfo?userId=3&page=4
И имеем определённый упс навроде
http://localhost/Reports/ImportantInfo?userId=3
Начинаются пляски с бубном:
  1. public class ReportsController : Controller
  2. {
  3.   public ActionResult ImportantInfo(int userId, int? page)
  4.   {
  5.     if (!page.HasValue)
  6.     {
  7.       return View("Error404");
  8.       // или
  9.       page = 0;
  10.     }
  11.     // собственно, сам контроллер.
  12.   }
  13. }
Проблема в том, что подобный код приходится вставлять в каждый Action Method, работающий с пагинацией. Дублирование кода - это плохо.

Возьмём другую ситуацию. Предположим, к ряду Action Method-ов (какой-нибудь /Admin/Settings и ещё тонна других) необходимо предоставить доступ только пользователю с правами администратора. Опять, в каждом Action Method пишем проверку на то, является ли текущий пользователь администратором. Опять дублирование кода.

Возьмём третью ситуацию. Action Method отдаёт какой-то JSON-объект для аяксовой начинки, и соответственно его не должен вызывать никто никак кроме как через аякс. Вновь пишем:
  1. public class AllAjaxController : Controller
  2. {
  3.   public JsonResult ImportantInfo(int userId, int? page)
  4.   {
  5.     if (Request.IsAjaxRequest())
  6.     {
  7.       // бла-бла-бла
  8.     }
  9.   }
  10. }

Подобных примеров можно придумать массу, и они имеют одно простое решение, опирающееся на мощный элемент .NET - атрибуты. И в частности в  MVC - на ActionFilterAttribute.

ActionFilterAttribute, примененный к какому-либо действию, имеет два забавных метода - OnActionExecuting() и OnActionExecuted(), которые автоматически вызываются, соответственно, до и после выполнения самого действия контроллера.

Рабочий пример с пагинацией:

  1. public sealed class PageMethodAttribute : ActionFilterAttribute
  2. {
  3.   private const int DefaultNumber = 0;
  4.  
  5.   public override void OnActionExecuting(ActionExecutingContext filterContext)
  6.   {
  7.     base.OnActionExecuting(filterContext);
  8.     // в ActionParameters попадают все параметры, указанные после знака ? в урле. Нету - добавим.
  9.     if (!filterContext.ActionParameters.ContainsKey("page"))
  10.     {
  11.       filterContext.ActionParameters.Add("page", null);
  12.     }
  13.     if (filterContext.ActionParameters["page"] == null)
  14.     {
  15.       filterContext.ActionParameters["page"] = DefaultNumber;
  16.     }
  17.   }
  18. }
После этого наш Action Method ImportantInfo (и все остальные того же рода) начинает выглядеть так:
  1. public class ReportsController : Controller
  2. {
  3.   [PageMethod]
  4.   public ActionResult ImportantInfo(int userId, int page)
  5.   {
  6.     // собственно, сам контроллер.
  7.   }
  8. }

Комментариев нет:

Отправить комментарий