Работа с областями

56

Инфраструктура MVC Framework поддерживает организацию веб-приложения в виде областей, где каждая область представляет функциональный сегмент приложения, такой как администрирование, выписка счетов-фактур, поддержка пользователей и т.д. Это полезно в крупных проектах, в которых наличие единственного набора папок для всех контроллеров, представлений и моделей может вызвать сложности в управлении.

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

Создание области

Средство областей, требует установки в проект нового пакета. Введите в окне консоли NuGet следующую команду:

Install-Package Microsoft.AspNet.Web.Optimization -version 1.1.0 -projectname UrlsAndRoutes

Пакет Optimization содержит функциональность для оптимизации файлов JavaScript и CSS в проекте. Данный пакет напрямую не используется, но от него зависит работоспособность областей.

Чтобы добавить область в пример приложения MVC, щелкните правой кнопкой мыши на элементе проекта UrlsAndRoutes в окне Solution Explorer и выберите в контекстном меню пункт Add --> Area (Добавить --> Область). Среда Visual Studio запросит имя для области:

Добавление области в приложение MVC

В данном случае мы создали область по имени Admin. Эта область создается довольно часто, т.к. многие веб-приложения должны отделять пользовательские функции от административных. Щелкните на кнопке Add, чтобы создать область.

После щелчка на кнопке Add среда Visual Studio добавит к проекту папку Areas. Эта папка содержит папку под названием Admin, которая представляет только что созданную область. При создании дополнительных областей в папке Areas будут появляться соответствующие им папки.

Внутри папки Areas/Admin вы увидите своего рода мини-проект MVC. Здесь присутствуют папки Controllers, Models и Views. Первые две из них пусты, но папка Views содержит папку Shared (и файл Web.config, который конфигурирует механизм визуализации, но мы пока отложим этот вопрос).

Кроме того, папка Areas содержит файл по имени AdminAreaRegistration.cs, в котором определен класс AdminAreaRegistration, как показано в примере ниже:

using System.Web.Mvc;

namespace UrlsAndRoutes.Areas.Admin
{
    public class AdminAreaRegistration : AreaRegistration 
    {
        public override string AreaName 
        {
            get 
            {
                return "Admin";
            }
        }

        public override void RegisterArea(AreaRegistrationContext context) 
        {
            context.MapRoute(
                "Admin_default",
                "Admin/{controller}/{action}/{id}",
                new { action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}

Наиболее интересной частью в этом классе является метод RegisterArea(). В примере видно, что он регистрирует маршрут с шаблоном URL вида "Admin/{controller}/{action}/{id}". В этом методе можно определять дополнительные маршруты, которые будут уникальными для данной области. Если маршрутам назначаются имена, они должны быть уникальными в пределах всего приложения, а не только внутри области, для которой они предназначены.

Нам не придется предпринимать никаких действий для обеспечения вызова упомянутого метода регистрации. Среда Visual Studio добавляет в файл Global.asax оператор, который позаботится о настройке областей при создании проекта:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace UrlsAndRoutes
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
    }
}

Вызов статического метода RegisterAllAreas() приводит к тому, что MVC Framework проходит по всем классам приложения, находит те из них, которые являются производными от AreaRegistration, и вызывает для каждого из них метод RegisterArea().

Не изменяйте порядок следования операторов, относящихся к маршрутизации, в методе Application_Start(). Если вызвать метод RegisterRoutes() до вызова AreaRegistration.RegisterAllAreas(), то ваши маршруты будут определены перед маршрутами областей. Учитывая, что маршруты оцениваются по порядку, это будет означать, что запросы к контроллерам областей, скорее всего, будут сопоставляться с неправильными маршрутами.

Экземпляр класса AreaRegistrationContext, передаваемый методу RegisterArea() каждой области, предоставляет набор методов MapRoute(). Данные методы область может использовать для регистрации маршрутов тем же способом, которым главное приложение делает это в методе RegisterRoutes() в файле Global.asax.

Методы MapRoute() класса AreaRegistrationContext автоматически ограничивают регистрируемые маршруты пространством имен, которое содержит контроллеры для области. Это означает, что при создании контроллера в области он должен быть оставлен в своем стандартном пространстве имен; в противном случае система маршрутизации найти его не сможет.

Заполнение области

Контроллеры, представления и модели в области создаются точно так же, как было показано в предшествующих примерах. Чтобы создать контроллер, щелкните правой кнопкой мыши на папке Controllers внутри области Admin и выберите в контекстном меню пункт Add --> Controller (Добавить --> Контроллер). В открывшемся диалоговом окне выберите в списке вариант MVC 5 Controller - Empty (Контроллер MVC 5 - Пустой), щелкните на кнопке Add, укажите имя контроллера и щелкните на кнопке Add для создания нового класса контроллера.

Чтобы продемонстрировать способ изолирования разделов приложения с помощью областей, в область Admin был добавлен контроллер Home. Содержимое файла Areas/Admin/Controllers/HomeController.cs приведено в примере ниже:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace UrlsAndRoutes.Areas.Admin.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
	}
}

Код контроллера изменяться не будет. В рассматриваемом примере будет достаточно только визуализации стандартного представления, связанного с методом действия Index().

