本鱼拟成立工作室承接项目开发/软件定制/云设施开发运维/办公设备技术支持等,如您有相关需求,欢迎来询 | ::博客文章推荐::

这个算是BUG吗? ASP.net 4.0/MVC3 RC2 中, 带有两个可选参数的路由, 同时没有指定参数时将无法生成正确的链接

: WEBFORM/MVC 木魚 3662℃

RT, 昨天刚刚升级到RC2, 今天就遇到比较郁闷的问题。 之前看到阿不提到过一些关于MVC3 RC2的BUG,感觉不会太大影响使用。

不过今天下午就遇到问题了。

本来很简单的路由注册,带有Area的路由, 突然发现用 Html.ActionLink 生成的链接是空地址,觉得很奇怪,用Url.Action测试也是空地址。但是如果调用默认路由的话,链接就完全正常,非常费解。

默认的路由注册如下:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
    routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new { controller = "Index", action = "Index", id = UrlParameter.Optional } // Parameter defaults
    );
}

区域的路由注册如下:

public override void RegisterArea(AreaRegistrationContext context)
{
    context.MapRoute(
        "Report_default",
        "Report/{controller}/{action}/{p1}/{p2}",
        new { action = "Index", p1 = UrlParameter.Optional, p2 = UrlParameter.Optional }
    );
}

看似非常正常的路由注册,但是在RC2中,第二段路由是没法生成地址的:

@Url.Action("EmptyBunk", new { controller = "Rent" })   => /Rent/EmptyBunk
@Url.Action("EmptyBunk", new { controller = "Rent", area = "Report" })  => 空地址

 
 但是如果你发现在注册路由时,对可选参数都传递了非空的值(不包括空字符串),会发现地址是正确的:

@Url.Action("EmptyBunk", new { controller = "Rent", area = "Report", p1 = "123", p2 = "456" })  =>  /Report/Rent/EmptyBunk/123/456

一直以为是路由注册中出现了问题,浪费了一个多小时才发现这原来是MVC自己的问题,如果路由规则中存在两个可选参数并且生成路由时没有指定参数值,就会导致生成的链接地址是空地址。

关于这个在Microsoft Connect上已经有人提交了BUG报告:http://connect.microsoft.com/VisualStudio/feedback/details/630568/url-routing-with-two-optional-parameters-unspecified-fails-on-asp-net-mvc3-rc2,不知道园子里是否有人会有一样的问题呢?

为了确认的确是ASP.net Routing的问题,创建一个新的MVC项目后做测试。注册路由如下:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
 
    routes.MapRoute(
    "Default", // Route name
    "Index/{action}/{id}", // URL with parameters
    new { controller = "Index", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
 
    routes.MapRoute(
    "Default1", // Route name
    "Test1/{action}/{p1}", // URL with parameters
    new { controller = "Test1", action = "Index", p1 = UrlParameter.Optional } // Parameter defaults
);
    routes.MapRoute(
    "Default2", // Route name
    "Test2/{action}/{p1}/{p2}", // URL with parameters
    new { controller = "Test2", action = "Index", p1 = UrlParameter.Optional, p2 = UrlParameter.Optional } // Parameter defaults
);
}

这里没有带上Area,因为实际上有没有Area对结果没有什么影响。 Default的路由是用来显示测试结果的,下面两个才是正事。

Index.cshtml中测试代码如下:

@helper DisplayValue(string str)
{
    if (string.IsNullOrEmpty(str))
    {
<span style="font-style: italic; color: Gray;">空地址</span>
    }
    else
    {@str;
    }
}
<h2>
Url.Action Tests</h2>
Url.Action("Index", new { controller = "Test1" }) =&gt; @DisplayValue(@Url.Action("Index", new { controller = "Test1" }))<br />
Url.Action("Index", new { controller = "Test2" }) =&gt; @DisplayValue(Url.Action("Index", new { controller = "Test2" }))<br />
Url.Action("Index", new { controller = "Test2", p1 = "", p2 = "" }) =&gt; @DisplayValue(Url.Action("Index", new { controller = "Test2", p1 = "", p2 = "" }))<br />
Url.Action("Index", new { controller = "Test2", p1 = "p1" }) =&gt; @DisplayValue(Url.Action("Index", new { controller = "Test2", p1 = "p1" }))<br />
Url.Action("Index", new { controller = "Test2", p1 = "p1", p2 = "p2" }) =&gt; @DisplayValue(Url.Action("Index", new { controller = "Test2", p1 = "p1", p2 = "p2" }))<br />
<h2>
Routes.GetVirtualPathForArea TEST</h2>
@{
var dic = new System.Web.Routing.RouteValueDictionary();
dic.Add("controller", "Test1");
dic.Add("action", "Index");
//for test1
var vp1 = System.Web.Routing.RouteTable.Routes.GetVirtualPath(Request.RequestContext, dic);
//for test2
dic.Remove("controller");
dic.Add("controller", "Test2");
var vp2 = System.Web.Routing.RouteTable.Routes.GetVirtualPath(Request.RequestContext, dic);
//fill optional parameters
dic.Add("p1", "1");
dic.Add("p2", "4");
var vp3 = System.Web.Routing.RouteTable.Routes.GetVirtualPath(Request.RequestContext, dic);
 
 
}VP1=@DisplayValue(vp1 == null ? "" : vp1.VirtualPath)
<br />
VP2=@DisplayValue(vp2 == null ? "" : vp2.VirtualPath)
<br />
VP3=@DisplayValue(vp3 == null ? "" : vp3.VirtualPath)

最终的测试页面显示没有出乎我的意料:

Url.Action Tests

Url.Action("Index", new { controller = "Test1" }) => /Test1
Url.Action("Index", new { controller = "Test2" }) => 空地址 
Url.Action("Index", new { controller = "Test2", p1 = "", p2 = "" }) => 空地址 
Url.Action("Index", new { controller = "Test2", p1 = "p1" }) => /Test2/Index/p1
Url.Action("Index", new { controller = "Test2", p1 = "p1", p2 = "p2" }) => /Test2/Index/p1/p2

Routes.GetVirtualPathForArea TEST

VP1=/Test1 
VP2=空地址 
VP3=/Test2/Index/1/4

可见直接使用 Routes.GetVirtualPath 也是无法获得正确结果的,因此可能是ASP.net 4中的Routing组件的问题….但是想想,这个问题应该很容易发现,为什么至今没见到有人提起呢?

不知道园子里有没有同学验证下这个是不是确实存在?晚上在我的台式机上再测试一下。

想掏出Reflector看看流程是不是存在啥问题~~~

[UPDATED @ 2010年12月16日 13:08:18]

评论中的两位同学怀疑是我 Url.Action 函数调用的问题。老实说我很奇怪,因为Url.Action函数本身存在8个版本,最终调用的全是 Routes.GetVirtualPath 函数,因此这里用的是哪个本无关紧要。但是为谨慎起见,还是进行测试,证实了 Url.Action(action, controller) 一样存在这个问题。

PS。这个问题在 ASP.net MVC3 RC1或者 MVC2中是不存在的,因此可能是新引入的问题。

PS2。在 ASP.net的官方网站上,有关于RC2的临时BUG修复方法:http://weblogs.asp.net/scottgu/archive/2010/12/14/update-on-asp-net-mvc-3-rc2-and-a-workaround-for-a-bug-in-it.aspx,但是根据我的测试,它针对这个BUG是无法修复的,它仅能修复 AllowHtml 无效和可空参数总为null的问题。

喜欢 (0)