Лучшие практики ASP.NET MVC
В этом документе содержится набор рекомендаций, которые помогут 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.
Обратной стороной использования этого паттерна является проигрыш в производительности, так как перенаправления вызывают дополнительные запросы на сервер. Понижение производительности должно компенсироваться преимуществами использования этого паттерна в процессе принятия решений.
Реализуйте HandleUnknowAction и HandleError
Ответом по умолчанию на не найденный action является ошибка 404 (не найдено). Если вы переопределите класс HandleUnknownAction в контроллере, то вы сможете реализовать представление по умолчанию для этой ошибки. Также вы можете пометить actions и (или) контроллер с помощью атрибута HandleError и указать представление на тот случай, если случится непредвиденная ошибка.
http://blogs.msdn.com/b/aspnetue/archive/2010/09/17/second_2d00_post.aspx