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

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

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

Листинг 13-34: Роут, чей URL паттерн соответствует дисковому файлу

public static void RegisterRoutes(RouteCollection routes) { routes.RouteExistingFiles = true; routes.MapRoute("DiskFile", "Content/StaticContent.html",

new {

controller = "Customer", action = "List",

});

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" });

}

Этот роут направляет запросы для URL Content/StaticContent.html действию List контроллера Customer. Вы можете увидеть, как это работает на рисунке 13-17, которые мы создали, запустив приложение и перейдя снова по URL /Content/StaticContent.html.

Рисунок 13-17: Перехват запроса для дискового файла при помощи роута

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

/Content/StaticContent.html будет соответствовать URL паттерн {controller}/{action}. Если вы не будете осторожны, вы можете в конечном итоге получить исключительно странные результаты и снижение производительности. Эту опцию стоит включать только в крайнем случае.

Обход системы маршрутизации

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

Противоположностью этой функции является возможность сделать систему маршрутизации менее содержательной, то есть ссылки не будут оцениваться по отношению к нашим роутам. Мы делаем это с помощью метода IgnoreRoute класса RouteCollection, как показано в листинге 13-35.

341

Листинг 13-35: Использование метода IgnoreRoute

public static void RegisterRoutes(RouteCollection routes) { routes.RouteExistingFiles = true; routes.IgnoreRoute("Content/{filename}.html"); routes.MapRoute("DiskFile", "Content/StaticContent.html",

new {

controller = "Customer", action = "List",

});

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" });

}

Мы можем использовать сегментные переменные, такие как {filename}, для соответствия с рядом URL. В этом случае URL паттерн будет соответствовать любому двухсегментному URL, где первым сегментом является Content, а второй сегмент имеет расширение .html.

Метод IgnoreRoute создает запись в RouteCollection, где обработчиком роута является экземпляр класса StopRoutingHandler, а не MvcRouteHandler. Система маршрутизации очень хорошо распознает этот обработчик. Если URL паттерн, переданный методу IgnoreRoute, находит соответствие, то никакие последующие роуты не будут оцениваться, как и в случае с обычным роутом. Отсюда следует, что важно то место, где вызывается метод IgnoreRoute. Если вы запустите приложение и снова перейдете по URL /Content/StaticContent.html, вы увидите содержимое HTML файла, так как объект StopRoutingHandler обрабатывается перед любым другим роутом.

Резюме

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

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

342

Расширенные функции маршрутизации

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

Подготовка проекта для примера

Мы собираемся продолжать использовать проект из предыдущей главы UrlsAndRoutes, но мы должны сделать несколько изменений, прежде чем начать.

Сначала удалите папку AdditionalControllers и файл HomeController.cs, который в ней содержится. Мы создали эту папку и файл, чтобы продемонстрировать, как расставить приоритеты для пространств имен, и нам они больше не нужны. Для выполнения удаления, щелкните правой кнопкой мыши по папке AdditionalControllers и выберите Delete из всплывающего меню.

Другое изменение, которые мы должны сделать, заключается в том, чтобы упростить роуты в приложении. Отредактируйте файл App_Start/RouteConfig.cs, чтобы он соответствовал содержанию, показанному в листинге 14-1.

Листинг 14-1: Упрощение роутов в фале RouteConfig.cs

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)

{

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

{

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

id = UrlParameter.Optional });

}

}

}

343

Создание исходящих URL в представлениях

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

Так заманчиво просто добавить статический элемент a, атрибут href которого нацелен на метод действия:

<a href="/Home/CustomVariable">This is an outgoing URL</a>

Этот HTML элемент создает ссылку, которая будет обработана как запрос для метода действия CustomVariable контроллера Home, с дополнительной переменной сегмента Hello. Определенные вручную URL, как этот, быстро и просто создавать. Они также чрезвычайно опасны, и вам нужно будет ломать все жестко закодированные URL при изменении URL схемы вашего приложения. Затем вам нужно будет пройти по всем представлениям в приложении и обновить все ссылки для контроллеров и методов действий: это утомительный, подверженный ошибкам и плохо тестируемый процесс.

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

Использование роутинговой системы для генерации исходящих URL

