Andrey on .NET | Задание маршрутов при помощи атрибутов

Задание маршрутов при помощи атрибутов

Одним из нововведений в ASP.NET MVC 5 является задание маршрутов при помощи атрибутов, по аналогии с Web API. Такой подход позволяет указывать необходимые настройки непосредственно в самих контроллерах.

Подключаем использование атрибутов

Чтобы активировать маршруты, заданные атрибутами, необходимо вызвать метод MapMvcAttributeRoutes() класса RouteCollection:

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        routes.MapMvcAttributeRoutes();  
        routes.MapRoute(...);
    }
}

При этом необходимо учесть что:

  • При использовании и атрибутов и правил для определения маршрутов, вызовы MapRoute() должны находиться после MapMvcAttributeRoutes().
  • У атрибутов приоритет выше, чем у правил:
    • действия с атрибутами получают получают маршруты согласно значениям этих атрибутов не смотря на заданные правила;
    • действия без атрибутов получают маршруты согласно правилам.
  • При использовании только атрибутов (без правил), не отмеченные ими действия будут не доступны.

Указание на уровне контроллера

Для контроллера можно задать следующие атрибуты:

  • [RouteArea("<значение>")] – устанавливает область для контроллера.
  • [RoutePrefix("<значение>")] – определяет значения префикса в маршруте. По сути, это позволяет переопределить имя контроллера.
  • [Route("{action=<значение>}")] – задает значения по умолчанию для всех действий (в данном примере это action).

Указанные значения будут добавляться к определениям маршрутов для всем действий этого контроллера.

    Указание на уровне действия

    Для отдельного действия применяется атрибут [Route]. Рассмотрим его использование на конкретных примерах.

    Определение маршрута

    public class BooksController : Controller { // Маршрут: /Books/Catalog/ [Route("Books/Catalog/")] public async Task<ActionResult> Catalog() { ... } } [RouteArea("Admin")] [RoutePrefix("Profiles")] public class UsersController : Controller { // Маршрут: /Admin/Profiles/List/ [Route("List")] public async Task<ActionResult> List() { ... } // Маршрут: /Admin/Profiles/{id}/Edit [Route("{id}/Edit")] public async Task<ActionResult> Edit(int id) { ... } }

    В последующих примерах будем считать что у контроллера не заданы [RouteArea] и [RoutePrefix].

    Значения по умолчанию

    [Route("Books/Read/{id=1}")]
    public async Task<ActionResult> Read(int id) { ... }
    

    Опциональные параметры ("?")

    [Route("Books/View/{id?}")]
    public async Task<ActionResult> View(int? id) { ... }
    

    Действие View будет соответствовать как маршруту /Books/View так и /Books/View/{id}.

    Указание абсолютного маршрута ("~")

    // Маршрут: /dashboard
    [Route("~/dashboard")]
    public async Task<ActionResult> UserDashboard() { ... }
    

    Ограничение типа значений параметров

    Возможны следующие ограничения значений параметров:

    Ограничение Описание Пример
    alpha Буквы латинского алфавита: A-Z и a-z. {val:alpha}
    bool Булевское значение. {val:bool}
    datetime Тип DateTime. {val:datetime}
    decimal Десятичное значение. {val:decimal}
    double Тип double (64-битное число с плавающей точкой). {val:double}
    float Тип float (32-битное число с плавающей точкой). {val:float}
    guid Тип Guid. {val:guid}
    int 32-битное целое число. {val:int}
    length Позволяет указать точную длину строки или ее диапазон. {val:length(8)}
    {val:length(3,6)}
    long 64-битное целое число. {val:long}
    max Максимальное значение параметра. {val:max(16)}
    maxlength Максимальная длина строки. {val:maxlength(8)}
    min Минимальное значение параметра. {val:min(10)}
    minlength Минимальная длина строки. {val:minlength(6)}
    range Устанавливает диапазон. {val:range(10,20)}
    regex Задает регулярное выражение, которому должно соответствовать значение. {val:regex(^\d{3}-\d{3}-\d{4}$)}

    Ограничитель отделяется от имени параметра двоеточием, Например, вот указание что id должен быть только целым числом:

    [Route("View/{id:int}")]
    public async Task<ActionResult> View(int id) { ... }
    

    Также можно указать несколько ограничений (в примере – целое числа не меньше 1):

    [Route("View/{id:int:min(1)}")]
    public async Task<ActionResult> View(int id) { ... }
    

    Если параметр является опциональным, то символ "?" ставится после ограничителей:

    [Route("View/{id:int:min(1)?}")]
    public async Task<ActionResult> View(int? id) { ... }
    

    Собственные ограничение типа значений параметров

    Разработчик может создать собственные ограничения. Для этого необходимо реализовать интерфейс IRouteConstraint.

    public class MyConstraint : IRouteConstraint
    {
        private readonly int _param;
    
        public MyConstraint(int param)
        {
            this._param = param;
        }
    
        public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            ...
        }
    }
    

    Метод Match() должен вернуть true, если значение удовлетворяет необходимым условиям.

    Остается зарегистрировать созданное ограничение и его имя в методе RegisterRoutes().

    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
     
            var constraintsResolver = new DefaultInlineConstraintResolver();
            constraintsResolver.ConstraintMap.Add("myConstraint", typeof(MyConstraint));
     
            routes.MapMvcAttributeRoutes(constraintsResolver);
        }
    }
    

    После этого можно использовать его в атрибутах маршрутизации:

    [Route("View/{id:myConstraint(42)")]
    public async Task<ActionResult> View(int id) { ... }
    

    Имена маршрутов

    Атрибуты позволяют определять имена для маршрутов с помощью параметра Name. Такой подход может быть полезен для упрощения поддержки проекта. Например, установим имя HomePage для главной страницы сайта:

    public class HomeController : Controller
    {
        [Route("", Name = "HomePage")]
        public async Task<ActionResult> Index() { ... }
    }
    

    Теперь все ссылки на нее можно задавать следующим образом:

    <a href="@Url.RouteUrl("HomePage")">Home</a>
    

    А теперь сделаем так, что бы главной страницей был список книг. Все что надо сделать – это переместить имя на нужное действие:

    public class BooksController : Controller
    {
        [Route("Books/Catalog/", Name = "HomePage")]
        public async Task<ActionResult> Catalog() { ... }
    }
    

    Ссылки на страницах обновятся автоматически.

    Добавить комментарий