ASP.NET MVC Framework

Простое приложение магазина E-Commerce

Я собираюсь использовать простое приложение магазина e-commerce, чтобы проиллюстрировать с его помощью как работает ASP.NET MVC Framework. В этой статье я планирую реализовать листинг/просмотр продуктов. В частности, мы создадим магазин, который позволит конечным пользователям просматривать список категорий продуктов, когда они будут за ходить на сайт через URL /Products/Categories

Когда пользователь кликает на ссылку с категорией на предыдущем странице, то он попадает на страницу со списоком продуктов (URL /Products/List/CategoryName), которая отображает активные продукты внутри данной категории:

Когда пользователь кликает на конкретный продукт, то он попадает на детальное описание (URL /Products/Detail/ProductID), в котором отображается подробная информация о выбранном продукте:

Мы создадим всю эту функциональность с помощью ASP.NET MVC Framework. Это позволит нам поддерживать “четкое разделение задач” между различными компонентами приложения, и позволит легче интегрировать unit testing и TDD (test driven development).

Создание нового приложения ASP.NET MVC

Framework ASP.NET MVC включает в себя шаблоны проектов для Visual Studio, что позволяет очень просто создавать новое веб-приложение. Просто зайдите в меню File->New Project и выберите шаблон “ASP.NET MVC Web Application” для создания нового веб-приложения.

По-умолчанию, когда вы создаете новое приложение, Visual Studio создаст для вас новый solution и добавит в него два проекта. Первый является веб-проектом, в котором вы будете реализовывать ваше приложение. Второй – тестовый проект, который вы можете использовать для написания юнит-тестов:

В ASP.NET MVC Framework вы можете использовать любой фреймворк для юнит-тестов (включая NUnit, MBUnit, MSTest, XUnit и другие). VS 2008 Professional сейчас содержит встроенную поддержку тестовых проектов для MSTest (ранее в VS 2005 требовалась Visual Studio Team System SKU), и наш шаблонный проект ASP.NET MVC автоматически создаст один из этих тестовых проектов в VS 2008.

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

Структура папок в проекте

Структура директорий приложения ASP.NET MVC, создаваемая по-умолчанию, имеет 3 главных директории:

* /Controllers
* /Models
* /Views

Как вы наверно уже предположили, мы рекомендуем помещать классы Контроллера внутри директории /Controllers, классы моделей данных – внутри директории /Models, и шаблоны вида – внутри директории /Views.

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

Маппинг URL на классы Контроллера

В большинстве веб платформ (ASP, PHP, JSP, формы ASP.NET и т.д.), входящая ссылка URL обычно маппируется на шаблонный файл, который хранится на диске. Например, ссылки “/Products.aspx” или “/Products.php” обычно имееют соответствующие файлы на диске Products.aspx или Products.php, который обрабатывают эту ссылку. Когда веб-приложению приходит http запрос, веб-платформа запускает код, который находится в шаблонном файле на диске, и этот код отвечает за обработку запроса. Часто этот код использует HTML разметку внутри файлов Products.aspx или Products.php, что помогает при создании ответа, отправляемого назад клиенту.

Платформа MVC обычно маппирует ссылки URL на серверный код другим способом. Вместо маппирования URL на шаблонный файл на диске, MVC маппирует URL сразу на классы. Эти классы называются “Контроллерами” (“Controller”) и они обрабатывают входящие запросы, обрабатывая ввод со стороны пользователя, и исполняя соответствующее приложение и логику данных. Класс Контроллера затем вызывает отдельный компонент “Вида” (“View”), который отвечает за генерацию ответного HTML кода для данного запроса.

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

Перенаправление URL на классы Контроллера, используемое по умолчанию

По умолчанию проекты ASP.NET MVC имеют заранее сконфигурированный набор правил для перенаправления URL, что позволяет вам легко начать работать над приложением без конфигурирования чего-либо. Вы можете начать писать код, используя стандартный набор правил маппинга URL, основанный на именах. Эти правила определены в классе приложения ASP.NET в файле Global.asax, который создается в каждом новом проекте ASP.NET MVC в Visual Studio.

Стандартные правила именования состоят в том, чтобы перенаправить путь URL входящего HTTP запроса (например, /Products/) на класс, чье имя соответствует шаблону UrlPathController (например, URL начинающийся с /Products/, будет перенаправлен на класс с именем ProductsController).

