Главная страница   /   9.6. Отладка маршрутов (ASP.NET MVC 4 в действии

ASP.NET MVC 4 в действии

ASP.NET MVC 4 в действии

Джеффри Палермо

9.6. Отладка маршрутов

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

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

Листинг 9-8: Определения роутов для адресации товаров
routes.MapRoute(
	"product",
	"products/{productCode}/{action}",
	new { controller = "Catalog", action = "Show" });
routes.MapPageRoute(
	"ProductsByCategory",
	"ProductsByCategory/{category}",
	"~/ProductsByCategory.aspx",
	checkPhysicalUrlAccess: true,
	defaults: new RouteValueDictionary(new{category="All"})
);

Строки 1-4: Роут информации о товаре

Строки 5-10: Роут списка категорий

Первый роут выводит информацию о товаре по URL /products/ProductName (например /products/mvc3-in-action), тогда как второй выводит товары по категориям на странице /ProductsByCategory.

Однако мы хотим изменить страницу /ProductsByCategory на /Products/ByCategory, чтобы роут соответствовал предыдущему. Если мы изменим URL для этого роута на Products/ByCategory/{category}, а затем попытаемся зайти на эту страницу, в конечном итоге мы увидим ошибку 404.

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

Установка Route Debugger

Route Debugger был написан Филом Хааком, старшим менеджером программ команды ASP.NET в Microsoft. Он доступен как пакет NuGet и может быть установлен с помощью диалогового окна Add Library Package Reference или через NuGet Package Manager Console. Если вы используете консоль, то для установки пакета нужно набрать следующую команду:

Install-Package routedebugger

Появится сообщение “Successfully installed”, как показано на рисунке 9-5.

Рисунок 9-5: Инсталляция Route Debugger с помощью Package Manager Console

Использование Route Debugger

После установки Route Debugger в ваш проект будет добавлена ссылка на RouteDebugger.dll, и в файл web.config будет добавлена новая настройка приложения, как показано здесь.

Листинг 9-9: Включение Route Debugger
<appSettings>
	<add key="webpages:Version" value="1.0.0.0" />
	<add key="ClientValidationEnabled" value="true" />
	<add key="UnobtrusiveJavaScriptEnabled" value="true" />
	<add key="RouteDebugger:Enabled" value="true" />
</appSettings>

Настройка RouteDebugger:Enabled определяет, включен ли Route Debugger. Если для нее установлено значение true, то при запуске приложения мы увидим диагностики роутов внизу каждого экрана, как показано на рисунке 9-6.

Рисунок 9-6: Экран диагностики роута

Примечание

Не забудьте отключить Route Debugger до того, как развернуть приложение, установив значение false для RouteDebugger:Enabled в web.config. Вряд ли вы хотите, чтобы пользователи видели информацию о диагностике на каждом экране!

Экран диагностики роутов предоставляет информацию о роуте, который соответствует текущему URL. В верхней части экрана раздел Route Data показывает параметры роута, которые соответствуют текущему запросу; раздел Data Tokens показывает все пользовательские маркеры данных, которые связаны с этим роутом.

В нижней части экрана раздел All Routes показывает роуты, которые потенциально соответствуют текущему запросу, отображая для них значение True в колонке Matches Current Request. Первый роут со значением True в этой колонке - тот, который был выбран для обработки текущего запроса.

Если мы теперь перейдем по нашей проблематичной ссылке /Products/ByCategory, то сможем увидеть причину проблемы, как показано на рисунке 9-7.

Рисунок 9-7: Проверка роута ProductsByCategory

Мы видим, что несколько роутов соответствуют URL /Products/ByCategory, в том числе и тот, который мы определили. Но он не является первым роутом, который соответствует этому URL.Страница с информацией о товаре также соответствует этому URL, потому что часть URL ByCategory соответствует сегменту {productCode} ссылки /products/{productCode}/{action}.

Вместо того, чтобы направить пользователя на страницу ProductsByCategory, мы направляем его на страницу с информацией о товаре. Наше действие контроллера пытается найти товар с названием ByCategory, и так как такого названия товара не существует, выводит ошибку 404.

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

Использование ограничений роута

Вместо того, чтобы позволить любым входным данным соответствовать сегменту {productCode}, мы можем использовать регулярное выражение для ограничения того, что может ему соответствовать, используя следующий параметр:

routes.MapRoute("product", "products/{productCode}/{action}",
	new { controller = "Catalog", action = "Show" },
	new { productCode = "(?!ByCategory).*" });

В этом случае мы используем регулярное выражение, чтобы исключить строку ByCategory из того, что может соответствовать коду товара. Теперь, если мы снова перейдем по ссылке, соответствие для нашего роута будет установлено правильно, как показано на рисунке 9-8.

Рисунок 9-8: Диагностика роута с ограничением

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

Листинг 9-10: Пользовательское ограничение роута
public class NotEqualConstraint
: IRouteConstraint
{
	private readonly string _input;
	public NotEqualConstraint(string input)
	{
		_input = input;
	}
	public bool Match(HttpContextBase httpContext,
		Route route, string parameterName,
		RouteValueDictionary values,
		RouteDirection routeDirection)
	{
		object matchingValue;
		if (values.TryGetValue(parameterName,
			out matchingValue))
		{
			if (_input.Equals((string)matchingValue,
				StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
		}
		return true;
	}
}

Строка 2: Реализация IRouteConstraint

Строка 7: Хранит в поле строки сравнения

Строки 18-19: Сравнивает значение роута со входными данными

Пользовательский класс ограничения роута – NotEqualConstraint – реализует интерфейс IRouteConstraint, определяя метод Match. Каждый раз, когда система маршрутизации пытается найти роут, соответствующий URL, она вызовет метод Match на любом установленном ограничении. Если мы не хотим, чтобы роуту было найдено соответствие, этот метод должен вернуть значение false. Метод Match принимает пять аргументов. Первый - это ссылка на контекст HTTP, второй - роут, для которого было определено ограничение. Третий – это имя параметра роута, для которого определено ограничение, четвертый - текущий набор значений роута (один из которых будет содержать имя параметра роута), а пятый показывает, используется ли роут для установления соответствия входящему запросу или генерации URL.

В данном случае NotEqualConstraint сначала извлекает значение указанного параметра роута (товарный код), а затем сравнивает его без учета регистра со строкой, которая была передана в его конструктор. Если строки идентичны, то ограничение роута возвращает false. Мы можем использовать это ограничение в определении роута:

routes.MapRoute("product", "products/{productCode}/{action}",
	new { controller = "Catalog", action = "Show" },
	new { productCode = new NotEqualConstraint("ByCategory") });

Здесь мы используем NotEqualConstraint внутри объекта ограничений вместо регулярного выражения, которое использовалось в предыдущем примере. Конечный результат будет точно таким же: если пользователь перейдет по URL /products/ByCategory, соответствие для этого роута не будет найдено.

Примечание

MVC поставляется с одной реализацией IRouteConstraint - HttpMethodConstraint. Это ограничение будет гарантировать, что соответствие для роута будет установлено, только если метод HTTP (такой как GET, POST, PUT или DELETE), который используется при обращении к URL, соответствует указанному методу. Таким образом, различные запросы к одному и тому же адресу могут быть направлены к различным контроллерам только на основе того, являются ли они запросами GET или POST.