ASP.NET MiniMVC

ASP.NET MVC是如何运行的

ASP.NET MVC由于采用了管道式设计,所以具有很好的扩展性,整个ASP.NET MVC应用框架就是通过扩展ASP.NET实现的。

ASP.NET MVC的扩展点主要体现在HttpModuleHttpHandler这两个核心组件之上,整个ASP.NET MVC框架就是通过自定义的HttpModuleHttpHandler建立起来的。

ASP.NET MVC从接收请求到响应回复的完整流程

ASP.NET MVC 利用路由系统对URL进行解析进而得到目标Controller和Action的名称,以及其他相应的路由数据。并根据Controller的名称解析出目标Controller的真正类型,并将其激活,默认情况下根据类型以反射的机制创建Controller对象。

接下来,ASP.NET MVC利用Action名称解析出定义在目标Controller类型中对应的方法,然后执行激活Controller对象的这个方法。Action方法可以在执行过程中直接对当前请求予以响应,也可以返回一个ActionResult对象来响应请求。对于后者,ASP.NET NVC在完成目标Action方法执行之后,会执行返回的ActionResult对象来对当前请求作最终的响应。

ASP.NET MiniMVC_第1张图片
MiniMVC

Global.asax

Global.asax全局应用程序类又称为ASP.NET应用程序文件,是一个可选文件,该文件包含响应ASP.NET或HTTP模块所引发的应用程序级别和会话级别事件的代码。

Global.asax文件驻留在ASP.NET应用程序的根目录中,运行时,分析Global.asax并将其编译到一个动态生成的.NET Framework类,该类是从HttpApplication基类派生的。配置ASP.NETE以便自动拒绝对Global.asax文件的任何直接的URL请求,外部用户不能下载或查看其中的代码。

Global.asax文件是可选的,只在希望处理应用程序事件或会话事件时,才创建它。

ASP.NET MiniMVC_第2张图片
Global.asax

当IIS接收到一个访问ASP.NET应用程序的请求时,IIS会将请求映射给 aspnet_isapi.dll ,当 aspnet_isapi.dll 接到这个请求后,会新建一个 aspnet_wp.exe 进程(Windows Server 2003下是w3wp.exe进程),此进程会将请求传递给一个被指定的 AppDomain 。

当这个AppDomain 被创建时,会去加载一些配置文件中的信息,加载的顺序是从 machine.config 文件到 Web.config 。当配置信息加载完毕后,AppDomain 会去获得 HttpApplication 的实例,此时 Global 类就会被编译加载了。接下来 AppDomain 会做一些相关的处理创建Page类的实例,最后页面呈现到客户端浏览器上。

需要注意的是,当配置文件被加载时,并不表示 AppDomain 会加载配置文件中所有的信息,仅仅是加载一些必要的信息。而有些配置信息,只有在需要时才会被 AppDomain 加载。例如Web.config文件中配置的HttpModule,当某个HttpModule被访问时,AppDomain才会去加载并处理这些信息。所以说Web.config文件和Global.asax没有先后执行的顺序,只是视具体配置什么时候被加载和处理而定。