Чтобы создать функциональность просмотра наших продуктов e-commerce, мы добавим новый класс “ProductsController” к нашему проекту (вы можете использовать меню “Add New Item” в Visual Studio, чтобы создать класс Контроллера на основе шаблона):

Наш класс ProductsController будет унаследован от базового класса System.Web.MVC.Controller. Наследование от этого класса не обязательно, но он содержит некоторые полезные методы и функциональность, возможностями которого мы воспользуемся немного позднее:

Как только мы определили класс ProductsController внутри нашего проекта, ASP.NET MVC framework будет использовать его по умолчанию, чтобы обрабатывать все входящие URL, которые начинаются с “/Products/”. Это значит, что данный класс будет автоматически вызываться для обработки URL “/Products/Categories”, “/Products/List/Beverages” и “/Products/Detail/3”, которые мы собираемся включить в наше приложение магазина.

В следующих статьях, мы также  добавим ShoppingCartController (чтобы позволить пользователям управлять их корзинами) и AccountController (чтобы позволить пользователям создавать новые аккаунты на сайте, а также входить/выходить на сайт под этими аккаунтами). Как только мы добавим эти два новых класса в проект, URL начинающиеся с /ShoppingCart/ и /Account/, будут автоматически перенаправлены в эти классы для обработки.

Замечание: В ASP.NET MVC framework не обязательно всегда использовать эту схему именования классов. Единственная причина, по которой наше приложение использует ее, – потому что правила перенаправления автоматически добавляются в наш класс приложения ASP.NET, когда мы создаем новый проект ASP.NET MVC в Visual Studio. Если вам не нравятся данные правила, или вы хотите изменить их, чтобы использовать другую схему маппинга URL, то просто откройте файл Global.asax и измените его. Я расскажу как это делается в следующих статьях (а также покажу несколько полезных возможностей механизма перенаправления URL).

Action методы контроллера

Теперь после того как мы создали класс ProductsController в нашем проекте, мы можем начать добавлять логику для обработки входящих URL “/Products/”.

Когда мы определяли принцип работы нашего магазина в начале этой статьи, я говорил, что мы будем реализовывать три основных метода: 1) просмотр всех категорий продуктов, 2) листинг продуктов внутри конкретной категории, и 3) показ детальной информации о выбранном продукте. Мы будем использовать следующие URL для обработки каждого из этих методов:

Формат URL                    Поведение                              Пример URL

/Products/Categories          Просмотр всех категорий продуктов     /Products/Categories
/Products/List/Category       Просмотр продуктов внутри категории   /Products/List/Beverages
/Products/Detail/ProductID    Показ детальной информации            /Products/Detail/34
                              выбранном продукте

Существует несколько способов, которые мы могли бы реализовать в нашем классе ProductsController, для обработки эти трех типов входящих URL. Один из способов состоит в переопределении “Execute” метода базового класса контроллера и написания нашей собственной логики с использованием if/else/switch, для того чтобы анализировать входящую URL и затем выполнять соответствующую обработку.

Однако, более простой способ состоит в использовании встроенных функций платформы MVC, что позволяет нам определить “action методы” в нашем контроллере, и затем, базовый класс контроллера автоматически будет вызывать соответствующие методы на основе правил перенаправления.

Например, мы можем добавить три action метода контроллера в наш класс ProductsController для обработки трех основных типов URL:

Правила перенаправления URL, сконфигурированые по умолчанию при создании проекта, берут название соответствующего action-а из подстроки URL, которая идет сразу после названия контроллера. Например, если мы получаем URL запрос /Products/Categories, то правила перенаправления выдадут “Categories” как имя action-а, и вызовется метод Categories() для обработки запроса. Если мы получаем URL запрос /Products/Detail/5, то правила перенаправления выдадут “Detail” как имя action-а, и вызовется метод Detail() для обработки запроса, и т.д.

Замечание: ASP.NET MVC framework не требует всегда использовать эту схему именования action-ов. Если вы хотите использовать другую схему маппинга URL, просто откройте файл Global.asax и измените его.

Маппинг параметров URL на action-методы контроллера

Существует несколько методов доступа к значениям параметров URL внутри action-методов контроллера.

Базовый класс контроллера предоставляет ряд Request и Response объектов, которыми можно пользоваться. Эти объекты имеют точно такую же структуру API, как и объекты HttpRequest/HttpResponse, с которыми вы уже знакомы в ASP.NET. Одно главное отличие состоит в том, что эти объекты теперь интерфейсные, а не sealed классы (в частности, MVC фреймворк имеет новые интерфейсы System.Web.IHttpRequest и System.Web.IHttpResponse). Преимущество этих интерфейсов в том, что теперь их легко тестировать – это позволяет легко производить юнит-тестирование для классов контроллера. Я расскажу об этом более подробно в следующей статье.

