Основы. Часть 8 - Области

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

Как правило блок администрирования располагается в каталоге Admin или Administrator, куда вход закрыт паролем. На текущем этапе не будем вдаваться в подробности авторизации в ASP.NET MVC. Поэтому пока просто сделаем так, чтобы управления каталогом была доступно по адресу http:///Admin/.

Решение “в лоб” и почему так делать не стоит

В качестве решения можно создать Контроллер Admin и переложить все функции на него. Вроде бы логично, но к чему это приведёт? Давайте представим, сколько может быть задачи у системы управления веб-приложением? Легко понять, что в реальном проекте такой Контроллер будет содержать очень большое число Действий. В результате, такой подход усложнит дальнейшее сопровождение кода.

Также необходимо отметить, что созданный в этом случае класс AdminController объединит в себе функции многих Контроллеров, у которых были четко определенные задачи. По сути, получится “мастер на все руки”, что является плохой практикой при проектировании и разработке приложений. При этом от слова Admin в тексте ссылки необходима только группировка функциональности по её общему смыслу.

Поэтому, для решения подобных задач в ASP.NET MVC была введена поддержка областей (Area).

Использование областей (Area) в ASP.NET MVC 3

Идея областей очень простая – позволить разделить большой проект на несколько логических частей, которые могут взаимодействовать друг с другом. При этом они вправе содержать собственные Контроллеры и Представления, а также расширять Модель своими классами.

Кроме того, таким образом можно разделить большой проект между несколькими разработчиками или группами. Например, на сайте могут быть разделы форум и блог со ссылками http:///Forum/ и http:///Blog/ соответственно. При этом они могут использовать общую Модель для идентификации посетителя сайта, но при этом иметь и собственные элементы бизнес-логики.

Рассмотрим создание области на конкретном примере.

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

Создание области ASP.NET MVCДля создания новой области выберем в контекстном меню проекта, открывающемся в Solution Explorer / Navigation Explorer, пункт “Add > Area …” и в появившемся диалоге “Add Area” введем имя Admin.

Помощник ASP.NET MVC 3 добавит в проект папку Areas, в которой будут размещены все дополнительные области. При этом главной или областью по умолчанию будем называть корневой каталог веб-приложения.

Структура области ASP.NET MVCВ папке Areas размещена подпапка только что созданной области Admin. Внутри последней создана структура, аналогичная структуре самого проекта: папки Controllers, Models и Views. Таким образом, область может использовать как классы проекта, так и добавлять свои специфические объекты.

Создание Контроллеров и Представлений осуществляется также с использованием помощника ASP.NET MVC или заготовок, для которых область указывается в качестве значения параметра -Area. И как еще один вариант, можно добавлять классы и файлы вручную.

Стоит обратить внимание на файл AreaRegistration.cs (в данном случае это AdminAreaRegistration.cs). В нем расположена информация для регистрации области в ядре ASP.NET MVC 3. Взглянем на него чуть подробнее:

namespace BookCatalog.Areas.Admin
{
    using System.Web.Mvc;

    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 {
                    controller = "Catalog",
                    action = "Index",
                    id = UrlParameter.Optional
                }
            );
        }
    }
}

Класс AdminAreaRegistration является наследником AreaRegistration. Его свойство AreaName содержит имя области, которое будет использоваться внутри ASP.NET MVC. Метод RegisterArea() предназначен для выполнения необходимых действий при её регистрации. В данном случае определяется маршрут, по которому будет доступна данная область. Выделенная в исходном коде строка добавлена для указания Контроллера по умолчанию.

Все области проекта регистрируется в Application_Start() при помощи AreaRegistration.RegisterAllAreas().

Рассмотрим необходимые действия для переноса Контроллеров и Представлений из главной в только что созданную область. При этом Модель затронута не будет.

Переносим функциональность

Для переноса функциональности в область Admin просто скопируем в неё все Контроллеры и их Представления. Сделать это можно просто перетащив их мышкой в Solution Explorer / Navigation Explorer. Затем исправим пространства имен с BookCatalog.Controllers на соответствующее новому месту расположения – BookCatalog.Areas.Admin.Controllers.

Создадим новый шаблон разметки _Layout.cshtml в папке Areas/Admin/Views/Shared. Его содержимое можно скопировать с существующего варианта в главной области. Исключение составит только новая ссылка для возврата к главной странице:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script>
</head>
<body>
    <div class="menu">
        @Html.ActionLink("Catalog", "Index", "Catalog")&nbsp;|&nbsp;
        @Html.ActionLink("Publishers", "Index", "Publishers")&nbsp;|&nbsp;
        @Html.ActionLink("Languages", "Index", "Languages")&nbsp;|&nbsp;
        @Html.ActionLink("Tags", "Index", "Tags")&nbsp;|&nbsp;
        @Html.ActionLink("Back", string.Empty, string.Empty, new { area = "" }, null)
    </div>
    @RenderBody()
