zoukankan      html  css  js  c++  java
  • [ASP.NET MVC]视图是如何呈现的

    为了搞清楚ASP.NET MVC的请求过程,我们计划从结果追踪到源头。使用VS2012创建一个空白的ASP.NET MVC项目

    然后创建一个HelloController

    创建一个HelloView。在Views文件夹下创建一个Hello的文件夹,然后创建一个名为Index的View

    然后再view中输入hello asp.net mvc4

    点击App_Start下的RouteConfig.cs,更改Default的路由的Controller为Hello

    OK,点击调适按钮,你可以得到如下结果:

     

    OK.我们就从这里出发,开始分析用户的请求是如何被处理的。首先我们再来看HelloController的代码

    恩,我们就从这里开始分析吧。

    (1)我们可以看到Index()返回的是一个View()。这个View()来自基类

    protectedinternalViewResult View()

    {

    return View(viewName: null, masterName: null, model: null);

    }

     

    (2) 我们继续找到对应的方法:

    protectedinternalvirtualViewResult View(string viewName, string masterName, object model)

    {

    if (model != null)

    {

    ViewData.Model = model;

    }

     

    returnnewViewResult

    {

    ViewName = viewName,

    MasterName = masterName,

    ViewData = ViewData,

    TempData = TempData,

    ViewEngineCollection = ViewEngineCollection

    };

    }

     

    通过C#4.0的方式,创建了一个ViewResult对象。通过查看ViewResult类,我们可以发现其继承了ViewResultBase类,我们可以用UML来表示出上面5个属性在两个类之间的关系

    (3) 再看第一步,传入的三个参数都为null,那么在创建ViewResult对象时,其他的几个属性ViewData,TempData和ViewEngineCollection的值是从哪里来的呢?

    通过上面的图,我们知道,这三个属性均继承自ViewResultBase类,因此我们去分析ViewResultBase类。

    ViewData

    get

    {

    if (_viewData == null)

    {

    _viewData = newViewDataDictionary();

    }

    return _viewData;

    }

    set { _viewData = value; }

    TempData

    get

    {

    if (_tempData == null)

    {

    _tempData = newTempDataDictionary();

    }

    return _tempData;

    }

    set { _tempData = value; }

    ViewEngineCollection

    get { return _viewEngineCollection ?? ViewEngines.Engines; }

    set { _viewEngineCollection = value; }

     

    到目前为止,我们并没有看到任何代码调用这些属性的Set方法,那么我们推断这三个属性的返回值分别是

    ViewData

    newViewDataDictionary();

    TempData

    newTempDataDictionary();

    ViewEngineCollection

    ViewEngines.Engines;

     

    为了验证我们的推断,我们可以通过调试来验证。

    恩,这下明确了。没有问题。那么我们现在需要知道ViewEngineCollection这个属性是如何赋值的。

    (4) 我们查看ViewEngines这个类的代码

    publicstaticclassViewEngines

    {

    privatestaticreadonlyViewEngineCollection _engines = newViewEngineCollection

    {

    newWebFormViewEngine(),

    newRazorViewEngine(),

    };

     

    publicstaticViewEngineCollection Engines

    {

    get { return _engines; }

    }

    }

    原来如此,原来如此,ViewEngine是一个静态类。仅仅包含了一个静态的构造函数一个静态的属性。返回一个ViewEngineCollection。那么ASP.NET MVS怎么就知道该使用哪个ViewEngine呢?

    在RouteConfig.cs文件中,我们注册了默认的Route

    routes.MapRoute(

    name: "Default",

    url: "{controller}/{action}/{id}",

    defaults: new { controller = "Hello", action = "Index", id = UrlParameter.Optional }

    );

     

    因此,系统可以知道当前的Controller为HelloController,Action为Index。ASP.NET MVC Framework使用ControllerFactory创建Controller实例,然后通过ControllerActionInvoker通过反射的方式把Action转化为HelloController类Index方法的调用,最后调用ViewResultBase的ExecuteResult方法,把方法返回的结果传递给对应的View,并在该View中把最终结果呈现给用户。

    这个过程相当复杂,设计众多的类。我们先来看一下概览图,我只列举了重要的类,

     

    这几个类的生命周期应该是这样子的:

    OK,下面我们来详细叙述一下整个过程,并加以代码分析

    1)ControllerActionInvoker的InvokeAction被调用。那么它被谁调用呢,通过类图,我们知道,InvokeAction方法是实现了IActionVoker接口,我们通过调用关系图,可以知道该方法是Controller.cs的ExecuteCore方法调用

    protectedoverridevoid ExecuteCore()

    {

    // If code in this method needs to be updated, please also check the BeginExecuteCore() and

    // EndExecuteCore() methods of AsyncController to see if that code also must be updated.

     

    PossiblyLoadTempData();

    try

    {

    string actionName = RouteData.GetRequiredString("action");

    if (!ActionInvoker.InvokeAction(ControllerContext, actionName))

    {

    HandleUnknownAction(actionName);

    }

    }

    finally

    {

    PossiblySaveTempData();

    }

    }

     

    而Controller类继承了ControllerBase类,ControllerBase类的Execute方法调用了Controll的ExecuteCore方法

    protectedvirtualvoid Execute(RequestContext requestContext)

    {

    if (requestContext == null)

    {

    thrownewArgumentNullException("requestContext");

    }

    if (requestContext.HttpContext == null)

    {

    thrownewArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");

    }

     

    VerifyExecuteCalledOnce();

    Initialize(requestContext);

     

    using (ScopeStorage.CreateTransientScope())

    {

    ExecuteCore();

    }

    }

     

    并且,ControllerBase的Execute方法实现了IController接口,此方法被MvcHandler类处理请求时调用

    protectedinternalvirtualvoid ProcessRequest(HttpContextBase httpContext)

    {

    SecurityUtil.ProcessInApplicationTrust(() =>

    {

    IController controller;

    IControllerFactory factory;

    ProcessRequestInit(httpContext, out controller, out factory);

     

    try

    {

    controller.Execute(RequestContext);

    }

    finally

    {

    factory.ReleaseController(controller);

    }

    });

    }

    OK,我们就到这里吧,关于用户的请求如何到MvcHandler这里,我将另外写一篇文章来介绍。当然,园子里很多同学也已经写了很多,大家可以去查阅,比如:…..

    2)下面,我们来看一下ControllerActionInvkoer类的InvokeAction方法具体做了哪些事情

    publicvirtualbool InvokeAction(ControllerContext controllerContext, string actionName)

    {

    if (controllerContext == null)

    {

    thrownewArgumentNullException("controllerContext");

    }

    if (String.IsNullOrEmpty(actionName))

    {

    thrownewArgumentException(MvcResources.Common_NullOrEmpty, "actionName");

    }

     

    ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);

    ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);

    if (actionDescriptor != null)

    {

    FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);

     

    try

    {

    AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);

    if (authContext.Result != null)

    {

    // the auth filter signaled that we should let it short-circuit the request

    InvokeActionResult(controllerContext, authContext.Result);

    }

    else

    {

    if (controllerContext.Controller.ValidateRequest)

    {

    ValidateRequest(controllerContext);

    }

     

    IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);

    ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);

    InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);

    }

    }

    catch (ThreadAbortException)

    {

    // This type of exception occurs as a result of Response.Redirect(), but we special-case so that

    // the filters don't see this as an error.

    throw;

    }

    catch (Exception ex)

    {

    // something blew up, so execute the exception filters

    ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);

    if (!exceptionContext.ExceptionHandled)

    {

    throw;

    }

    InvokeActionResult(controllerContext, exceptionContext.Result);

    }

     

    returntrue;

    }

     

    // notify controller that no method matched

    returnfalse;

    }

    最重要的代码,我用黄色highlights出来了。下面我们来分析一下这三行代码

    2)A 检查Action是否有Authroization特性,如果有进行验证。验证的具体代码如下

    if (AuthorizeCore(filterContext.HttpContext))

    {

    HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;

    cachePolicy.SetProxyMaxAge(newTimeSpan(0));

    cachePolicy.AddValidationCallback(CacheValidateHandler, null/* data */);

    }

    else

    {

    HandleUnauthorizedRequest(filterContext);

    }

     

    如果验证成功,那么AuthorizationContext的Result属性为NULL,否则返回newHttpUnauthorizedResult();HttpUnauthorizedResult继承HttpStatusCodeResult,而HttpStatusCodeResult继承ActionResult。如果验证失败,那么调用InvokeActionResult方法。

    protectedvirtualvoid InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)

    {

    actionResult.ExecuteResult(controllerContext);

    }

     

    可见,InvokeActionResult方法,实际调用ActionResultExecuteResult方法。在当前的情况下(验证失败),此时的authContext.Result的具体类型是HttpUnauthorizedResult,它继承了HttpStatusCodeResult, 所以InvokeActionResult进入的是HttpStatusCodeResult类的ExecuteResult方法

    publicoverridevoid ExecuteResult(ControllerContext context)

    {

    if (context == null)

    {

    thrownewArgumentNullException("context");

    }

     

    context.HttpContext.Response.StatusCode = StatusCode;

    if (StatusDescription != null)

    {

    context.HttpContext.Response.StatusDescription = StatusDescription;

    }

    }

     

    设置完StatusCode和StatusDescription之后,将直接返回,不会寻找对应的View。

     

    2)B 如果不涉及验证,或者验证成功。那么首先获取action的参数IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor); 比如我们当前的请求为/Hello/Index/2013
    那么parameters的值为[id, 2013].请注意这个和你注册RouteData的格式相关联。如果你RouteData注册为为{controlller}/{action}/{no},那么parameters的值为[no,2013]

    接着,获取postActionContext对象ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);该方法包括两个主要的步骤:其一为创建ActionExecutedContext,其二为调用与Action对应的Controller的方法,并将方法的结果保存在ActionResult之上。这个ActionResult的具体类型可能是ContentResultViewResult,或者JsonResult等等继承了ActionResult的各个子类。

    为了模拟这个过程,我们创建如下两行代码模拟上面过程的执行结果:

    // 返回ContentResult

    ActionResult result1 = CreateActionResult(ControllerContext, actionDescriptor, "123");

    // 返回ViewResult

    ActionResult result2 = CreateActionResult(ControllerContext, actionDescriptor, View());

     

    OK,如果是ContentResult,那么它的ExecuteResult方法如下

    publicoverridevoid ExecuteResult(ControllerContext context)

    {

    if (context == null)

    {

    thrownewArgumentNullException("context");

    }

     

    HttpResponseBase response = context.HttpContext.Response;

     

    if (!String.IsNullOrEmpty(ContentType))

    {

    response.ContentType = ContentType;

    }

    if (ContentEncoding != null)

    {

    response.ContentEncoding = ContentEncoding;

    }

    if (Content != null)

    {

    response.Write(Content);

    }

    }

     

    可见,直接把内容输出到浏览器

    如果是ViewResult,那么首先调用基类ViewResultBase的ExecuteResult方法

    publicoverridevoid ExecuteResult(ControllerContext context)

    {

    if (context == null)

    {

    thrownewArgumentNullException("context");

    }

    if (String.IsNullOrEmpty(ViewName))

    {

    ViewName = context.RouteData.GetRequiredString("action");

    }

     

    ViewEngineResult result = null;

     

    if (View == null)

    {

    result = FindView(context);

    View = result.View;

    }

     

    TextWriter writer = context.HttpContext.Response.Output;

    ViewContext viewContext = newViewContext(context, View, ViewData, TempData, writer);

    View.Render(viewContext, writer);

     

    if (result != null)

    {

    result.ViewEngine.ReleaseView(context, View);

    }

    }

     

    该方法调用ViewResult的FindView方法

    protectedoverrideViewEngineResult FindView(ControllerContext context)

    {

    ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName);

    if (result.View != null)

    {

    return result;

    }

     

    // we need to generate an exception containing all the locations we searched

    StringBuilder locationsText = newStringBuilder();

    foreach (string location in result.SearchedLocations)

    {

    locationsText.AppendLine();

    locationsText.Append(location);

    }

    thrownewInvalidOperationException(String.Format(CultureInfo.CurrentCulture,

    MvcResources.Common_ViewNotFound, ViewName, locationsText));

    }

     

    自此,Controller的执行结果与View建立关联,最后ASP.NET MVC Framework把结果通过对应的视图显示到用户的浏览器中

    FindView包含三个参数:context这个是ControllerContext;第二个是ViewName,它的值为ViewName = context.RouteData.GetRequiredString("action"),其实就是Action的值;第三个是MasterName,我们的例子中为空。

     

    那么如何找到WebFormView呢?

    我们在HomeController中创建一个List方法,并在View/Hello文件夹下创建List.aspx文件

    publicActionResult List()

    {

    return View();

    }

     

    然后执行调适:

     

    由于当前默认的请求是/Hello/Index,因此MVC Framework会自动寻找
    ~/Views/Hello/Index.cshtml

    ~/Views/Hello/Index.vbhtml

    ~/Views/Shared/Index.cshtml

    ~/Views/Shared/Index.cshtml

    实际上,Razor引擎不会真正的在硬盘上寻找上面的文件,因为,这些文件都已经编译成C#的类。所以Razor在编译后的类中寻找对应的视图。

     

     

     

    Next

    1. How the controller is initialized?
    2. How the request comes to MvcHandler
    3. View, IView, ViewEngine, RazorViewEngile, RazorView

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    大多人应该都知道用户向IIS发送一个ASP.NET请求后,IIS处理请求并向用户返回对应的结果。也有人知道,当一个ASP.NET请求到达IIS后,进入CLR,然后由HttpApplication创建HttpContext并找到对应的HttpHandler处理请求。最后把结果返回到用户端。那么从进入CLR之后,一个ASP.NET请求的生命周期具体是怎么样的,要经历那些重要的对象呢? 本文主要介绍这两个方面。

    ASP.NET Request Liftcycle

     

     

     

    首先,我们来看看一个request在进入CLR之前,发生了什么?

    在IIS6和IIS7中,所有的HTTP请求均由HTTP侦听器捕获。那么HTTP侦听器是什么,其实就是http.sys,它运行在内核级别。至于具体什么是内核模块,以及如何运行,超出本文的范畴。如果你有兴趣,请自己查阅MSDN.

    HTTP侦听器把捕获到的HTTP请求放到对应的应用程序池的请求队列中。所谓应用程序池

     

    HttpRuntime, HttpApplicationFactory, HttpApplication,HttpContext, HttpHandler, HttpModule

     

    动态编译

  • 相关阅读:
    卸载软件
    SourceTree安装
    lombok安装及使用
    Element学习使用
    Vue基础学习
    SpringBoot与Swagger整合
    @Valid 数据校验 + 自定义全局异常信息
    Spring Boot 运行原理
    JNDI学习总结(一):JNDI到底是什么?
    Spring Boot AOP之对请求的参数入参与返回结果进行拦截处理
  • 原文地址:https://www.cnblogs.com/yang_sy/p/ASPNET_MVC_VIEW_INNER.html
Copyright © 2011-2022 走看看