Маршрутизация запросов к файлам

174

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

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Простой HTML-документ</title>
</head>
<body>
    Это простой HTML-файл (~/Content/StaticContent.html)
</body>
</html>

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

Запрос файла со статическим содержимым

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

Если есть соответствие между запрошенным URL и файлом на диске, данный файл обслуживается, и маршруты, определенные в приложении, использоваться не будут. Установив в true свойство RouteExistingFiles коллекции RouteCollection, это поведение можно изменить на противоположное, и маршруты будут проверяться перед дисковыми файлами:

using System.Web.Mvc;
using System.Web.Routing;
using UrlsAndRoutes.Infrastructure;

namespace UrlsAndRoutes
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.RouteExistingFiles = true;
            routes.MapMvcAttributeRoutes();

            // ...
        }
    }
}

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

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

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

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

Сведения о конфигурации IIS Express

Щелкните на ссылке 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. Вместо содержимого файла выводится сообщение об ошибке, представленное на рисунке ниже:

Запрос URL статического содержимого, обработанный системой маршрутизации

Такая ошибка возникает из-за того, что запрос к HTML-файлу был передан системе маршрутизации MVC, но маршрут, соответствующий URL, направляет на контроллер Content, которого не существует.

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

После установки свойства RouteExistingFiles в true можно определять маршруты для сопоставления с URL, которые соответствуют дисковым файлам, как показано в примере ниже:

using System.Web.Mvc;
using System.Web.Routing;
using UrlsAndRoutes.Infrastructure;

namespace UrlsAndRoutes
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.RouteExistingFiles = true;
            routes.MapMvcAttributeRoutes();

            routes.MapRoute("DiskFileRoute", "Content/StaticContent.html",
                new
                {
                    controller = "Customer",
                    action = "List"
                });

            // ...
        }
    }
}

Этот маршрут отображает запросы к URL вида Content/StaticContent.html на действие List контроллера Customer. На рисунке ниже данное отображение URL показано в работе, для чего приложение было запущено и произведен переход на URL вида /Content/StaticContent.html:

Перехват запроса к дисковому файлу с использованием маршрута

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

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

Например, как было показано в предыдущем примере, запрос /Content/StaticContent.html будет соответствовать шаблону URL вида {controller}/{action}. Не проявив должной осторожности, вы можете получить исключительно странные результаты и сниженную производительность. Таким образом, устанавливать свойство RouteExistingFiles в true следует только в крайнем случае.

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

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

Противоположностью этого средства является возможность сделать систему маршрутизации менее инклюзивной и предотвратить оценку URL относительно маршрутов. Это делается с применением метода IgnoreRoute() класса RouteCollection, как показано в примере ниже:

using System.Web.Mvc;
using System.Web.Routing;
using UrlsAndRoutes.Infrastructure;

namespace UrlsAndRoutes
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.RouteExistingFiles = true;
            routes.MapMvcAttributeRoutes();

            routes.IgnoreRoute("Content/{filename}.html");

            routes.MapRoute("DiskFileRoute", "Content/StaticContent.html",
                new
                {
                    controller = "Customer",
                    action = "List"
                });

            // ...
        }
    }
}

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

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

Если вы запустите приложение и перейдете на URL вида /Content/StaticContent.html, то увидите содержимое HTML-файла, поскольку объект StopRoutingHandler обрабатывается перед любыми другими маршрутами, которые могли бы дать соответствие указанному URL.

Пройди тесты
Лучший чат для C# программистов