Pull to refresh

ASP.NET MVC на реальном примере. Теория и вступление.

Reading time9 min
Views65K
Команда Microsoft очень интенсивно развивает свои продукты и средства для разработчиков. На эту тему уже и выхлопы шуточные были, по поводу выхода новых версий фреймворков. Разработчики, которые работают в крупных компаниях, ввязаны в большие проекты в общем-то без особого энтузиазма на это смотрят, так как такие махины нельзя в короткие сроки перевести на новую версию. Может быть чревато как всплыванием багов, так и изменением всей структуры проекта, что делать не всегда получается легко. Сказанное выше, к сожалению (или к счастью), меня не касается и это дает мне возможность использовать все самое новое без оглядки на бекграунд. Проекты довольно таки обозримые, часто переводятся на новую версию безболезненно, и новые фичи начинаю внедрять уже при реализации следующей задачи в пректе. На момент внедрения это, конечно, вносит некий хаос в код, так как в разных кусках кода используются разные принципы (например, внедрение LINQ), но последующий рефакторинг кода приводит все к единому виду и все приходит в норму.

К чему все это?


Одним из таких нововведений является ASP.NET MVC — реализация шаблона Model-View-Controller под платформу .NET. Попробуем разобраться что же это такое, зачем нужно, и применим наши знания на простом, но реальном приложении.


Принцип работы MVC


Архитектурный шаблон Model-View-Controller подразумевает под собой разделение приложения на три основных компонента: Model, View и Controller, странно правда? :) Каждый из этих компонентов отвечает за свои задачи. Если в общих чертах, то:
  • Model – это в общем-то вся бизнесс логика нашего приложения. В ней находятся классы, которые отвечают за наши данные (сущности), манипулируют ними, записывают в базу данных и читают от туда, а так же за взаимодействие между самими сущностями. Если наше приложение небольшое, то, скорее всего, мы прямо здесь и будем работать с данными. Если же проект более крупный, содержит много логики, то лучше вынести логику, и работу с данными в отдельные проекты (сборки). А когда столкнемся с совсем крупным приложением, то прийдется и физически выносить на другие сервера (Application Server).
  • View отвечает за взаимодействие с пользователем (UI). Посредством него мы будем выводить наши данные клиенту, реализовывать интерфейс для их редактирования и многое другое. Но только то что связано с отображением. Никакой логики!
  • Controller – это наше связующее звено между первыми двумя компонентами. Controller Получает данные о нашем запросе серверу, как-то эти данные обрабатывает (например получает значения из отправленной формы) и передает их в Model. После обработки или получения данных он выбирает каким же именно способом мы отобразить данные клиенту (нужный View).


Что нам это дает полезного? Мы получаем полный контроль над выводимым HTML. Более «легкие» приложения. Приверженцы TDD (Test-Driven Development) будут в восторге, MVC позволяет этим подходом пользоваться на полную катушку и тестировать практически все. Ещё мы получаем полное разделение логики от представления данных. Кто-то просто скажет «ну наконец-то все для людей», а кого-то это будет дисциплинировать, чтобы не писали в обработчике нажатия на кнопку код для работы с данными. К стати я упомянул обработчики? Забудьте. Этого больше нет. Нет обработчиков событий, нет ViewState, нет Drag'n'Drop контролов на форму. Да, руками прийдется больше хардкодить, кому-то может даже подучить и узнать как это все на самом деле работает. В тоже время если мы лишились фич, которые просто противоречат идее MVC, мы не лишились основного. В нашем распоряжении остались MasterPages, UserControls, Membership. В общем все не так уж плохо, как может показаться на первый взгляд. Пришел ли MVC на смену WebForms? Нет, WebForms будут так же жить и развиваться. MVC просто дает возможность писать приложения по другому. Если у вас есть приложение по работе с кучей данных, редактировании в GridView и т.д., то для вас WebForms и останутся правильным решением. Ещё с MVC вы забудете о всех проблемах с URL-Rewriting, возможно это и не настолько проблема в WebForms, конечно, но для MVC — это родная фича.

От теории к практике?


На днях попался мне проект от хорошего знакомого. Нужен новый сайт для сервисного центра портативной электроники. Старый есть, но немного неустраивает. По сути ничего сложного: пяток информационных страниц, каталог доступных аксессуаров с ценами и интеграция с 1C. Ради последнего и поднялся вопрос. Вся база ведется на 1С, хочется, чтобы клиент мог, зайдя на сайт и введя номер своей квитанции, увидеть состояние ремонта: готов не готов, все ли отремонтировали по гарантии или за что-то прийдется доплатить.

Подготовка к работе


