Маршрутизация с помощью атрибутов

76

Маршруты во всех примерах, приведенных в предыдущих статьях, были определены посредством методики, которая называется маршрутизацией на основе соглашений. В версии MVC 5 появилась поддержка новой методики, известной как маршрутизация с помощью атрибутов, при которой маршруты определяются атрибутами C#, примененными непосредственно к классам контроллеров. В последующих разделах будет показано то, как создавать и конфигурировать маршруты с использованием атрибутов, которые могут свободно смешиваться со стандартными маршрутами на основе соглашений.

Сравнение маршрутизации на основе соглашений и маршрутизации с помощью атрибутов

Маршрутизация с помощью атрибутов входит в число крупных добавлений для версии MVC 5, но я должен отметить, что не являюсь особым ее поклонником. Как было указано ранее, одной из главных целей шаблона MVC является разделение приложения на разные части, чтобы упростить написание, тестирование и сопровождение. Я отдаю предпочтение маршрутизации на основе соглашений, поскольку контроллеры не осведомлены и никак не зависят от конфигурации маршрутизации. В противоположность этому я считаю маршрутизацию с помощью атрибутов запутывающей дело и размывающей границы между двумя важными компонентами приложения.

Тем не менее, с выходом версии MVC 5 маршрутизация с помощью атрибутов поддерживается как часть инфраструктуры MVC Framework и вы должны прочитать последующие разделы и сформировать о ней собственное мнение. Тот факт, что мне не нравится та или иная возможность, не должен служить для вас причиной отказа от ее применения в собственных проектах.

Хорошие новости заключаются в том, что оба подхода к созданию маршрутов используют одну и ту же лежащую в основе инфраструктуру, а это значит, что их можно применять в рамках одного проекта без каких-либо негативных последствий.

Включение и применение маршрутизации с помощью атрибутов

Маршрутизация с помощью атрибутов по умолчанию отключена. Включается она посредством расширяющего метода MapMvcAttributeRoutes(), который вызывается на объекте RouteCollection, передаваемом в качестве аргумента статическому методу RegisterRoutes(). В примере ниже приведено содержимое файла RouteConfig.cs, в который был добавлен вызов метода MapMvcAttributeRoutes(), а также упрощены маршруты в приложении, чтобы можно было сосредоточить внимание на использовании атрибутов:

using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Mvc.Routing.Constraints;
using UrlsAndRoutes.Infrastructure;

namespace UrlsAndRoutes
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapMvcAttributeRoutes();

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new
                {
                    controller = "Home",
                    action = "Index",
                    id = UrlParameter.Optional
                },
                namespaces: new[] { "UrlsAndRoutes.Controllers" });
        }
    }
}

Вызов метода MapMvcAttributeRoutes() заставляет систему маршрутизации проинспектировать классы контроллеров в приложении в поисках атрибутов, конфигурирующих маршруты. Наиболее важный атрибут называется Route, и в примере ниже применяется к методу действия Index():

using System.Web.Mvc;

namespace UrlsAndRoutes.Controllers
{
    public class CustomerController : Controller
    {
        // ...

        [Route("Test")]
        public ActionResult Index()
        {
            ViewBag.Controller = "Customer";
            ViewBag.Action = "Index";
            return View("ActionName");
        }
	}
}

В примере продемонстрировано базовое использование атрибута Route, предусматривающее определение статического маршрута для метода действия. В атрибуте Route определены два свойства:

Свойство Name

Назначает маршруту имя, используемое для генерации исходящих URL из специфичного маршрута.

Свойство Template

Определяет шаблон, который будет использоваться для сопоставления с URL, нацеленными на данный метод действия.

Если во время применения атрибута Route предоставляется только одно значение (как делалось в примере выше), то это значение считается шаблоном, который будет использоваться при сопоставлении с маршрутами. Шаблоны для атрибута Route следуют той же самой структуре, как и при маршрутизации на основе соглашений, хотя есть ряд отличий, когда дело доходит до ограничений сопоставления маршрутов.

