Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

.pdf
Скачиваний:
25
Добавлен:
19.03.2016
Размер:
17.66 Mб
Скачать

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

Чтобы решить эту проблему, мы можем сказать MVC фреймворку отдавать предпочтение определенным пространствам имен при попытке выбрать имя класса контроллера, как показано в листинге 13-24.

Листинг 13-24: Указание порядка приоритета пространств имен

using System;

using System.Collections.Generic; using System.Linq;

using System.Web; using System.Web.Mvc;

using System.Web.Routing; namespace UrlsAndRoutes

{

public class RouteConfig

{

public static void RegisterRoutes(RouteCollection routes)

{

routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}", new

{

controller = "Home", action = "Index",

id = UrlParameter.Optional },

new[] { "URLsAndRoutes.AdditionalControllers" });

}

}

}

Мы выражаем пространства имен как массив строк, и в листинге мы сказали MVC смотреть сначала на пространство имен URLsAndRoutes.AdditionalControllers, прежде чем искать в другом месте.

Если подходящий контроллер не может быть найден в этом пространстве имен, тогда MVC по вернется к своему поведению по умолчанию и будет искать совпадение в доступных пространствах имен. Если вы запустите приложение после этого дополнения, вы увидите результат, продемонстрированный на рисунке 13-11. Здесь показан запрос для корневого URL, который переводится в запрос для метода действия Index контроллера Home. Это запрос был отправлен контроллеру, который мы определили в пространстве имен AdditionalControllers.

Рисунок 13-11: Приоритет контроллера в определенном пространстве имен

331

Пространства имен, добавленные в роут, обладают равным приоритетом. MVC не проверяет первое пространство имен, прежде чем перейти ко второму и так далее. Например, предположим, что мы добавили оба наших пространства имен в роут:

...

routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}", new { controller = "Home", action = "Index",

id = UrlParameter.Optional },

new[] { "URLsAndRoutes.AdditionalControllers", "UrlsAndRoutes.Controllers" });

...

Мы увидели бы ту же ошибку, что показана на рисунке 13-10, потому что MVC пытается найти имя класса контроллера во всех пространствах имен, которые мы добавили к роуту. Если мы хотим отдать предпочтение одному контроллеру в одном пространстве имен, но и не хотим иметь проблемы с контроллерами в других пространствах имен, нам нужно создать несколько роутов, как показано в листинге 13-25.

Листинг 13-25: Использование нескольких роутов для выбора нужного пространства имен

using System;

using System.Collections.Generic; using System.Linq;

using System.Web; using System.Web.Mvc;

using System.Web.Routing; namespace UrlsAndRoutes

{

public class RouteConfig

{

public static void RegisterRoutes(RouteCollection routes)

{

routes.MapRoute("AddContollerRoute", "Home/{action}/{id}/{*catchall}", new

{

controller = "Home", action = "Index",

id = UrlParameter.Optional },

new[] { "URLsAndRoutes.AdditionalControllers" }); routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}", new

{

controller = "Home", action = "Index",

id = UrlParameter.Optional },

new[] { "URLsAndRoutes.Controllers" });

}

}

}

Наш первый роут применяется тогда, когда пользователь явно запрашивает URL, первым сегментом которого является Home, и будет нацелен на контроллер Home в папке AdditionalControllers. Все другие запросы, в том числе те, где не указан первый сегмент, будут обработаны контроллерами в папке Controllers.

Мы можем сказать MVC фреймворку искать только в пространствах имен, которые мы указали. Если соответствующий контроллер не может быть найден, то фреймворк не будет искать в другом месте. Листинг 13-26 показывает, как используется эта функция.

332

Листинг 13-26: Отключение не используемых пространств имен

using System;

using System.Collections.Generic; using System.Linq;

using System.Web; using System.Web.Mvc;

using System.Web.Routing; namespace UrlsAndRoutes

{

public class RouteConfig

{

public static void RegisterRoutes(RouteCollection routes)

{

Route myRoute = routes.MapRoute("AddContollerRoute", "Home/{action}/{id}/{*catchall}",

new

{

controller = "Home", action = "Index",

id = UrlParameter.Optional },

new[] { "URLsAndRoutes.AdditionalControllers" }); myRoute.DataTokens["UseNamespaceFallback"] = false;

}

}

}

