这几天一直在抽时间算是较深入的了解MVC框架模式M-V-C这三种具体是如何进行交互,在与传统的Codebehind web应用程序相比,URL是如何映射的,以及IView,IViewEngine WebFormViewEngine,VirtualPathProviderViewEngine,WebFormView这几个接口和类具体干些什么事,了解的差不多了,所以要写一篇算是记录和总结.

一、首先还是先说一下MVC的URL映射

MVC的地址都是localhost/home/index,完全不同于传统的web应用程序localhost/home/Index.aspx,那MVC这个地址是如何映射出来的呢?

先把结构图贴上来:

asp.net mvc 返回 json asp.net mvc jwt_正则表达式


第一步、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
    以上大概将要记录的要讲的基本写完,这中间肯定有不足之处,如有机会如再完善.