这几天一直在抽时间算是较深入的了解MVC框架模式M-V-C这三种具体是如何进行交互,在与传统的Codebehind web应用程序相比,URL是如何映射的,以及IView,IViewEngine WebFormViewEngine,VirtualPathProviderViewEngine,WebFormView这几个接口和类具体干些什么事,了解的差不多了,所以要写一篇算是记录和总结.
一、首先还是先说一下MVC的URL映射
MVC的地址都是localhost/home/index,完全不同于传统的web应用程序localhost/home/Index.aspx,那MVC这个地址是如何映射出来的呢?
先把结构图贴上来:
第一步、Brower发出请求后,这个地址会被MVC路由映射截获(UrlRoutingModule),那这个路由映射的规则我们是可控的,在Global.asax中可对其进行配置,具体的映射规则马上我会贴出来,
也是从网上的各位大牛的文章中直接挪过来,我先说一下具体请求过程,下面继续.由路由映射系统解析过地址以后,会分别解析出ContollerName,ActionName,还有Parameters。
第二步、这个时候就会在Controllers文件夹下我们可以找到HomeController.cs, 这里使用了一个约定, 就是如果URL中获取到的Controller名字是Home, 则他的Controller类名就是HomeController.
在URL中的名字后加上"Controller".
第三步、在HomeController.cs我们可以看到有多个Action,其中包括Index这个Action,在Action中就会调用Model中的方法(Model相当于业务逻辑层)并返回的数据放入ViewData容器.
第四步、数据有了,如何再从Cotorller跳转到View上,就在return
默义情况下Action的方法名对应一个View,当然上面View("index")可带一个参数,如果把Index改为Default,就会跳到Default页面.
二、Routing(这段来自于网上)
1、使用MapRoute方法
这是最简单的为ASP.NET MVC添加识别规则的方法.此方法有如下重载:
MapRoute(string name, string url);
MapRoute(string name, string url, object defaults);
MapRoute(string name, string url, string[] namespaces);
MapRoute(string name, string url, object defaults, object constraints);
MapRoute(string name, string url, object defaults, string[] namespaces);
MapRoute(string name, string url, object defaults, object constraints, string[] namespaces);
name参数
规则名称, 可以随意起名.当时不可以重名,否则会发生错误:
路由集合中已经存在名为“Default”的路由。路由名必须是唯一的。
url参数
url获取数据的规则, 这里不是正则表达式, 将要识别的参数括起来即可, 比如: {controller}/{action}
最少只需要传递name和url参数就可以建立一条Routing(路由)规则.比如实例中的规则完全可以改为:
routes.MapRoute(
"Default",
"{controller}/{action}");
default参数
url参数的默认值.如果一个url只有controller: localhost/home/
而且我们只建立了一条url获取数据规则: {controller}/{action}
那么这时就会为action参数设置defaults参数中规定的默认值. defaults参数是Object类型,所以可以传递一个匿名类型来初始化默认值:
new { controller = "Home", action = "Index" }
实例中使用的是三个参数的MapRoute方法:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
constrains参数
用来限定每个参数的规则或Http请求的类型.constraints属性是一个RouteValueDictionary对象,也就是一个字典表, 但是这个字典表的值可以有两种:
- 用于定义正则表达式的字符串。正则表达式不区分大小写。
一个用于实现 IRouteConstraint 接口且包含 Match 方法的对象。
通过使用正则表达式可以规定参数格式,比如controller参数只能为4位数字:
new { controller = @"\d{4}"}
通过第IRouteConstraint 接口目前可以限制请求的类型.因为System.Web.Routing中提供了HttpMethodConstraint类, 这个类实现了IRouteConstraint 接口. 我们可以通过为RouteValueDictionary字典对象添加键为"httpMethod", 值为一个HttpMethodConstraint对象来为路由规则添加HTTP 谓词的限制, 比如限制一条路由规则只能处理GET请求:
httpMethod = new HttpMethodConstraint("GET", "POST")
完整的代码如下:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" }, // Parameter defaults
new { controller = @"\d{4}" , httpMethod = new HttpMethodConstraint( "GET", "POST" ) }
);
当然我们也可以在外部先创建一个RouteValueDictionary对象在作为MapRoute的参数传入, 这只是语法问题.
namespace参数
此参数对应Route.DataTokens属性. 官方的解释是:
获取或设置传递到路由处理程序但未用于确定该路由是否匹配 URL 模式的自定义值。
2、MapRoute的实例
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
#region 酒店频道部分
// hotels/list-beijing-100,200-3
routes.MapRoute(
"酒店列表页",
"hotels/{action}-{city}-{price}-{star}",
new { controller = "Hotel", action = "list", city = "beijing", price="-1,-1", star="-1" },
new { city=@"[a-zA-Z]*",price=@"(\d)+\,(\d)+", star="[-1-5]"}
);
//hotels/所有匹配
routes.MapRoute(
"酒店首页",
"hotels/{*values}",
new { controller = "Hotel", action = "default", hotelid = "" }
);
#endregion
//网站首页.
routes.MapRoute(
"网站首页",
"{*values}",
new { controller = "Home", action = "index"}
);
}
实现的功能:
(1)访问 localhost/hotels/list-beijing-100,200-3 会访问酒店频道的列表页,并传入查询参数
(2)访问 localhost/hotels 下面的任何其他页面地址, 都会跳转到酒店首页.
(3)访问 localhost 下面的任何地址, 如果未匹配上面2条, 则跳转到首页.
简单总结:
(1)Routing规则有顺序(按照添加是的顺序), 如果一个url匹配了多个Routing规则, 则按照第一个匹配的Routing规则执行.
(2)由于上面的规则, 要将具体频道的具体页面放在最上方, 将频道首页 和 网站首页 放在最下方.
(3) {*values} 表示后面可以使任意的格式.
三、Controller、Action、View
1、页面处理流程发送请求 –> UrlRoutingModule捕获请求 –> MvcRouteHandler.GetHttpHandler() –> MvcHandler.ProcessRequest()
2.MvcHandler.ProcessRequest() 处理流程:使用工厂方法获取具体的Controller –> Controller.Execute() –> 释放Controller对象
3.Controller.Execute() 处理流程获取Action –> 调用Action方法获取返回的ActionResult –> 调用ActionResult.ExecuteResult() 方法
4.ActionResult.ExecuteResult() 处理流程获取IView对象-> 根据IView对象中的页面路径获取Page类-> 调用IView.RenderView() 方法(内部调用Page.RenderView方法)
通过对MVC的分析,我们了解到Controller对象的职责是传递数据,获取View对象(实现了IView接口的类),通知View对象显示.View对象的作用是显示.虽然显示的方法RenderView()是由Controller调用的,
但是Controller仅仅是一个"指挥官"的作用, 具体的显示逻辑仍然在View对象中.需要注意IView接口与具体的ViewPage之间的联系.在Controller和View之间还存在着IView对象.对于ASP.NET程序提供了
WebFormView对象实现了IView接口.WebFormView负责根据虚拟目录获取具体的Page类,然后调用 Page.RenderView().
四、说一下关键的几个接口和类
大家看到IView这个接口,从字面来看,是View抽象出来的接口。其实不然,从实际情况来看,真正的View是aspx/ascx,而在MVC中这种页面又是继承ViewPage/ViewUserControl这两个类.那这两者如何串起来的呢?
在ActionResult方法最后return
在PartialViewResult或者是ViewResult中会有个重写的方法FindView,返回ViewEngineResult类,这个ViewEngineResult就是一次查询的结果,并且其中就包括了我们要找的IView以及IViewEngine.
那IViewEngine是什么.ViewEngine即视图引擎, 在ASP.NET MVC中将ViewEngine的作用抽象成了 IViewEngine 接口.并且定义两个方法,FindPartialView和FindView.实现的类有两个,
关系是:WebFormViewEngine : VirtualPathProviderViewEngine : IViewEngine
引擎的作用有两个:
1.寻找Page/用户控件的路径
2.根据路径创建IView对象.也就是根据页面的物理文件创建IView接口对象.
以WebFormViewEngine为例, 在WebFormViewEngine类中定义了 MasterLocationFormats/ViewLocationFormats /PartialViewLocationFormats ,在调用FindPartialView/FindView方法时,
首先找到View对象的磁盘路径, 然后使用CreatePartialView/CreateView方法将磁盘路径转化实现了IView接口的WebFormView对象.看下WebFormViewEngine构造函数的源代码:
隐藏行号 复制代码 ?这是一段程序代码。public WebFormViewEngine()
{
base.MasterLocationFormats = new string[] { "~/Views/{1}/{0}.master", "~/Views/Shared/{0}.master" };
base.AreaMasterLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.master", "~/Areas/{2}/Views/Shared/{0}.master" };
base.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.aspx", "~/Views/{1}/{0}.ascx", "~/Views/Shared/{0}.aspx", "~/Views/Shared/{0}.ascx" };
base.AreaViewLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.aspx", "~/Areas/{2}/Views/{1}/{0}.ascx", "~/Areas/{2}/Views/Shared/{0}.aspx",
"~/Areas/{2}/Views/Shared/{0}.ascx" };
base.PartialViewLocationFormats = base.ViewLocationFormats;
base.AreaPartialViewLocationFormats = base.AreaViewLocationFormats;
}
protected override IView CreateView(ControllerContext controllerContext,string viewPath,string masterPath)
{
return new WebFormView(viewPath, masterPath);
}
public virtual ViewEngineResult FindView(ControllerContext controllerContext,string viewName,string masterName,bool useCache)
{
string[] strArray;
string[] strArray2;
if (controllerContext == null)
throw new ArgumentNullException("controllerContext");
if (string.IsNullOrEmpty(viewName))
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName");
string requiredString = controllerContext.RouteData.GetRequiredString("controller");
string str2 = this.GetPath(controllerContext, this.ViewLocationFormats, this.AreaViewLocationFormats, "ViewLocationFormats", viewName, requiredString, "View", useCache, out strArray);
string str3 = this.GetPath(controllerContext, this.MasterLocationFormats, this.AreaMasterLocationFormats, "MasterLocationFormats", masterName, requiredString, "Master", useCache, out strArray2);
if (!string.IsNullOrEmpty(str2) && (!string.IsNullOrEmpty(str3) || string.IsNullOrEmpty(masterName)))
return new ViewEngineResult(this.CreateView(controllerContext, str2 , str3 ), this);
return new ViewEngineResult(strArray.Union<string>(strArray2));
}
实现了IView接口的对象也只有一个:WebFormView
WebFormViewEngine 根据页面路径, 将一个页面地址转化为一个WebFormView对象,也就是一个IView接口对象.至此IView接口和Page页面类仍然没有任何关系,
IView对象只是保存了页面的物理路径.接着在IView的Render事件中,根据物理路径创建了一个页面的object实例,注意看这一段代码:
隐藏行号 复制代码 ?这是一段程序代码。
public virtual void Render(ViewContext viewContext, TextWriter writer)
{
if (viewContext == null)
throw new ArgumentNullException("viewContext");
object obj2 = this.BuildManager.CreateInstanceFromVirtualPath(this.ViewPath, typeof(object));
if (obj2 == null)
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, MvcResources.WebFormViewEngine_ViewCouldNotBeCreated, new object[] { this.ViewPath }));
ViewPage page = obj2 as ViewPage;
if (page != null)
this.RenderViewPage(viewContext, page);
else
{
ViewUserControl control = obj2 as ViewUserControl;
if (control == null)
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, MvcResources.WebFormViewEngine_ViewCouldNotBeCreated, new object[] { this.ViewPath }));
this.RenderViewUserControl(viewContext, control);
}
}
- 只有在这个时候才会转化为ViewPage/ViewUserControl
以上大概将要记录的要讲的基本写完,这中间肯定有不足之处,如有机会如再完善.