Для начала у вас должена быть установлена Microsoft Visual Studio 2008 (если не ошибаюсь, то должна подойти и Express редакция), .NET Framework 3.5, а так же сам ASP.NET MVC (на момент написания статьи доступен Preview 3). После успешной установки всего добра создадим наш проект. File -> New -> Project… В открывшемся окне выбираем в Project Types язык на котором пишете и тип проекта Web, среди предложеных шаблонов нам нужен ASP.NET MVC Web Application. У меня проект называется «ITService».

Создание проекта

Далее нам предложит создать Unit Test Project для нашего приложения, пока обойдемся без него.
В итоге мы получили наш чистенький проект вот в таком виде:

Содержимое проекта ASP.NET MVC

Наряду с уже привычными директориями появились и незнакомые нам. Пройдемся по порядку:
/Properties — стандартная директория для Web Application, содержит настройки проекта.
/References — ссылки на другие сборки, проекты и т.д.
/App_Data — специальная директория для хранения данных
/Content — в этой директории мы будем хранить изображения, стили и ему подобное. В общем-то, весь статический контент сайта
/Controllers — классы, отвечающие за компоненты Controller
/Models — классы с нашей логикой
/Views — непосредственно UI нашего приложения. В этой директории создаются директории для каждого контроллера (/Views/Home в нашем случае). И уже в них по aspx странице для каждого из методов котроллера.
/View/Shared — содержит то, что нам может пригодиться для всех контроллеров, например MasterPages и UserControls.

Чтоже, попробуем запустить? Вуаля! Получили результат:

Первый запуск приложения

Понажимайте по ссылкам в верхнем меню и посмотрите как организованы URL в MVC по умолчанию. Страница About имеет адрес localhost:55515/Home/About (у вас порт может отличаться). Получается мы имеем такую структуру mysite{контроллер}/{действие}. Чтоже, вполне неплохо.

Что нам нужно


Как я уже сказал, мне необходим сайт с несколькими информационными страницами, каталогом аксессуаров и специальной страницей, которая будет получать и выводить данные из 1С. ОК, попробуем для начала реализовать наши статические страницы, сверстаем MasterPage, пропишем нужные стили и добьемся их работы. Конечно в более сложном приложении следовало бы начать с разработки бизнес логики, подумать над тем как мы будем хранить данные, все это написать и только потом приступать к интерфейсу. Но в нашем случае проект маленький, поэтому этой последовательностью можно пренебречь.

Облегчаем себе жизнь при написании кода


Если заглянуть в наш Site.master, то мы увидим, что путь к css, наприме, там прописан относительный, т.е. ../../Content/Site.css. Нам это не подходит, так как вложенность наших страниц будет разная, и ссылка будет теряться, нужно исправить её на путь от корня. Для CSS можно было бы конечно и ручками прописать, но забегая наперед скажу, что у нас ещё и картинки будут на сайте. Каждый раз писать к ним достаточно длинный путь скучно, поэтому, сделаем Helper. Создадим папку Helpers, в неё добавим класс AppHelper со следующим кодом:

using System.Web;

public static class AppHelper
{
  public static string ContentRoot
  {
    get
    {
      string contentVirtualRoot = "~/Content";
      return VirtualPathUtility.ToAbsolute(contentVirtualRoot);
    }
  }

  public static string ImageRoot
  {
    get { return string.Format("{0}/{1}", ContentRoot, «Images»); }
  }

  public static string CssRoot
  {
    get { return string.Format("{0}/{1}", ContentRoot, «Css»); }
  }
  
  public static string ImageUrl(string imageFile)
  {
    string result = string.Format("{0}/{1}", ImageRoot, imageFile);
    return result;
  }

  public static string CssUrl(string cssFile)
  {
    string result = string.Format("{0}/{1}", CssRoot, cssFile);
    return result;
  }
}


В нем мы объявили статическое свойство ContentRoot – путь к нашему контенту, пути к директориям с изображениями и css файлами, а также 2 статических метода, ImageUrl и CssUrl, которые принимают имя файла и возвращают соответствующий путь к нему в нашем приложении. Для того чтобы воспользоваться нашим классом изменим тег link подключающий стили в Site.Master на следующий:

<link href="<%= AppHelper.CssUrl("Site.css") %>" rel=«stylesheet» type=«text/css» />


И не забудьте переместить Site.css в созданную директорию /Content/Css, отделим статический контент по его типу. Теперь если мы перекомпилируем наш проект, то путь пропишется вполне корректно:

<link href="/Content/css/Site.css" rel=«stylesheet» type=«text/css» />


На данном этапе мы написали свой Helper для прописывания правильного пути к изображениям и таблицам стилей. Далее рассмотрим как вообще мы можем работать с HTML и что нам предлагается.

Работа с HTML


Microsoft для облегчения написания кода предлагает HtmlHelper. Это класс с набором статических методов, который позволяет рендерить необходимые HTML-теги. Например, чтобы вывести картинку, достаточно написать

<%=Html.Image(«image.png», «Alt text»)%>


На странице это будет выглядеть как