Ниже приведен пример того, как мы можем использовать Request API, чтобы вручную запросить значение ID из строки запроса, находясь внутри action-метода Detail в классе ProductsController:

ASP.NET MVC framework также поддерживает автоматический маппинг параметров URL на аргументы action-методов. По умолчанию, если у вас есть аргумент в action-методе, MVC framework проверяет данные входящего запроса – есть ли в соответствующем HTTP запросе значение с таким же именем. Если такое значение найдено, то оно автоматически будет передано в качестве параметра action-методу.

Например, мы можем переписать наш action-метод Detail, чтобы воспользоваться этой фичей и сделать код более простым:

Кроме маппинга значений из строки запроса/формы, ASP.NET MVC framework также позволяет вам использовать механизм перенаправления URL, чтобы включить значения параметров внутрь самой URL (например, вместо /Products/Detail?id=3 вы можете использовать /Products/Detail/3).

Стандартное правило перенаправления определяется, когда вы создаете новый проект MVC, и имеет следующий формат: “/[controller]/[action]/[id]”. Это означает, что если в URL после названия контроллера и action-a, есть подстрока, то она будет рассмотрена в качестве параметра с именем “id”, и этот параметр может быть автоматически передан action-методу контроллера как аргумент.

Теперь мы можем в нашем методе Detail также получать аргумент ID сразу из URL (например, /Products/Detail/3):

Мы можем использовать такой же подход при получении action для списка продуктов, т.е. мы можем передавать название категории как часть URL (например: /Products/List/Beverages). Для того чтобы сделать код более читабельным, я изменил правила перенаправления так, чтобы аргумент назывался “category” вместо “id”.

Ниже приведен наш класс ProductsController, в котором теперь реализовано полное перенаправление URL и маппирование параметров:

Заметим, что в вышеприведенном action-методе List, параметр category является частью URL, второй параметр – номер страницы page, является опциональным и берется из строки запроса (мы реализуем листинг страниц на стороне сервера и параметр page будет означать, какую по номеру страницу для данной категории необходимо показать).

Опциональные параметры в MVC framework задаются nullable типом в action-методах контроллера. Поскольку параметр page в методе List является nullable int, то MVC framework либо передаст значение, если оно присутствует в URL, либо передаст null в противном случае. Читайте мою предыдущую статью об операторе ??, поддерживающем значение null, чтобы узнать некоторые полезные советы по работе с nullable типами, которые передаются в качестве аргументов, как в данном случае.

Построение объектов модели данных

Сейчас у нас есть класс ProductsController и три action-метода, которые могут обрабатывать входящие веб-запросы. Нашим следующим шагом является построение нескольких классов, которые помогут нам работать с базой данных для получения соответствующих данных, необходимых для обработки этих веб-запросов.

В мире MVC, “модели” являются компонентами приложения, которые отвечают за поддержания состояния. В веб приложениях это состояние обычно находится внутри базы данных (например, мы могли бы иметь объект Product, который использовался бы для предоставления данных о продукте из таблицы Products внутри базы данных SQL).

ASP.NET MVC Framework позволяет вам использовать любую схему доступа к данным или любую библиотеку, какую вы захотите, для того чтобы получать и управлять вашими моделями. Если вы хотите, то вы можете использовать ADO.NET DataSets/DataReaders (или абстракции, построенные на их основе). Если вы предпочитаете ORM (object relational mapper – объектно-реляционное отображение), такие как NHibernate, LLBLGen, WilsonORMapper, LINQ to SQL/LINQ to Entities, то вы также можете ими воспользоваться.

Для нашего приложения e-commerce я собираюсь использовать ORM LINQ to SQL, входящий в состав .NET 3.5 и VS 2008. Вы можете почитать более подробно о LINQ to SQL в серии моих туториалов (в частности, обязательно посмотрите статьи Part1, Part2, Part3 и Part4).

Сначала я кликаю на поддиректории “Models” нашего веб-проекта MVC в VS и выбираю пункт “Add New Item”, для того чтобы добавить модель LINQ to SQL. В дизайнере ORM LINQ to SQL я определяю три класса модели данных, которые ссылаются на таблицы Categories, Produdcts и Suppliers в базе данных Northwind, взятой в качестве примера базы данных SQL Server (для того, чтобы узнать как это делается, читайте статью Part 2 из серии LINQ to SQL):

