请考虑以下路线:
routes.MapRoute(
"route1",
"{controller}/{month}-{year}/{action}/{user}"
);
routes.MapRoute(
"route2",
"{controller}/{month}-{year}/{action}"
);
以及以下测试:
TEST 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);
}
TEST 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);
}
此测试模拟了一种情况,即我在请求上下文中已经有了路由值,并尝试使用 UrlHelper 生成传出 url。
问题是(在测试 2 中呈现),如果我有预期路线中所有路段的值(此处route1
)并尝试通过以下方式替换其中一些routeValues
参数,所需的路线被省略,并使用下一个合适的路线。
因此测试 1 效果很好,因为请求上下文已经具有路由 1 的 5 个段中的 3 个的值,以及缺少的两个段的值(即,year
and month
)通过routeValues
范围。
测试 2 具有请求上下文中所有 5 个段的值。我想用其他值替换其中一些(即月份和年份)routeValues
。但路线1似乎是不合适并使用路线2。
为什么?我的路线有什么问题?
在这种情况下我是否需要手动清除请求上下文?
EDIT
[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);
}
这个测试让事情变得更加混乱。这里我正在测试路线2。它有效!我在请求上下文中拥有所有 4 个段的值,通过传递其他值routeValues
,生成的外发url就OK了。
所以,问题出在route1上。我缺少什么?
EDIT
From Sanderson S. Freeman A. - 专业 ASP.NET MVC 3 框架第三版:
路由系统按照路由的顺序处理路由
添加到传递给 RegisterRoutes 的 RouteCollection 对象
方法。检查每条路线是否匹配,
需要满足三个条件:
- URL 模式中定义的每个段变量都必须有一个可用的值。为了找到每个段变量的值,路由
系统首先查看我们提供的值(使用
匿名类型的属性),那么变量值
当前请求,最后是在中定义的默认值
路线。
- 我们为段变量提供的任何值都不能与路由中定义的仅默认变量不一致。我
这些路由没有默认值
- 所有段变量的值必须满足路线约束。我对这些路线没有限制
因此,根据我在匿名类型中指定的第一条规则,我没有默认值。当前请求的变量值- 我认为这是来自请求上下文的值。
这些推理对于路线 2 来说有什么问题,而对于路线 1 来说却很有效?
EDIT
实际上,一切都不是从单元测试开始的,而是从真正的 mvc 应用程序开始的。我在那里用过UrlHelper.Action 方法(字符串、对象) http://msdn.microsoft.com/en-us/library/dd460294%28v=vs.108%29.aspx生成传出网址。由于此方法在布局视图(大多数视图的父视图)中使用,因此我已将其纳入我的扩展辅助方法(以排除视图中的额外逻辑),此扩展方法从请求中提取操作名称上下文,作为参数传递给它。我知道我可以通过请求上下文提取所有当前路由值并替换它们year and month(或者我可以创建一个匿名路由值集合,包含上下文中的所有值),但我认为这是多余的,因为 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
does.
EDIT
另一句话。只要请求上下文中的值是类型string
,我决定尝试将它们设置为字符串作为上下文,以确保不存在类型混淆(int 与 string)。我不明白这方面发生了什么变化,但一些路线开始正确生成。但并非全部……这还没有什么意义。我在实际应用程序中更改了此设置,而不是测试,因为测试已int
在上下文中,而不是字符串。
嗯,我已经找到了条件route1使用 - 仅当以下值时才使用month
and year
上下文中的值等于匿名对象中给出的值。如果它们不同(在测试中这对于int
and string
), route2用来。但为什么?
这证实了我在实际应用中的情况:
- I had
string
s 在上下文中,但提供int
通过匿名对象,它以某种方式混淆了 mvc 并且无法使用route1
.
- 我变了
int
匿名对象中的 sstring
s,以及 urlmonth
and year
上下文中的内容等于匿名对象中的内容,开始正确生成;而其他人则没有。
所以,我看到一条规则:匿名对象的属性应该是类型string
以匹配请求上下文中路由值的类型。
但这条规则似乎不是强制性的, as in Test3,我更改了类型(您现在可能会在上面看到它)并且它仍然可以正常工作。 MVC 设法正确地自行转换类型。
最后我找到了所有这些行为的解释。请看我下面的回答。