Лучшие практики ASP.NET MVC

суббота, 2 июля 2011, Александр Краковецкий

В этом документе содержится набор рекомендаций, которые помогут ASP.NET MVC разработчикам создавать сложные приложения. Несомненно, последнее решение по поводу использования того или иного совета в вашем приложении лежит на вас. По мотивам статьи Best Practices for ASP.NET MVC. Код был переписан с использованием Razor синтаксиса.

Рекомендации по работе с моделью (Model)

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

Размещайте ваши модели в отдельной сборке

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

Размещайте всю бизнес логику в модели

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

  • менее дублируемую бизнес логику;
  • более читабельное представление, когда в нем нет бизнес логики;
  • тестирование бизнес правил независимо от модели.

Например, если у вас есть бизнес требование отобразить ФИО пользователя с фамилией вначале. В этом случае вы можете написать следующий код в представлении:

 @if (String.Compare((string)TempData["displayLastNameFirst"], "on") == 0)
 { 
     Welcome,@Model.lastName, @Model.firstName
 }
 else
 { 
      Welcome,@Model.firstName @Model.lastName
 }

Но вам необходимо будет дублировать эту логику в каждом месте, где это описано в бизнес требованиях. Вместо этого вы можете поместить эту логику в модель путем добавления нового свойства, которое будет инкапсулировать эту логику:

public string combinedName
{
    get
    {
        return (displayLastNameFirst ? lastName + " " + firstName : firstName + " " + lastName);
    }
}

Теперь отобразить имя можно очень просто:

Welcome, @Model.combinedName 

Храните всю логику валидации в модели

Вся валидация должна происходить на уровне модели. Это включает в себя клиентскую валидацию, что важно для производительности. Но клиентскую валидацию можно обойти (например, с помощью инструмента Fiddler).

Вы можете использовать ModelState для проверки. Следующий пример показывает, как можно явно добавить проверку ModelState:

if (String.IsNullOrEmpty(userName))
{
   ModelState.AddModelError("username", Resources.SignUp.UserNameError);
}  

Однако, в .NET Framework есть System.ComponentModel.DataAnnotations, который должен быть приоритетным способом организации проверок данных. Эти аннотации могут быть добавлены как атрибуты к свойствам модели, как это показано в примере:

public class User
{
   [Required(ErrorMessageResourceName = "nameRequired", ErrorMessageResourceType = typeof(Resources.User))]
   public String userName { get; set; }
       ...
}

Объявляйте интерфейсы для доступа к данным

Желательно использовать интерфейсы для работы с данными, что приведет к уменьшению связности компонентов. Рассматривайте использование Entity Framework или LINQ to SQL как врапперов для работы с базой данных. Оба провайдера также позволяют использовать хранимые процедуры.

Размещайте всю логику работы с состоянием в модели

Детальное рассмотрение механизмов хранения состояния в модели выходит за рамки этого документа. В качестве отправной точки ознакомьтесь с несколькими способами хранения состояния:

Сравнение способов хранения состояния
Способ Преимущества Недостатки
In Process Нет необходимости в дополнительной настройке. Не работает если приложение должны быть масштабируемым.
Session State Service Небольшой сервис выполняется на каждой машине в веб-ферме. Работает быстрее чем хранение сессии в базе данных. Данные сессии потеряются, если сервер выйдет из строя.
Database Данные сессии сохраняются. Медленнее, чем session state. Стоимость обслуживания достаточно высока.

Рекомендации по работе с представлениями (Views)

Главный принцип представления – отображение модели. Представление выбирается контроллером. Бизнес логика не должна присутствовать в представлении, потому что это прерогатива модели. Механизм представления может быть очень гибким. Например, одно из представлений может быть в HTML формате, другое – в виде XML или веб-сервиса.

Размещайте HTML в представлениях и Partial Views (не в контроллере)

Преимущество шаблона представления – читабельность. Для движка представления по умолчанию ASP.NET предлагает следующие типы файлов: HTML (.aspx), partial HTML view (.ascx), и эталонные страницы (.master). Эталонные страницы позволяют задать общий шаблон представления. Также они могут быть вложенными, что позволяет создавать иерархии вложенных страниц представления.