Метод MapRoute возвращает объект Route. Мы игнорировали это в предыдущих примерах, потому что нам не нужно было вносить любые изменения в роуты, которые были созданы. Чтобы отключить поиск контроллеров в других пространствах имен, мы берем объект Route и устанавливаем ключ

UseNamespaceFallback в свойстве коллекции DataTokens на false.

Эта настройка будет передана компоненту, отвечающему за поиск контроллеров, который известен как фабрика контроллеров и который мы подробно обсудим в главе 17. Результат этого дополнения заключается в том, что запросы, которые не могут быть удовлетворены контроллером Home в папке

AdditionalControllers, не сработают.

Ограничение роутов

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

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

Ограничение роута при помощи регулярного выражения

Первый способ, который мы рассмотрим, заключается в ограничении роута при помощи регулярных выражений. Листинг 13-27 содержит пример.

333

Листинг 13-27: Использование регулярного выражения для ограничения роута

...

public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",

new { controller = "Home", action = "Index", id = UrlParameter.Optional }, new { controller = "^H.*"},

new[] { "URLsAndRoutes.Controllers"});

}

...

Мы определяем ограничения путем передачи их в качестве параметра методу MapRoute. Как и значения по умолчанию, ограничения выражаются в виде анонимного типа, где свойства типа соответствуют именам сегментных переменных, которые мы хотим ограничить.

В этом примере мы использовали ограничение с помощью регулярных выражений. Оно заключается в том, что URL и роут соответствуют друг другу только тогда, когда значение переменой controller начинается с буквы H.

Примечание

Значения по умолчанию используются до проверки на ограничения. Так, например, если мы запрашиваем URL /, для controller применяется значение по умолчанию Home. Затем проверяются ограничения, и поскольку значение controller начинается с H, то URL по умолчанию будет соответствовать роуту.

Ограничение роута до набора указанных значений

Мы можем использовать регулярные выражения, чтобы ограничить роут таким образом, что только конкретные значения URL сегмента будут способствовать соответствию URL и роута. Это можно сделать, используя символ вертикальной черты (|), как показано в листинге 13-28.

Листинг 13-28: Ограничение роута до определенного набора значений сегментных переменных

...

public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",

new { controller = "Home", action = "Index", id = UrlParameter.Optional }, new { controller = "^H.*", action = "^Index$|^About$"},

new[] { "URLsAndRoutes.Controllers"});

}

...

Это ограничение позволяет роуту соответствовать только тем URL, где значениями сегмента действия являются Index или About. Ограничения применяются вместе, так что ограничения, накладываемые на значение переменной action, сочетаются со значениями, накладываемыми на переменную controller. Это означает, что роут в листинге 13-28 будет только в том случае соответствовать URL, если переменная controller начинается с буквы H, а для переменной action есть значения Index или About. Итак, теперь вы видите, что мы понимаем под созданием идеально точных роутов.

Ограничение роутов при помощи HTTP методов

Мы можем ограничить роуты, чтобы они соответствовали URL только тогда, когда он запрашивается с помощью конкретного HTTP метода, как показано в листинге 13-29.

334

Листинг 13-29: Ограничение роутов при помощи HTTP методов

...

public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",

new { controller = "Home", action = "Index", id = UrlParameter.Optional }, new { controller = "^H.*", action = "Index|About",

httpMethod = new HttpMethodConstraint("GET") }, new[] { "URLsAndRoutes.Controllers" });

}

...

Формат для указания ограничения при помощи HTTP метода немного странный. Не имеет никакого значения, какое имя мы дали свойству, поскольку мы присвоили его экземпляру класса HttpMethodConstraint. В листинге мы назвали наше свойство для ограничения HttpMethod, чтобы его можно было отличить от других видов ограничений.

Примечание

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

конвейере запросов, и они определяют имена контроллера и действия, необходимых для обработки запроса. Атрибуты методов действия используются для определения, какие конкретные методы действий будут использоваться для обработки запроса контроллером. Мы подробно расскажем о том, как работать с различными видами HTTP методов (в том числе таким, как PUT и DELETE), в главе 14.

