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

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

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

"https", "myserver.mydomain.com", " myFragmentName", new { id = "MyId"},

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

</body>

Это перегруженный метод ActionLink с большинством параметров, что позволяет нам предоставлять значения для протокола (https, в нашем примере), имя целевого сервера (myserver.mydomain.com) и URL фрагмент (myFragmentName), а также все другие опции, которые вы видели раньше. При отображении в представлении вызов метода генерирует следующий HTML:

<a class="myCSSClass" href="https://myserver.mydomain.com/Home/Index/MyId#myFragmentName" id="myAnchorID">This is an outgoing URL</a>

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

Генерация URL (а не ссылок)

Вспомогательный метод Html.ActionLink создает полные HTML элементы <a>, а это именно то, чего мы хотим практически всегда. Тем не менее, бывают случаи, когда нам просто нужен URL. Это может случиться, потому что мы хотим отобразить URL, построить HTML для ссылки вручную, отобразить значение URL или включить URL в качестве элемента данных в HTML страницу, подлежащую отображению.

При таких обстоятельствах мы можем использовать метод Url.Action для создания только URL, а не окружающего HTML. Листинг 14-9 показывает изменения, которые мы внесли в файл

ActionName.cshtml, чтобы создать URL при помощи Url.Action.

Листинг 14-9: Создание URL без окружающего HTML

@{

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>

This is a URL:

@Url.Action("Index", "Home", new { id = "MyId" }) </div>

</body>

</html>

Метод Url.Action работает таким же образом, как и метод Html.ActionLink, за исключением того, что он генерирует только URL. Перегруженные версии метода и параметры, которые они принимают, одинаковы для обоих методов, и вы можете сделать все то же самое с Url.Action, что

351

мы продемонстрировали для Html.ActionLink в предыдущих разделах. На рисунке 14-3 показано, как отображается URL из листинга 14-9.

Рисунок 14-3: Отображение URL в представлении

Создание исходящих URL в методах действия

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

Листинг 14-10: Создание исходящего URL в методе действия

public ViewResult MyActionMethod() {

string myActionUrl = Url.Action("Index", new { id = "MyID" });

string myRouteUrl = Url.RouteUrl(new { controller = "Home", action = "Index" }); //... сделать что-то с URL...

return View();

}

Для роутинга в нашем примере приложения переменной myActionUrl будет присвоено значение /Home/Index/MyID, а переменной myRouteUrl будет присвоено значение /, что согласуется с результатами, которые получаются из вызова этих вспомогательных методов в представлении.

Более общим требованием является перенаправление браузера клиента на другой URL. Мы можем сделать это, возвращая результат вызова метода RedirectToAction, как показано в листинге 14-11.

Листинг 14-11: Перенаправление на другое действие

public RedirectToRouteResult MyActionMethod() { return RedirectToAction("Index");

}

Результатом метода RedirectToAction является RedirectToRouteResult, который указывает MVC выполнять инструкцию перенаправления к URL, который будет вызывать определенное действие. Есть обычные перегруженные версии метода RedirectToAction, которые указывают контроллер и значения для сегмента переменных в генерируемых URL.

Если вы хотите сделать перенаправление, используя URL, сгенерированный только из свойств объекта, вы можете использовать метод RedirectToRoute, как показано в листинге 14-12.

352

Листинг 14-12: Перенаправление на URL, который сгенерирован из свойств анонимного типа

public RedirectToRouteResult MyActionMethod() { return RedirectToRoute(new {

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

id = "MyID" });

}

Этот метод также возвращает объект RedirectToRouteResult и имеет тот же результат, что и вызов метода RedirectToAction.

Генерирование URL из конкретного роута

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

Листинг 14-13: Изменение роутинговой конфигурации

public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute("MyRoute", "{controller}/{action}"); routes.MapRoute("MyOtherRoute", "App/{action}", new { controller = "Home" });

}

Мы указали имена для обоих роутов: MyRoute и MyOtherRoute. Есть две причины именования роутов:

В качестве напоминания о целях роута

Чтобы вы могли выбрать конкретный роут для генерации исходящего URL

