21 January 2009

MVC Framework: большое введение для начинающих

.NET
Необходимое отступление: не так давно я разместил статью предназначавшуюся для печатного издания. Приведенная ниже статья имеет ту же самую судьбу: она не попала в печать в связи с тяжелым положением журнала. Как и в прошлый раз, я решил опубликовать статью на Хабре, благо тематика попадает под формат. Необходимо заметить, что статья оформлена и содержит текст для журнала, если бы она готовилась для Хабра, то некоторые часть могли бы быть изменены. Надеюсь, статья вам понравится.

В последнее время заметно, что компания Microsoft уделяет повышенное внимание развитию своих средств разработки, новым инструментам и механизмам разработки программ на своей платформе .net. Быстро развивается язык C#, четвертая версия которого не за горами. Представлен и активно продвигается новый язык F#. Для разработчиков баз данных разработан Entity Framework, который уже доступен в виде финальной версии в первом сервиспаке к .Net Framework 3.5 и Visual Studio 2008. Microsoft активно занялась и клиентской частью разработки web-проектов. Для нашего внимания предложен путь развития Ajax.Net 4.0. Internet Explorer 8 все больше соответствует стандартам и становится привлекательным инструментом для web-программистов, так например, его вкладка Developer Tools включает в себя профайлер JavaScript. Очень хорошей новостью стало недавно объявление о полной поддержке и включении JavaScript-библиотеки jQuery в следующее обновление Visual Studio. В этом свете возникает вопрос, что же предложено разработчикам ASP.NET? Ответ — MVC Framework. Целью данной статьи рассмотреть некоторые общие проблемы, с которыми могут столкнуться программисты, решившие использовать MVC Framework для своих web-проектов, и их решения.

O MVC


Паттерн Модель-представление-контроллер или по-английски Model-view-controller используется очень давно. Еще в 1979 году его описал Тригве Реенскауг в своей работе «Разработка приложений на Smalltalk-80: как использовать Модель-представление-контроллер». С тех пор паттерн зарекомендовал себя как очень удачная архитектура программного обеспечения.



Пользователь, работая с интерфейсом, управляет контроллером, который перехватывает действия пользователя. Далее контроллер уведомляет модель о действиях пользователя, тем самым изменяя состояние модели. Контроллер также уведомляет представление. Представление, используя текущее состояние модели, строит пользовательский интерфейс.
Основой паттерна является отделение модели данных приложения, его логики и представления данных друг от друга. Таким образом, следуя правилу «разделяй и властвуй», удается строить стройное программное обеспечение, в котором, во-первых, модель не зависит от представления и логики, а во-вторых, пользовательский интерфейс надежно отделен от управляющей логики.

На данный момент паттерн MVC реализован в том или ином виде для большинства языков программирования используемых для разработки web-приложений. Самое большое количество реализаций имеет PHP, но и для Java, Perl, Python, Ruby есть свои варианты. До появления версии MVC от Microsoft, для платформы .NET так же существовали свои варианты: Maverick.NET и MonoRail.

ASP.NET MVC Framework


10 декабря 2007 года Microsoft представила свой вариант реализации MVC для ASP.NET. Он по-прежнему базируется на .aspx, .ascx и .master файлах, полностью поддерживает аутентификацию на базе форм, роли, кэширование данных, управление состоянием сессий, health monitoring, конфигурирование, архитектуру провайдеров и другое.

С другой стороны, MVC Framework не предполагает использование классических web-форм и web-элементов управления, в нем отсутствуют такие механизмы как обратные вызовы (postbacks) и состояние представления (viewstate). MVC Framework так же предлагает использование URL-mapping и архитектуру REST в качестве модели запросов, что положительно повлияет на поисковую оптимизацию web-проектов.
--- Классический ASP.NET MVC
Модель запросов postback REST
Модель данных через код cs-файла страницы (code-behind) определяется паттерном MVC
Разработка интерфейса web-controls чистый html, MVC UI Helpers
Авто-генерация id да нет
ViewState есть нет
URL mapping нет есть

В целом, высказывая свое мнение, я могу сказать, что MVC Framework предложил для разработчиков ASP.NET новый стиль, ориентированный на качество клиентского кода. Генерируемый MVC код страниц не содержит ничего автоматически создаваемого, здесь нет раздутых идентификаторов, нет огромных viewstate, написание клиентских скриптов упрощено в связи с тем, что код страницы представляет собой чистый, созданный самим программистом HTML. В эпоху, когда понятие web 2.0 прочно вошло в нашу жизнь полный контроль над страницей на клиентской стороне – это залог успеха любого web-проекта.