Мы передаем имена нужных HTTP методов в виде строковых параметров в конструктор класса HttpMethodConstraint. В листинге мы ограничили роут запросом GET, но мы могли бы легко добавить поддержку других методов, например:

...

httpMethod = new HttpMethodConstraint("GET", "POST") },

...

Юнит тестирование: ограничение роутов

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

...

[TestMethod]

public void TestIncomingRoutes() { TestRouteMatch("~/", "Home", "Index"); TestRouteMatch("~/Home", "Home", "Index"); TestRouteMatch("~/Home/Index", "Home", "Index"); TestRouteMatch("~/Home/About", "Home", "About");

TestRouteMatch("~/Home/About/MyId", "Home", "About", new { id = "MyId" }); TestRouteMatch("~/Home/About/MyId/More/Segments", "Home", "About",

new {

id = "MyId",

catchall = "More/Segments"

335

});

TestRouteFail("~/Home/OtherAction");

TestRouteFail("~/Account/Index");

TestRouteFail("~/Account/About");

}

...

Определение пользовательского ограничения

Если стандартные ограничения не удовлетворяют ваши потребности, вы можете определить свои собственные ограничения путем реализации интерфейса IRouteConstraint. Чтобы продемонстрировать эту возможность, мы добавили в проект папку Infrastructure и создали новый файл класса UserAgentConstraint.cs, содержание которого показано в листинге 13-30.

Листинг 13-30: Создание пользовательского ограничения роутов

using System.Web;

using System.Web.Routing;

namespace UrlsAndRoutes.Infrastructure

{

public class UserAgentConstraint : IRouteConstraint

{

private string requiredUserAgent;

public UserAgentConstraint(string agentParam)

{

requiredUserAgent = agentParam;

}

public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)

{

return httpContext.Request.UserAgent != null && httpContext.Request.UserAgent.Contains(requiredUserAgent);

}

}

}

Интерфейс IRouteConstraint определяет метод Match, который можно использовать для того, чтобы определить, было ли сделано нужное ограничение. Параметры метода Match обеспечивает доступ к запросу от клиента, роуту, который в настоящее время оценивается, имени параметра ограничения, сегментным переменным, извлеченным из URL, а также информации о том, является ли это запросом на проверку входящего или исходящего URL. В нашем примере мы проверяем значение свойства UserAgent запроса клиента, чтобы увидеть, если оно содержит значение, которое было передано в наш конструктор. Листинг 13-31 показывает пользовательское ограничение, которое используется для роута.

Листинг 13-31: Применение пользовательского ограничения к роуту

using System;

using System.Collections.Generic; using System.Linq;

using System.Web; using System.Web.Mvc;

using System.Web.Routing;

using UrlsAndRoutes.Infrastructure; namespace UrlsAndRoutes

{

public class RouteConfig

{

public static void RegisterRoutes(RouteCollection routes)

{

336

routes.MapRoute("ChromeRoute", "{*catchall}", new { controller = "Home", action = "Index" }, new

{

customConstraint = new UserAgentConstraint("Chrome")

},

new[] { "UrlsAndRoutes.AdditionalControllers" }); routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}", new

{

controller = "Home", action = "Index",

id = UrlParameter.Optional },

new[] { "URLsAndRoutes.Controllers" });

}

}

}

В листинге мы ограничили первый роут так, что он будет соответствовать только запросы от браузеров, где строка пользовательского агента содержит Chrome. Если роут совпадет, то запрос будет отправлен методу действия Index в контроллере Home, определенном в папке AdditionalControllers, независимо от структуры и содержания URL, который был запрошен. Наш URL паттерн состоит только из переменной сегмента catchall, что означает, что значения переменных сегмента controller и action всегда будут браться из значений по умолчанию, а не из самого URL.

Второй роут будет соответствовать всем другим запросам и целевыми контроллерами в папке Controllers. Результат использования этих роутов заключается в том, что определенный вид браузера всегда останавливается на том же месте в приложении. Вы можете увидеть это на рисунке 13-12, где показан переход к приложению с помощью Google Chrome.

Рисунок 13-12: Переход к приложению при помощи Google Chrome

На рисунке 13-13 показан переход к приложению при помощи Internet Explorer.

Рисунок 13-13: Переход к приложению при помощи Internet Explorer

337

Примечание

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