</body>
</html>

Ссылки, создаваемые с помощью ActionLink(), осуществляют навигацию в границах текущей области. Чтобы выйти за её пределы необходимо явно указать это в параметрах метода. В выделенной строке осуществляется переход к главной странице веб-приложения. При этом будут задействованы Контроллер и Действие по умолчанию, определенные для главной области.

Следующим шагом является создание файла _ViewStart.cshtml с указанием Представлениям в Areas/Admin/Views использовать новый шаблон разметки:

@{
    Layout = "~/Areas/Admin/Views/Shared/_Layout.cshtml";
}

Добавляем функции в главную область

Новый Контроллер

Поскольку в главной области не осталось Контроллеров, добавим в неё один для отображения списка книг. С этой целью создадим класс CatalogController в папке Controllers со следующим кодом:

namespace BookCatalog.Controllers
{
    using System.Web.Mvc;
    using BookCatalog.Models.Repositories;

    public class CatalogController : Controller
    {
        private readonly IBookDetailsRepository _bookdetailsRepository;

        // If you are using Dependency Injection, you can delete the following constructor
        public CatalogController()
            : this(new BookDetailsRepository())
        {
        }

        public CatalogController(IBookDetailsRepository bookdetailsRepository)
        {
            this._bookdetailsRepository = bookdetailsRepository;
        }

        // GET: /Catalog/
        public ViewResult Index()
        {
            return this.View(
                this._bookdetailsRepository.AllIncluding(
                    bookdetails => bookdetails.Language,
                    bookdetails => bookdetails.Publisher,
                    bookdetails => bookdetails.Tags));
        }
    }
}

Здесь определено только одно Действие Index, ответственное за вывод списка. Обратите внимание, что разные области могут содержать Контроллеры с одинаковыми именами.

Представление для отображения каталога книг

Теперь добавим Представление для нового Контроллера: в папке View создадим подпапку Catalog и разместим в ней файл Index.cshtml:

@model IEnumerable<BookCatalog.Models.BookDetails>
@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<table>
    <tr>
        <th>Title</th>
        <th>Author</th>
        <th>PublishedAt</th>
        <th>Url</th>
        <th>Description</th>
        <th>Tags</th>
        <th>Rating</th>
        <th>IsFree</th>
        <th>Language</th>
        <th>Publisher</th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>@item.Title</td>
        <td>@item.Author</td>
        <td>@String.Format("{0:D}", item.PublishedAt)</td>
        <td>@item.Url</td>
        <td>@item.Description</td>
        <td>@string.Join(", ", item.Tags.Select(tag => tag.Text).ToArray())</td>
        <td>@item.Rating</td>
        <td>@item.IsFree</td>
        <td>@(item.Language == null ? "None" : item.Language.Name)</td>
        <td>@(item.Publisher == null ? "None" : item.Publisher.Title)</td>
    </tr>
}

</table>

В отличие от Представления из области Admin, здесь удалён вывод ссылок на создание, редактирование и удавление записей о книгах. Кроме того, отсутствует столбец IsVisible, который в дальнейшем и будет отвечать за отображение записи в данном списке.

Изменяем файл разметки

Последнее изменение в главной области – файл разметки. Там также больше не нужны ссылки на различные таблицы. Вместо них поставим только одну ссылку – на область администрирования:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script>
</head>
<body>
    <div class="menu">
        @Html.ActionLink("Adminisrtrator", string.Empty, string.Empty, new { area = "Admin" }, null)
    </div>
    @RenderBody()
</body>
</html>

Здесь также явно указана область, к которой осуществляется переход. При этом будут использованы Контроллер и Действие по умолчанию.

Одинаковые имена Контроллеров

Если сейчас запустить веб-приложение на выполнение, то результатом будет сообщение об ошибке. Дело в том, что ядро ASP.NET MVC 3 видит два класса с одинаковыми именами CatalogController. Оба могут быть Контроллером Catalog, указанным как вариант по умолчанию.

Чтобы разрешить эту ситуацию, необходимо явно указать пространство имен при настройке маршрутов. Для этого в файл Global.asax внесем соответствующее изменение:

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

    routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new {
            controller = "Catalog",
            action = "Index",
            id = UrlParameter.Optional
        }, // Parameter defaults
        new string[] { "BookCatalog.Controllers" }
    );
}

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


Исходный код проекта (C#, Visual Studio 2010): mvc3-in-depth-basics-08.zip