Пожалуйста, рассмотрите следующие маршруты:
routes.MapRoute(
"route1",
"{controller}/{month}-{year}/{action}/{user}"
);
routes.MapRoute(
"route2",
"{controller}/{month}-{year}/{action}"
);
И следующие тесты:
ТЕСТ 1
[TestMethod]
public void Test1()
{
RouteCollection routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
RequestContext context = new RequestContext(CreateHttpContext(),
new RouteData());
DateTime now = DateTime.Now;
string result;
context.RouteData.Values.Add("controller", "Home");
context.RouteData.Values.Add("action", "Index");
context.RouteData.Values.Add("user", "user1");
result = UrlHelper.GenerateUrl(null, "Index", null,
new RouteValueDictionary(
new
{
month = now.Month,
year = now.Year
}),
routes, context, true);
//OK, result == /Home/10-2012/Index/user1
Assert.AreEqual(string.Format("/Home/{0}-{1}/Index/user1", now.Month, now.Year),
result);
}
ТЕСТ 2
[TestMethod]
public void Test2()
{
RouteCollection routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
RequestContext context = new RequestContext(CreateHttpContext(),
new RouteData());
DateTime now = DateTime.Now;
string result;
context.RouteData.Values.Add("controller", "Home");
context.RouteData.Values.Add("action", "Index");
context.RouteData.Values.Add("user", "user1");
context.RouteData.Values.Add("month", now.Month + 1);
context.RouteData.Values.Add("year", now.Year);
result = UrlHelper.GenerateUrl(null, "Index", null,
new RouteValueDictionary(
new
{
month = now.Month,
year = now.Year
}),
routes, context, true);
//Error because result == /Home/10-2012/Index
Assert.AreEqual(string.Format("/Home/{0}-{1}/Index/user1", now.Month, now.Year),
result);
}
Этот тест эмулирует ситуацию, когда у меня уже есть значения маршрута в контексте запроса и вы пытаетесь создать исходящий URL с UrlHelper.
Проблема заключается в том, что (представлен в тесте 2), если у меня есть значения для всех сегментов из ожидаемого маршрута (здесь route1
) и попробуйте заменить некоторые из них с помощью параметра routeValues
, нужный маршрут опущен и используется следующий подходящий маршрут.
Итак, тест 1 работает хорошо, поскольку контекст запроса уже имеет значения для 3 из 5 сегментов маршрута 1, а значения для отсутствующих двух сегментов (а именно, year
и month
) передаются через routeValues
параметр.
Тест 2 имеет значения для всех 5 сегментов в контексте запроса. И я хочу заменить некоторые из них (а именно месяц и год) другими значениями через t routeValues
. Но маршрут 1, по-видимому, не подходит, и используется маршрут 2.
Почему? Что не соответствует моим маршрутам?
Как я ожидал, чтобы очистить контекст запроса вручную при таких обстоятельствах?
ИЗМЕНИТЬ
[TestMethod]
public void Test3()
{
RouteCollection routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
RequestContext context = new RequestContext(CreateHttpContext(),
new RouteData());
DateTime now = DateTime.Now;
string result;
context.RouteData.Values.Add("controller", "Home");
context.RouteData.Values.Add("action", "Index");
context.RouteData.Values.Add("month", now.Month.ToString());
context.RouteData.Values.Add("year", now.Year.ToString());
result = UrlHelper.GenerateUrl(null, "Index", null,
new RouteValueDictionary(
new
{
month = now.Month + 1,
year = now.Year + 1
}),
routes, context, true);
Assert.AreEqual(string.Format("/Home/{0}-{1}/Index", now.Month + 1, now.Year + 1),
result);
}
Этот тест усложняет ситуацию. Здесь я тестирую route2. И это работает! У меня есть значения для всех 4 сегментов в контексте запроса, передайте другие значения через routeValues
, а сгенерированный исходящий URL-адрес в порядке.
Итак, проблема связана с route1. Что мне не хватает?
ИЗМЕНИТЬ
От Сандерсона С. Фримана А. - Про ASP.NET MVC 3 Framework Третье издание:
Система маршрутизации обрабатывает маршруты в том порядке, в котором они были добавлен в объект RouteCollection, переданный в RegisterRoutes метод. Каждый маршрут проверяется, будет ли он соответствовать, требует трех условий:
- Значение должно быть доступно для каждой переменной сегмента, определенной в шаблоне URL. Чтобы найти значения для каждой переменной сегмента, маршрутизация система сначала смотрит на значения , которые мы предоставили (используя свойства анонимного типа), то значения переменных для текущий запрос и, наконец, значения по умолчанию, определенные в маршрут.
- Ни одно из значений, которые мы предоставили для переменных сегмента, может не совпадать с переменными по умолчанию, определенными в маршруте. я не имеют значений по умолчанию в этих маршрутах.
- Значения для всех переменных сегмента должны удовлетворять ограничениям маршрута. У меня нет ограничений в этих маршрутах.
Итак, согласно первому правилу, я указал значения в анонимном типе, у меня нет значений по умолчанию. Значения переменных для текущего запроса. Я полагаю, что это значения из контекста запроса.
Что не так с этими рассуждениями для route2, пока они хорошо работают для route1?
ИЗМЕНИТЬ
На самом деле все началось не с модульных тестов, а из реального приложения mvc. Там я использовал UrlHelper.Action Method (String, Object) для генерации исходящих URL-адресов. Поскольку этот метод используется в представлении макета (родительский для большинства всех представлений), я включил его в мой вспомогательный метод расширения (чтобы исключить дополнительную логику из представлений). Этот метод расширения извлекает имя действия из запроса контекст, переданный ему в качестве аргумента. Я знаю, что могу извлечь все текущие значения маршрута через контекст запроса и заменить те год и месяц (или я могу создать коллекцию значений анонимного маршрута, содержащую все значения из контекста), но я думал, что это было излишним, поскольку mvc автоматически учитывали значения, содержащиеся в контексте запроса. Таким образом, я извлек только имя действия, так как не было перегрузки UrlHelper.Action без имени действия (или мне бы даже понравилось "не указывать" имя действия) и добавили новый месяц и год через объект анонимного значения маршрута.
Это метод расширения:
public static MvcHtmlString GetPeriodLink(this HtmlHelper html,
RequestContext context,
DateTime date)
{
UrlHelper urlHelper = new UrlHelper(context);
return MvcHtmlString.Create(
urlHelper.Action(
(string)context.RouteData.Values["action"],
new { year = date.Year, month = date.Month }));
}
Как я описал в вышеприведенных тестах, он работал для более коротких маршрутов (когда контекст запроса содержал только контроллер, год и месяц и действие), но не удалось для более длинного (когда в контексте запроса содержался контроллер, год и месяц, действие, и пользователь).
Я опубликовал обходной путь, который я использую, чтобы сделать работу по маршрутизации так, как мне нужно.
Хотя я действительно хотел бы узнать, почему мне приходится делать какие-либо обходные пути в таком сценарии и каково ключевое различие между этими двумя маршрутами, что мешает route1
работать так, как это делает route2
.
ИЗМЕНИТЬ
Еще одно замечание. Поскольку значения в контексте запроса имеют тип string
, я решил попытаться установить их в контексте как строки, чтобы убедиться, что не было путаницы типа (int vs string). Я не понимаю, что изменилось в этом отношении, но некоторые из маршрутов начали генерировать правильно. Но не все... Это еще меньше смысла. Я изменил это в реальном приложении, а не на тестах, поскольку тесты имеют int
в контексте, а не в строках.
Ну, я нашел условие, при котором используется маршрут1 - он используется только тогда, когда значения month
и year
в контексте равны значениям, указанным в анонимном объекте. Если они отличаются (в тестах это верно как для int
, так и string
), используется маршрут2. Но почему?
Это подтверждает то, что у меня есть в моем реальном приложении:
- У меня был
string
в контексте, но при условииint
через анонимный объект он каким-то образом смутил mvc, и он не мог использоватьroute1
. - Я изменил
int
в анонимном объекте наstring
s, а URL-адреса, гдеmonth
иyear
в контексте равны тем, которые находятся в анонимном объекте, начали генерировать правильно; тогда как все остальные не сделали.
Итак, я вижу одно правило: свойства анонимного объекта должны иметь тип string
, чтобы соответствовать типу значений маршрута в контексте запроса.
Но это правило кажется не обязательным, как в Test3, я изменил типы (вы можете увидеть это сейчас выше), и он по-прежнему работает правильно. MVC правильно управляет типами.
Наконец, я нашел объяснение для всего этого поведения. Пожалуйста, см. Мой ответ ниже.