Мы организовали роуты так, чтобы наименее специфичный появился первым в списке. Это означает, что если бы нам надо было создать такую ссылку с помощью метода ActionLink:

@Html.ActionLink("Click me", "Index", "Customer")

исходящая ссылка всегда будет генерироваться при помощи MyRoute:

<a href="/Customer/Index">Click me</a>

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

@Html.RouteLink("Click me", "MyOtherRoute","Index", "Customer")

В результате этого ссылка, сгенерированная вспомогательным методом, выглядит вот так:

<a Length="8" href="/App/Index?Length=5">Click me</a>

В данном случае контроллер, который мы указали, Customer, переписан роутом, и ссылка вместо этого нацелена на контроллер Home.

Проблема именованных роутов

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

353

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

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

Настройка системы маршрутизации

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

Создание пользовательской реализации RouteBase

Если вам не нравится способ, которым стандартные объекты Route соответствуют URL, или вы хотите реализовать что-то необычное, вы можете создать альтернативный класс из RouteBase. Это дает вам контроль над тем, как URL находят соответствие, как извлекаются параметры и как создаются исходящие URL.

Чтобы создать класс из RouteBase, вам нужно реализовать два метода:

GetRouteData(HttpContextBase httpContext): это механизм, при котором работает

соответствие входящих URL. Фреймворк вызывает этот метод для каждой записи RouteTable.Routes, пока одна из них не вернет значение не-null.

GetVirtualPath(RequestContext requestContext, RouteValueDictionary values): Это

механизм, при котором работает генерация исходящих URL. Фреймворк вызывает этот метод для каждой записи RouteTable.Routes, пока одна из них не вернет значение не-null.

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

Для начала нам нужно создать контроллер, который будет получать наследные запросы. Мы назвали наш контроллер LegacyController, и его содержимое показано в листинге 14-14.

Листинг 14-14: Класс LegacyController

using System.Web.Mvc;

namespace UrlsAndRoutes.Controllers

{

public class LegacyController : Controller

{

public ActionResult GetLegacyURL(string legacyURL)

{

return View((object)legacyURL);

}

}

}

354

В этом простом контроллере метод действия GetLegacyURL принимает параметр и передает его в качестве модели представления в представление. Если бы мы действительно реализовали этот контроллер, мы бы использовали этот метод для извлечения файлов, которые были запрошены, а сейчас мы просто собираемся показать URL в представлении.

Совет

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

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

Представление, которое мы связали с этим методом действия, называется GetLegacyURL.cshtml. Оно показано в листинге 14-15.

Листинг 14-15: Представление GetLegacyURL

@model string @{

ViewBag.Title = "GetLegacyURL"; Layout = null;

}

<h2>GetLegacyURL</h2>

The URL requested was: @Model

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

Роутинг входящих URL

Мы создали класс LegacyRoute, который мы поместили в папку Infrastructure (сюда мы помещаем классы поддержки, которые действительно не попадают в другие категории). Класс показан в листинге 14-16.

Листинг 14-16: Класс LegacyRoute

using System; using System.Linq; using System.Web;

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

namespace UrlsAndRoutes.Infrastructure

{

public class LegacyRoute : RouteBase

{

private string[] urls;

public LegacyRoute(params string[] targetUrls)

{

urls = targetUrls;

}

public override RouteData GetRouteData(HttpContextBase httpContext)

355

{

RouteData result = null; string requestedURL =

httpContext.Request.AppRelativeCurrentExecutionFilePath;

if (urls.Contains(requestedURL, StringComparer.OrdinalIgnoreCase))

{

result = new RouteData(this, new MvcRouteHandler()); result.Values.Add("controller", "Legacy"); result.Values.Add("action", "GetLegacyURL"); result.Values.Add("legacyURL", requestedURL);

}

return result;

}

public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)

{

return null;

}

}

}

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

Если мы не можем обработать запрос, то мы просто возвращаем null, и система маршрутизации перейдет к следующему роуту в списке и повторит процесс. Если мы можем обработать запрос, нам необходимо вернуть экземпляр класса RouteData, содержащий значения для переменных controller и action, и все остальное, что мы хотим передать методу действия.

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

