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" }) => @DisplayValue(@Url.Action("Index", new { controller = "Test1" }))<br /> Url.Action("Index", new { controller = "Test2" }) => @DisplayValue(Url.Action("Index", new { controller = "Test2" }))<br /> Url.Action("Index", new { controller = "Test2", p1 = "", p2 = "" }) => @DisplayValue(Url.Action("Index", new { controller = "Test2", p1 = "", p2 = "" }))<br /> Url.Action("Index", new { controller = "Test2", p1 = "p1" }) => @DisplayValue(Url.Action("Index", new { controller = "Test2", p1 = "p1" }))<br /> Url.Action("Index", new { controller = "Test2", p1 = "p1", p2 = "p2" }) => @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的问题。