В рассматриваемом примере атрибут Route применяется для указания на то, что действие Index контроллера Customer может быть доступно через URL вида /Test. Результат можно видеть на рисунке ниже:

Эффект от применения атрибута Route

Когда метод действия декорирован атрибутом Route, он больше не может быть доступен через маршруты на основе соглашений, определенные в файле RouteConfig.cs. В данном примере это означает, что действие Index контроллера Customer не будет доступно через URL вида /Customer/Index. Подобный подход к системе маршрутизации можно увидеть на популярном конструкторе логотипов http://logoservis.ru/.

Атрибут Route препятствует нацеливанию маршрутов, основанных на соглашениях, на метод действия, даже если маршрутизация с помощью атрибутов отключена. Позаботьтесь о вызове метода MapMvcAttributeRoutes() в файле RouteConfig.cs, иначе вы создадите недостижимые методы действий.

Атрибут Route влияет только на методы, к которым он применен, а это значит, что хотя метод действия Index() в контроллере Customer достижим через URL вида /Test, к методу действия List() по-прежнему придется обращаться с использованием URL в форме/Customer/List. Атрибут Route можно применять к одному и тому же методу действия несколько раз, при этом каждый случай использования приводит к созданию нового маршрута.

Создание маршрутов с переменными сегментов

Средство маршрутизации с помощью атрибутов поддерживает все возможности маршрутизации на основе соглашений, хотя и выраженные через атрибуты. Это включает создание маршрутов, которые содержат переменные сегментов; пример такого маршрута приведен в примере ниже:

using System.Web.Mvc;

namespace UrlsAndRoutes.Controllers
{
    public class CustomerController : Controller
    {
        // ...

        [Route("Users/Add/{user}/{id}")]
        public string Create(string user, int id)
        {
            return string.Format("Пользователь: {0}, Id: {1}", user, id);
        }
	}
}

Здесь был добавлен метод действия по имени Create(), который принимает аргументы string и int. Для простоты из метода возвращается результат типа string, поэтому создавать представление не придется. Маршрут, определенный атрибутом Route, смешивает статический префикс (Users/Add) с переменными сегментов user и id, которые соответствуют аргументам метода.

Инфраструктура MVC Framework применяет средство привязки моделей, чтобы преобразовать значения переменных сегментов в подходящие типы для вызова метода Create(). Результат перехода на URL вида /Users/Add/Alex/120 показан на рисунке ниже:

Переход на URL с переменными сегментов

Обратите внимание на то, что каждый экземпляр атрибута Route функционирует независимо, а это означает возможность создания совершенно разных маршрутов для нацеливания на методы действий в контроллере, как показано в таблице ниже:

Действия в контроллере Customer и маршруты, которые нацелены на них
Действие URL
Index /Test
Create /Users/Add/Alex/120 (или любые другие значения для последних двух сегментов)
List /Customer/List (через маршрут, определенный в файле RouteConfig.cs)

Применение ограничений маршрутов в атрибутах

Маршруты, определенные с использованием атрибутов, могут быть ограничены подобно маршрутам, которые определяются в файле RouteConfig.cs, хотя применяемый прием имеет более прямой характер. Чтобы продемонстрировать это в работе, в контроллер Customer добавлен новый метод действия, как показано в примере ниже:

using System.Web.Mvc;

namespace UrlsAndRoutes.Controllers
{
    public class CustomerController : Controller
    {
        // ...

        [Route("Users/Add/{user}/{id:int}")]
        public string Create(string user, int id)
        {
            return string.Format("Пользователь: {0}, Id: {1}", user, id);
        }

        [Route("Users/Add/{user}/{password}")]
        public string ChangePass(string user, string password)
        {
            return string.Format(
                "Метод действия ChangePass() - Пользователь: <em>{0}</em>, Пароль: <em>{1}</em>",
                user, password);
        }
	}
}

Новый метод действия по имени ChangePass() принимает два аргумента типа string. С помощью атрибута Route действие ассоциируется с тем же самым шаблоном, что и метод Create(): статическим префиксом /Users/Add, за которым следуют две переменные сегментов. Чтобы различать эти действия, к атрибуту Route для метода Create() применяется ограничение:

[Route("Users/Add/{user}/{id:int}")]

Здесь указано имя переменной сегмента (id), двоеточие и затем int. Это сообщает системе маршрутизации о том, что метод действия Create() должен быть ориентирован на запросы, в которых значение, предоставляемое для сегмента id, является допустимым значением типа int. Ограничение int соответствует классу ограничения IntRouteConstraint, и в таблице из предыдущей статьи был приведен набор атрибутов, которые можно использовать для доступа к встроенным ограничениям на основе типов и значений.

Чтобы увидеть эффект от примененного ограничения, необходимо запустить приложение и запросить URL двух видов - /Users/Add/Alex/120 и /Users/Add/Alex/Пароль12345. Последний сегмент в первом URL представляет собой допустимое значение int и направляется методу Create(). Последний сегмент второго URL не является значением int, поэтому он направляется методу ChangePass(), как показано на рисунке ниже:

Результат применения ограничения в атрибуте Route

Комбинирование ограничений

К переменной сегмента можно применять несколько ограничений с целью дальнейшего сужения диапазона значений, которые будут соответствовать маршруту. В следующем примере демонстрируется комбинирование ограничений alpha и length в маршруте для метода ChangePass():

// ...
[Route("Users/Add/{user}/{password:alpha:length(6)}")]
public string ChangePass(string user, string password)
{
    return string.Format(
        "Метод действия ChangePass() - Пользователь: <em>{0}</em>, Пароль: <em>{1}</em>",
        user, password);
}
// ...

Несколько ограничений выстраиваются в цепочку с использованием того же формата, что и в одиночном ограничении: двоеточия, за которым следует имя ограничения и значения в круглых скобках, когда оно требуется. Маршрут, созданный атрибутом в рассматриваемом примере, будет соответствовать только строкам, которые содержат в точности шесть алфавитных английских символов.

Применяя ограничения, соблюдайте осторожность. Маршруты, определяемые с помощью атрибута Route, работают точно таким же образом, как маршруты, определяемые в файле RouteConfig.cs, и для URL, которые не соответствуют ни одному из методов действий, браузеру будет отправляться результат 404 - Not Found (не найдено). Всегда предусматривайте запасной маршрут, который будет давать соответствие независимо от значений, содержащихся в URL.

Использование префикса маршрута

С помощью атрибута RoutePrefix можно определить общий префикс, который будет применяться ко всем маршрутам, заданным в контроллере. Это может оказаться удобным при наличии множества методов действий, направление на которые должно осуществляться с использованием одного и того же корня URL. В примере ниже демонстрируется применение атрибута RoutePrefix к классу CustomerController:

using System.Web.Mvc;

namespace UrlsAndRoutes.Controllers
{
    [RoutePrefix("Users")]
    public class CustomerController : Controller
    {
        public ActionResult List()
        {
            // ...
        }

        [Route("~/Test")]
        public ActionResult Index()
        {
            // ...
        }

        [Route("Add/{user}/{id:int}")]
        public string Create(string user, int id)
        {
            // ...
        }

        [Route("Add/{user}/{password:length(6)}")]
        public string ChangePass(string user, string password)
        {
            // ...
        }
	}
}

Атрибут RoutePrefix используется для указания на то, что маршруты, связанные с методом действия, должны снабжаться префиксом Users. Определив такой общий префикс, можно удалить его из атрибутов Route для методов действий Create() и ChangePass(). Инфраструктура MVC Framework будет комбинировать общий префикс с шаблоном URL автоматически при создании маршрутов.

Обратите внимание на изменение также и шаблона URL для атрибута Route, применяемого к методу действия Index(), следующим образом:

[Route("~/Test")]

За счет предварения URL префиксом "~/" мы указываем инфраструктуре MVC Framework на то, что атрибут RoutePrefix не должен применяться к методу действия Index(), поэтому он по-прежнему будет доступен через URL вида /Test.

Пройди тесты
Лучший чат для C# программистов