ASP.NET MVC 4

ASP.NET MVC 4

Адам Фриман

Использование статических URL сегментов

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

http://mydomain.com/Public/Home/Index

Мы можем сделать это с помощью такого паттерна, как тот, что показан в листинге 13-11.

Листинг 13-11: URL паттерн со статическими сегментами
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace UrlsAndRoutes
{
	public class RouteConfig
	{
		public static void RegisterRoutes(RouteCollection routes)
		{
			routes.MapRoute("MyRoute", "{controller}/{action}",
			new { controller = "Home", action = "Index" });
			routes.MapRoute("", "Public/{controller}/{action}",
			new { controller = "Home", action = "Index" });
		}
	}
}

Этот новый паттерн будет соответствовать только тем URL, которые содержат три сегмента, первый из которых обязательно должен быть Public. Два других сегмента могут содержать любые значения и будут использоваться для переменных controller и action. Если последние два сегмента опущены, то будут использоваться значения по умолчанию.

Мы также можем создавать URL паттерны, которые имеют сегменты, содержащие как статические элементы, так и элементы переменных, как показанный в листинге 13-12.

Листинг 13-12: URL паттерн со смешанным сегментом
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace UrlsAndRoutes
{
	public class RouteConfig
	{
		public static void RegisterRoutes(RouteCollection routes)
		{
			routes.MapRoute("", "X{controller}/{action}");
			routes.MapRoute("MyRoute", "{controller}/{action}",
			new { controller = "Home", action = "Index" });
			routes.MapRoute("", "Public/{controller}/{action}",
			new { controller = "Home", action = "Index" });
		}
	}
}

Паттерн в этом роуте подходит любому двухсегментному URL, где первый сегмент начинается на букву X. Значение для controller взято из первого сегмента, за исключением X. Значение action берется из второго сегмента. Вы можете увидеть результат использования этого роута, если запустите приложение и перейдите по /XHome/Index. Результат показан на рисунке 13-5.

Рисунок 13-5: Статические элементы и элементы переменных в одном сегменте

Порядок роутов

В листинге 13-12 мы определили новый роут и разместили его перед всеми другими в методе RegisterRoutes. Мы сделали это, потому что роуты применяются в том порядке, в котором они появляются в объекте RouteCollection. Метод MapRoute добавляет роут в конец коллекции, что обозначает, что роуты обычно применяются в том порядке, в котором их добавляют. Мы говорим, "обычно", потому что есть методы, которые позволяют нам вставлять роуты в определенные места. Мы, как правило, не используем эти методы, потому что применение роутов в том порядке, в котором они определены, упрощает понимание маршрутизации для приложения. Система маршрутизации пытается сопоставить входящий URL с URL паттерном роута, который был определен первым, и переходит к следующему роуту, только если совпадения нет. И так система проходит по очереди по роутам, пока не будет найдено совпадение или пока не закончатся роуты. Поэтому сперва мы должны определять самые конкретные роуты. Роут, который мы добавили в листинге 13-12, является более конкретным, чем роут, который за ним следует. Предположим, что мы отменили порядок роутов, например:

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

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

http://mydomain.com/XHome/Index

будут нацелены на контроллер XHome, который не существует, и это приведет к ошибке 404—Not Found, которая будет отправлена пользователю.

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

Мы можем объединять статические URL сегменты и значения по умолчанию для создания алиаса для конкретного URL. Это может быть полезно, если вы опубликовали URL схему публично, и с ней работает пользователь. Если вы в этой ситуации вы проводите рефакторинг приложения, вы должны сохранить прежний формат URL так, чтобы любые избранные URL или макро скрипты, которые создал пользователь, продолжали работать. Давайте представим, что у нас был контроллер Shop, который в настоящее время заменен контроллером Home. В листинге 13-13 показано, как мы можем создать роут, чтобы сохранить старую URL схему.

Листинг 13-13: Смешивание статических URL сегментов и значений по умолчанию
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace UrlsAndRoutes
{
	public class RouteConfig
	{
		public static void RegisterRoutes(RouteCollection routes)
		{
			routes.MapRoute("ShopSchema", "Shop/{action}",
			new { controller = "Home" });
			routes.MapRoute("", "X{controller}/{action}");
			routes.MapRoute("MyRoute", "{controller}/{action}",
			new { controller = "Home", action = "Index" });
			routes.MapRoute("", "Public/{controller}/{action}",
			new { controller = "Home", action = "Index" });
		}
	}
}

Роут, который мы добавили, соответствует любому двухсегментному URL, где первым сегментом является Shop. Значение action берется из второго сегмента URL. URL паттерн не содержит сегмента для controller, поэтому используется данное нами значение по умолчанию. Это означает, что запрос для действии контроллера Shop переводится в запрос для контроллера Home. Вы можете увидеть результат использования этого роута, если запустите приложение и перейдете по URL /Shop/Index. Как показано на рисунке 13-6, роут, который мы добавили, заставляет MVC нацеливаться на действие Index в контроллере Home.

Рисунок 13-6: Создание алиаса для сохранения URL схемы

И мы можем пойти еще дальше и создать алиасы для методов действий, которые также были переделаны и больше не присутствует в контроллере. Чтобы сделать это, мы просто создаем статический URL и добавляем значения controller и action в качестве значений по умолчанию, как показано в листинге 13-14.

Листинг 13-14: Создание алиасов для контроллера и действия
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace UrlsAndRoutes
{
	public class RouteConfig
	{
		public static void RegisterRoutes(RouteCollection routes)
		{
			routes.MapRoute("ShopSchema2", "Shop/OldAction",
			new { controller = "Home", action = "Index" });
			routes.MapRoute("ShopSchema", "Shop/{action}",
			new { controller = "Home" });
			routes.MapRoute("", "X{controller}/{action}");
			routes.MapRoute("MyRoute", "{controller}/{action}",
			new { controller = "Home", action = "Index" });
			routes.MapRoute("", "Public/{controller}/{action}",
			new { controller = "Home", action = "Index" });
		}
	}
}

Обратите внимание еще раз, что мы разместили наш новый роут так, чтобы он определялся в первую очередь. Это потому что он является более конкретным, чем роуты, которые следуют за ним. Если бы запрос для Shop/OldAction был обработан, например, следующим роутом, мы получили бы другой результат, а не тот, что мы хотим. Запрос бы обработался с ошибкой 404—Not Found, а не переведен, чтобы сохранить контакт с нашими клиентами.

Юнит тест: тестирование статических сегментов

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

...
[TestMethod]
public void TestIncomingRoutes() {
	TestRouteMatch("~/", "Home", "Index");
	TestRouteMatch("~/Customer", "Customer", "Index");
	TestRouteMatch("~/Shop/Index", "Home", "Index");
	TestRouteMatch("~/Customer/List", "Customer", "List");
	TestRouteFail("~/Customer/List/All");
}
...
или RSS канал: Что новенького на smarly.net