Роутовые запросы для дисковых файлов

Не все запросы для MVC приложения предназначены для контроллеров и действий. Нам попрежнему нужен способ обрабатывать такой контент, как изображения, статические HTML файлы, JavaScript библиотеки и так далее. В качестве демонстрации мы создали файл StaticContent.html в папке Content нашего MVC приложения, используя HTML Page. Листинг 13-32 показывает содержимое этого файла.

Листинг 13-32: Файл StaticContent.html

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Static HTML Content</title></head> <body>

This is the static html file (~/Content/StaticContent.html) </body>

</html>

Система маршрутизации обеспечивает интегрированную поддержку для обслуживания такого содержания. Если вы запустите приложение и запросите URL /Content/StaticContent.html, вы увидите содержимое этого простого HTML файла, отображенного в браузере, как показано на рисунке 13-14.

Рисунок 13-14: Запрос файла со статическим содержание

По умолчанию система маршрутизации проверяет URL на соответствие дисковому файлу перед оценкой роутов приложения, и именно поэтому нам не нужно было добавлять роут, чтобы получить результат, показанный на рисунке 13-14.

338

Если есть соответствие между запрашиваемым URL и дисковым файлом, то будет обработан дисковый файл, а роуты, определенные приложением, никогда не будут использованы. Мы можем изменить это поведение так, чтобы наши роуты оценивались раньше дисковых файлов, если мы установим свойство RouteExistingFiles RouteCollection на true, как показано в листинге 13-33.

Листинг 13-33: Включение оценки роутов до проверки файлов

public static void RegisterRoutes(RouteCollection routes) { routes.RouteExistingFiles = true; routes.MapRoute("ChromeRoute", "{*catchall}",

new { controller = "Home", action = "Index" }, new {

customConstraint = new UserAgentConstraint("Chrome") },

new[] { "UrlsAndRoutes.AdditionalControllers" }); routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",

new { controller = "Home", action = "Index", id = UrlParameter.Optional },

new[] { "URLsAndRoutes.Controllers" });

}

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

Конфигурация сервера приложения

Visual Studio 2012 использует IIS Express в качестве сервера приложений для MVC проектов. Мы не только должны установить свойство RouteExistingFiles в методе RegisterRoutes на true, мы также должны сказать IIS Express не перехватывать запросы для дисковых, прежде чем они попадут в систему маршрутизации MVC.

Прежде всего, запустите IIS Express. Самый простой способ сделать это – это запустить MVC приложение из Visual Studio, которое покажет иконку IIS Express на панели задач. Щелкните правой кнопкой мыши по иконке и выберите из всплывающего меню Show All Applications. Нажмите на UrlsAndRoutes в столбце Site Name для отображения информации о конфигурации IIS, как показано на рисунке 13-15.

Рисунок 13-15: Информация о конфигурации IIS Express

339

Нажмите на ссылку Config в нижней части окна, чтобы открыть конфигурационный файл IIS Express в Visual Studio. Теперь нажмите Ctrl + F и найдите UrlRoutingModule-4.0. Там будет запись, найденная в разделе modules конфигурационного файла, и нам нужно установить для атрибута

preCondition пустую строку:

<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="" />

Теперь перезапустите приложение в Visual Studio, чтобы измененные настройки вступили в силу, и перейдите по URL /Content/StaticContent.html. Вместо того чтобы увидеть содержимое файла, вы увидите сообщение об ошибке, показанное на рисунке 13-16. Эта ошибка возникла потому, поскольку запрос для HTML файла был передан системе маршрутизации MVC, но роут, совпадающий с URL, направляет запрос к контроллеру Content, который не существует.

Рисунок 13-16: Запрос файла со статическим контентом, который обрабатывается роутинговой системой

Совет

Альтернативным подходом является использование сервера разработки Visual Studio, который вы можете активировать в разделе Web конфигурации проекта, если вы выберете при выборе пункт UrlsAndRoutes Properties из Visual Studio меню Project. Сервер разработки довольно прост и не является урезанной версией

IIS, как IIS Express, и поэтому он не перехватывает запросы таким же образом.

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

Как только свойство было установлено на true, мы можем определить роуты, подходящие URL, которые соответствуют дисковым файлам, как показано в листинге 13-34.

340

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]