在asp.net mvc2中添加Area及相关的路由定义

       在一个较复杂的Mvc项目中,我们可以利用Area对模块进行划分。在MVC2中,添加Area后,经常出现找不到视图的情况,此时,我们必须自行定义一个视图引擎,以告知相关的View的路径,形成正确的ViewEngineResult返回。具体步骤如下:

 

一、定义一个视图引擎,继承自WebFormViewEngine

    1.1 在其构造函数中指定ViewLocationFormats与MasterLocationFormats,将Area中对应的area参数加到寻找View的路径中

    1.2 override基类型WebFormViewEngine中的两个方法:FindView与FindPartialView,对出现area的路由,指明正确寻找View的方法,以便返回正确的ViewEngineResult结果。

    1.3 示例代码如下:

public class MyViewEngine:WebFormViewEngine

    {

        public MyViewEngine()

            : base()

        {

            // 视图位置匹配规则设置  

            ViewLocationFormats = new string[]  

           {  

               "~/{0}.aspx"

               "~/{0}.ascx"

               "~/Views/{1}/{0}.aspx"

               "~/Views/{1}/{0}.ascx"

               "~/Views/Shared/{0}.aspx"

               "~/Views/Shared/{0}.ascx",   

           };

            // 母版页匹配规则设置  

            MasterLocationFormats = new string[]  

           {  

               "~/{0}.Master"

               "~/Shared/{0}.Master"

               "~/Views/{1}/{0}.Master"

               "~/Views/Shared/{0}.Master"

           };  

 

        }

 

        ///<summary>

        ///重写视图推送的方法

        ///</summary>

        ///<param name="controllerContext"></param>

        ///<param name="viewName"></param>

        ///<param name="masterName"></param>

        ///<param name="useCache"></param>

        ///<returns></returns>

        public overrideViewEngineResult FindView(ControllerContext controllerContext,string viewName, string masterName,bool useCache)

        {

            ViewEngineResult areaResult = null;

            if (controllerContext.RouteData.Values.ContainsKey("area")) {

                //如果存在area

                //将原来寻找view的路径“~/Views/controller/viewName”改为"Areas/area/Views/controller/viewName"

                string areaViewName = FormatViewName(controllerContext, viewName);

                //根据view生成一个ViewEngineResult对象

                areaResult = base.FindView(controllerContext, areaViewName, masterName, useCache);

                if (areaResult != null && areaResult.View != null) {

                    return areaResult;

                }

                //没有找到对应的view,则到share下去找是否有共用的view

                //将原来的从shared下找view的路径"~/Views/Shared/viewName"改为"Areas/area/Views/Shared/viewName"

                string sharedAreaViewName = FormatSharedViewName(controllerContext, viewName);

                //根据shared下的view重新生成ViewEngineResult,返回

                areaResult = base.FindView(controllerContext, sharedAreaViewName, masterName, useCache);

                if (areaResult != null && areaResult.View != null) {

                    return areaResult;

                }

            }

            //没有找到相关结果,返回默认的

            return base.FindView(controllerContext, viewName, masterName, useCache);

        }

 

        ///<summary>

        ///重写PartialView推送的方法

        ///</summary>

        ///<param name="controllerContext"></param>

        ///<param name="partialViewName"></param>

        ///<param name="useCache"></param>

        ///<returns></returns>

        public overrideViewEngineResult FindPartialView(ControllerContext controllerContext,string partialViewName, bool useCache)

        {

            ViewEngineResult areaResult = null;

            if (controllerContext.RouteData.Values.ContainsKey("area")) {

                string areaPartialName = FormatViewName(controllerContext, partialViewName);

                areaResult = base.FindPartialView(controllerContext, areaPartialName, useCache);

                if (areaResult != null && areaResult.View != null) {

                    return areaResult;

                }

                string sharedAreaPartialName = FormatSharedViewName(controllerContext, partialViewName);

                areaResult = base.FindPartialView(controllerContext, sharedAreaPartialName, useCache);

                if (areaResult != null && areaResult.View != null) {

                    return areaResult;

                }

            }

            return base.FindPartialView(controllerContext, partialViewName, useCache);

        }

 

        private staticstring FormatViewName(ControllerContext controllerContext,string viewName)

        {

            string controllerName = controllerContext.RouteData.GetRequiredString("controller");

            string area = controllerContext.RouteData.Values["area"].ToString();

            return string.Format("Areas/{0}/Views/{1}/{2}", area, controllerName, viewName);

        }

        private staticstring FormatSharedViewName(ControllerContext controllerContext,string viewName)

        {

            string area = controllerContext.RouteData.Values["area"].ToString();

            return string.Format("Areas/{0}/Views/Shared/{1}", area, viewName);

        }

 }

 

