Статьи

Генерация традиционных URL с помощью ASP.NET MVC3

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


URL обозначает Uniform Resource Locator; он сообщает веб-клиентам, где найти определенный ресурс в Интернете. URL-адрес http://www.example.com/directory/file.html указывает на физический файл (ресурс) с именем file.html который находится в каталоге с именем directory на веб-сервере, найденном в домене example.com . Когда веб-сервер для example.com получает запрос на этот URL, он точно знает, где искать ресурс. Если он находит файл, он обслуживает его содержимое; если нет, он отвечает с ошибкой.

Традиционные веб-серверы делают то же самое для запросов к каталогам.

Рассмотрим URL-адрес http://www.example.com/directory/ . Этот URL заканчивается косой чертой, обозначающей каталог. Когда веб-сервер получает этот запрос, он ищет directory , а если он его находит, он получает документ по умолчанию и возвращает его клиенту. В противном случае он отвечает с ошибкой.

Эти два примера демонстрируют простой процесс, но он может усложниться при неоднозначных запросах. Например, URL http://www.example.com/ambiguous указывает на ресурс, называемый ambiguous , но неясно, что это за ресурс. Это может быть файл, но нет расширения; это может быть каталог, но нет косой черты. При получении запроса на этот ресурс традиционный веб-сервер проходит следующий процесс:

  • Сервер ищет файл с именем ambiguous . Если он находит, он возвращает его. В противном случае…
  • Он отправляет ответ клиенту, перенаправляя его по http://www.example.com/ambiguous/
  • Клиент запрашивает новый URL
  • Сервер ищет ambiguous каталог и возвращает соответствующий ответ

Без явного указания ресурса запрашивающая сторона создает накладные расходы как во время обработки, так и в использовании полосы пропускания.

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

Так в чем же ASP.NET MVC? Ну, как вы, наверное, знаете, ASP.NET обычно работает на веб-сервере Microsoft, который называется IIS. По умолчанию IIS ведет себя так же, как и любой другой веб-сервер, но его истинная сила заключается в способности передавать обработку запросов в модули ISAPI или, что еще лучше, в код .NET. В случае приложения ASP.NET MVC IIS передает каждый запрос приложению MVC для обработки. Там приложение определяет, должен ли запрос быть направлен методу на контроллере или он должен передать управление обратно в IIS для поиска физического файла или каталога в файловой системе. Таким образом, процесс обработки запроса на http://www.example.com/ambiguous IIS и приложением MVC выглядит примерно так:

Подожди, подожди, подожди! Если приложения MVC игнорируют косую черту, в чем проблема?

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

Подожди, подожди, подожди! Если приложения MVC игнорируют косую черту, в чем проблема? Технически, нет ни одного. Неоднозначный URL фактически указывает на ресурс на сервере: метод на объекте контроллера в приложении. Но, как указывалось ранее, веб-разработчики ставили косые черты в конце своих URL-адресов еще до того, как Microsoft выпустила инфраструктуру MVC. Это привычка и обычай делать это.

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

  • Создайте метод расширения, который генерирует URL
  • Напишите настроенные версии метода RouteLink() .

Поскольку это решение использует варианты RouteLink() , важно, чтобы ваши маршруты были названы. Если вы не называете свои маршруты, вы должны! Наконец, немного кода!


UrlHelper MVC предоставляет класс с именем UrlHelper . Как следует из названия, его цель — помочь с созданием URL для нашего приложения. У него есть статический метод GenerateUrl() , который сделает большую часть тяжелой работы за нас. Все, что нам нужно сделать, это предоставить название маршрута и значения маршрута. Мы создадим метод расширения для объектов HtmlHelper именем RouteUrl() , и он будет иметь две перегрузки.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public static class HtmlHelperExtensions
{
    public static string RouteUrl(this HtmlHelper htmlHelper, string routeName, RouteValueDictionary routeValues)
    {
        string url = UrlHelper.GenerateUrl(
            routeName,
            null /*actionName*/,
            null /*controllerName*/,
            routeValues,
            htmlHelper.RouteCollection,
            htmlHelper.ViewContext.RequestContext,
            true
        );
 
        return String.Format(«{0}/», url);
    }
}

