ASP.NET

В ожидании ASP.NET 4.0

Скотт Аллен

Когда в следующем году будут выпущены Visual Studio 2010 и .NET 4.0, мы, разработчики на ASP.NET, получим две зрелые инфраструктуры для создания веб-приложений: ASP.NET Web Forms и ASP.NET MVC. Обе инфраструктуры базируются на исполняющей среде ASP.NET, и в обеих появятся некоторые новые средства, которые откроют дорогу для развития на ближайшее десятилетие.

В одной статье нет места, чтобы рассказать обо всех изменениях в ASP.NET, так как в обе инфраструктуры и нижележащую исполняющую среду было внесено огромное количество усовершенствований. Поэтому я рассмотрю лишь самые важные новые средства для Web Forms и MVC.

Новшества в ASP.NET Web Forms

К моменту выпуска четвертой версии ASP.NET Web Forms исполнится восемь лет, и группа, отвечающая за эту инфраструктуру, продолжает ее совершенствовать. В своей прошлой статье я кратко описал некоторые из усовершенствований, например новые классы, упрощающие использование средств перенаправления URL (URL routing features), теперь включены в базовые сервисы ASP.NET, а новые свойства MetaKeywords и MetaDescription базового класса Page облегчают управление контектом метатегов в форме. Однако это сравнительно мелкие изменения.

Основные изменения в Web Forms учитывают часто высказывавшуюся критику в адрес этой инфраструктуры. Многим разработчикам нужен более полный контроль над HTML, генерируемым веб-формой и ее элементами управления, в том числе над идентификаторами на клиентской стороне, создаваемыми внутри HTML. В версии 4.0 многие серверные элементы управления ASP.NET были переработаны, чтобы генерировать HTML, более простой для применения стилей с помощью CSS и удовлетворяющий сложившимся традициям.  Кроме того, в базовые классы добавлены новые свойства, дающие разработчикам больше контроля над идентификаторами на клиентской стороне, которые генерируются инфраструктурой. Эти изменения мы и обсудим в следующих разделах.

HTML, дружественный к CSS

Один из примеров серверного элемента управления ASP.NET, который весьма утомительно «стилизовать» с помощью CSS — меню. При рендеринге меню генерируются вложенные теги table, включающие атрибуты cellpadding, cellspacing и border. Еще хуже, что элемент управления «меню» встраивает информацию о стилях в ячейки вложенных таблиц и помещает блок подставляемых стилей в начало страницы. В качестве примера рассмотрим следующее определение простого меню:

<asp:Menu runat="server" ID="_menu">
    <Items>
        <asp:MenuItem Text="Home" NavigateUrl="~/Default.aspx" />
        <asp:MenuItem Text="Shop" NavigateUrl="~/Shop.aspx" />
    </Items>
</asp:Menu>

В ASP.NET 3.5 простое меню генерировало такой HTML (некоторые атрибуты опущены или сокращены для ясности):

<table class="..." cellpadding="0" cellspacing="0" border="0">
    <tr id="_menun0">
        <td>
            <table cellpadding="0" cellspacing="0" 
                border="0" width="100%">
                <tr>
                    <td style="...">
                        <a class="..." href="Default.aspx">Home</a>
                    </td>
                </tr>
            </table>
        </td>
    </tr>
</table>

В ASP.NET 4.0 компания Microsoft переработала элемент управления «меню» для создания разметки с более внятной семантикой. Тот же элемент управления «меню» в ASP.NET 4.0 генерирует следующий HTML:

<div id="_menu">
    <ul class="level1">
        <li><a class="level1" href="Default.aspx" target="">Home</a></li>
    </ul>
</div>