二、在Global.asax中指定自定义视图引擎,并注册Area路由

    2.1 清空默认的视图引擎,并指定新的视图引擎为自定义的视图引擎

    2.2 注册相关的Area路由

    2.3 示例代码:   

public class MvcApplication : System.Web.HttpApplication

    {

        public staticvoid RegisterRoutes(RouteCollection routes)

        {

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

 

            routes.MapRoute(

                "Default", //默认的路由

                "Yang/{controller}/{action}/{id}",

                new { area="Yang",controller ="Root", action = "Index", id =UrlParameter.Optional },

                new string[] {"MvcWebForTest2.Areas.Yang.Controllers" }

            );

  

            routes.MapRoute(

                "Root", // 默认首页指定到一个Area下的HomeControllerIndex

                 "",      // Url,即网站名的url

                 new { area="Xiao",controller ="Home", action = "Index", id =UrlParameter.Optional },

                 new string[] { "MvcWebForTest2.Areas.Xiao.Controllers" }

            );

        }

 

        protected void Application_Start()

        {

            //清除原来所有的视图引擎

            ViewEngines.Engines.Clear();

            //加上自定义的视图引擎

            ViewEngines.Engines.Add(newMyViewEngine());

            //注册所有的Area

            AreaRegistration.RegisterAllAreas();

            //注册路由表

            RegisterRoutes(RouteTable.Routes);

        }

    }

 

 2.4 补充说明

        在MVC2中,当在一个项目中添加一个Area后,VS会自动为这个Area添加一个继承自AreaRegistration的类型,这个类型的作用是,当2.3中的AreaRegistration.RegisterAllAreas()方法执行时,项目下所有继承自AreaRegistration的子类的对应的命名空间都会注册到路由表中去。AreaRegistration子类型的示例如下(VS自动生成):

using System.Web.Mvc;

namespace MvcWebForTest2.Areas.Yang

{

    public classYangAreaRegistration:AreaRegistration

    {

        public overridestring AreaName

        {

            get

            {

                return "Yang";

            }

        }

 

        public overridevoid RegisterArea(AreaRegistrationContext context)

        {

            context.MapRoute(

                "Yang_default",

                "Yang/{controller}/{action}/{id}",

                new { action = "Index", id = "" }

            );

        }

 

    }

}

 

通过对System.Web.Mvc的源码进行分析可以看出RegisterAllAreas()方法的执行是非常有趣的。

a、首先读取缓存中是否保存了相关的AreaRegistration子类型对应的Area的命名空间记录(注:缓存只在.net FrameWork4.0中才会启用)

b、如果缓存中没有找到,它通过反射,获取所有的Type相符(属于AreaRegistration子类)的子类型的命名空间(在.net FrameWork4.0中它会把找到的类型的命名空间序列化后写入缓存的"MVC-AreaRegistrationTypeCache.xml"的中),以下节选自System.Web.Mvc项目源码中的表述

// Go through all assemblies referenced by the application and search for types matching a predicate

上文中是否相符的type的判断条件

type != null && type.IsPublic && type.IsClass && !type.IsAbstract

且typeof(AreaRegistration).IsAssignableFrom(type) &&type.GetConstructor(Type.EmptyTypes) != null;

c、利用AreaRegistrationContext对象,对所有符合条件的命名空间进行注册

        internal void CreateContextAndRegister(RouteCollection routes,object state) {

            AreaRegistrationContext context =new AreaRegistrationContext(AreaName, routes, state);

 

            string thisNamespace = GetType().Namespace;

            if (thisNamespace != null) {

                context.Namespaces.Add(thisNamespace + ".*");

            }

            RegisterArea(context);

        }

(注:如果在global.ascx中没有执行RegisterAllAreas()或者在对应的area下没有添加继承自AreaRegistration的子类,在对应的Html方法输出Html.ActionLink时,即使指定了routeDictionary中的area参数,也会出现链接出错的情况,此时就需要自行在application_start中对这个area进行注册了,此部分详见第四大点)

 

三、在View中使用Html.ActionLink在不同Area之间进行跳转     

<%=Html.ActionLink("链接到xiao","Me""Home",new { area="Xiao" },null)%>

 

四、在global.ascx的Application_Start方法中自行注册Area的方式:

         在项目中,若不喜欢在每个area的根文件夹下都生成一个继续自AreaRegistration子类的cs文件,或者在Application_Start()方法中没有调用AreaRegistration.RegisterAllAreas()方法,或者希望整个项目下的路由路径(URL)可以统一集中到Global中管理,我们也可以自行利用MapRoute或自定义扩展方法来注册area的命名空间。但不推荐此种做法。下面给出一个RouteCollection类型扩展CreateArea的方法的示例,以便在RegisterRoutes中一次性注册某个Area下的所有Controller,扩展方法CreateArea的示例代码如下:           

namespace System.Web.Routing

{

    //请注册扩展方法的命名空间

    public staticclass RoutingExtension

    {

        public staticvoid CreateArea(thisRouteCollection routeCollection, string area, string controllerNameSpace, params Route[] routeEntities)

        {

            if (routeEntities == null || routeEntities.Length <= 0) {

                return;

            }

            foreach (Route routein routeEntities) {

                if (route.Constraints == null) {

                    route.Constraints = new RouteValueDictionary();

                }

                if (route.Defaults == null) {

                    route.Defaults = new RouteValueDictionary();

                }

                if (route.DataTokens == null) {

                    route.DataTokens = new RouteValueDictionary();

                }

                route.Constraints.Add("area", area);

                //将一个area下的controllers命名空间加入

                route.DataTokens.Add("namespaces",new[] { controllerNameSpace });

                route.Defaults.Add("area", area);

                if (!routeCollection.Contains(route)) {

                    routeCollection.Add(route);

                }

            }

        }

    }

}

 

然后在Global的RegisterRoutes方法中如下图所示调用CreateArea扩展方法注册某个area下的所有路由

public static void RegisterRoutes(RouteCollection routes)

        {

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

 

            // Area Yang下的控制器组注册

            routes.CreateArea("Yang",

                                      "MvcWebForTest2.Areas.Yang.Controllers",

                                      new Route[]{

                                        routes.MapRoute("yangRt1","Yang/{controller}/{action}", new { controller = "Home", action = "Index" }),

                                        routes.MapRoute("yangRt2","Yang/myurl/{action}",new {controller="Home",action="Index"})

                                      });

 

            //默认路由及首页设置,定位到指定Area

            routes.CreateArea("Xiao",

                                       "MvcWebForTest2.Areas.Xiao.Controllers",

                                        new Route[]{

                                          routes.MapRoute("Default","Xiao/{controller}/{action}", new { controller = "Home", action = "Index" }),

                                          routes.MapRoute("Root","", new { controller ="Home", action = "Index" })

                                     }); 

            }

    这样的话,即使在每个area下没有继承自AreaRegistration的子类,或者没有调用RegisterAllAreas()方法,都可以正常地路由所有area下的页面。

 

示例下载

posted @ 2013-05-14 15:49  羊圈里最帅的羊  阅读(399)  评论(0编辑  收藏  举报