Метод расширения принимает три аргумента. Первый — это объект HtmlHelper которым работает этот метод. Обратите внимание, что при вызове этого метода вам не нужно передавать объект HtmlHelper ; это делается автоматически для вас. Второй аргумент — это строка, содержащая имя маршрута. Метод GenerateUrl() будет использовать имя маршрута для возврата URL-адреса, правильно отформатированного в соответствии с шаблоном маршрута, определенным в таблице маршрутизации. Последний аргумент — это объект RouteValueDictionary , который содержит набор пар ключ / значение со всей информацией, необходимой для создания URL-адреса для маршрута. Сюда входят имена контроллеров и действий, а также любые параметры, UrlHelper могут понадобиться UrlHelper для создания URL-адреса для указанного маршрута.

Первый оператор метода вызывает UrlHelper.GenerateUrl() для генерации URL. Существуют две перегрузки для метода GenerateUrl() , и вышеприведенный код вызывает метод с наименьшим количеством параметров. Имя маршрута передается, но вы пропускаете параметры, определяющие действие и имена контроллеров. Эти значения фактически содержатся в объекте routeValues , который передается в GenerateUrl() качестве четвертого аргумента. Объект HtmlHelper предоставляет вам следующие две части необходимой информации, а последний переданный аргумент говорит GenerateUrl() включить неявные значения MVC «action» и «controller».

После того, как URL сгенерирован, метод RouteUrl() вызывает String.Format() для создания новой строки, по существу, конкатенируя URL и косую черту. Обратите внимание, что String.Format() не требуется, и вы можете просто написать return url + "/"; , Как вы создаете строку, зависит от вас.

С основной перегрузкой рабочего написано, теперь добавьте ленивую. Вот его код:

1
2
3
4
public static string RouteUrl(this HtmlHelper htmlHelper, string routeName, object routeValues)
{
    return RouteUrl(htmlHelper, routeName, new RouteValueDictionary(routeValues));
}

Этот код прост. Он вызывает основную перегрузку RouteUrl() , передавая соответствующие данные. Поскольку методы расширения являются статическими, их можно вызывать так же, как и любой другой статический метод, как в этом коде. Обратите внимание, что вы могли бы так же легко написать этот код, вызвав основной RouteUrl() как метод instance (extension), например так:

1
2
3
4
5
// Alternative version
public static string RouteUrl(this HtmlHelper htmlHelper, string routeName, object routeValues)
{
    return htmlHelper.RouteUrl(routeName, new RouteValueDictionary(routeValues));
}

Это не важно В любом случае, работа выполнена; это зависит от ваших личных предпочтений.

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

1
2
3
4
5
6
7
Html.RouteUrl(«routeName», new
   {
       controller = «ControllerName»,
       action = «ActionName»,
       parameterOne = «Hello»,
       parameterTwo = «World»
   })

Конечно, значения для controller и action , а также параметры будут отличаться для вашего конкретного приложения MVC. Но это дает вам представление о том, как использовать метод. Теперь, когда вы можете создавать красивые URL с косой чертой, пришло время написать больше методов расширения для генерации ваших ссылок.


Одним из наиболее полезных методов HtmlHelper является метод RouteLink() . Все, что вам нужно сделать, это предоставить ему имя маршрута и значения, чтобы получить строку, содержащую элемент привязки с довольно относительным URL-адресом указанного действия. Разумеется, RouteLink() возвращает элементы привязки, содержащие URL, без завершающей косой черты; Итак, вам нужно написать свой собственный метод, который использует RouteUrl() .

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

Основная перегрузка будет принимать пять аргументов: экземпляр HtmlHelper , текст ссылки, имя маршрута, RouteValueDictionary содержащий информацию о маршруте, и словарь атрибутов HTML для применения к элементу привязки. Как и RouteUrl() , MyRouteLink() является статическим методом статического класса HtmlHelperExtensions . Вот его код (для экономии места объявление класса опущено):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public static MvcHtmlString MyRouteLink(
    this HtmlHelper htmlHelper,
    string linkText,
    string routeName,
    RouteValueDictionary routeValues,
    IDictionary<string, object> htmlAttributes)
{
    string url = RouteUrl(htmlHelper, routeName, routeValues);
 
    TagBuilder tagBuilder = new TagBuilder(«a»)
    {
        InnerHtml = (!String.IsNullOrEmpty(linkText)) ?
    };
 
    tagBuilder.MergeAttributes(htmlAttributes);
    tagBuilder.MergeAttribute(«href», url);
         
    return MvcHtmlString.Create((tagBuilder.ToString(TagRenderMode.Normal)));
}