Теперь, как только мы определили наши классы моделей данных LINQ to SQL, я добавляю новый partial class NorthwindDataContext также в нашу директорию Models:

В этом классе я определю несколько полезных методов, в которых используются выражения LINQ, при помощи которых мы можем получить уникальные объекты Category из базы данных, получить все объекты Products внутри указанной категории из базы данных, а также получить объект Product по указанному ProductID:

Эти полезные методы позволяют нам легко получать необходимые объекты модели данных в нашем классе ProductsController (без написания запросов LINQ в самом классе контроллера):

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

Завершение реализации нашего класса ProductsController

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

Контроллеры обычно не генерируют HTML ответ для запроса. Задача генерации HTML ответа лежит на компонентах “Вид” (“View”) в приложении – которые реализованы в виде отдельных классов/шаблонов. Предполагается, что Виды полностью сфокусированы на логике отображения, и не должны содержать никакой логики приложения или кода по работе с базой данных (вся логика приложения должна обрабатываться контроллером).

В обычном MVC веб процессе, action-методы контроллера обрабатывают входящие веб запросы, используют входящие значения параметров чтобы выполнить соответствующий код приложения, запрашивают или обновляют объекты модели данных из бд, и затем передают управление “Виду”, чтобы отрисовать соответсвующий пользовательский интерфейс в ответ браузеру. При выборе соответствующего вида для отрисовки, контроллер явно передает все необходимые данные и переменные для вида (в качестве аргументов), чтобы тот отрисовал соответствующий ответ:

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

При реализации трех action методов контроллера нашего класса ProductsController, мы будем использовать значения параметров URL для выбора соответствующих объектов из БД, и затем брать компонент “Вида” для отрисовки соответствующего HTML ответа. Мы будем использовать один из методов RenderView() базового класса Controller, для того чтобы указать вид, который мы хотим использовать, а также для явной передачи данных, которые вид будет использовать для отрисовки ответа.

Ниже приведена конечная реализация нашего ProductsController:

Заметим, что число строк кода в наших action методах очень мало (по две строки на метод). С одной стороны, это так, благодаря логике парсинга параметров URL, которая полностью выполняется за нас MVC фреймворком (избавляя нас тем самым от написания большого количества кода). С другой стороны, потому что схема просмотра продукта достаточно простая с точки зрения бизнес логики.

В общем, у вас часто будут контроллеры, которые иногда называют “тощими контроллерами” (“skinny controllers”), потому что методы контроллера очень сжаты (менее 10 строк кода). Это часто является хорошим знаком того, что вы точно инкапсулировали вашу логику по работе с данными и хорошо факторизовали логику контроллера.

Юнит тестирование ProductsController

Вы должно быть удивлены, что на следующем шаге мы собираемся работать над тестированием логики приложения и функциональности. Вы можете спросить – как это вообще возможно? Ведь мы даже не реализовали Виды и наше приложение не может отрисовать ни одного тега HTML. Однако, одно из достоинств модели MVC состоит в том, что мы можем проводить юнит тесты контроллера и модели абсолютно независимо от логики генерации вида/HTML. Как вы увидите ниже, мы можем проводить юнит тесты до того, как мы создали Виды.

Для того чтобы провести юнит тестирование класса ProductsController, над которым мы сейчас работаем, нам нужно создать класс ProductsControllerTest в Test Project, который был добавлен по умолчанию при создании приложения ASP.NET MVC в Visual Studio:

Затем определяем простой юнит тест, который тестирует action Detail:

ASP.NET MVC framework был специально разработан для упрощения юнит тестирования. Все основные API и контракты в фреймворке являются интерфейсами, и точки расширения позволяют легко внедрять и изменять объекты (включая возможность использования IOC контейнеров, таких как Windsor, StructureMap, Spring.NET, и ObjectBuilder). Разработчики могут использовать встроенные mock классы, или использовать любую .NET type-mocking библиотеку, для того чтобы симулировать их собственные тестовые версии MVC объектов.

В вышеприведенном юнит-тесте, вы можете видеть пример того, как мы включаем модельную реализацию “ViewFactory” в наш класс ProductsController перед вызовом action-метода Detail(). Тем самым мы переопределяем стандартную ViewFactory, которая бы в противном случае обрабатывала создание и отрисовку нашего вида.

 

перевод

Читайте также: