ASP.NET MVC3中MapRoute蹊跷之处

asp.net mvc3中maproute设计的蹊跷之处  近日在使用MVC3进行开发时,碰到要将一个不存在的路径剔除出来,不要在当前的Route中匹配。但是根据官方的方法怎么也没有办法。感觉MapRoute的处理方法相当的怪异。

  首先说明下需求:

一个Area: admin

我们有一个路径:admin/system-monitor/state.axd,该路径没有控制器来接管,也没有物理文件,而是有专门的Handler来处理,因此需要将此路径排除匹配。

我们在Global.asax中的配置大概如下:

            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                "Default", 
                "{manager}/{controller}/{action}/{id}/{p}", 
                new { manager="Sino", controller = "Home", action = "Index", id = UrlParameter.Optional, p = UrlParameter.Optional },
                new string[] { "Sino.Nico.Controllers" }
            );

这个是配置前台的路由,问题不大,后台的路由配置:

            context.MapRoute(
                "admin",
                "admin/{manager}/{controller}/{action}",
                new { manager= "Sino", controller = "Home", action = "Index"},
                new string[] { "Sino.Nico.Areas.Admin.Controllers" }
                ); 

这两个配置在没有加入排除路径的前提下,运行正常。为了排除路径,我们能想到的最简单的方法就是使用IgnoreRoute了,于是,我们在Global.asax中添加,变成如下:

 public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            routes.IgnoreRoute("admin/system-monitor/state.axd/{*pathInfo}");

            routes.MapRoute(
                "Default", 
                "{manager}/{controller}/{action}/{id}/{p}",
                new { manager="Sino", controller = "Home", action = "Index", id = UrlParameter.Optional, p = UrlParameter.Optional },
                new string[] { "Sino.Nico.Controllers" }
            );
        }

可是这样处理并没有正常的排除掉这个路径,这个路径的访问还是被admin这个Area的路由匹配,结果找不到资源。

MS的官方说,如果使用IgnoreRoute方式来处理后的路径,如果第一时间匹配之后,会直接排除而不会进行任何路由匹配,看来在MVC3中,这个说法好像并不是很确切。 —— 也许是个人理解的出入。为了解决这个问题,我们只能换一种方法来处理,在Area中的RegisterArea方法中创建MapRoute的时候,添加约束,Areas中的代码:

public override void RegisterArea(AreaRegistrationContext context)
        {

            context.MapRoute(
                "admin",
                "admin/{manager}/{controller}/{action}",
                new { manager= "Sino", controller = "Home", action = "Index"},
                new { manager= @"^(?!system-monitor$)" }, // 使用约束将system-monitor强行不进行匹配
                new string[] { "Sino.Nico.Areas.admin.Controllers" }
                ); 
        }

注意,这时候我们并没有在Global.asax中添加IgnoreRoute了,遗憾的是,这时候,我们访问的时候,system-monitor匹配到了Global.asax中的路由,直接报没有找到资源。

Server Error in '/' Application.

The resource cannot be found.

Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable.  Please review the following URL and make sure that it is spelled correctly. 

Requested URL: /admin/system-monitor/

Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.272

最后,我们只好做两件事情:

1. 我们将路径在Area中直接过滤掉;

2. 在Global.asax中使用IgnoreRoute排除该路径。

这回好了,不再报找不到路径了,但是等我们回过神来,却发现,后台所有的路径都有问题了,都是找不到资源,毫不犹豫肯定是manager的约束出现问题了。正则我们看了又看,肯定是没有问题的,如果manager不为system-monitor的时候,IsMatch肯定是返回true的。

在纠结一段时间之后,我们决定看看MVC内部到底是如何设计的,怎么会出现这种问题,结果,打开代码的那一刹那,我惊了:

protected virtual bool ProcessConstraint(HttpRequestMessage request, object constraint, string parameterName, HttpRouteValueDictionary values, HttpRouteDirection routeDirection)
        {
            IHttpRouteConstraint customConstraint = constraint as IHttpRouteConstraint;
            if (customConstraint != null)
            {
                return customConstraint.Match(request, this, parameterName, values, routeDirection);
            }

            // If there was no custom constraint, then treat the constraint as a string which represents a Regex.
            string constraintsRule = constraint as string;
            if (constraintsRule == null)
            {
                throw Error.InvalidOperation(SRResources.Route_ValidationMustBeStringOrCustomConstraint, parameterName, RouteTemplate, typeof(IHttpRouteConstraint).Name);
            }

            object parameterValue;
            values.TryGetValue(parameterName, out parameterValue);
            string parameterValueString = Convert.ToString(parameterValue, CultureInfo.InvariantCulture);
            string constraintsRegEx = "^(" + constraintsRule + ")$";
            return Regex.IsMatch(parameterValueString, constraintsRegEx, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
        }

原来他自带^(...)$进行头尾匹配!

那么我们的正则再到这里就变成了:^(^(?!system-monitor$))$这个和^(?!system-monitor$)相差也太远了。看到问题了,解决起来就非常简单了。将代码变成如下这样:

public override void RegisterArea(AreaRegistrationContext context)
        {

            context.MapRoute(
                "admin",
                "admin/{manager}/{controller}/{action}",
                new { manager= "Sino", controller = "Home", action = "Index"},
                new { manager= @"(?!system-monitor$)[\w\W]*" }, // 使用约束将system-monitor强行不进行匹配
                new string[] { "Sino.Nico.Areas.admin.Controllers" }
                ); 
        }

编译,运行,通过。

小结:

虽然问题已经解决,但是疑问却在心里堆积,没有思考明白:

1. routes.IgnoreRoute("....") 添加排除的URL为什么在Area中会失效,难道不是全局排除吗,还是在哪里用错了?

2. 为什么在正则匹配的时候,系统需要添加^(...)$这样的头尾限制来处理,是出于什么目的?

* BTW:系统中有些地方和原始代码相比,有些改动,但整体大致一致。

Tuesday, August 07, 2012 | .NET技术

文章评论

No comments posted yet.

发表评论

Please add 7 and 4 and type the answer here:

关于博主

  一枚成分复杂的网络IT分子,属于互联网行业分类中的杂牌军。