/*功能:所有应用、应用状态、程序是否被访问、用户退出...*/
public class Global : System.Web.HttpApplication
{
    /*在每个HttpApplication实例初始化时执行*/
    protected void Application_Init()
    {
    }
    /*在每个HttpApplication实例被销毁前执行*/
    protected void Application_Disposed()
    {
    }
    /**
     * 程序初始化时执行
     * IIS请求一开始就执行此方法,相当于Main函数。
     * 在Web应用程序的生命周期里仅执行一次,存放公用信息如HttpApplicationState。
     */
    protected void Application_Start(object sender, EventArgs e)
    {
    }
    /**
     * 会话开始执行
     * 此处的Session是服务端为每个浏览器保存数据开辟的临时存储空间
     * 当浏览器关闭或切换用户时重新开启内存空间用来保存会话
     * 每个浏览器访问页面均存在一个Session
     * 一个浏览器的一个用户公用一个Session
     */
    protected void Session_Start(object sender, EventArgs e)
    {
    }
    /**
     * 会话结束或过期时执行
     * 断开会话,超时后会被调用
     */
    protected void Session_End(object sender, EventArgs e)
    {
    }
    /**
     * BeginRequest是在收到Request时第一个触发的事件,此方法第一个执行。
     * 每个应用均会触发它
     * 如需查看当前请求的URL,可使用 HttpContext.Current.Request.URL
     */
    protected void Application_BeginRequest(object sender, EventArgs e)
    {
    }
    /*当安全模块已经建立了当前用户的标识后执行*/
    protected void Application_AuthenticateRequest(object sender, EventArgs e)
    {
    }
    /*当安全模块已经验证了当前用户的授权时执行*/
    protected void Application_AuthorizeRequest()
    {
    }
    /**
     * 当ASP.NET完成授权事件使缓存模块从缓存中为请求提供服务时发生
     * 从而跳过处理程序或WebService的执行,可改善网站的性能。
     * 此事件可用来判断正文是不是从Cache中获得的
     */
    protected void Application_ResolveRequestCache()
    {
    }
    /*当ASP.NET获取当前请求所关联的当前状态时执行,如Session*/
    protected void Application_AcquireRequestState()
    {
    }
    /*在ASP.NET执行完所有请求处理程序后执行,ReleaseRequestState事件将使当前状态数据被保存。*/
    protected void Application_ReleaseRequestState()
    {
    }
    /*当ASP.NET即将把请求发送到处理程序对象或WebService之前执行,此时Session就可以使用了。*/
    protected void Application_PreRequestHandleExecute()
    {
    }
    /*当处理程序对象工作完成后执行*/
    protected void Application_PostRequestHandlerExecute()
    {
    }
    /*在ASP.NET执行完处理程序后为了后续请求而更新响应缓存时执行*/
    protected void Application_UpdateRequestCache()
    {
    }
    /**所有没有处理的错误都会导致此方法的执行
     * 异常处理模块,调用后可爆出异常信息。
     */
    protected void Application_Error(object sender, EventArgs e)
    {
    }
    /**
     * 应用程序结束时,在最后一个HttpApplication销毁后执行。
     * 对应Appilcation_Start,在整个生命周期中仅执行一次。
     * 程序被关闭时执行一次,IIS被关闭才执行。
     */
    protected void Application_End(object sender, EventArgs e)
    {
    }
    /*EndRequest是在响应Request时最后一个触发的事件,此方法自然是最后一个执行的。*/
    protected void Application_EndRequest()
    {
    }
    /*向客户端发送HTTP标头前执行*/
    protected void Application_PreSendRequestHeaders()
    {
    }
    /*向客户端发送HTTP正文前执行*/
    protected void Application_PreSendRequestContent()
    {
    }
}
  • Application_InitApplication_Start 事件在应用程序第一次启动时被触发一次。
  • Application_DisposedApplication_End 事件在应用程序终止时被触发一次。
  • Session_StartSession_End 基于会话的事件只有在用户进入和离开站点时被使用。

Request请求相应的事件执行顺序如下

  1. BeginRequest
  2. AuthenticateRequest
  3. AuthorizeRequest
  4. ResolveRequestCache
  5. AcquireRequestState
  6. PreRequestHandlerExecute
  7. PostRequestHandlerExecute
  8. ReleaseRequestState
  9. UpdateRequestState
  10. EndRequest

需要注意的是只有被设置为“应用程序”的虚拟目录,而非普通的虚拟目录所属的Global.asax才有效。IIS服务只是Windows的一种服务,当IIS服务开启后第一次访问网站或系统资源紧张时,应用程序域回收或自动重启时,Global.asax才会执行。只是重启IIS是无法激活Global.asax执行的。

路由匹配

由于UrlRoutingModule这个HttpModule被注册到Web应用中,所以对于每个抵达的请求来说,当代表当前引用的HttpApplication对象的PostResolveRequestCache事件被触发的时候,UrlRoutingModule会利用RouteTable表示的路由表(实际上RouteTable的静态属性Routes返回的RouteDictionary对象代表这个路由表)针对当前请求实施路由解析。

