生成传出URL意想不到路线选择(Unexpected route chosen while gene

2019-08-01 23:43发布

Please, consider the following routes:

routes.MapRoute(
    "route1",
    "{controller}/{month}-{year}/{action}/{user}"
);
routes.MapRoute(
    "route2",
     "{controller}/{month}-{year}/{action}"
);

And the following tests:

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

This test emulates a situation when I already have route values in request context and try to generate an outgoing url with UrlHelper.

The problem is that (presented in test 2), if I have values for all the segments from the expected route (here route1) and try to replace some of them through routeValues parameter, the wanted route is omitted and the next suitable route is used.

So test 1 works well, as the request context already has values for 3 of 5 segments of route 1, and values for the missing two segments (namely, year and month) are passed through the routeValues parameter.

Test 2 has values for all 5 segments in the request context. And I want to replace some of them (namely, month and year) with other values throught routeValues. But route 1 appears to be not suitable and route 2 is used.

Why? What is incorrect with my routes?

Am I expected to clear request context manually in such circumstances?

EDIT

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

This test makes things more confused. Here I'm testing route2. And it works! I have values for all 4 segments in the request context, pass other values through routeValues, and the generated outgoing url is OK.

So, the problem is with route1. What am I missing?

EDIT

From Sanderson S. Freeman A. - Pro ASP.NET MVC 3 Framework Third Edition:

The routing system processes the routes in the order that they were added to the RouteCollection object passed to the RegisterRoutes method. Each route is inspected to see if it is a match, which requires three conditions to be met:

  1. A value must be available for every segment variable defined in the URL pattern. To find values for each segment variable, the routing system looks first at the values we have provided (using the properties of anonymous type), then the variable values for the current request, and finally at the default values defined in the route.
  2. None of the values we provided for the segment variables may disagree with the default-only variables defined in the route. I don't have default values in these routes
  3. The values for all of the segment variables must satisfy the route constraints. I don't have constraints in these routes

So, according to the first rule I've specified values in an anonymous type, I don't have default values. The variable values for the current request - I suppose this to be the values from the request context.

What is wrong with these reasonings for route2, while they work well for route1?

EDIT

Actually everything started not from unit tests, but from a real mvc application. There I used UrlHelper.Action Method (String, Object) to generate outgoing urls. Since this method is utilized in a layout view (the parent one for the majority of all views), I've taken it into my extension helper method (to exclude extra logic from views), This extension method extracts the action name from the request context, passed to it as an argument. I know I could extract all the current route values through the request context and replace those year and month (or could I create an anonymous route value collection, containing all the values from the context), but I thought it was superfluous, as mvc automatically took into account the values contained in the request context. So, I extracted only action name, as there were no UrlHelper.Action overload without action name (or I'd have liked even to "not specify" action name too), and added the new month and year through anonymous route value object.

This is an extension method:

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

As I described in the tests above, it worked for shorter routes (when request context contained only controller, year and month, and action), but failed for a longer one (when request context contained controller, year and month, action, and user).


I've posted a workaround I use to make the routing work the way I need.

While indeed I would love to find out, why on earth do I have to make any workaround in such scenario and what is the key difference between these two routes that prevents route1 from working the way route2 does.


EDIT

另外的话。 至于在请求上下文的值类型的string ,我决定尝试将它们设置成背景为字符串,以确保没有任何类型的混乱(INT VS字符串)。 我不明白,是什么在这方面有所改变,但部分路线的正确启动产生。 但并不是所有的...这使得尚未意义不大。 我在实际的应用中,不改变测试这一点,因为测试必须int在上下文中,而不是字符串。

好吧,我发现在其下ROUTE1使用条件-当的值,它是只用monthyear的背景下,等于在匿名对象给出的那些。 如果它们之间的区别(在测试中,这是真正的既为intstring ), 路径2被使用。 但为什么?

这证实了我在我的实际应用:

  1. 我有string S IN的范围内,但所提供int通过匿名对象S,它在某种程度上混淆了MVC和它不能使用route1
  2. 我改变int S IN的匿名对象到string s,而其中的URL monthyear在上下文中是等于匿名对象的那些,开始正确生成; 而所有其他人没有。

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

但这个规则似乎不是强制性的 ,如Test3的 ,我改变了类型(你可以看到,现在上图),它仍然可以正常工作。 MVC管理,以正确地投类型本身。


最后,我找到了解释这一切的行为。 请参阅我的回答如下。

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

我只是删除了monthyear从条目context.RouteData.Values 我只是更换monthyear的请求上下文项。 如果从上下文中删除它们(正如我在第一次做),他们将是这个后调用的助手方法不可用。

这种方法使得通过该方案,在测试1描述了我的扩展方法的工作(请参见问题)。


FINALY

仔细的阅读桑德森S.,弗里曼A. -临ASP.NET MVC 3框架(第3版)我至少发现的所有这些东西的解释:

2 ASP.NET MVC部分详细

第11章的URL,路由和地区

生成传出网址

在节段了解变量重用

路由系统将只在较早URL模式发生比被提供给Html.ActionLink方法的任何参数段变量重用值。

至于我的month-year段后立即会见了controller和我做了规定值monthyear ,所有的尾段( actionuser )都不要再用。 至于我,也不在我的匿名对象指定他们,他们似乎是路线不可用。 因此,ROUTE1无法比拟的。

在这本书中甚至有一个警告:

对付这种行为的最好办法是防止它发生。 我们强烈建议您不要依赖于这种行为,而你所有的URL图案中片段变量提供值。 依托这种行为不仅使你的代码难以阅读,但你最终作出关于在用户提出请求的顺序,这是一件好事,这将最终咬你为你的应用程序进入维护假设。

那么,它咬我)))

值得失去100代表要记得(我甚至会再重复它在这里)规则: 路由系统将只针对此前在URL模式发生比被提供的任何参数段变量的回用价值。



Answer 2:

系统将针对用户的默认一条路线就够了吗? 那是

routes.MapRoute(
    "route1",
    "{controller}/{month}-{year}/{action}/{user}",
    new { user = "" }
);

否则,你就必须让更多的用户特定的路线东西,所以在创建网址时,它不会路径2首先匹配。

更新:我建议你留下帮手,并直接与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}");
}

}


文章来源: Unexpected route chosen while generating an outgoing url