Версии MVC Framework


MVC Framework от версии к версии все улучшается, и за период с сентября по октябрь вышли две версии, которые принесли много нового, так в Preview 5 произошли следующие изменения:
  • Добавлена поддержка рендеринга частичных представлений, по сути можно вставлять в представление свои пользовательские элементы;
  • Добавлен атрибут AcceptVerbs, который позволяет задавать action для конкретного типа запроса (POST или GET);
  • Добавлен атрибут ActionName, который позволяет задавать методу имя action. По умолчанию, имена action и имени метода совпадают, но теперь появляется возможность делать их разными;
  • Изменена работа атрибута HandleError. Теперь для разработчиков могут быть выведены стандартные «желтые страницы смерти» с информацией об ошибках;
  • Появилась поддержка комплексных типов через ModelBinder.

Вышедшая в середине октября MVC Framework Beta так же содержит массу дополнений:
  • Новое меню «Add View» в Visual Studio
  • Папка \Scripts и поддержка jQuery
  • Встроенная поддержка Model Binder для комплексных типов
  • Перестроена инфраструктура Model Binder
  • Улучшены методы UpdateModel и TryUpdateModel
  • Улучшено тестирование сценариев UpdateModel и TryUpdateModel
  • Типизирован атрибут AcceptVerbs. Добавлено перечисление HttpVerbs.
  • Улучшены сообщения об ошибках по умолчанию при валидации
  • Модифицированы некоторые хелпер методы. Изменено создание формы. Методы стали extension-методами класса HtmlHelper.
  • Поддержка проектов с Silverlight 2
  • Сборка ASP.NET MVC Futures для этой беты вынесена отдельно и не входит в поставку
  • Поддержка размещения сборок в GAC

Советы


Как задать на странице html-элемент select


Для создания элемента используется метод DropDownList хелпер класса Html:
<%=Html.DropDownList("", «sampleSelect») %>

Первый параметр задает строку, которая добавится в список элемента, как строка по умолчанию, второй параметр – это имя нашего select.
Самое интересно заключается в том, как добавить для select данные. Допустим, мы имеем следующую модель данных:
public class Product
{
public string Name
{
get;
set;
}
public int Id
{
get;
set;
}
}


* This source code was highlighted with Source Code Highlighter.

Чтобы поместить в select набор данных типа Product необходимо проделать следующие действия:
var products = GetProducts();
ViewData[«sampleSelect»] = new SelectList(products, «Id», «Name»);

Где
GetProducts – это некий метод, источник данных в виде IEnumerable, такие источники данных следует располагать в модели данных, здесь приведено упрощенно для примера;
SelectList – это вспомогательный класс определенный в System.Web.MVC.

Здесь, через ViewData передается набор данных созданных с помощью класса SelectList, конструктор которого через первый параметр принимает данные в виде products. Имена свойств, определенных в классе Product, для значений и выводимого текста в select передается вторым и третьим параметрами.

Как отобразить пользовательский элемент управления


Для того чтобы отобразить свой элемент управления используется представленный в preview 5 метод Html.RenderPartial(...). Для того чтобы использовать пользовательский элемент управления в своих проектах он должен быть создан как MVC View User Control, а не как обычный Web User Control. Разница заключается в том, от какого класса наследуется созданный элемент. В случае MVC, элемент будет наследоваться от System.Web.Mvc.ViewUserControl.

Вывод пользовательского элемента не составляет труда:
<% Html.RenderPartial(«SampleUserCtrl»); %>

Где SampleUserCtrl – это имя класса, который представляет пользовательский элемент управления.

Как присвоить элементу, созданному через класс Html, атрибут class


Хэлпер класс HTML – это очень полезный инструмент MVC Framework, который позволяет единообразно создавать элементы управления на странице. И, конечно, методы этого класса позволяет задавать атрибуты html-элементов. На примере, мы зададим гиперссылке с текстом SampleLink, действием ActionName с параметром product.Id тэг rel = «nofollow».
Html.ActionLink(«SampleLink», ActionName, new {product.Id}, new {rel = «nofollow» })