MvcRouteHandler, который и придает смысл значениям controller и action:

result = new RouteData(this, new MvcRouteHandler());

Для подавляющего большинства MVC приложений вам потребуется этот класс, так как он объединяет систему маршрутизации и модель контроллер/действие MVC приложения. Но вы можете заменить MvcRouteHandler, и мы покажем вам это в разделе «Создание пользовательского обработчика роутов» далее в этой главе.

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

Заметьте, что имя этого свойства совпадает с именем параметра нашего метода действия, и это гарантирует, что значение, которое мы генерируем здесь, будет передано в метод действия с помощью параметра.

Последним шагом является регистрация нового роута, который использует наш вариант RouteBase. Вы можете увидеть, как это делается, в листинге 14-17, который показывает дополнение к файлу

RouteConfig.cs.

Листинг 14-17: Регистрация пользовательской реализации RouteBase

public static void RegisterRoutes(RouteCollection routes) { routes.Add(new LegacyRoute(

"~/articles/Windows_3.1_Overview.html",

356

"~/old/.NET_1.0_Class_Library")); routes.MapRoute("MyRoute", "{controller}/{action}");

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

}

Мы создаем новый экземпляр нашего класса LegacyRoute и передаем ему нужные URL. Затем мы добавляем объект в RouteCollection с помощью метода Add. Теперь, когда мы запустим приложение и запросим один из URL, которые мы определили, запрос будет обрабатываться нашим пользовательским классом и будет нацелен на наш контроллер, как показано на рисунке 14-4.

Рисунок 14-4: Роутинг запросов при помощи пользовательской реализации RouteBase

Генерирование исходящих URL

Для поддержки создания исходящих URL мы должны реализовать метод GetVirtualPath в нашем классе LegacyRoute. Еще раз, если мы не в состоянии справиться с запросом, мы даем знать об этом системе маршрутизации, возвращая null. В противном случае мы возвращаем экземпляр класса VirtualPathData. Листинг 14-18 показывает нашу реализацию этого метода.

Листинг 14-18: Реализация метода GetVirtualPath

public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)

{

VirtualPathData result = null;

if (values.ContainsKey("legacyURL") && urls.Contains((string)values["legacyURL"], StringComparer.OrdinalIgnoreCase))

{

result = new VirtualPathData(this, new UrlHelper(requestContext)

.Content((string)values["legacyURL"]).Substring(1));

}

return result;

}

Мы передали сегментные переменные и другую сопутствующую информацию, используя анонимные типы, а система маршрутизации преобразовала их в объекты RouteValueDictionary. Листинг 14-19 показывает дополнение к ActionName.cshtml, который генерирует исходящий URL с помощью нашего пользовательского роута.

Листинг 14-19: Создание исходящего URL при помощи пользовательского роута

@{

Layout = null;

357

}

<!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>

This is a URL:

@Html.ActionLink("Click me", "GetLegacyURL",

new { legacyURL = "~/articles/Windows_3.1_Overview.html" })

</div>

</body>

</html>

Когда это представление рендерится, вспомогательный метод ActionLink генерирует следующий HTML, как вы и могли ожидать:

<a href="/articles/Windows_3.1_Overview.html">Click me</a>

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

Если мы получаем соответствие, мы создаем новый экземпляр VirtualPathData, передавая ему ссылку на текущий объект и исходящий URL. Мы использовали метод Content класса UrlHelper для преобразования относительного URL в тот, который может быть передан в браузер. Система маршрутизации добавляет дополнительный / в URL, поэтому мы должны позаботиться о том, чтобы удалить символ из нашего генерируемого URL.

Создание пользовательского обработчика роутов

Мы работали с MvcRouteHandler в наших примерах, потому что он соединяет систему маршрутизации к MVC фреймворком. И поскольку наше внимание сосредоточено MVC, это нам фактически всегда подходит. Несмотря на это, система маршрутизации позволяет нам определять наш собственный обработчик роутов путем реализации интерфейса IRouteHandler. Листинг 14-20 показывает содержимое класса CustomRouteHandler, который мы добавили в папку Infrastructure нашего проекта.

