请考虑以下路线:
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);
}
这个测试模拟了我在请求上下文中已经有路由值并尝试使用 UrlHelper 生成传出 url 的情况。
问题是(在测试 2 中提出),如果我有来自预期路线(此处route1
)的所有段的值并尝试通过routeValues
参数替换其中的一些,则省略所需路线并使用下一个合适的路线。
所以测试 1 运行良好,因为请求上下文已经具有路由 1 的 5 个段中的 3 个的值,并且缺少的两个段(即year
和month
)的值通过routeValues
参数传递。
测试 2 具有请求上下文中所有 5 个段的值。我想用其他值替换其中的一些(即月份和年份) throught 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 上。我错过了什么?
编辑
来自Sanderson S. Freeman A. - Pro ASP.NET MVC 3 Framework 第三版:
路由系统按照路由添加到传递给 RegisterRoutes 方法的 RouteCollection 对象的顺序处理路由。检查每条路由是否匹配,这需要满足三个条件:
- 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
。
编辑
另一个说法。至于请求上下文中的值是 type string
,我决定尝试将它们作为字符串设置到上下文中,以确保没有类型混淆(int vs string)。我不明白,这方面发生了什么变化,但一些路线开始正确生成。但并非全部......这更没有意义。我在一个真实的应用程序中改变了这一点,而不是测试,因为测试int
在上下文中,而不是字符串。
好吧,我已经找到了使用route1的条件——它仅在上下文中的month
和的值year
等于匿名对象中给定的值时使用。如果它们不同(在测试中对于int
和都是如此string
),则使用route2。但为什么?
这证实了我在实际应用程序中的内容:
- 我
string
在上下文中有 s ,但是int
通过匿名对象提供了 s ,它以某种方式混淆了 mvc 并且它不能使用route1
. - 我
int
将匿名对象中的 s 更改为string
s,并且上下文中的 urlmonth
与year
匿名对象中的相同,开始正确生成;而所有其他人都没有。
所以,我看到了一条规则:匿名对象的属性应该是string
与请求上下文中路由值的类型相匹配的类型。
但是这条规则似乎不是强制性的,就像在Test3中一样,我改变了类型(你现在可能在上面看到了),它仍然可以正常工作。MVC 设法正确地转换类型。
最后我找到了所有这些行为的解释。请看下面我的回答。