Следующий пример демонстрирует представление, которое вызывает partial view:

Below is a list of items submitted by <b>
@Html.Encode(ViewData["name"])</b>.

...
<div id="items">
    @{Html.RenderPartial("ItemsByName");}
</div> 

Partial view (ItemsByName.ascx) имеет следующий вид:

   @foreach (Seller.Controllers.Items item in (IEnumerable)ViewData.Model)
   { 
    <tr>
        <td>
            @Html.Encode(item.title)
        </td>
        <td>
            @Html.Encode(item.price)
        </td>               
    </tr>        
   }

Partial view – это мощный расширяемый механизм. Мы можем использовать представление много раз без написания дополнительного кода.

Получайте доступ к данным в представлениях с помощью ViewData

ASP.NET предоставляет несколько механизмов для получения доступа к данным из представлений:

  • Объект ViewData.Model, который возвращается в action методе контроллера (return View(myModelObject)).
  • Объект ViewData, с помощью которого можно получить доступ ко всем данным коллекции, которые были добавлены в action методе контроллера (ViewData[“key”] = value).

Используйте ViewData Model вместо ViewData где это только возможно, так как этот механизм более типобезопесен. Также рекомендуется использовать механизм доступа к данным вместо использования объектов Request/Session напрямую из представлений.

Если у вас есть объект, свойства которого вы хотите отобразить, то вы должны использовать ViewData.Model и создать строго типизированное представление для этого типа объекта. Например, если у вас есть страница продавца, и класс Seller содержит такие свойства как name, phone, address, email и т.д., то вы должны присвоить ViewData.Model экземпляр класса Seller перед генерацией представления. Если у вас есть дополнительные данные – такие как #, имя пользователя и текущее время, то используйте коллекцию ViewData.

Избегайте работы с данными напрямую, когда используете связывание модели (model binding). Другими словами, получателем данных из базы данных должен быть контроллер перед вызовом представления. Легкие объекты модели представления не получают данные в процессе генерации представления.

Включайте автоматически генерируемую клиентскую валидацию

Ранее веб-разработчики сталкивались с дилеммой как синхронизировать клиентскую и серверную валидацию. Начиная с ASP.NET 2 добавление клиентской валидации стало простым процессом.

Для добавления клиентской валидации необходимо:

1) Добавить валидацию данных в модель, как об этом говорилось ранее.

2) Удостовериться, что следующие JavaScript файлы присутствуют в папке Scripts вашего проекта:

  • MicrosoftAjax.js
  • MicrosoftMvcValidation.js

3) Добавить следующие строки на вашу страницу:

<script src="@Url.Content("~/Scripts/MicrosoftAjax.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/MicrosoftMvcValidation.js")" type="text/javascript"></script>

4) Вставить следующий код перед тегом form:

@{Html.EnableClientValidation();}

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

Вставляйте серверные комментарии

Используйте серверные комментарии в представлениях. Они будут вырезаны в тот момент, когда сервер будет возвращать HTML код на клиент. Следующий код демонстрирует серверные комментарии:

<%-- This is a server side template comment --%>

Razor:

@* This is a server side template comment *@

Не используйте HTML комментарии в представлениях так как они будут отображены в HTML разметке и любой (потенциально опасный) пользователь может воспользоваться этой информацией.

Используйте методы-расширения HTMLHelper

Класс System.Web.Mvc.Html содержит полезные HTML методы-расширения. Эти расширения включают методы для:

  • Генерации формы (BeginForm)
  • Полей ввода (checkbox, hidden, radio button, textbox)
  • Генерации ссылок (ActionLink)
  • Защиты от XSS (Encode)

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

@Html.ActionLink(“Home page”, "Default")

Рекомендации по работе с контроллерами (Controller)

Контроллер (и конкретный action метод) вызывается системой роутинга на базе совпадения адреса (URL). Контроллер получает данные от системы роутинга, включая данные о контексте HTTP запроса (session, cookies, browser и т.д.).

Используйте связывание модели вместо ручного разбора запроса

ASP.NET MVC абстрагирует много кода для десериализации объектов, используя связывание модели. Связывание модели – это механизм, с помощью которого данные контекста запроса через рефлексию сериализируются в тип объекта, заданного в action методе.

