请考虑以下路线:
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
和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)来生成传出网址。由于此方法用于布局视图(大多数视图的父视图),我已将其纳入我的扩展帮助方法(从视图中排除额外的逻辑),此扩展方法从中提取操作名称请求上下文,作为参数传递给它。我知道我可以通过请求上下文提取所有当前路由值并替换那些年和月(或者我可以创建一个匿名路由值集合,包含来自的所有值上下文),但我认为这是多余的,因为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
{1}}。
修改
另一个评论。至于请求上下文中的值是string
类型的值,我决定尝试将它们设置为上下文作为字符串,以确保没有类型混淆(int vs string)。我不明白,在这方面发生了什么变化,但有些路线开始正确生成。但并非所有......这使得意义不大。我在实际的应用程序中更改了这个,而不是测试,因为测试在上下文中有int
,而不是字符串。
好吧,我已经找到了使用 route1 的条件 - 它仅在month
和year
的值中使用上下文等于匿名对象中给出的上下文。如果它们不同(在测试中对于int
和string
都是如此),则使用 route2 。但为什么呢?
这证实了我在实际应用中的含义:
string
个,但是通过匿名对象提供了int
,它以某种方式混淆了mvc并且它无法使用route1
。 int
更改为string
s,并且上下文中month
和year
的网址与匿名对象中的网址相同,开始正确生成;而其他所有人都没有。因此,我看到一条规则:匿名对象的属性应该是string
类型,以匹配请求上下文中路由值的类型。
但是这个规则似乎不是强制性的,就像在 Test3 中一样,我改变了类型(你现在可能会看到它)并且它仍然可以正常工作。 MVC设法正确地转换类型。
最后,我找到了所有这些行为的解释。请看下面的答案。
答案 0 :(得分:3)
这是我用来实现它的快速解决方法:
public static MvcHtmlString GetPeriodLink(this HtmlHelper html,
RequestContext context,
DateTime date)
{
UrlHelper urlHelper = new UrlHelper(context);
context.RouteData.Values["month"] = date.Month;
context.RouteData.Values["year"] = date.Year;
return MvcHtmlString.Create(
urlHelper.Action(
(string)context.RouteData.Values["action"]));
}
我只是删除我只需替换month
中的year
和context.RouteData.Values
条目。month
和year
条目在请求上下文。如果从上下文中删除它们(正如我之前所做的那样),它们将不适用于在此之后调用的助手方法。
这种方法使我的扩展方法按照 Test 1 中描述的方案工作(请参阅问题)。
<强> FINALY 强>
仔细阅读 Sanderson S.,Freeman A. - Pro ASP.NET MVC 3 Framework(第3版)我至少找到了所有这些内容的解释:
第2部分ASP.NET MVC详细
第11章网址,路由和区域
生成外发网址
部分了解段变量重用:
路由系统将仅为段变量重用值 在URL模式中出现的时间早于提供的任何参数 到Html.ActionLink方法。
就month-year
之后的controller
段而言,我确实为month
和year
指定了值,所有尾随段(action
, user
)不会被重复使用。就我而言,也没有在我的匿名对象中指定它们,它们似乎无法用于路线。所以,route1无法匹配。
在书中甚至还有一个警告:
处理此行为的最佳方法是阻止它 发生。我们强烈建议您不要依赖此 行为,并为所有段变量提供值 在URL模式中。依靠这种行为不仅会让你的 代码难以阅读,但您最终会对订单做出假设 您的用户在其中发出请求,这是可以的 当您的应用程序进入维护状态时,最终会咬你。
嗯,它咬了我)))
值得记住丢失100个代表(我将在此再次重复)规则:路由系统将仅重用URL模式中较早出现的段变量的值,而不是任何提供的参数。< /强>
答案 1 :(得分:-1)
一条具有默认用户的路由是否足够?那是
routes.MapRoute(
"route1",
"{controller}/{month}-{year}/{action}/{user}",
new { user = "" }
);
否则,您必须允许更具体的用户路线,以便在创建URL时不会首先匹配route2。
更新:我建议你离开你的助手并直接使用Url.Action,这里有两个针对你的场景的测试:
[TestFixture]
public class RouteRegistrarBespokeTests
{
private UrlHelper _urlHelper;
[SetUp]
public void SetUp()
{
var routes = new RouteCollection();
routes.Clear();
var routeData = new RouteData();
RegisterRoutesTo(routes);
var requestContext = new RequestContext(HttpMocks.HttpContext(),
routeData);
_urlHelper = new UrlHelper(requestContext, routes);
}
[Test]
public void Should_be_able_to_map_sample_without_user()
{
var now = DateTime.Now;
var result = _urlHelper.Action("Index", "Sample",
new { year = now.Year, month = now.Month });
Assert.AreEqual(string.Format("/Sample/{0}-{1}/Index",
now.Month, now.Year ), result);
}
[Test]
public void Should_be_able_to_map_sample_with_user()
{
var now = DateTime.Now;
var result = _urlHelper.Action("Index", "Sample",
new { user = "user1", year = now.Year,
month = now.Month });
Assert.AreEqual(string.Format("/Sample/{0}-{1}/Index/{2}",
now.Month, now.Year, "user1"), result);
}
private static void RegisterRoutesTo(RouteCollection routes)
{
routes.MapRoute("route1", "{controller}/{month}-{year}/{action}/{user}");
routes.MapRoute("route2", "{controller}/{month}-{year}/{action}");
}
}