Самый простой способ генерации исходящих URL в представлении заключается в вызове вспомогательного метода Html.ActionLink, как показано на листинге 14-2, где продемонстрированы дополнения, которые мы внесли в файл представления /Views/Shared/ActionName.cshtml.

Листинг 14-2: Использование вспомогательного метода Html.ActionLink для генерации исходящих

URL

@{

Layout = null;

}

<!DOCTYPE html> <html>

<head>

<meta name="viewport" content="width=device-width" /> <title>ActionName</title>

</head>

<body>

<div>The controller is: @ViewBag.Controller</div> <div>The action is: @ViewBag.Action</div>

<div>

@Html.ActionLink("This is an outgoing URL", "CustomVariable") </div>

</body>

</html>

Параметрами метода ActionLink являются текст для ссылки и имя метода действия, на который должна быть нацелена ссылка. Вы можете увидеть результат этого дополнения, запустив приложение и позволив браузеру перейти в корневой URL, как показано на рисунке 14-1.

344

Рисунок 14-1: Добавление в представление исходящего URL

HTML, который генерирует метод ActionLink, основывается на текущей конфигурации маршрутизации. Например, при использовании схемы, определенной в листинге 14-1 (и предполагая, что представление создается запросом к контроллеру Home), мы получаем этот HTML:

<a href="/Home/CustomVariable">This is an outgoing URL</a>

Может показаться, что мы прошли длинный путь, чтобы воссоздать определенный вручную URL, который мы показали вам раньше, но преимущество такого подхода заключается в том, что он автоматически реагирует на изменение конфигурации маршрутизации. В качестве демонстрации мы добавили новый роут в файл RouteConfig.cs, как показано в листинге 14-3.

Листинг 14-3: Добавление роута в приложение

public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute("NewRoute", "App/Do{action}",

new { controller = "Home" }); routes.MapRoute("MyRoute", "{controller}/{action}/{id}",

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

}

Новый роут меняет URL схему для запросов, нацеленных на контроллер Home. Если вы запустите приложение, вы увидите, что это изменение будет отражено в HTML, который создается вспомогательным методом ActionLink:

<a href="/App/DoCustomVariable">This is an outgoing URL</a>

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

Рисунок 14-2: Результат нажатия на ссылку заключается в том, чтобы сделать исходящий URL входящим запросом

345

Понимание того, как выбираются роуты для генерации URL

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

Значение должно быть доступно для каждой сегментной переменной, определенной в URL паттерне. Чтобы найти значения для каждой сегментной переменной, система маршрутизации прежде всего рассматривает значения, которые предоставили мы (используя свойства анонимного типа), затем значения переменных для текущего запроса, и, наконец, значения по умолчанию, определенные в роуте. (Мы вернемся ко второму виду этих значений далее в этой главе).

Ни одно из значений, которые мы предоставили для сегментных переменных, не может конфликтовать с переменными «только по умолчанию» (default-only), определенными в роуте. Это переменные, для которых были предоставлены значения по умолчанию, но которые не появляются в URL паттерне. Например, в этом определении роута переменная myVar является default-only:

routes.MapRoute("MyRoute", "{controller}/{action}", new { myVar = "true" });

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

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

Чтобы внести ясность: система маршрутизации не пытается найти роут, который обеспечивает наиболее подходящее совпадение. Она находит только первое совпадение, и в этот момент она использует роут для генерации URL; любые последующие роуты игнорируются. По этой причине вы должны определить первыми наиболее конкретные роуты. Важно также тестировать генерирование исходящих URL. Если вы попытаетесь создать URL, для которых не может быть найден соответствующий роут, вы создадите ссылку, которая содержит пустой атрибут href:

<a href="">About this application</a>

Ссылка будет показывать представление должным образом, но не будет функционировать, как нужно, когда пользователь нажмет на нее. Если вы создаете только URL (мы покажем вам, как то делается, далее в этой главе), то результат будет null, который отображается в представлениях как пустая строка. Вы можете контролировать соответствие роутов с помощью именованных роутов. См. раздел "Создание URL из конкретного роута" далее в этой главе.

Первый объект Route, отвечающий этим критериям, создаст не-null URL, и это завершит процесс генерирования URL. Выбранные значения параметров будут заменены для каждого параметра сегмента. Если вы установили явные параметры, которые не соответствуют параметрам сегмента или параметрам по умолчанию, тогда метод будет добавлять их в виде набора пар имя/значение строки запроса.

Юнит тест: тестирование исходящих URL

346

Самый простой способ проверить генерацию исходящего URL – то использовать статический метод UrlHelper.GenerateUrl, который имеет параметры для всех способов, которыми вы можете направлять генерирование роута, например, указав имя роута, контроллер, действие, значения сегментов и так далее. Вот тестовый метод, который проверяет генерирование URL по отношению к роуту, определенному в листинге 14-3. Мы добавили его в файл RouteTests.cs тестового проекта (потому что он использует тестовые методы, которые мы определили в предыдущей главе):

[TestMethod]

public void TestIncomingRoutes() {

// ...код удален, чтобы предотвратить ошибки теста...

}

[TestMethod]

public void TestOutgoingRoutes() { // Arrange

RouteCollection routes = new RouteCollection(); RouteConfig.RegisterRoutes(routes);

RequestContext context = new RequestContext(CreateHttpContext(), new RouteData()); // Act - сгенерировать URL

string result = UrlHelper.GenerateUrl(null, "Index", "Home", null, routes, context, true);

// Assert Assert.AreEqual("/App/DoIndex", result);

}

...

Мы генерируем URL, а не ссылку, потому нам не нужно беспокоиться о тестировании окружающего HTML. Метод UrlHelper.GenerateUrl требует объект RequestContext, который мы создаем с помощью mock-объекта HttpContextBase вспомогательного метода тестирования CreateHttpContext. Прочитайте о юнит тестировании входящих URL в предыдущей главе, чтобы увидеть полный исходный код CreateHttpContext.

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

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

Версия по умолчанию метода ActionLink предполагает, что вы хотите работать с методом действия в том же контроллере, который вызвал отображение представления. Чтобы создать исходящий URL, нацеленный на другой контроллер, вы можете использовать другой перегруженный вариант, который позволяет указать имя контроллера, как показано в листинге 14-4. Здесь мы внесли изменение в представление ActionName.cshtml.

Внимание

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

Листинг 14-4: Нацеленность на другой контроллер при помощи вспомогательного метода

ActionLink

@{

Layout = null;

}

<!DOCTYPE html>

347

<html>

<head>

<meta name="viewport" content="width=device-width" /> <title>ActionName</title>

</head>

<body>

<div>The controller is: @ViewBag.Controller</div> <div>The action is: @ViewBag.Action</div>

<div>

@Html.ActionLink("This is an outgoing URL", "CustomVariable") </div>

<div>

@Html.ActionLink("This targets another controller", "Index", "Admin") </div>

</body>

</html>

Когда вы отобразите представление, вы увидите следующий сгенерированный HTML:

<a href="/Admin">This targets another controller</a>

Наш запрос для URL, предназначенного для метода действия Index контроллера Admin, был выражен методом ActionLink как /Admin. Система маршрутизации довольно умна, и она знает, что роут, определенный в приложении, будет использовать по умолчанию метод действия Index, что позволяет опускать ненужные сегменты.

Передача особых значений

Вы можете передать значения для сегментных переменных, используя анонимный тип, при помощи свойств, представляющих сегменты. В листинге 14-5 приведен пример, который мы добавили в файл представления ActionName.cshtml.

Листинг 14-5: Предоставление значений для сегментных переменных

<body>

<div>The controller is: @ViewBag.Controller</div> <div>The action is: @ViewBag.Action</div>

<div>

@Html.ActionLink("This is an outgoing URL", "CustomVariable", new { id = "Hello" })

</div>

</body>

В этом примере мы предоставили значение для сегментной переменной id. Если наше приложение использует роут, показанный в листинге 14-3, то мы получим следующий HTML, когда отобразим представление:

<a href="/App/DoCustomVariable?id=Hello">This is an outgoing URL</a>

Обратите внимание, что значение, которое мы передали, было добавлено в качестве части строки запроса, чтобы вписаться в URL паттерн, описанный роутом, который мы добавили в листинге 14-3. Это потому что нет никакой сегментной переменной, которая соответствует id в этом роуте. В листинге 14-6 мы отредактировали роуты в файле RouteConfig.cs, чтобы использовался лишь тот роут, который имеет сегмент id.