В следующем примере класс Seller определяет данные, которые могут быть отправлены для регистрации продавцов:

public class Seller
{
    public Int64 ID { get; set; }
    public string Name { get; set; }
    public string Phone { get; set; }
    public string Address { get; set; }
}

Форма, с помощью которой пользователь сможет заполнить данные о продавце, имеет такой вид (Register view):

@using (Html.BeginForm()) {   
  <legend>Account Information</legend>
  <p>             
      @Html.TextBox("Name") 
  </p>
  <p>
      @Html.TextBox("Phone") 
  </p>
  <p>
      @Html.TextBox("Address") 
  </p>
  <p>
      <input value="Register" type="submit" />
  </p>
} 

Контроллер нуждается в action методе Register, который предоставит связывание модели как это показано в примере:

public ActionResult Register([Bind(Exclude="ID")] Seller newSeller) 
{
   ...
}

Каждое свойство класса будет просмотрено в следующем порядке (на примере свойства name):

  • Request.Form["Name"]
  • RouteData.Values["Name"]
  • Request.QueryString["Namel"]
  • Вернет null

Как вы видите из action метода Register существует несколько атрибутов, с помощью которых можно пометить объект, который будет вызван в процессе связывания данных.

Система связывания модели также выполняет валидацию, логика которой задана с помощью атрибутов (data annotations).

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

Используйте явные имена представлений в action методах

После того, как вы установили контекст в action методе для генерации HTML, вы возвращаете объекты ViewResult или PartialViewResult. Если вы не передадите имя представления в результирующий класс, файл представления будет выбран по названию action метода. Например, есть контроллер с названием Products и action метод с названием List. Вы можете вызвать “return View()” в action методе List без параметров. Фреймворк попытается найти представление /Views/Products/List.aspx. Если такой файл отсутствует, фреймворк попытается найти /Views/Products/List.ascx, потом /Views/Shared/List.aspx и /Views/Shared/List.ascx. Поэтому вы можете использовать папку /Views/Shared для хранения представлений для различных контроллеров.

Во избежании конфузов используйте явные названия представлений, например "return View("explicitViewName")", в action методах. Это позволит вам вызывать List с разных action методов без необходимости фреймворка искать подходящее представление.

Используйте Post/Redirect/Get (PRG) когда отправляете формы

Согласно определению терминов HTTP POST и GET:

  • HTTP GET используется для неизменяемых данных вашей модели.
  • HTTP POST используется для изменения данных вашей модели.

Используя эти простые определения, получая данные формы в action методе возвращайте RedirectToAction(actionName), который возвратит код HTTP 302 (временное перенаправление) и сгенерирует GET в actionName. В этом заключается смысл шаблона Post-Redirect-Get.

Однако не используйте HTTP GET для отправки данных форм, так как это не согласовывается с назначением HTTP GET.

В дополнение, классические ASP.NET постбеки могут стать причиной проблемных зацикливаний в процессе отправки форм.

Например, на диаграмме ниже показана схема работы стандартного постбека, где отправляется GET и POST запросы на одной и той же странице (create.aspx). Проблема в том, что пользователь может, не дождавшись окончания отправки данных, нажать кнопку обновления (F5) и отправить данные на сервер еще раз, а ваш сайт должен эти данные повторно обработать. Эта проблема может быть решена в MVC с помощью шаблона Post-Redirect-Get.

alt text

Обратной стороной использования этого паттерна является проигрыш в производительности, так как перенаправления вызывают дополнительные запросы на сервер. Понижение производительности должно компенсироваться преимуществами использования этого паттерна в процессе принятия решений.

Реализуйте HandleUnknowAction и HandleError

Ответом по умолчанию на не найденный action является ошибка 404 (не найдено). Если вы переопределите класс HandleUnknownAction в контроллере, то вы сможете реализовать представление по умолчанию для этой ошибки. Также вы можете пометить actions и (или) контроллер с помощью атрибута HandleError и указать представление на тот случай, если случится непредвиденная ошибка.


Ищите нас в интернетах!

Комментарии

Свежие вакансии