【发布时间】:2012-10-26 06:29:48
【问题描述】:
请考虑以下路线:
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 个段的值。我想通过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) 生成传出网址。由于此方法用于布局视图(大多数视图的父视图),我已将其纳入我的扩展辅助方法(从视图中排除额外逻辑),此扩展方法从请求中提取操作名称上下文,作为参数传递给它。我知道我可以通过请求上下文提取所有当前路由值并替换那些 year 和 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 的方式工作是的。
编辑
另外一句话。至于请求上下文中的值是string 类型,我决定尝试将它们作为字符串设置到上下文中,以确保没有类型混淆(int vs string)。我不明白,这方面发生了什么变化,但一些路线开始正确生成。但并非全部......这更没有意义。我在实际应用程序中更改了这一点,而不是测试,因为测试在上下文中有 int,而不是字符串。
好吧,我找到了使用 route1 的条件——它仅在上下文中 month 和 year 的值等于匿名中给出的值时使用目的。如果它们不同(在测试中,int 和 string 都是如此),则使用 route2。但为什么呢?
这证实了我在实际应用中拥有的内容:
- 我在上下文中有
strings,但通过匿名对象提供了ints,它以某种方式混淆了mvc,它无法使用route1。 - 我将匿名对象中的
ints 更改为strings,并且上下文中month和year的url 与匿名对象中的相同,开始正确生成;而其他所有人都没有。
所以,我看到了一条规则:匿名对象的属性应该是 string 类型,以匹配请求上下文中路由值的类型。
但这条规则似乎不是强制性的,就像在 Test3 中一样,我更改了类型(您现在可以在上面看到它)并且它仍然可以正常工作。 MVC 设法正确地转换类型。
最后我找到了所有这些行为的解释。请看下面我的回答。
【问题讨论】:
-
在不知道“确定”的情况下,我相信当您使用 URLHelper.GenerateUrl 生成路由时,您传递的路由值字典会覆盖路由值字典中已经存在的所有内容。尝试在 GenerateUrl 调用中添加用户参数,看看是否能解决问题。
标签: c# asp.net-mvc-3 asp.net-mvc-routing