在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下的HomeController的Index

"", // 空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下的页面。

示例下载