Создайте папку Areas/Admin/Views/Home, щелкните на ней правой кнопкой мыши в окне Solution Explorer и выберите в контекстном меню пункт Add --> MVC 5 View Page (Razor) (Добавить --> Страница представления MVC 5 (Razor)). Укажите в качестве имени Index.cshtml, щелкните на кнопке ОК для создания файла и приведите его содержимое в соответствие с кодом:

@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title></title>
</head>
<body>
    <div>
        <h2>Область Admin, метод действия Index</h2>
    </div>
</body>
</html>

Смысл всего этого - продемонстрировать, что работа внутри области является точно такой же, как и в главной части проекта MVC. Запустив приложение и перейдя на /Admin/Home/Index, вы увидите представление, добавленное в область Admin:

Визуализация представления области

Решение проблемы неоднозначности контроллеров

Следует признать, что ранее я несколько покривил душой: области не являются настолько самодостаточными, насколько могли бы быть. Если перейти на URL вида /Home/Index, возникнет ошибка, показанная на рисунке ниже:

Ошибка неоднозначности контроллеров

Когда область зарегистрирована, любые определенные в ней маршруты ограничиваются пространством имен, которое ассоциировано с этой областью. Именно поэтому мы имеем возможность запрашивать URL вида /Admin/Home/Index и получать экземпляр класса HomeController из пространства имен UrlsAndRoutes.Areas.Admin.Controllers.

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

// ...
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapMvcAttributeRoutes();

            routes.Add(new Route("SayHello", new CustomRouteHandler()));

            routes.Add(new LegacyRoute(
                "~/articles/About_ASPNET_MVC",
                "~/old/NET_Framework_4"
                ));

            routes.MapRoute("MyRoute", "{controller}/{action}");
            routes.MapRoute("MyOtherRoute", "App/{action}", new { controller = "Home" });
        }
// ...

Маршрут с именем MyRoute транслирует входящий URL из браузера в действие Index контроллера Home. В этом месте мы получаем ошибку, поскольку для данного маршрута отсутствуют ограничения по пространствам имен и MVC Framework сможет обнаружить два класса HomeController. Чтобы решить проблему, необходимо установить приоритет для пространства имен главного контроллера во всех маршрутах, которые могут приводить к конфликту, как показано в примере ниже:

// ...
        public static void RegisterRoutes(RouteCollection routes)
        {
            // ...

            routes.MapRoute("MyRoute", "{controller}/{action}", 
                namespaces: new[] {"UrlsAndRoutes.Controllers"});
            routes.MapRoute("MyOtherRoute", "App/{action}", new { controller = "Home" },
                namespaces: new[] { "UrlsAndRoutes.Controllers" });
        }
// ...

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

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

Области можно создавать также и за счет применения атрибута RouteArea к классам контроллеров. В примере ниже демонстрируется назначение методов действий контроллера Customer (в котором ранее мы использовали атрибуты маршрутизации) новой области по имени Services:

using System.Web.Mvc;

namespace UrlsAndRoutes.Controllers
{
    [RouteArea("Services")]
    [RoutePrefix("Users")]
    public class CustomerController : Controller
    {
        public ActionResult List()
        {
            // ...
        }
        
        [Route("~/Test")]
        public ActionResult Index()
        {
            // ...
        }
        
        [Route("Add/{user}/{id:int}", Name="AddRoute")]
        public string Create(string user, int id)
        {
            // ...
        }

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

Атрибут RouteArea перемещает все маршруты, определенные с помощью атрибута Route, в указанную область. Результат влияния этого атрибута в сочетании с атрибутом RoutePrefix заключается в том, что для достижения, например, метода действия Create() понадобится создать URL следующего вида:

http://localhost:64399/Services/Users/Add/Alex/120

Атрибут RouteArea не влияет на маршруты, которые определены посредством атрибута Route и начинаются с ~/. Это означает, что для обращения к методу действия Index() можно продолжать пользоваться приведенным ниже URL:

http: //localhost:64399/Test/

Обратите внимание, что атрибут RouteArea не влияет на методы действий, к которым не был применен атрибут Route, т.е. маршрутизация для метода действия List() определяется в файле RouteConfig.cs, а не на основе атрибутов.

Генерация ссылок на действия в областях

Для создания ссылок, указывающих на действия в области, к которой относится текущий запрос, никаких дополнительных шагов предпринимать не требуется. Инфраструктура MVC Framework определяет, что текущий запрос относится к конкретной области, и обеспечивает поиск соответствия средством генерации исходящих Url только среди маршрутов, определенных для этой области. Например, следующее дополнение к вызову вспомогательного метода Html.ActionLink() в представлении Admin:

@Html.ActionLink("Админка", "Index")

генерирует такую HTML-разметку:

<a href="/Admin/Home">Админка</a>

Чтобы создать ссылку на действие в другой области или вообще вне какой-либо области, понадобится создать переменную по имени area и применять ее для указания имени необходимой области, например:

@Html.ActionLink("Админка", "Index", new { area = "Admin" })

По этой причине имя area запрещено использовать в качестве переменной сегмента. HTML-разметка, сгенерированная таким вызовом, будет выглядеть следующим образом:

<a href="/Admin/Home">Админка</a>

Чтобы ссылка указывала на действие одного из контроллеров верхнего уровня (контроллера из папки /Controllers), для значения переменной area должна быть задана пустая строка:

...
@Html.ActionLink("Ссылка", "Index", new { area = "" })
...
Пройди тесты
Лучший чат для C# программистов