Проблема начинается тогда, когда название атрибута совпадает с зарезервированным словом в C#, которым, конечно, является «class». Решение простое:
Html.ActionLink(«SampleLink», ActionName, new {product.Id}, new {@class = «sample-css-class» })

Когда мало ViewData


Иногда, требуется передать данные от одного action к другому. Скажем, на странице с полем ввода во время обработки введенных данных у нас произошла исключительная ситуация, о которой необходимо сообщить, вернувшись обратно на страницу. Так как обработкой введенных данных занимался один action, а обработкой страницы заведует другой, то, используемая в иных случаях ViewData, нам не поможет.
Для таких случаев в MVC существует TempData, структура данных, которая существует только во время запроса и потом удаляется.
<% if (TempData[«Message»] != null) {%>
<%=HttpUtility.HtmlEncode(TempData[«Message»].ToString()) %>
<%} %>

Данный код отобразит сообщение, в случае, когда оно существует. Само сообщение задается элементарно:
if (String.IsNullOrEmpty(UserText))
{
TempData["Message"] = "Введите текст сообщения";
return RedirectToAction("Index");
}


* This source code was highlighted with Source Code Highlighter.

Разделяем логику GET и POST запросов


MVC Framework preview 5 принес, кроме всего прочего, один замечательный механизм, который позволяет разделить логику для POST и GET запросов. Производится такое разделение через атрибут AcceptVerbs. Для примера допустим у нас на главной странице есть форма для ввода логина и пароля. Так же есть action Login, который отвечает за проверку введенного логина и пароля, аутентификацию и авторизацию пользователя. Для безопасности можно ограничить действие этого action заставив обрабатывать его только POST запросы от формы на странице. А все остальные запросы отправить в другой action, который бы просто возвращал ту же самую страницу.
[AcceptVerbs("GET")]
public ActionResult Login()
{
return View("Index");
}

[AcceptVerbs("POST")]
public ActionResult Login(string userLogin, string userPass)
{
[… реализуем логику …]
return RedirectToAction("Index", "Home");
}


* This source code was highlighted with Source Code Highlighter.

Помимо этого, в MVC Framework Beta появилось перечисление HttpVerbs, с помощью которого, можно задать параметр атрибута AcceptVerbs строго типизированным значением.
[AcceptVerbs(HttpVerbs.Post)]

Управление кэшированием


MVC Framework позволяет управлять кэшированием результата каждого action. То есть, вы можете задать каким образом и сколько будет кэшироваться тот или иной запрос на сервере или на клиенте. Для этого существует аналог директивы классического asp.net <%@ OutputCache %> атрибут [OutputCache]. Ниже перечислены его параметры:
Параметр Описание
VaryByHeader строка с разделенными через точку с запятой значениями http-заголовков по которым будет производиться условное кэширование
VaryByParam задает условное кэширование основанное на значениях строки запроса при GET или параметров при POST
VaryByContentEncoding указывает условие кэширование в зависимости от содержимого директивы http-заголовка Accept-Encoding
Duration Duration задает значение времени в секундах, в течение которого страница или пользовательский элемент кэшируются
NoStore принимает булево значение. Если значение равно true, то добавляет в директиву http-заголовка Cache-Control параметр no-store
CacheProfile используется для указания профиля кэширования заданного через web.config и секцию caching
OutputCacheLocation этот параметр описывает правило для места хранения кэша и принимает одно из значений перечисления OutputCacheLocation
VaryByCustom любой текст для управление кэшированием. Если этот текст равен «browser», то кэширование будет производиться условно по имени браузера и его версии (major version). Если у VaryByCustom будет указана строка, то вы обязаны переопределить метод GetVaryByCustomString в файле Global.asax для осуществления условного кэширования.

Использование OutputCache очень элементарное. Следующий пример показывает, как кэшировать результат action Index на 10 секунд.
[OutputCache(Duration = 10)]
public ActionResult Index()
{

}


* This source code was highlighted with Source Code Highlighter.

Сложные страницы и пользовательские модели


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

Такие данные при хранении в базах данных хранятся не в одной таблице, а в нескольких, может даже в нескольких десятках. В таком случае, если вы используете ОRM – объектную модель базы данных, то для создания одной страницы MVC Framework вам придется инициализировать множество списочных или других переменных и заполнить их данными.