<img src=«logo.png» alt=«Alt text» />


Если мы воспользуемся нашим AppHelper для вычисления пути к картинке, то мы напишем так:

<%=Html.Image(AppHelper.ImageUrl(«logo.png»), «Alt text»)%>


Этот код уже сгенерирует правильную картинку и пропишет нужный путь:

<img src="/Content/Images/logo.png" alt=«Alt text» />


Ещё из интересных методов – это Html.ActionLink:

<%=Html.ActionLink(«О нас», «About», «Home») %>


Этот метод сгенерирует ссылку на метод «About» контроллера «Номе» с текстом «О нас»
Можно и более «современными» средствами написать тот же код используя Lambda Expressions:

<%=Html.ActionLink<ITService.Controllers.HomeController>(x => x.About(), «О нас») %>


Что здесь происходит, думаю понятно по синтаксису.

Маленький хинт: чтобы не писать каждый раз пространство имен ITService.Controllers, пропишем его в web.config в секции <system.web><pages><namespaces>:
<add namespace=«ITService.Controllers» />

Теперь подобные ссылки можно писать короче:
<%=Html.ActionLink<HomeController>(x => x.About(), «О нас») %>



Отдельно стоит упомянуть формы. С ними нам теперь прийдется работать достаточно в явном виде (никаких Postback, помните?). Простейшая форма будет выглядеть так:

<%using (Html.Form<HomeController>(x => x.Index())) { %>
<%= Html.TextBox(«name») %>
  <%= Html.SubmitButton(«cmdSend», «Отправить») %>
<%} %>


Для работы с тегом from нам необходимо использовать директиву using и в области её действия прописать все поля формы. Это обеспечит нам закрывающийся тег . Ещё же объявление Html.Form(x => x.Index()) указывает на то, что форма будет отправлена методу «Index» контроллера HomeController. Считаю, что довольно таки здоров придумали. Я вначале, когда увидел эту смесь html и серверных частей кода – пришел в ужас, думаю, все приплыли. Здавствуй снова классический ASP, где этой вермишели было полно. А вот все же не так уж страшно. Это совсем не то, что кажется на первый взгляд, когда начинаешь с ним работать. Все эти средства действительно мощные.

ASP.NET Routing


Что такое роутеры? Роутеры позволяют нам работать с URL, которые не указывают на физические файлы на нашем сервере, вместо этого путь разбирается на параметры, которые мы уже используем в нашем приложении. Так же определенную часть пути мы может сопоставить с определенным контроллером и его методом. Такая модель используется по умолчанию, но мы можем задавать соответствия, которые удобны нам. С одной стороны, это похоже на старый добрый URL-Rewriting, о котором уже писали подробно на хабре в статье, но это только с одной стороны. На самом деле это разные вещи. Если мы используем UrlRewriting, то запрос вида mysite.com/products/product1 преобразуется при выполнении к mysite.com/products.aspx?id=product1. Нативно мы не можем генерировать URL, которые бы соответствовали нашим правилам перезаписи URL. Поэтому, если мы где-то изменим логику и поменяем шаблоны адресов, нам прийдется ручками править все места, где эти URL генерируются. В MVC никаких преобразований не происходит, так как сам без проблем разбирает путь, который ему передается.

Вся работа с Роутерами в MVC организована в Global.asax.cs. Заглянем туда в нашем проекте и увидим следующее:

routes.MapRoute(
  «Default»,                       // Route name
  "{controller}/{action}/{id}",              // URL with parameters
  new { controller = «Home», action = «Index», id = ""// Parameter defaults
  );


Как видим, мы в коллекцию роутеров, которыми оперирует наше приложение добавляем свой. С именем Default, прописываем маску URL и создаем анонимный тип, в котором прописываем параметры по умолчанию. Изменения мы будем сюда вносить, когда перейдем непосредственно к реализации. В тоже время, если вас устраивает такая схема формирования адресов, то почему бы и нет? Можно оставить все как есть и это тоже будет работать.

Конец начала


Ну что, это была вступительная часть, в которой я постарался раскрыть самые базовые понятия о платформе ASP.NET MVC. Насколько это у меня получилось, судить вам. Я сам не являюсь экспертом в данной области, просто стало интересно самому, а заодно решил и с людьми поделиться, так как информации в рунете по этой теме совсем немного.

Хочу отметить, что мне важно ваше мнение обо всем, поэтому любой отзыв будет к стати. Если критика, то я, конечно, уверен, что она будет конструктивной ;)

В следующих статьях я перейду непосредственно к реализации задуманного приложения. Скорее всего, будет больше кода. И это меня не радует с таким форматированием, как на хабре :)

* Весь код в статье был подсвечен, насколько это удалось, при помощи Source Code Highlighter.
Tags:
Hubs:
Total votes 36: ↑33 and ↓3+30
Comments166

Articles