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

Одним из нововведений в 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]. Рассмотрим его использование на конкретных примерах.

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

<p>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) { ... }
}
</p>

В последующих примерах будем считать что у контроллера не заданы [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}
int32-битное целое число.{val:int}
lengthПозволяет указать точную длину строки или ее диапазон.{val:length(8)}

{val:length(3,6)}
long64-битное целое число.{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() { ... }
}

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