生成传出意外的URL路径选择 [英] Unexpected route chosen while generating an outgoing url

查看:166
本文介绍了生成传出意外的URL路径选择的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下路线:

  routes.MapRoute(
    ROUTE1
    {控制器} / {}月 - {一年} / {行动} / {}用户
);
routes.MapRoute(
    路径2
     {控制器} / {}月 - {一年} / {}行动
);

和以下测试:

TEST 1

  [TestMethod的]
公共无效的Test1()
{
    RouteCollection路线=新RouteCollection();
    MvcApplication.RegisterRoutes(路线);
    RequestContext的背景=新的RequestContext(CreateHttpContext()
                                                新的RouteData());    现在的DateTime = DateTime.Now;
    字符串结果;    context.RouteData.Values​​.Add(控制器,家);
    context.RouteData.Values​​.Add(行动,指数);
    context.RouteData.Values​​.Add(用户,USER1);
    结果= UrlHelper.GenerateUrl(NULL,索引,空,
                                    新RouteValueDictionary(
                                        新
                                        {
                                            月= now.Month,
                                            年= now.Year
                                        }),
                                    路线,上下文,真);
    // OK,结果== /主页/ 10-2012 /索引/ USER1
    Assert.AreEqual(的String.Format(/首页/ {0} - {1} /索引/ USER1,now.Month,now.Year)
                    结果);
}

测试2

  [TestMethod的]
公共无效的Test2()
{
    RouteCollection路线=新RouteCollection();
    MvcApplication.RegisterRoutes(路线);
    RequestContext的背景=新的RequestContext(CreateHttpContext()
                                                新的RouteData());    现在的DateTime = DateTime.Now;
    字符串结果;    context.RouteData.Values​​.Add(控制器,家);
    context.RouteData.Values​​.Add(行动,指数);
    context.RouteData.Values​​.Add(用户,USER1);
    context.RouteData.Values​​.Add(月,now.Month + 1);
    context.RouteData.Values​​.Add(年,now.Year);
    结果= UrlHelper.GenerateUrl(NULL,索引,空,
                                    新RouteValueDictionary(
                                        新
                                        {
                                            月= now.Month,
                                            年= now.Year
                                        }),
                                    路线,上下文,真);
    //错误,因为结果== /主页/ 10-2012 /索引
    Assert.AreEqual(的String.Format(/首页/ {0} - {1} /索引/ USER1,now.Month,now.Year)
    结果);
}

该测试模拟的时候我已经有路由值在请求上下文,并尝试生成UrlHelper传出URL的情况。

问题是,($ P $测试2 psented),如果我有从期望的路线所有段的值(这里 ROUTE1 ),并试图取代通过 routeValues​​ 参数其中的一些,省略了想要的路线和下一个合适的路由被使用。

因此​​,测试1的效果很好,因为请求上下文已经失踪两个段(3 5路由1段值,值即)通过 routeValues​​ 参数传递。

测试2拥有在请求上下文中的所有5段的值。我想用throught routeValues​​ 其他值替换其中一些(即,月,年)。但是路线1似乎是的不适合的布线2被使用。

为什么呢?什么是不正确我的路线?

我是希望手动清除请求上下文在这种情况下?

修改

  [TestMethod的]
公共无效Test3的()
{
    RouteCollection路线=新RouteCollection();
    MvcApplication.RegisterRoutes(路线);
    RequestContext的背景=新的RequestContext(CreateHttpContext()
                                                新的RouteData());    现在的DateTime = DateTime.Now;
    字符串结果;    context.RouteData.Values​​.Add(控制器,家);
    context.RouteData.Values​​.Add(行动,指数);
    context.RouteData.Values​​.Add(月,now.Month.ToString());
    context.RouteData.Values​​.Add(年,now.Year.ToString());
    结果= UrlHelper.GenerateUrl(NULL,索引,空,
                                    新RouteValueDictionary(
                                        新
                                        {
                                            一个月= now.Month + 1,
                                            年= now.Year + 1
                                        }),
                                    路线,上下文,真);
    Assert.AreEqual(的String.Format(/首页/ {0} - {1} /索引,now.Month + 1,now.Year + 1)
                    结果);
}