Этот тип дружественной к CSS разметки можно было реализовать и в предыдущих версиях ASP.NET, если вы использовали адаптер элемента управления, предоставляющий альтернативную логику рендеринга для данного элемента управления, но теперь разметка дружественна к CSS по умолчанию. Если у вас уже есть таблицы стилей и клиентский сценарий, написанный в расчете на HTML, генерируемый ASP.NET 3.5, вы можете задать атрибут controlRenderingCompatibilityVersion в разделе pages файла web.config равным значению «3.5», и тогда элемент управления будет создавать разметку с вложенными таблицами, как было показано ранее. Значение этого атрибута по умолчанию — «4.0». Заметьте, что в версии 4.0 элемент управления «меню» по-прежнему создает блок стилей в начале страницы, но вы можете отключить эту функцию, установив свойство IncludeStyleBlock элемента управления в false.

Многие другие элементы управления в версии 4.0 тоже дружественны к CSS. Например, такие элементы управления для верификации, как RangeValidator и RequiredFieldValidator, больше не выполняют рендеринг подставляемых стилей (inline styles), а шаблонные элементы управления вроде FormView, Login и Wizard больше не вставляют себя в тег table (но только если свойство RenderOuterTable этих элементов управления установлено в false). Прочие элементы управления также изменены. В частности, вы можете заставить элементы управления RadioButtonList и CheckBoxList помещать свои входные данные внутрь элементов list, задав свойству RepeatLayout значение OrderedList или UnorderedList, что приводит к рендерингу этих элементов управления с использованием элементов ol или li соответственно.

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

Если вы уже писали клиентский сценарий для операций с DOM, то, вероятно, знаете о том, что ASP.NET изменяет атрибуты ID на клиентской стороне. Пытаясь добиться уникальности всех атрибутов ID на странице, ASP.NET формирует клиентский идентификатор из значения свойства ID элемента управления и дополнительной информации. На сервере вы можете обращаться к сгенерированному значению, используя свойство ClientID элемента управления.

В качестве примера, если элемент управления находится внутри контейнера именования (элемента управления, реализующего интерфейс INamingContainer, как это делают пользовательские элементы управления и эталонные страницы), ASP.NET формирует значение ClientID, предваряя идентификатор элемента управления идентификатором контейнера именования. В случае элементов управления, связанных с данными и создающими повторяющиеся блоки HTML, ASP.NET добавит префикс, который включает последовательные номера. Если вы просматривали исходный код любой ASP.NET-страницы, то наверняка встречали идентификаторы вроде «ctl00_content_ctl20_ctl00_loginlink». Такие значения создают дополнительный уровень сложности при написании клиентского сценария для страницы Web Forms.

В Web Forms 4.0 во все элементы управления введено новое свойство ClientIDMode. Это свойство позволяет влиять на алгоритм ASP.NET, применяемый для генерации значения ClientID элемента управления. Установив его в Static, вы сообщаете ASP.NET использовать ID элемента управления как его ClientID без всякой конкатенации или добавления префикса. Например, CheckBoxList в следующем коде будет генерировать тег <ol> с ClientID равным checklist независимо от того, где именно находится данный элемент на странице:

<asp:CheckBoxList runat="server" RepeatLayout="OrderedList" 
                  ID="checklist" ClientIDMode="Static">
    <asp:ListItem>Candy</asp:ListItem>
    <asp:ListItem>Flowers</asp:ListItem>
</asp:CheckBoxList>

Установив ClientIDMode в Static, вы должны гарантировать уникальность клиентских идентификаторов. Если на странице есть дублирующиеся значения идентификаторов, вы в конечном счете нарушите работу любых сценариев, которые ищут DOM-элементы по их значениям ID.

Свойство ClientIDMode поддерживает еще три значения. Значение Predictable полезно для элементов управления, реализующих IDataBoundListControl, например для GridView и ListView. Использование Predictable в сочетании со свойством ClientIDRowSuffix этих элементов управления приводит к генерации клиентских идентификаторов со специфическими значениями, помещаемыми в конец идентификаторов. Так, следующий ListView будет связан со списком объектов Employee. У каждого объекта есть свойства EmployeeID и IsSalaried. Комбинация значений свойств ClientIDMode и ClientIDRowSuffix заставит CheckBox сгенерировать клиентский идентификатор наподобие employeeList_IsSalaried_10, где 10 представляет идентификатор соответствующего сотрудника (employee):