Этот код может показаться вам знакомым, если вы когда-либо изучали исходный код ASP.NET MVC . Класс HtmlHelper имеет закрытый метод GenerateRouteLink() , который послужил источником вдохновения для MyRouteLink() . Ваш метод возвращает объект MvcHtmlString , который представляет собой строку в кодировке HTML (вам не нужно ничего кодировать самостоятельно). Первая инструкция этого метода использует ваш метод RouteUrl() для получения вашего специального URL. Затем вы используете объект TagBuilder для создания элемента привязки, InnerHtml его свойство InnerHtml текстом ссылки. Затем к элементу добавляются атрибуты HTML, предоставленные словарем и атрибутом href . Наконец, вывод HTML создается как объект MvcHtmlString и возвращается вызывающей стороне.

Теперь все просто, поскольку оставшиеся перегрузки будут так или иначе вызывать эту версию MyRouteLink() . Следующая перегрузка имеет похожую подпись; значения и атрибуты маршрута будут просто объектами. Вот его код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public static MvcHtmlString MyRouteLink(
    this HtmlHelper htmlHelper,
    string linkText,
    string routeName,
    object routeValues,
    object htmlAttributes)
{
    return MyRouteLink(
        htmlHelper,
        linkText,
        routeName,
        new RouteValueDictionary(routeValues),
        new RouteValueDictionary(htmlAttributes)
    );
}

Этот код не требует пояснений. Вы вызываете основную перегрузку MyRouteLink() , передавая соответствующие данные. Обратите внимание, что вы используете объект RouteValueDictionary для атрибутов HTML; это подходящий контейнер для атрибутов HTML.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static MvcHtmlString MyRouteLink(
    this HtmlHelper htmlHelper,
    string linkText,
    string routeName,
    RouteValueDictionary routeValues)
{
    return MyRouteLink(
        htmlHelper,
        linkText,
        routeName,
        routeValues,
        new RouteValueDictionary()
    );
}
 
public static MvcHtmlString MyRouteLink(
    this HtmlHelper htmlHelper,
    string linkText,
    string routeName,
    object routeValues)
{
    return MyRouteLink(
        htmlHelper,
        linkText,
        routeName,
        new RouteValueDictionary(routeValues)
    );
}

Первая перегрузка в приведенном выше коде пропускает коллекцию атрибутов HTML и указывает объект RouteValueDictionary для значений маршрута. Он вызывает перегрузку с подписью MyRouteLink(HtmlHelper, string, string, RouteValueDictionary, IDictionary<string, object>) и передает пустой объект RouteValueDictionary для атрибутов HTML. Вторая перегрузка имеет немного другую подпись, принимая объект для значений маршрута. Он вызывает первую перегрузку в этом листинге кода для генерации ссылки.

Чтобы использовать этот вспомогательный метод, убедитесь, что вы импортируете пространство имен, содержащее класс HtmlHelperExtensions в вашем представлении. Затем вы можете написать что-то вроде этого:

1
2
3
4
5
6
7
8
@Html.MyRouteLink(«My Link Text», «route name», new
    {
        controller = «ControllerName»,
        action = «ActionName»,
        parameterOne = «Hello»,
        parameterTwo = «World»
    }
);

Этот код использует синтаксис Razor, но вы можете сделать то же самое, если используете механизм просмотра ASPX, например так:

1
2
3
4
5
6
7
8
<%:Html.MyRouteLink(«My Link Text», «route name», new
    {
        controller = «ControllerName»,
        action = «ActionName»,
        parameterOne = «Hello»,
        parameterTwo = «World»
    }
) %>

Этот код использует новый <%: %> для вывода кодировки HTML (представлен в .NET 4). Прелесть MvcHtmlString возвращаемая MyRouteLink() том, что она уже закодирована в формате HTML и не будет перекодирована с использованием нового слепка. Таким образом, вы можете использовать <%: %> или <%= %> не беспокоясь о кодировке или перекодировании.


Как упоминалось ранее, нет ничего технически неправильного в URL-адресах, создаваемых инфраструктурой MVC. Они не вызывают накладных расходов, поскольку указывают на реальный ресурс на сервере. Но если они были прослушаны вами, теперь вы можете генерировать традиционно правильные URL-адреса с небольшой предварительной работой. Однако Microsoft права: будьте последовательны. Независимо от того, какой стиль URL вы предпочитаете, убедитесь, что вы постоянно используете один и тот же стиль.

Дайте мне знать, если у вас есть какие-либо вопросы в комментариях и большое спасибо за чтение!