具体来说,UrlRoutingModule会调用代表路由表的RouteDictionary对象的GetRouteData()方法。如果定义在某个Route对象上的路由规则与当前请求相匹配,那么该方法执行结束之后会返回一个RouteData对象,包括目标ControllerAction名称的路由变量被包含在这个RouteData对象之中。

接下来UrlRoutingModule通过RouteData对象的RouteHandler属性得到匹配Route对象采用的RouteHandler对象,在默认情况下这是一个MvcRouteHandler对象。UrlRoutingModule随后会调用这个MvcRouteHandler对象的GetHttpHandler()方法得到一个HttpHandler对象。UrlRoutingModule随之调用当前GetHttpHandler()具体返回的一个MvcHandler对象。UrlRoutingModule随之调用当前HTTP上下文的MapHttpHandler()对得到的HttpHandler对象实施映射,那么此HttpHandler将最终接管当前请求的处理。

Controller激活

对于MvcHandler来说或,但它被用来处理当前请求的时候,会利用RouteData对象得到目标的名称,并借助于注册的ControllerFactory激活对应的Controller对象。目标Controller被激活之后,它的Execute()MvcHandler调用。

如果被激活的Controller对象的类型是ControllerBase的子类,但它的Execute()被执行的时候,它会调用ActionInvoker对象的InvokeAction()方法来执行目标Action()并对当前请求予以响应。

Action执行

默认采用的ActionInvoker是一个ControllerActionInvoker对象,但它的InvokerAction()方法被执行的时候,它会利用注册的ModelBinder采用Model绑定的方式生成目标Action()方法的参数列表,并利用ActionExecutor对象以“表达式树”的方式执行目标Action()方法。

目标Action()方法执行之后总是会返回一个ActionResult,对于返回类型不是ActionResultAction()方法来说,ASP.NET MVC总是会将执行的结果转换成一个ActionResult对象。ControllerActionInvoker会通过执行此ActionResult对象来对请求做最终的响应。

路由匹配

对于ASP.NET MVC应用来说,针对HTTP请求的实现在目标Controller类型的某个Action方法中,每个HTTP请求不再像ASP.NET WebForms应用一样是针对一个物理文件,而是针对某个Controller的某个Action方法。

目标Controlller和Action的名称由HTTP请求的URL来决定,但ASP.NET MVC接收到抵达的请求后,其首要任务是通过当前HTTP请求的解析得到目标Controller和Action的名称,这个过程是通过ASP.NET MVC的路由系统来实现的。

RouteDictionary

RouteDictionary表示一个具名的Route对象的列表,继承自泛型的字典类型Dictionary,其中Key表示Route对象的注册名称。在GetRouteData方法中,遍历集合找到指定的HttpContextBase对象匹配的Route对象,并得到对应的RouteData。

using System.Collections.Generic;
using System.Web;

namespace MiniMVC.Framework
{
    public class RouteDictionary : Dictionary
    {
        public RouteData GetRouteData(HttpContextBase httpContext)
        {
            foreach(var route in this.Values)
            {
                RouteData routeData = route.GetRouteData(httpContext);
                if(routeData != null)
                {
                    return routeData;
                }
            }
            return null;
        }
    }
}

RouteTable

ASP.NET定义了一个全局的路由表,路由表中的每个Route对象包含了一个路由模板。目标Controller和Action的名称可通过路由变量以占位符的形式定义在路由模板中,也可以作为路由对象的默认值。

namespace MiniMVC.Framework
{
    // 全局路由表
    public class RouteTable
    {
        //路由模板,目标Controller和Action的名称通过变量以占位符的形式定义在路由模板中
        public static RouteDictionary Routes { get; private set; }

        static RouteTable()
        {
            Routes = new RouteDictionary();
        }
    }
}

一个Web应用可采用多种不同的URL模式,所以需要注册多个继承自RouteBase的Route对象,多个Route对象组成了一个路由表。路由表通过类型RouteTable表示,RouteTable仅仅具有一个类型为RouteDictionary的Routes属性表示针对整个Web引用的全局路由表。

RequestContext

RequestContext表示当前HTTP请求的上下文,其核心就是对当前HttpContext和RouteData的封装。

using System.Web;