<asp:ListView runat="server" ID="employeeList" 
                      ClientIDMode="Predictable"
                      ClientIDRowSuffix="EmployeeID">
            <ItemTemplate>
                <asp:CheckBox runat="server" ID="IsSalaried" 
                              Checked=<%# Eval("IsSalaried") %> />
            </ItemTemplate>
        </asp:ListView>

Другое возможное значение для ClientIDMode — Inherit. Все элементы управления на странице используют это значение свойства ClientIDMode по умолчанию. Inherit означает, что у элемента управления будет то же значение ClientIDMode, что и у родительского элемента управления. В предыдущем примере кода CheckBox наследовал свое значение ClientIDMode от ListView, у которого оно было Predictable. И последнее возможное значение для ClientIDMode — AutoID. Оно указывает ASP.NET применять тот же алгоритм генерации значения свойства ClientID, что и в версии 3.5. По умолчанию свойство ClientIDMode страницы устанавливается в AutoID. А поскольку по умолчанию ClientIDMode всех элементов управления на странице равно Inherit, перенос существующего ASP.NET-приложения на версию 4.0 не изменит алгоритм, применяемый исполняющей средой для генерации клиентских идентификаторов, если только вы не модифицируете значение свойства ClientIDMode. Это свойство также можно настраивать в разделе pages файла web.config, чтобы указывать другое его значение по умолчанию для всех страниц в приложении.

Новый шаблон проектов

Шаблоны проектов Web Application и Web Site в Visual Studio 2008 содержат страницу Default.aspx, файл web.config и папку App_Data. Эти стартовые шаблоны просты и требуют дополнительной работы, прежде чем вы сможете приступить к созданию реального приложения. Те же шаблоны в Visual Studio 2010 предоставляют более обширную инфраструктуру, необходимую для создания приложения с применением современных методик. Экранный снимок нового приложения, генерируемого этими шаблонами, показан на рис. 1.

Новое веб-приложение в Visual Studio 2010

Рис. 1. Новое веб-приложение в Visual Studio 2010

Обратите внимание на то, что новое приложение включает эталонную страницу по умолчанию (Site.master). Все файлы .aspx в новом проекте будут страницами контента, в которых содержатся элементы управления ContentPlaceholder для вставки контента в структуру, определяемую эталонной страницей. Заметьте также, что новый проект включает таблицу стилей в каталоге Content (Site.css). Эталонная страница вставляет эту таблицу стилей с помощью тега link, и внутри таблицы стилей вы найдете ряд стилей, определенных для управления внешним видом тела страницы, заголовками, основной разметкой и т. д. В новый проект входит и каталог Scripts с новейшей версией библиотеки jQuery — инфраструктурой JavaScript с открытым исходным кодом, официально поддерживаемой Microsoft и поставляемой с Visual Studio 2010.

Новый шаблон проекта — благодаря применению эталонных страниц и таблиц стилей — поможет начать работу в правильном направлении при использовании Web Forms. Выполняемая версия нового приложения приведена на рис. 2. Visual Studio 2010 также будет включать шаблоны «Empty» для веб-сайтов и веб-приложений. В этих пустых шаблонах не будет никаких файлов и каталогов, поэтому вы сможете создавать свое приложение с нуля.

Выполнение нового ASP.NET-приложения

Рис. 2. Выполнение нового ASP.NET-приложения

Еще одна добрая весть по поводу новых проектов в ASP.NET 4.0 — файл web.config создается почти пустым. Большая часть конфигурационных параметров, к которым мы привыкли в файлах web.config в ASP.NET 3.5, теперь содержатся в файле machine.config, который находится внутри каталога установки инфраструктуры версии 4.0. К ним относятся конфигурация элементов управления из каталога System.Web.Extensions, HTTP-обработчики и модули, настроенные на поддержку JavaScript-прокси для веб-сервисов, и раздел system.webserver для веб-сайтов под управлением IIS 7.

Новшества в ASP.NET MVC

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

Области