Листинг 14-20: Реализация интерфейса IRouteHandler

using System.Web;

using System.Web.Routing;

namespace UrlsAndRoutes.Infrastructure

{

public class CustomRouteHandler : IRouteHandler

{

public IHttpHandler GetHttpHandler(RequestContext requestContext)

{

return new CustomHttpHandler();

}

}

public class CustomHttpHandler : IHttpHandler

{

358

public bool IsReusable

{

get { return false; }

}

public void ProcessRequest(HttpContext context)

{

context.Response.Write("Hello");

}

}

}

Цель интерфейса IRouteHandler заключается в обеспечении средства для генерации реализаций интерфейса IHttpHandler, который отвечает за обработку запросов. При MVC реализации этих интерфейсов находятся контроллеры, вызываются методы действия, рендерятся представления и результаты записываются в ответ. Наша реализация немного проще. Она просто пишет клиенту слово Hello (не HTML документ, содержащий это слово, а только текст). Мы можем зарегистрировать наш пользовательский обработчик в файле RouteConfig.cs, когда мы определяем роут, как показано в листинге 14-21.

Листинг 14-21: Использование пользовательского обработчика роутов

public static void RegisterRoutes(RouteCollection routes) { routes.Add(new Route("SayHello", new CustomRouteHandler())); routes.Add(new LegacyRoute(

"~/articles/Windows_3.1_Overview.html", "~/old/.NET_1.0_Class_Library"));

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

}

Когда мы запрашиваем URL /SayHello, наш обработчик используется для обработки запроса. На рисунке 14-5 показан результат.

Рисунок 14-5: Использование пользовательского обработчика роутов

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

Лучшие примеры URL схемы

После всего этого вам, наверное, остается только гадать, с чего начать, чтобы создать собственную URL схему. Вы можете просто принять схему по умолчанию, которую Visual Studio генерирует для вас, но есть некоторые преимущества в создании чего-то своего.

359

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

Делайте свои URL чистыми и дружелюбными

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

http://www.amazon.com/Pro-ASP-NET-MVC-Professional- Apress/dp/1430242361/ref=la_B001IU0SNK_1_5?ie=UTF8&qid=1349978167&sr=1-5

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

http://www.amazon.com/books/pro-aspnet-mvc4-framework

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

Примечание

Чтобы внести ясность, мы очень уважаем Amazon, который продает больше наших книг, чем все остальные, вместе взятые. Мы знаем, что каждый член команды Amazon является поразительно умным и красивым человеком. И никто из них не является мелочным, чтобы прекратить продажи наших книг из-за чего-то настолько незначительного, как критика их URL формата. Мы любим Amazon. Мы обожаем Amazon. Мы просто хотим, чтобы они поправили свои URL.

Вот несколько советов по созданию дружелюбных URL:

Создавайте URL, описывая их содержание, а не внося информацию о реализации вашего приложения. Используйте /Articles/AnnualReport, а не

/Website_v2/CachedContentServer/FromCache/AnnualReport.

Предпочитайте названия номерам ID. Используйте /Articles/AnnualReport, а не /Articles/2392. Если вы должны использовать номер ID (чтобы различать элементы с одинаковыми названиями, или чтобы избежать дополнительных запросов к базе данных, необходимых для нахождения элемента по его названию), делайте это вот так: /Articles/2392/AnnualReport (используйте и название, и ID). Это займет больше времени,

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

Не используйте расширения имен файлов для HTML страниц (например, .aspx or .mvc), но используйте их для специализированных типов файлов (например, .jpg, .pdf и .zip). Веббраузеры не сильно заботятся о расширениях имен файлов, если надлежащим образом установить MIME тип, но люди все еще ожидают PDF файлы на конце с .pdf.

Придавайте смысловую иерархию (например, /Products/Menswear/Shirts/Red), чтобы ваш пользователь мог понять, какой URL относится к родительской категории.

Не учитывайте регистр (пусть ваш URL будет регистронезависимым). Роутинговая система ASP.NET по умолчанию не учитывает регистр.

360

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