Я рекомендую в таком случае сделать свою комплексную модель данных, реализовав букву M из слова MVC. Ниже я приведу пример одного из вариантов реализации такой модели:
public class BankInfoModel
{
public Bank Bank;
public IEnumerable<BankBranch> BankBranches;
}


* This source code was highlighted with Source Code Highlighter.

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

Для прозрачного использования нашей модели мы должны сделать следующее: переделать базовый класс нашего представления с
public partial class Index: ViewPage

на
public partial class Index : ViewPage<BankInfoModel>

* This source code was highlighted with Source Code Highlighter.

Теперь, в коде представления, нам доступен инстанцированный экземпляр модели типа BankInfoModel через ViewData.Model. Конечно, контроллер должен его инициализировать для нас, но это элементарно:

public ActionResult Info(int? id)
{
var info = new BankInfoModel {Bank = db.Banks.Single(x => x.id == id)};
info.BankBranches = info.Bank.LocationBranches
.Where(x => x.Address.Street.cityId == 1).SelectMany(x => x.BankBranches);

return View(info);
}


* This source code was highlighted with Source Code Highlighter.

Где int? id – это параметр, который указывает на идентификатор банка

Использование экземпляра нашей модели в представлении также просто:
<div class="vcard">
<h1><span class="fn org"><%=ViewData.Model.Bank.shortName %></span></h1>
<p>Телефон: <span class="fn phone"><%=ViewData.Model.Bank.phone %></span></p>
</div>


* This source code was highlighted with Source Code Highlighter.

Сложные типы, ModelBinder


MVC Framework так устроен, что передача значений формы производится через параметры action. Например, ниже представлена форма с двумя полями для ввода данных логина и пароля.
<form method="post" action="<%= Html.AttributeEncode(Url.Action("Login", "Home")) %>">
<ul><li>
<label for="txtLogin">Введите логин</label><%= Html.TextBox("userLogin", “”, new {id = "txtLogin"}) %></li><li>
<label for="txtPass">Введите пароль</label><%= Html.Password("userPass", “”, new {id = "txtPass"}) %></li><li>
<input type="submit" value="Войти" />
</li></ul>
</form>


* This source code was highlighted with Source Code Highlighter.

Данные, которые пользователь ввел в этой форме, передадутся в action через соответствующие параметры, так как показано ниже:
[AcceptVerbs(«POST»)]
public ActionResult Login(string userLogin, string userPass)

Но что делать, когда форма содержит десятки вводимых полей? Неужели создавать десятки параметров у action? Нет, MVC Framework содержит механизм, который позволяет избежать такого некрасивого шага, как многочисленные параметры метода. Такой механизм называется ModelBinder. Для начала объявим класс, описывающий нашу форму:
public class LoginModel
{
public string userLogin { get; set; }
public string userPass { get; set; }

public LoginModel()
{
}
}


* This source code was highlighted with Source Code Highlighter.

Затем, нам необходимо определить класс реализующий интерфейс IModelBinder:
public class LoginModelBinder : IModelBinder
{
public object GetValue(ControllerContext controllerContext, string modelName,
Type modelType, ModelStateDictionary modelState)

{
LoginModel modelData = new LoginModel ();
modelData.userLogin = controllerContext.HttpContext.Request["userLogin"];
modelData.userPass = controllerContext.HttpContext.Request["userPass"];

return modelData;
}
}


* This source code was highlighted with Source Code Highlighter.

Обратите внимание, мы определяем метод GetValue и заполняем в нем данные модели через контекст запроса.

Перед заключительным шагом нам необходимо указать для нашего класса модели LoginModel атрибут ModelBinder:
[ModelBinder(typeof(LoginModelBinder))]
public class LoginModel
{
public string userLogin { get; set; }
public string userPass { get; set; }

public LoginModel()
{
}
}


* This source code was highlighted with Source Code Highlighter.

В заключение, используем наши конструкции для автоматической инициализации параметров переданных с формы:
[AcceptVerbs(«POST»)]
public ActionResult Login([ModelBinder(typeof(LoginModelBinder))] LoginModel loginModelData)

Данный вариант появился в MVC Framework preview 5.
MVC Framework Beta принесла значительные улучшения в это плане. Теперь существует встроенный binder, который автоматически обслуживает передачу стандартных .NET типов. То есть, вместо того, чтобы создавать свой ModelBinder в предыдущем примере мы можем создать упрощенный код:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Login(LoginModel loginModelData)