Листинг 14-6: Отключение роута

...

public static void RegisterRoutes(RouteCollection routes) {

348

// Это выражения под комментариями

//routes.MapRoute("NewRoute", "App/Do{action}", // new { controller = "Home" });

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

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

}

...

Если мы запустим приложение еще раз, URL в представлении ActionName.cshtml создаст следующий HTML элемент:

<a href="/Home/CustomVariable/Hello">This is an outgoing URL</a>

На этот раз значение, которое мы назначили свойству id, включено в качестве URL сегмента, в соответствии с активным роутом в конфигурации приложения. Мы ведь говорили, что система маршрутизации довольно умна?

Повторное использование сегментных переменных

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

Представьте, что у нашего приложения есть единственный роут:

routes.MapRoute("MyRoute", "{controller}/{action}/{color}/{page}");

Теперь представьте, что пользователь на данный момент переходит по URL /Catalog/List/Purple/123, и мы отображаем ссылку следующим образом:

@Html.ActionLink("Click me", "List", "Catalog", new {page=789}, null)

Можно было бы ожидать, что система маршрутизации не в состоянии найти соответствие с роутом, потому что мы не предоставили значение переменной сегмента color, и для нее нет значения по умолчанию. Однако, это не так. Система маршрутизации найдет соответствие с роутом, который мы определили. Она сгенерирует следующий HTML:

<a href="/Catalog/List/Purple/789">Click me</a>

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

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

@Html.ActionLink("Click me", "List", "Catalog", new {color="Aqua"}, null)

349

Мы предоставили значение для color, но не для page. Но color появляется перед page в URL паттерне, и поэтому система маршрутизации не будет повторно использовать значения из входящего URL, и роут не будет совпадать.

Лучший способ борьбы с таким поведением – это не дать ему случиться. Мы настоятельно рекомендуем вам не полагаться на такое поведение, а также чтобы вы задавали значения для всех переменных сегмента в URL паттерне. Такое поведение не только сделает ваш код трудно читаемым, но вы в конечном итоге только и будете что сидеть и думать о том, каким же образом ваши пользователи делают запросы, что приведет к неоправданным временным и трудовым расходам.

Указание HTML атрибутов

Мы сосредоточили свое внимание на URL, которые генерирует вспомогательный метод ActionLink, но помните, что метод создает полный якорь HTML элемента (a). Мы можем установить атрибуты для этого элемента, предоставляя анонимный тип, свойства которого соответствуют требуемым атрибутам. Листинг 14-7 показывает, как мы изменили представление ActionName.cshtml с целью установить атрибут id и присвоить класс HTML элементу a.

Листинг 14-7: Генерирование якорного элемента с атрибутами

<body>

<div>The controller is: @ViewBag.Controller</div> <div>The action is: @ViewBag.Action</div>

<div>

@Html.ActionLink("This is an outgoing URL",

"Index", "Home", null, new {id = "myAnchorID", @class = "myCSSClass"}) </div>

</body>

Мы создали новый анонимный тип, который имеет свойства id и class, и передали его в качестве параметра методу ActionLink. Мы передали null для дополнительных значений сегментных переменных, указывая, что у нас нет никаких значений, которые мы можем предоставить.

Совет

Обратите внимание, что мы поставили перед свойством class символ @. Это

возможность языка C#, которая позволяет нам использовать зарезервированные ключевые слова в качестве имен членов класса.

Когда отображается ActionLink, мы получаем следующий HTML:

<a class="myCSSClass" href="/" id="myAnchorID">This is an outgoing URL</a>

Создание полных URL в ссылках

Все ссылки, которые мы генерировали, содержали относительные URL, но мы также можем использовать вспомогательный метод ActionLink, чтобы генерировать полные (абсолютные) ссылки, как показано в листинге 14-8.

Листинг 14-8: Создание абсолютного URL

<body>

<div>The controller is: @ViewBag.Controller</div> <div>The action is: @ViewBag.Action</div>

<div>

@Html.ActionLink("This is an outgoing URL", "Index", "Home",

350

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