Один из подходов к построению крайне больших ASP.NET-приложений Web Forms — разбиение приложения на множество подпроектов (реализацию облегчает библиотека P&P Web Client Composite Library). Этот подход трудно осуществить в ASP. NET MVC 1.0, потому что он противоречит целому ряду соглашений MVC. Однако в MVC 2.0 будет официальная поддержка подобного варианта с применением концепции области. Область (area) позволяет делить MVC-приложение на проекты веб-приложений или каталоги внутри единого проекта. Области помогают логически отделять разные части одного приложения, упрощая его поддержку и сопровождение.

Родительская область (parent area) веб-приложения на основе MVC — MVC-проект, который включает global.asax и файл web.config корневого уровня для приложения. Родительская область также может содержать общие части контента вроде глобальных для всего приложения таблиц стилей, библиотек JavaScript и эталонных страниц. Дочерние области (child areas) также являются MVC-проектами веб-приложений, но поскольку эти проекты в период выполнения физически существуют под проектом родительской области, предок и его потомки выглядят единым приложением.

В качестве примера вообразите большое приложение складского учета (inventory application). ПОмимо родительской области в нем можно выделить области обработки заказов, дистрибуции, отчетности и административного управления. Каждая область может находиться в отдельном веб-проекте MVC, и в каждом проекте нужно зарегистрировать его маршрут, включив класс, производный от абстрактного базового класса AreaRegistration. В следующем коде мы переопределяем свойство AreaName, чтобы получать описательное название области отчетности, и метод RegisterArea, чтобы определять маршруты (routes) в той же области:

public class ReportingAreaRegistration : AreaRegistration
{
    public override string AreaName
    {
        get { return "Reporting"; }
    }

    public override void RegisterArea(AreaRegistrationContext context)
    {
        context.MapRoute(
            // route name
            "ReportingDefault",
            // url pattern
            "reporting/{controller}/{action}",
            // route defaults
            new { controller = "Home", action = "Index" },
            // namespaces
            new string[] { "Reporting.Controllers" });            
    }
}

Заметьте, что мы включаем строковый массив пространств имен, необходимый для поиска контроллера области отчетности. Ограничение пространств имен, в которых ведется поиск, позволяет размещать контроллеры в разных областях с одинаковыми именами (в приложении, например, может существовать несколько классов HomeController).

Атрибуты DataAnnotation для упрощения проверок

DefaultModelBinder в ASP.NET MVC отвечает за передачу данных из среды запроса в свойства модели. Например, когда средство привязки модели (model binder) видит объект модели со свойством, которое называется Title, он просматривает форму, строку запроса и серверные переменные, чтобы найти переменную с совпадающим именем (Title). Однако средство привязки модели не выполняет никаких проверок на допустимость, кроме преобразований простых типов. Если вы хотите, чтобы свойство Title в вашем объекте модели содержало только строки с 50 символами или менее, вам нужна соответствующая проверка на допустимость при выполнении действия вашего контроллера, а также требуется реализация собственного средства привязки модели или реализация интерфейса IDataErrorInfo в вашей модели.

DefaultModelBinder в ASP.NET MVC 2.0 ищет в объектах модели атрибуты DataAnnotation. Эти атрибуты позволяют вводить в вашу модель ограничения допустимости. Для примера рассмотрим следующий класс Movie:

public class Movie
{
    [Required(ErrorMessage="The movie must have a title.")]
    [StringLength(50, ErrorMessage="The movie title is too long.")]
    public string Title { get; set; }
}

Атрибуты свойства Title сообщают средству привязки модели, что Title является обязательным строковым полем, а его максимальная длина не может превышать 50 символов. Инфраструктура MVC может автоматически выводить текст ErrorMessage в браузере, если проверка дает неудачный результат. Дополнительные встроенные атрибуты проверки включают атрибуты для контроля диапазона и проверки на совпадение с результатом регулярного выражения.

Пока что исполняющая среда MVC использует только атрибуты проверки для контроля на серверной стороне. Как ожидается, группа, отвечающая за MVC, создаст логику проверки на клиентской стороне к моменту выпуска MVC 2.0. Управление проверкой на серверной и клиентской сторонах с помощью атрибутов станет огромным шагом вперед на пути к упрощению сопровождения больших приложений.