此测试使事情变得更加混乱。在这里,我测试路径2。和它的作品!我有在请求上下文中的所有4个段值,通过 routeValues​​ 通过其他值,生成的URL传出的确定。

那么,问题是ROUTE1。我缺少什么?

修改

从的桑德森S. A.弗里曼 - 临ASP.NET MVC框架3第三版的:


  

路由系统处理的路由的顺序,他们
  加到传递到的RegisterRoutes的RouteCollection对象
  方法。每个路由被检查,看它是否是一个匹配,这
  要求必须满足三个条件:


  
  

      
  1. 的值必须是可用于URL模式定义的每个细分变量。要查找值为每个段的变量,路由
      系统会首先在我们所提供的值(使用
      匿名类型的属性),则对于可变值
      当前请求,最后在默认值在所定义的
      路线。

  2.   
  3. 我们的细分变量提供的值都不可以在路由定义的默认只变量不同意。的
      不必在航线默认值

  4.   
  5. 对于所有的段变量的值必须满足路由约束。的我没有在这些路线
  6. 限制
      

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

什么是错的,这些推理的路径2,而他们的工作ROUTE1好?

修改

其实一切都开始不是从单元测试,但距离真正的MVC应用程序。在那里,我用 UrlHelper.Action方法(String,Object)已生成传出的URL。由于这种方法是利用在布局视图(父一个广大的所有视图),我已经采取了到我的扩展的辅助方法(以排除看法额外的逻辑),这种扩展方法从请求中提取动作的名称情况下,传递给它作为参数。我知道我可以通过请求上下文中提取当前所有的路由值并更换的的和的的(或者我可以创建一个匿名的路线收藏价值,包含所有的值上下文),但我认为这是多余的,因为MVC会自动考虑到包含在请求上下文中的值。所以,我只提取操作名称,因为有没有动作名称没有UrlHelper.Action过载(或我会喜欢甚至为未指定动作名称太),并通过匿名航线的值对象增加了新的月份和年份。

这是一个扩展方法:

 公共静态MvcHtmlString GetPeriodLink(这个HTML的HtmlHelper,
                                          RequestContext的背景下,
                                          日期时间为准)
{
    UrlHelper urlHelper =新UrlHelper(背景);
    返回MvcHtmlString.Create(
              urlHelper.Action(
                 (串)context.RouteData.Values​​ [行动]
                 新的一年{= date.Year,月= date.Month}));
}

正如我在测试如上所述,它为短路线时(请求上下文只包含控制器,年和月,和动作),但是失败了较长的一个(当请求上下文包含控制器,年份和月份,动作和用户)。


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

虽然确实是我很想搞清楚,为什么在地球上做的我做任何变通办法在这样的情况下,是什么这两条路是prevents之间的关键区别 ROUTE1 从工作方式路径2 一样。


修改

另外的话。至于在请求上下文中的值类型的字符串,我决定尝试将它们设置成背景为字符串,以确保没有任何类型的困惑(INT VS字符串) 。我不明白,是什么在这方面有所改变,但部分路线的开始正确生成。但并不是所有的......这使得少又感。我在实际的应用中,而不是测试改变了这种,因为测试有 INT 在上下文中,而不是字符串。

好吧,我发现其下的 ROUTE1 的使用情况 - 它只是用来当和<$ C $的值C>年在上下文等于在匿名对象给出的那些。如果它们之间的区别(在测试中,这是真实的既为 INT 字符串)的路径2 被使用。但是,为什么?

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


  1. 字符串■在上下文,但提供 INT s到匿名对象,它在某种程度上混淆了MVC和它不能使用 ROUTE1

  2. 我在匿名对象改为 INT s到字符串 s和网址,其中在上下文中都等于匿名对象的那些,开始正确生成;而所有其他人没有。

所以,我看到一个原则:匿名对象的属性应该是类型字符串来匹配请求上下文路径值的类型。

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


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


解决方案

这是一个快速的解决方法我用得到它的工作:

 公共静态MvcHtmlString GetPeriodLink(这个HTML的HtmlHelper,
                                          RequestContext的背景下,
                                          日期时间为准)
{
    UrlHelper urlHelper =新UrlHelper(背景);    context.RouteData.Values​​ [月] = date.Month;
    context.RouteData.Values​​ [年] = date.Year;    返回MvcHtmlString.Create(
              urlHelper.Action(
                 (串)context.RouteData.Values​​ [行动]));
}

