【问题标题】:Unexpected route chosen while generating an outgoing url生成传出 url 时选择了意外的路由
【发布时间】: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 个的值,并且缺少的两个段(即 yearmonth)的值通过 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 对象 方法。检查每条路由是否匹配 需要满足三个条件:

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

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

这些对 route2 的推理有什么问题,而它们对 route1 很有效?

编辑

实际上一切都不是从单元测试开始的,而是从一个真正的 mvc 应用程序开始的。在那里我使用UrlHelper.Action Method (String, Object) 生成传出网址。由于此方法用于布局视图(大多数视图的父视图),我已将其纳入我的扩展辅助方法(从视图中排除额外逻辑),此扩展方法从请求中提取操作名称上下文,作为参数传递给它。我知道我可以通过请求上下文提取所有当前路由值并替换那些 yearmonth (或者我可以创建一个匿名路由值集合,其中包含来自上下文),但我认为这是多余的,因为 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 }));
}

正如我在上面的测试中所描述的,它适用于较短的路由(当请求上下文仅包含控制器、年份和月份以及操作时),但对于较长的路由则失败(当请求上下文包含控制器、年份和月份、操作时)和用户)。


我已经发布了一个解决方法,用于使路由按我需要的方式工作。

虽然我确实很想知道,但究竟为什么我必须在这种情况下采取任何解决方法,这两条路线之间的主要区别是什么,阻止了 route1route2 的方式工作是的。


编辑

另外一句话。至于请求上下文中的值是string 类型,我决定尝试将它们作为字符串设置到上下文中,以确保没有类型混淆(int vs string)。我不明白,这方面发生了什么变化,但一些路线开始正确生成。但并非全部......这更没有意义。我在实际应用程序中更改了这一点,而不是测试,因为测试在上下文中有 int,而不是字符串。

好吧,我找到了使用 route1 的条件——它仅在上下文中 monthyear 的值等于匿名中给出的值时使用目的。如果它们不同(在测试中,intstring 都是如此),则使用 route2。但为什么呢?

这证实了我在实际应用中拥有的内容:

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

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

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


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

【问题讨论】:

  • 在不知道“确定”的情况下,我相信当您使用 URLHelper.GenerateUrl 生成路由时,您传递的路由值字典会覆盖路由值字典中已经存在的所有内容。尝试在 GenerateUrl 调用中添加用户参数,看看是否能解决问题。

标签: c# asp.net-mvc-3 asp.net-mvc-routing


【解决方案1】:

这是我用来让它工作的快速解决方法:

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"]));
}

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

这种方法使我的扩展方法在 Test 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 段在controller 之后遇到并且我确实为monthyear 指定了值,所有尾随段(actionuser)都不会被重用。至于我没有在我的匿名对象中指定它们,它们似乎对路线不可用。所以,route1 无法匹配。

书中甚至有一个警告:

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

好吧,它咬了我)))

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

【讨论】:

    【解决方案2】:

    一个用户默认的路由就足够了吗?那是

    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}");
    }
    
    }
    

    【讨论】:

    • 您的回答如何解释我的“非常简短”的回答和“不再简短”的赏金信息中描述的所有行为?
    • 稍后我会进行更全面的阅读,对于一项简单的任务来说似乎非常复杂,但不会让您尝试更全面地理解事物。
    • 尽管如此,我已经检查了您的建议,并将我的两条路线都替换为您的路线。结果似乎与我在问题的最后一次编辑中描述的 100% 相同。什么也没有变。因此,它既不能解决问题(我希望,我已经在上面发布的自己的答案中解释了原因),也没有解释它。
    • 我试图发展你的想法并为action添加了一个默认值:new { user = "", action = "Index" } - 这条路线从{month}-{year}右边的两个段都有默认值......但是结果仍然不是我想要的 - 它用默认值(即空字符串)替换了user 的段值(在请求上下文中可用)......这一切都非常适合桑德森之后书中提供的解释S.,弗里曼 A.
    • 我猜这样的猜测不会奏效。而我唯一应该做的就是改变我的方法并通过匿名对象提供所有必要的值
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-05
    • 2012-02-03
    • 2013-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多