Вспомогательные методы шаблонов

Вспомогательные методы шаблонов (templated helpers) в ASP.NET MVC 2.0 также используют атрибуты DataAnnotation, но не для управления логикой проверки, а для управления отображением модели в UI. К ним относятся, в частности, новые вспомогательные HTML-методы DisplayFor и EditorFor. Эти вспомогательные методы ищут шаблоны для данной модели, исходя из ее типа. Например, давайте используем уже рассмотренный класс Movie, но с дополнительным свойством:

public class Movie
{        
    // ...

    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
}

В этом случае в каждом объекте Movie хранится дата выпуска кинофильма, но вряд ли кого-то интересует день его выхода. Нам нужно выводить информацию только о дате, а не о времени. Поэтому данное свойство дополнено атрибутом DataType, который сообщает о наших намерениях.

Чтобы правильно показывать дату выпуска, требуется шаблон отображения. Такой шаблон — просто частичное представление с расширением .ascx, которое находится в папке DisplayTemplates. Сама папка DisplayTemplates может содержаться в папке представления контроллера (тогда шаблон применяется только к представлениям для данного контроллера) или в папке общих представлений (в этом случае шаблон применяется ко всем представлениям). В нашем примере шаблон нужно назвать Date.ascx, и он должен выглядеть так:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%= Html.Encode(String.Format("{0:d}", Model)) %>

Чтобы инфраструктура MVC использовала этот шаблон, при визуализации свойства ReleaseDate надо задействовать вспомогательный метод DisplayFor. Код, показанный на рис. 3, взят из другого шаблона отображения — Movie.ascx.

Рис. 3. Шаблон отображения Movie.ascx

<%@ Control Language="C#" 
    Inherits="System.Web.Mvc.ViewUserControl<Movie>" %>

    <fieldset>
        <legend>Fields</legend>
        <p>
            Title:
            <%= Html.LabelFor(m => m.Title) %>
            <%= Html.DisplayFor(m => m.Title) %>
        </p>
        <p>
            <%= Html.LabelFor(m => m.ReleaseDate) %>
            <%= Html.DisplayFor(m => m.ReleaseDate) %>
        </p>
    </fieldset>

Заметьте: вспомогательные методы LabelFor и DisplayFor являются строго типизированными, что помогает в распространении изменений при модификации модели. Чтобы использовать шаблон Movie.ascx для отображения даты выпуска кинофильма в любой части приложения, нам вновь понадобится метод DisplayFor. Следующий код взят из представления, строго типизированного на основе класса Movie:

<asp:Content ID="detailContent" 
             ContentPlaceHolderID="MainContent" 
             runat="server">                    
        Movie:
        <%= Html.DisplayFor(m => m) %>
        
    </p>
</asp:Content>

Метод DisplayFor строго типизируется с использованием той же модели, что и страница представления, поэтому параметр m в лямбда-выражении DisplayFor имеет тип Movie. При отображении даты выпуска кинофильма DisplayFor будет автоматически использовать шаблон Movie.ascx (который в свою очередь ищет шаблон Date.ascx с помощью DisplayFor). Если бы мы не указали атрибут DataType для свойства ReleaseDate кинофильма, DisplayFor не обратился бы к шаблону Date.ascx и вывел бы дату и время из ReleaseDate, но атрибут DataType помогает направить инфраструктуру к корректному шаблону. Эта концепция строго типизированных, вложенных шаблонов и аннотаций типов данных обладает широкими возможностями и обеспечит серьезный выигрыш в скорости разработки.

Скотт Аллен (Scott Allen) — технический сотрудник Pluralsight и учредитель OdeToCode. С ним можно связаться по адресу scott@OdeToCode.com или через его блог odetocode.com/blogs/scott.

Выражаю благодарность за рецензирование данной статьи экспертам Филу Хааку (Phil Haack) и Мэтью Осборну (Matthew Osborn).

Вопросы и комментарии (на английском языке) присылайте по адресу xtrmasp@microsoft.com.