Кроме того, для разработчиков доступен атрибут Bind, который позволяет управлять префиксом имен параметров формы, так что возможно изменить его значение или указать, что префикса не будет вовсе. Этот же атрибут позволяет задавать «белые» или «черные» списки свойств формы, которые будут связываться со значениями параметра комплексного типа.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Login([Bind(Prefix=””, Include=”userLogin, userPass”)] LoginModel loginModelData)

И хотя мы не обязаны создавать свои варианты ModelBinder этот инструмент все же может пригодиться и будет полезным для тонкой настройки обработки значений комплексных типов переданных с формы.

Перехват необработанных ошибок, неверных URL


Известно, что ничего так не сбивает с толку пользователя как непонятная ошибка, возникшая на ровном месте. Даже, если в вашем проекте такие ошибки время от времени происходят, необходимо позаботится о том, чтобы пользователь получал уведомление как можно более дружелюбно. Для таких целее используются страницы ошибок, перехват необработанных исключений, а так же обработка 404 ошибки http «Страница не найдена».

Для перехвата необработанных исключений необходимо создать в папке Views/Shared страницу Error.aspx с информацией об ошибке, которая содержит, например, такой код:
<span>
Ой! Извините, но на нашей странице произошла ошибка.<br />
Текст ошибки: <%= ViewData.Model.Exception.Message %>
</span>


* This source code was highlighted with Source Code Highlighter.

Для того, чтобы все необработанные исключения перенаправлялись на нашу страницу необходимо каждому контроллеру указать атрибут HandleError:
[HandleError]
public class HomeController: Controller

Далее, чтобы обрабатывать все неверные URL, которые не подпадают под наш маршрут заданный в global.asax, необходимо создать еще один маршрут, который бы направлял все неверные запросы на специальную страницу:
routes.MapRoute(«Error», "{*url}", new
{
controller = «Error»,
action = «Http404»
});

Как можно заметить все неверные URL, будут приводить к action Http404 контроллера Error. Необходимо создать такой контроллер и добавить action:
public class ErrorController : Controller
{
public ActionResult Index()
{
return RedirectToAction("Http404");
}
public ActionResult Http404()
{
Response.StatusCode = 404;
return View();
}
}


* This source code was highlighted with Source Code Highlighter.

Содержимое представления Http404.aspx элементарно:
<h1>404</h1>
<p>Запрошенной страницы не существует</p>


* This source code was highlighted with Source Code Highlighter.

Таким образом, мы отловим попытки переходов по неверному маршруту, но что делать, если маршрут попадает под шаблон, но все равно неверен? Выходом может стать проверка на местах с генерацией исключения:
public ActionResult Info(int? id)
{
if (!id.HasValue)
throw new HttpException("404");

[ …работаем дальше… ]
}


* This source code was highlighted with Source Code Highlighter.

Для перехвата такого пользовательского исключения необходимо создать или изменить в web.config раздел customErrors:
<customErrors mode="RemoteOnly">
<error statusCode="404" redirect="~/Error/Http404"/>
</customErrors>


* This source code was highlighted with Source Code Highlighter.

Таким образом, все наши пользовательские исключения типа 404 будут также перенаправляться на нашу страницу Http404.aspx, что позволит сохранить общий подход и объединить под понятие «ненайденной страницы» как неверные URL, так и URL-введенные с ошибкой или в силу каких-то других причин неприемлемые для обработки, например из-за нарушения прав доступа.

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

Заключение


В статье я попытался описать некоторые аспекты работы с MVC Framework. Многие моменты – элементарны, некоторые не так просты, как кажутся, часть – хорошо описана в интернете, часть – не описана вовсе. Во всех случаях приведенный материал может сослужить хорошую службу как начинающим знакомство с MVC Framework, так и тем, кто уже имеет опыт создания web-приложений с его помощью.

MVC Framework сегодня – это, всего лишь, бета-версия того продукта, который будет в итоге. Но уже сейчас этот инструмент позволяет создавать web-приложения, используя все мощь паттерна MVC. Возможно, когда вы будете читать эту статью, выйдет финальный релиз MVC Framework, который ожидается к концу 2008 года, но можно предположить, что большинство функций уже не будет изменено.

Progg it
Tags:.netCSharpmvc framework
Hubs: .NET
+53
69.7k 214
Comments 71