我只是从背景项删除一个月。 RouteData.Values​​ 我只需更换每月项请求上下文。如果从上下文中删除它们(正如我在第一次做),他们会为这一个调用后,外籍佣工方法不可用。

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


FINALY

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

第2部分ASP.NET MVC详细

第11章的URL,路由和地区

生成传出网址

在节的了解段可变重用


  

路由系统将重用值仅适用于细分变量
  此前在URL模式发生的比被提供任何参数
  到Html.ActionLink方法。


至于我的月 - 年段满足控制器之后和我做<$指定值C $ C>月和,所有尾随段(动作用户)不重用。至于我,也不在我的匿名对象指定他们,他们似乎是路线不可用。因此,ROUTE1无法比拟的。

在书中甚至有一个警告:


  

要解决这个问题的最好办法是从prevent它
  发生。我们强烈建议您不要依赖这个
  行为,您为所有的细分变量提供值
  在URL模式。依托这种行为将不仅使您的
  code难读,但你最终使有关订单的假设
  在你的用户发出请求,这是一件好事,这将
  最终咬你为你的应用程序进入维护。


那么,它咬我)))

值得失去100代表要记得(我即使在这里重复一遍)规则:路由系统将重用值仅适用于比供给任何参数的URL模式中更早发生段变量<。 / STRONG>

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

Another remark. As far as the values in request context are of type string, I decided to try to set them into the context as strings to ensure there were no type confusion (int vs string). I do not understand, what has changed in this respect, but some of the routes started generating correctly. But not all...This makes yet less sense. I've changed this in a real application, not tests, as the tests have int in context, not strings.

Well, I've found the condition under which route1 is used - it's only used when the values of month and year in the context are equal to the ones given in the anonymous object. If they differ (in the tests this is true both for int and string), route2 is used. But why?

This confirms what I have in my real application:

  1. I had strings in the context, but provided ints through the anonymous object, it somehow confused the mvc and it couldn't use route1.
  2. I changed ints in the anonymous object to strings, and the urls where month and year in context are equal to the ones in the anonymous object, started generating correctly; whereas all others didn't.

So, I see one rule: the properties of the anonymous object should be of type string to match the type of the route values in the request context.

But this rule seems to be not compulsory, as in Test3, I changed the types (you may see it now above) and it still works correctly. MVC manages to cast types itself correctly.


Finally I found the explanation to all this behaviour. Please, see my answer below.

解决方案

This is a quick workaround I use to get it work:

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

I simply remove the month and year entries from the context.RouteData.Values. I simply replace the month and year entries on the request context. If delete them from context (as I did at first), they would be unavailable for the helpers' methods invoked after this one.

This approach makes my extension method work by the scenario, described in Test 1 (please, see the question).


FINALY

Carefully rereading Sanderson S., Freeman A. - Pro ASP.NET MVC 3 Framework (3rd edition) I at least found the explanation of all this stuff:

Part 2 ASP.NET MVC in detail

Chapter 11 URLs, Routing and Areas

Generating outgoing URLs

in section Understanding segment variable reuse:

The routing system will reuse values only for segment variables that occur earlier in the URL pattern than any parameters that are supplied to the Html.ActionLink method.

As far as my month-year segment is met right after controller and I do specify values for month and year, all trailing segments (action,user) are not reused. As far as I do nor specify them in my anonymous object, they appear to be unavailable for the route. So, route1 cannot match.

In the book there is even a warning:

The best way to deal with this behavior is to prevent it from happening. We strongly recommend that you do not rely on this behavior, and that you supply values for all of the segment variables in a URL pattern. Relying on this behavior will not only make your code harder to read, but you end up making assumptions about the order in which your users make requests, which is something that will ultimately bite you as your application enters maintenance.

Well, it has bitten me)))

Worth to lose 100 rep to remember (I shall even repeat it here again) the rule: The routing system will reuse values only for segment variables that occur earlier in the URL pattern than any parameters that are supplied.

这篇关于生成传出意外的URL路径选择的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