namespace MiniMVC.Framework
{
    public class RequestContext
    {
        public virtual HttpContextBase HttpContext { get; set; }
        public virtual RouteData RouteData { get; set; }
    }
}

IRouteHandler

ASP.NET MVC本质上是由两个自定义的ASP.NET组建来实现的,一个是自定义的HttpModule,另一个是自定义的HttpHandler。HttpHandler从RouteData对象的RouteHandler属性获得。RouteData的RouteHandler属性类型为IRouteHandler接口。IRouteHandler接口具有唯一的GetHttpHandler方法返回真正用于处理HTTP请求的HttpHandler对象。

using System.Web;
using System.Web.Routing;

namespace MiniMVC.Framework
{
    public interface IRouterHandler
    {
        IHttpHandler GetHttpHandler(RequestContext requestContext);
    }
}

IRouteHandler接口的GetHttpHandler方法具有一个类型为RequestContext的参数。顾名思义,RequestContext表示当前HTTP请求的上下文,其核心就是对当前HttpContext和RouteData的封装。

RouteBase

承载路由变量的RouteData对象由路由表中与当前请求相匹配的Route对象生成,可通过RouteData的Route属性获得这个Route对象,该属性的类型为RouteBase。

RouteBase是一个抽象类,仅仅包含一个返回类型为RouteData的GetRouteData方法。RouteBase的GetRouteData方法具有一个类型为HttpContextBase的参数,代表针对当前接收请求的HTTP上下文。

GetRouteData方法被执行的时候,会判断自身定义的路由规则是否与当前请求相匹配,并在成功匹配的情况下实施路由解析,并将路由变量封装成RouteData对象返回。如果路由规则与当前请求不匹配,则该方法直接返回Null。

using System.Web;
using System.Web.Routing;

namespace MiniMVC.Framework
{
    /*
     * 承载路由变量的RouteData对象
     * 由路由表中与当前请求相匹配的Route对象生成
     * 可通过RouteData的Route属性获得这个Route对象
     * Route属性的类型为RouteBase
     */
    public abstract class RouteBase
    {
        // HttpContextBase代表针对当前接收请求的HTTP上下文
        // 该方法被执行时会判断自身定义的路由规则是否与当前请求相匹配,并在匹配成功的情况下实施路由解析,将得到的路由变量封装成RouteData对象返回。
        public abstract RouteData GetRouteData(HttpContextBase httpContext);
    }
}

RouteData

对于每个抵达的HTTP请求,路由系统会遍历路由表并找到一个具有与当前URL模式相匹配的Route对象,然后利用它解析出以Controller和Action名称为核心的路由数据。

RouteData 定义了两个字典类型的属性Values和DataTokens,他们代表具有不同来源的路由变量,前者是由对请求URL实施路由解析获得。表示Controller和Action名称的属性直接从Values属性表示的字典中提取,对应的Key分别为“controller”和“action”。

using System.Collections.Generic;
using System.Web.Routing;

namespace MiniMVC.Framework
{
    public class RouteData
    {
        // 代表具有不同来源的路由变量:由对请求URL实施路由解析获得,表示Controller和Action名称的属性ControllerName和ActionName
        // 直接从Values属性表示的字典中提取,对应的Key分别为controller和action
        public IDictionary Values { get; private set; }
        // 代表具有不同来源的路由变量 :从RouteData对象的RouteHandler属性获得,RouteData的RouteHandler属性类型为IRouteHandler接口
        public IDictionary DataTokens { get; private set; }

        public IRouteHandler RouteHandler { get; set; }
        public RouteBase Route { get; set; }
        public RouteData()
        {
            this.Values = new Dictionary();
            this.DataTokens = new Dictionary();
            this.DataTokens.Add("namespaces", new List());
        }
        public string ControllerName
        {
            get
            {
                object controllerName = string.Empty;

                this.Values.TryGetValue("controller", out controllerName);

                return controllerName.ToString();
            }
        }
        public string ActionName
        {
            get
            {
                object actionName = string.Empty;

                this.Values.TryGetValue("action", out actionName);

                return actionName.ToString();
            }
        }
    }
}

你可能感兴趣的:(ASP.NET MiniMVC)