14

请考虑以下路线:

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 个的值,并且缺少的两个段(即yearmonth)的值通过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 对象的顺序处理路由。检查每条路由是否匹配,这需要满足三个条件:

  1. URL 模式中定义的每个段变量都必须有一个值。为了找到每个段变量的值,路由系统首先查看我们提供的值(使用匿名类型的属性),然后是当前请求的变量值,最后是路由中定义的默认值。
  2. 我们为段变量提供的值都不能与路由中定义的仅默认变量不一致。我在这些路线中没有默认值
  3. 所有段变量的值必须满足路径约束。我在这些路线上没有限制

因此,根据我在匿名类型中指定值的第一条规则,我没有默认值。当前请求的变量值- 我想这是来自请求上下文的值。

这些对 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。但为什么?

这证实了我在实际应用程序中的内容:

  1. string在上下文中有 s ,但是int通过匿名对象提供了 s ,它以某种方式混淆了 mvc 并且它不能使用route1.
  2. int将匿名对象中的 s 更改为strings,并且上下文中的 urlmonthyear匿名对象中的相同,开始正确生成;而所有其他人都没有。

所以,我看到了一条规则:匿名对象的属性应该是string与请求上下文中路由值的类型相匹配的类型。

但是这条规则似乎不是强制性的,就像在Test3中一样,我改变了类型(你现在可能在上面看到了),它仍然可以正常工作。MVC 设法正确地转换类型。


最后我找到了所有这些行为的解释。请看下面我的回答。

4

2 回答 2

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我只是yearcontext.RouteData.Values. 我只是替换了请求上下文中的monthandyear条目。如果从上下文中删除它们(就像我一开始所做的那样),它们对于在此之后调用的助手方法将不可用。

这种方法使我的扩展方法适用于测试 1中描述的场景(请参阅问题)。


最后

仔细重读Sanderson S., Freeman A. - Pro ASP.NET MVC 3 Framework (3rd edition)我至少找到了所有这些东西的解释:

第 2 部分 ASP.NET MVC 详解

第 11 章 URL、路由和区域

生成传出 URL

在部分了解段变量重用

路由系统将仅重用在 URL 模式中出现早于提供给 Html.ActionLink 方法的任何参数的段变量的值。

只要我的month-year段在之后得到满足,并且我确实为andcontroller指定了值,所有尾随段 ( , ) 都不会被重用。至于我没有在我的匿名对象中指定它们,它们似乎对路由不可用。所以,route1 无法匹配。monthyearactionuser

书中甚至有一个警告:

处理这种行为的最好方法是防止它发生。我们强烈建议您不要依赖此行为,而是为 URL 模式中的所有段变量提供值。依赖这种行为不仅会使您的代码更难阅读,而且您最终会对用户发出请求的顺序做出假设,这最终会在您的应用程序进入维护阶段时对您产生影响。

好吧,它咬了我)))

值得失去 100 个代表来记住(我什至会在这里再次重复)规则:路由系统将仅重用在 URL 模式中出现早于提供的任何参数的段变量的值。

于 2012-10-30T04:31:35.260 回答
-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}");
}

}
于 2012-10-31T12:01:24.523 回答