前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ASP.Net请求处理机制初步探索之旅 - Part 5 ASP.Net MVC请求处理流程

ASP.Net请求处理机制初步探索之旅 - Part 5 ASP.Net MVC请求处理流程

作者头像
Edison Zhou
发布于 2018-08-20 06:31:09
发布于 2018-08-20 06:31:09
1.2K00
代码可运行
举报
文章被收录于专栏:EdisonTalkEdisonTalk
运行总次数:0
代码可运行

开篇:上一篇我们了解了在WebForm模式下一个Page页面的生命周期,它经历了初始化Init、加载Load以及呈现Render三个重要阶段,其中构造了页面控件树,并对页面控件树进行了大量的递归操作,最后将与模板结合生成的HTML返回给了浏览器。那么,在ASP.NET MVC模式下,一个页面的生命周期又经历了哪些步凑呢?别急,本篇漫漫道来!

一、开放的ASP.NET MVC代码

2009年,Microsoft推出了ASP.NET MVC,也将ASP.NET MVC项目作为开源项目推送到了开源社区中,至今时间也过去快6年了,ASP.NET MVC已经到了5.0的版本阶段了。我们看到ASP.NET MVC从一个不完整的小孩长成一个日渐成熟的巨人,我们可以从开源社区找到ASP.NET MVC的源码,相比之前我们需要Reflector进行反编译查看,这次则轻松得多。

  这里我们选择ASP.NET MVC 4的源码作为分析对象,我已经将其上传到了网盘中,你可以通过下面这个地址进行下载:

  传送门:http://pan.baidu.com/s/1bnF8ZPt

  下载完成后,打开ASP.NET MVC 4的源代码,你会看到如下解决方案:这里我们主要关注System.Web.Mvc这个类库项目

二、从MvcHandler.ProcessRequest开始

从Part 3中我们知道了在请求处理管道中的第7个事件生成了MvcHandler,在第11和第12个事件之间调用了MvcHandler的ProcessRequest方法开始了ASP.NET MVC的处理响应之旅。那么,我们就从MvcHandler的ProcessRequest方法开始查看,一个ASP.NET MVC页面是如何加载出来一个HTML页的!

(1)Controller的激活

  ①借助HttpConetxtWrapper封装HttpContext

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    protected virtual void ProcessRequest(HttpContext httpContext)
    {
          HttpContextBase httpContextBase = new HttpContextWrapper(httpContext);
          ProcessRequest(httpContextBase);
     }

  可以看出,这里通过了一个基于包装器(又称装饰者)模式实现的一个HttpContextWrapper类对HttpContext进行了一个封装,并调用重载的另一个ProcessRequest方法进行继续处理。

PS:有关ASP.NET MVC中HttpContext, HttpContextBase, HttpContextWrapper三者之间的联系请参考:http://blog.csdn.net/sundacheng1989/article/details/10551091

  ②控制器工厂根据URL创建控制器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        protected internal virtual void ProcessRequest(HttpContextBase httpContext)
        {
            IController controller;
            IControllerFactory factory;
            ProcessRequestInit(httpContext, out controller, out factory);

            try
            {
                controller.Execute(RequestContext);
            }
            finally
            {
                factory.ReleaseController(controller);
            }
        }

  可以看出,这里通过调用ProcessRequestInit方法将上下文对象传入进行处理,然后返回生成的控制器实例以及控制器工厂。因此,我们转入ProcessRequestInit方法看看:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory)
        {
            ......
            string controllerName = RequestContext.RouteData.GetRequiredString("controller");
            factory = ControllerBuilder.GetControllerFactory();
            controller = factory.CreateController(RequestContext, controllerName);
            ......
        }

  在这个方法中,首先根据RouteData路由数据取得要请求的Controller名称,然后取得ControllerFactory(控制器工厂)对象,通过ControllerFactory来创建指定名称的控制器,最后将控制器作为out参数传递出去。

  ③调用控制器的Execute方法进入Action

  具体实现了IController接口的Controller对象通过调用Excute方法开始执行具体的Action,那么Action究竟又是怎样被触发的呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public interface IController
    {
        void Execute(RequestContext requestContext);
    }

(2)Action的触发

  ①从ControllerBase的Excute方法开始 

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public abstract class ControllerBase : IController
    {
        protected virtual void Execute(RequestContext requestContext)
        {
            if (requestContext == null)
            {
                throw new ArgumentNullException("requestContext");
            }
            if (requestContext.HttpContext == null)
            {
                throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");
            }

            VerifyExecuteCalledOnce();
            Initialize(requestContext);

            using (ScopeStorage.CreateTransientScope())
            {
                ExecuteCore();
            }
        }
     // 抽象方法-让Controller去具体实现
        protected abstract void ExecuteCore();
    }

  首先,Controller并没有实现IController接口,而是Controller的基类ControllerBase实现了IController接口;然后,ControllerBase中定义了一个抽象方法ExcuteCore,让其子类去具体执行,这里主要是让Controller类对象执行这个方法。

  ②根据URL获取Action名称并准备触发Action

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public abstract class Controller : ControllerBase, IActionFilter, IAuthenticationFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IAsyncManagerContainer
    {
        protected override void ExecuteCore()
        {
            PossiblyLoadTempData();
            try
            {
                string actionName = GetActionName(RouteData);
                if (!ActionInvoker.InvokeAction(ControllerContext, actionName))
                {
                    HandleUnknownAction(actionName);
                }
            }
            finally
            {
                PossiblySaveTempData();
            }
        }
    }

  首先,通过路由数据获取Action名称,例如请求URL为:http://xxx.com/Home/Index,这里获取的Action名称即为Index。然后,通过ActionInvoker.InvokeAction去执行具体的Action。那么问题来了,这个ActionInvoker又是啥东东?我们先看看这个接口的定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public interface IActionInvoker
    {
        bool InvokeAction(ControllerContext controllerContext, string actionName);
    }

  通过查阅资料,我们发现原来是一个叫做ControllerActionInvoker的类实现了IActionInvoker接口,那么我们就去看看这个ControllerActionInvoker类吧。

  ③获取Controller与Action的描述信息和过滤器信息

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
    {
        ......
        ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
        ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
        if (actionDescriptor != null)
        {
           FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);
           ......
        }   
        ......
    }

  看到这里,也许会有人问什么是描述信息?那么看到我们在开发中经常给Controller或者Action添加的Attribute信息也许就不会感到陌生了:例如我们给某个名为Index的Action添加了[HttpPost]或者[HttpGet]特性,在请求时需要通过HTTP报文请求方式来区分这两个Action。

  那么,什么又是过滤器信息?首先,过滤器涉及到一个叫做AOP(面向切面编程)的概念,我们可以通过前面的请求处理管道进行理解,虽然我们的ASP.NET页面请求处理部分只是其中一小部分,但是在这部分执行之前还经历了许多事件,在这之后又经历了许多事件,而这些事件都是可以自定义逻辑的,它们都可以叫做过滤器。ASP.NET MVC默认为我们提供了四种类型的过滤器(Filter),如下图所示:

PS:对过滤器不熟悉的朋友可以看看我的另一篇对ASP.NET MVC基础知识中的过滤器(Filter)的介绍:http://www.cnblogs.com/edisonchou/p/3932640.html

  ④获取参数信息并开始真正执行Action:Filter->Action->Filter

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
    {
        ......
        IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
        ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
        ......
    }

  通过上面所获取的各种描述信息与过滤器信息找到Action并获取所需的参数,然后调用InvokeActionMethodWithFilters方法执行Action。因此,再转到InvokeActionMethodWithFilters方法看看:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        protected virtual ActionExecutedContext InvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
        {
            ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters);
            Func<ActionExecutedContext> continuation = () =>
                new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */)
                {
                     Result = InvokeActionMethod(controllerContext, actionDescriptor, parameters)
                };

            Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate(continuation,
                                                                            (next, filter) => () => InvokeActionMethodFilter(filter, preContext, next));
            return thunk();
        }

  在这个方法中,首先将上下文对象、描述信息、参数信息传入InvokeActionMethod方法中,得到了一个Result对象。这个Result对象又是什么?转到定义一看,原来不就是我们在开发中经常返回的ActionResult类型吗?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public ActionResult Result
    {
        get { return _result ?? EmptyResult.Instance; }
        set { _result = value; }
    }

  那么,在InvokeActionMethod方法中又是如何返回Result的呢?再次转到定义看看:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        protected virtual ActionResult InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
        {
            object returnValue = actionDescriptor.Execute(controllerContext, parameters);
            ActionResult result = CreateActionResult(controllerContext, actionDescriptor, returnValue);
            return result;
        }

  在这个方法中,首先执行了指定的Action,然后获得了一个returnValue返回值,通过传入返回值创建具体类型的ActionResult作为方法的返回值。这里需要注意的是,ActionResult是一个抽象类,像什么JsonResult、EmptyResult、ViewResult等都是其子类,而这里的CreateActionResult就是要创建其具体子类的实例并返回。

  现在将目光返回到InvokeActionMethodWithFilters方法中,看到代码最后声明了一个委托thunk,它是过滤器结合经过反转之后再合并之前声明的委托continuation之后的一个新委托(它所持有的委托链顺序会协调一致),目的是为了完成AOP的效果,比如首先要执行Action执行之前的过滤器,才能执行Action方法。

  ⑤ActionResult闪亮登场:Filter->Result

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
    {
        ......
        InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, challengeContext.Result ?? postActionContext.Result);
        ......
    }

  现在回到InvokeAction这个主方法中,刚刚执行完Action之后将结果都保存在了postActionContext中的Result中,现在继续执行过滤器(比如:可以对刚刚的Action结果进行一些处理),目的也是为了完成AOP的效果,比如执行完Action之后,必须要执行Action结束后的过滤器业务逻辑方法。那么,这里又是进行了什么操作呢?转到InvokeActionResultWithFilters方法中去看看:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    private ResultExecutedContext InvokeActionResultFilterRecursive(IList<IResultFilter> filters, int filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult)
    {
         ......
         if (filterIndex > filters.Count - 1)
         {
              InvokeActionResult(controllerContext, actionResult);
              return new ResultExecutedContext(controllerContext, actionResult, canceled: false, exception: null);
         }  
         
         IResultFilter filter = filters[filterIndex];
         filter.OnResultExecuting(preContext);  
         ......
         int nextFilterIndex = filterIndex + 1;
         postContext = InvokeActionResultFilterRecursive(filters, nextFilterIndex, preContext, controllerContext, actionResult);
         ......
    }

  首先,判断过滤器执行的序号是否已经到了最后,如果不是,则继续递归执行本方法调用过滤器(这里对应的过滤器是OnResultExecuting事件,即在Result被生成时之前进行触发)。如果到了最后,则开始生成最终的ActionResult。看看这个InvokeActionResult方法,它是一个虚方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        protected virtual void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
        {
            actionResult.ExecuteResult(controllerContext);
        }

(3)View的呈现

  我们知道ActionResult是一个抽象类,那么这个InvokeActionResult应该是由其之类来实现。于是,我们找到ViewResult,但是其并未直接继承于ActionResult,再找到其父类ViewResultBase,它则继承了ActionResult。于是,我们来查看它的ExecuteResult方法:

  ①约定大于配置的缘故

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public override void ExecuteResult(ControllerContext context)
        {
            ......
            if (String.IsNullOrEmpty(ViewName))
            {
                ViewName = context.RouteData.GetRequiredString("action");
            }
            ......
        }

  我们在日常开发中,总是被告知约定大于配置,View中的名字必须与Controller中Action的名字一致。在这了,我们知道了原因,可以看出,这里就是国通URL来取得ViewName然后去查找View的。

  ②找到ViewEngine视图引擎并获取ViewEngineResult

  首先,我们了解一下什么是ViewEngine视图引擎:我们在ASP.NET MVC开发中一般会有两个选择,一个是aspx视图引擎,另一个是ASP.NET MVC 3.0推出的Razor视图引擎。Razor视图引擎在减少代码冗余、增强代码可读性和Visual Studio智能感知方面,都有着突出的优势。因此,Razor一经推出就深受广大ASP.Net开发者的喜爱。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public override void ExecuteResult(ControllerContext context)
    {
        ......
        ViewEngineResult result = null;
        if (View == null)
        {
            result = FindView(context);
            View = result.View;
        }
        ......      
    }

   这里通过FindView方法获取到具体的View对象,而FindView又是ViewResultBase的一个抽象方法。这时,我们需要到ViewResult中去看看这个FindView方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        protected override ViewEngineResult FindView(ControllerContext context)
        {
            ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName);
            if (result.View != null)
            {
                return result;
            }
            ......
        }    

  这里通过在ViewEngineCollection视图引擎集合中调用FindView方法返回一个ViewEngineResult对象,而View则作为属性存在于这个ViewEngineResult对象之中。

  ③加载ViewData/TempData等数据生成ViewContext

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        protected override ViewEngineResult FindView(ControllerContext context)
        {
            ......
            TextWriter writer = context.HttpContext.Response.Output;
            ViewContext viewContext = new ViewContext(context, View, ViewData, TempData, writer);
            ......
        }    

  这里开始加载ViewData、TempData等数据生成ViewContext,可以在ViewContext的构造函数中看到如下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public ViewContext(ControllerContext controllerContext, IView view, ViewDataDictionary viewData, TempDataDictionary tempData, TextWriter writer)
            : base(controllerContext)
        {
            if (controllerContext == null)
            {
                throw new ArgumentNullException("controllerContext");
            }
            if (view == null)
            {
                throw new ArgumentNullException("view");
            }
            if (viewData == null)
            {
                throw new ArgumentNullException("viewData");
            }
            if (tempData == null)
            {
                throw new ArgumentNullException("tempData");
            }
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }

            View = view;
            ViewData = viewData;
            Writer = writer;
            TempData = tempData;
        }

  现在知道我们在Action方法中定义的那些ViewData或者TempData是在哪里被存入上下文了吧?

  ④开始Render:HTML页面的呈现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        protected override ViewEngineResult FindView(ControllerContext context)
        {
            ......
            View.Render(viewContext, writer);
            ......
        }    

  ViewContext上下文对象已生成好,TextWriter已经拿到,现在就开始对View进行正式的呈现了,也就是返回给浏览器端请求的HTML。由于这里View对象是一个实现了IView接口的类对象,于是我们找到RazorView,但是它并未直接实现IView接口,于是我们找到它的父类BuildManagerCompiledView 

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public abstract class BuildManagerCompiledView : IView
    {
        public virtual void Render(ViewContext viewContext, TextWriter writer)
        {
            if (viewContext == null)
            {
                throw new ArgumentNullException("viewContext");
            }

            object instance = null;

            Type type = BuildManager.GetCompiledType(ViewPath);
            if (type != null)
            {
                instance = ViewPageActivator.Create(_controllerContext, type);
            }

            if (instance == null)
            {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        MvcResources.CshtmlView_ViewCouldNotBeCreated,
                        ViewPath));
            }

            RenderView(viewContext, writer, instance);
        }
    }

  首先,通过ViewPath获取View的类型(Type),这里也是通过BuildManger来完成的,每个cshtml都会被asp.net编译成一个类。然后,通过反射生成了View的具体实例。最后,通过RendView方法进行下一步的呈现工作。RenderView是一个抽象方法,具体实现是在RazorView类或WebFormView类中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance)
        {
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }

            WebViewPage webViewPage = instance as WebViewPage;
            if (webViewPage == null)
            {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        MvcResources.CshtmlView_WrongViewBase,
                        ViewPath));
            }

            webViewPage.OverridenLayoutPath = LayoutPath;
            webViewPage.VirtualPath = ViewPath;
            webViewPage.ViewContext = viewContext;
            webViewPage.ViewData = viewContext.ViewData;

            webViewPage.InitHelpers();

            if (VirtualPathFactory != null)
            {
                webViewPage.VirtualPathFactory = VirtualPathFactory;
            }
            if (DisplayModeProvider != null)
            {
                webViewPage.DisplayModeProvider = DisplayModeProvider;
            }

            WebPageRenderingBase startPage = null;
            if (RunViewStartPages)
            {
                startPage = StartPageLookup(webViewPage, RazorViewEngine.ViewStartFileName, ViewStartFileExtensions);
            }
            webViewPage.ExecutePageHierarchy(new WebPageContext(context: viewContext.HttpContext, page: null, model: null), writer, startPage);
        }

  在此方法中,首先将传递过来的实例转换成了一个WebViewPage类的实例,然后将ViewContext、ViewData等数据赋给WebViewPage实例作为属性,以便在View中获取。然后,如果有开始页则先执行开始页。最后,将HttpContext、Page与Model对象封装为一个WebPageContext对象传入ExecutePageHierarchy方法中进行执行页面的渲染。

  首先,我们从字面上来看,Hierarchy代表层次,那么方法名的意思大概是:根据层次执行页面。那么,什么是页面的层次?

  在执行ExecutePageHierachy这个方法来渲染View时,这个方法里面要完成相当多的工作,主要是ViewStart的执行,和Layout的执行。这里的困难之处在于对于有Layout的页面来说,Layout的内容是先输出的,然后是RenderBody内的内容,最后还是Layout的内容。如果仅仅是这样的话,只要初始化一个TextWriter,按部就班的往里面写东西就可以了,但是实际上,Layout并不能首先执行,而应该是View的代码先执行,这样的话View就有可能进行必要的初始化,供Layout使用。例如我们有如下的一个View:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@{
    ViewBag.Title = "Code in View";
    Layout = "_LayoutPage1.cshtml";
}

  这个Layout的内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@{ 
    Layout = "~/Views/Shared/_Layout.cshtml";
    ViewBag.ToView = "Data from Layout";
}
<div>
    Data In View: @ViewBag.Title
</div>
<div>
    @RenderBody();    
</div>

  这样可以在页面显示Code in View字样。 但是反过来,如果试图在View中显示在Layout里面的"Data from Layout" 则是行不通的,什么也不会被显示。所以RenderBody是先于Layout中其他代码执行的,这种Layout的结构称为 Page Hierachy

  在这样的代码执行顺序下,还要实现文本输出的顺序,因此asp.net mvc这里的实现中就使用了栈,这个栈是OutputStack,里面压入了TextWriter。注意到这只是一个页面的处理过程,一个页面之中还会有Partial View 和 Action等,这些的处理方式都是一样的,因此还需要一个栈来记录处理到了哪个(子)页面,因此还有一个栈,称之为TemplateStack,里面压入的是PageContext,PageContext维护了view的必要信息,比如Model之类的,当然也包括上面提到的OutputStack。有了上面的基本信息,下面看代码,先看入口点:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public void ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) 
        {
            PushContext(pageContext, writer);
            if (startPage != null) {
                if (startPage != this) {
                    var startPageContext = Util.CreateNestedPageContext<object>(parentContext: pageContext, pageData: null, model: null, isLayoutPage: false);
                    startPageContext.Page = startPage;
                    startPage.PageContext = startPageContext;
                }
                startPage.ExecutePageHierarchy();
            }
            else {
                ExecutePageHierarchy();
            }
            PopContext();
        }

  这个方法中,第一步首先将pageContext入栈:PushContext

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public void PushContext(WebPageContext pageContext, TextWriter writer)
        {
            _currentWriter = writer;
            PageContext = pageContext;
            pageContext.Page = this;

            InitializePage();

            // Create a temporary writer
            _tempWriter = new StringWriter(CultureInfo.InvariantCulture);

            // Render the page into it
            OutputStack.Push(_tempWriter);
            SectionWritersStack.Push(new Dictionary<string, SectionWriter>(StringComparer.OrdinalIgnoreCase));

            // If the body is defined in the ViewData, remove it and store it on the instance
            // so that it won't affect rendering of partial pages when they call VerifyRenderedBodyOrSections
            if (PageContext.BodyAction != null)
            {
                _body = PageContext.BodyAction;
                PageContext.BodyAction = null;
            }
        }

第二步判断是否存在ViewStart文件,如果有,就执行startPage.ExecutePageHierachy()。如果不存在,则直接执行ExecutePageHierachy()

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public override void ExecutePageHierarchy()
    {
           ......
           TemplateStack.Push(Context, this);
           try
           {
               // Execute the developer-written code of the WebPage
               Execute();
           }
           finally
           {
               TemplateStack.Pop(Context);
           }        
    }    

  这个方法就是将context压栈,然后执行相应的view的代码,然后出栈。有了这些出入栈的操作,可以保证View的代码,也就是Execute的时候的writer是正确的。Execute中的方法除去PartialView,Action之类的,最终调用的是WebPageBase中的WriteLiteral方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public override void WriteLiteral(object value)
        {
            Output.Write(value);
        }

  这里的Output属性是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        public TextWriter Output
        {
            get 
            { 
                return OutputStack.Peek(); 
            }
        }

  在调用了Excute方法后,页面上的HTML内容基本输出完毕,至此View就渲染完毕了

第三步,pageContext出栈,主要是栈中的元素的清理工作。

三、一图胜千言,总体上概览

参考资料

致谢:本文参阅了大量园友的相关文章,向以下文章作者表示感谢!

(1)Darren Ji,《ASP.NET MVC请求处理管道声明周期的19个关键环节》:http://www.cnblogs.com/darrenji/p/3795661.html

(2)初心不可忘,《综述:ASP.NET MVC请求处理管道》:http://www.cnblogs.com/luguobin/archive/2013/03/15/2962458.html

(3)学而不思则罔,《ASP.NET Routing与MVC之二:请求如何激活Controller与Action》:http://www.cnblogs.com/acejason/p/3886968.html

(4)王承伟,《ASP.NET MVC请求原理与源码分析》:http://bbs.itheima.com/thread-134340-1-1.html

(5)Ivony,《通过源代码研究ASP.NET MVC中的Conroller和View》:http://www.cnblogs.com/Ivony/archive/2010/11/13/aspnet-mvc-by-source-1.html

(6)痞子一毛,《ASP.NET MVC请求处理图解》:http://www.cnblogs.com/piziyimao/archive/2013/02/27/2935969.html

(7)蒋金楠,《ASP.NET MVC中的View是如何被呈现出来的》:http://www.cnblogs.com/artech/archive/2012/08/22/view-engine-01.html

(8)yinzixin,《深入ASP.NET MVC之七:ActionResult的执行》:http://www.cnblogs.com/yinzixin/archive/2012/12/05/2799459.html (一篇好文,值得阅读)

作者:周旭龙

出处:http://edisonchou.cnblogs.com/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015-01-19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
SpringBoot 设置项目首页 & 指定路径跳转页面
然后 在resources文件夹下面创建一个static的文件夹 里面放一个叫 index.html 即可, 访问项目路径,就跳到首页了。
收心
2022/01/17
3.6K0
SpringBoot 设置项目首页 & 指定路径跳转页面
Spring Security (二) Guides
上一篇文章《Spring Security(一)--Architecture Overview》,我们介绍了Spring Security的基础架构,这一节我们通过Spring官方给出的一个guides例子,来了解Spring Security是如何保护我们的应用的,之后会对进行一个解读。 2 Spring Security Guides 2.1 引入依赖 <dependencies> <dependency> <groupId>org.springframework.boot</g
程序猿DD
2018/02/01
1.1K0
Spring Security (二) Guides
Spring Boot 自定义Spring MVC 配置: WebMvcConfigurationSupport
Spring Boot 自定义Spring MVC 配置: WebMvcConfigurationSupport
一个会写诗的程序员
2018/08/17
7280
SpringBoot2.x开发案例之整合Quartz任务管理系统
基于spring-boot 2.x + quartz 的CRUD任务管理系统,适用于中小项目。
小柒2012
2018/04/11
1.9K4
SpringBoot2.x开发案例之整合Quartz任务管理系统
Spring Boot 2.X(十一):全局异常处理
在 Java Web 系统开发中,不管是 Controller 层、Service 层还是 Dao 层,都有可能抛出异常。如果在每个方法中加上各种 try catch 的异常处理代码,那样会使代码非常繁琐。在Spring MVC 中,我们可以将所有类型的异常处理从各个单独的方法中解耦出来,进行异常信息的统一处理和维护。
朝雾轻寒
2019/10/26
1.7K0
Spring Boot 2.X(十一):全局异常处理
关于SpringMVC自动配置
https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/htmlsingle/#boot-features-developing-web-applications
程序员阿杜
2021/03/16
7100
SpringBoot2.x开发案例之整合Quartz任务管理系统
基于spring-boot 2.x + quartz 的CRUD任务管理系统,适用于中小项目。 基于spring-boot +quartz 的CRUD任务管理系统: https://gitee.com/52itstyle/spring-boot-quartz 开发环境 JDK1.8、Maven、Eclipse 技术栈 SpringBoot2.0.1、thymeleaf3.0.9、quartz2.3.0、iview、vue、layer、AdminLTE、bootstrap 启动说明 项目使用的数据库为My
小柒2012
2018/04/13
1.9K0
SpringBoot2.x开发案例之整合Quartz任务管理系统
springboot开发之引入资源并实现跳转到登录界面
我们可以在External Libraries下找相应的jar,并查看里面的目录结构:比如
西西嘛呦
2020/08/26
6640
springboot开发之引入资源并实现跳转到登录界面
关于RestfulCRUD
2)、使用ResourceBundleMessageSource管理国际化资源文件
程序员阿杜
2021/03/16
3410
关于RestfulCRUD
SpringBoot2.1自定义默认首页
在webmvcconfigurer中重写匿名类的addViewControllers方法
Tom2Code
2022/04/15
7430
SpringBoot2.1自定义默认首页
Java学习之Spring MVC路由映射
访问http://xxx/login/login 即可访问到/WEB-INF/view/login.jsp文件
全栈程序员站长
2022/07/13
6700
Spring Boot 2.X(三):使用 Spring MVC + MyBatis + Thymeleaf 开发 web 应用
Spring MVC 是构建在 Servlet API 上的原生框架,并从一开始就包含在 Spring 框架中。本文主要通过简述 Spring MVC 的架构及分析,并用 Spring Boot + Spring MVC + MyBatis (SSM)+ Thymeleaf(模板引擎) 框架来简单快速构建一个 Web 项目。
朝雾轻寒
2019/10/18
1.4K1
Spring Boot 2.X(三):使用 Spring MVC + MyBatis + Thymeleaf 开发 web 应用
springboot 自动配置和拦截器相关
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置 类生效;
java攻城狮
2020/10/10
6580
Validating Form Input With Spring Boot
这个例子用于演示在Spring Boot应用中如何验证Web 应用的输入,我们将会建立一个简单的Spring MVC应用,来读取用户输入并使用validation注解来检查,并且当用户输入错误时,应用需要再屏幕上显示错误信息提示用户重新输入。
阿杜
2018/08/06
4880
Spring Boot 2.X(四):Spring Boot 自定义 Web MVC 配置
Spring Boot 不仅提供了相当简单使用的自动配置功能,而且开放了非常自由灵活的配置类。Spring MVC 为我们提供了 WebMvcConfigurationSupport 类和一个注解 @EnableWebMvc 以帮助我们减少配置 Bean 的声明。本文简单说明如何自定义 Web MVC 配置。 首先需要使用 @Configuration 将 WebMvcConfig 类标注为 Spring 配置类,示例代码如下:
朝雾轻寒
2019/10/18
1.4K0
Spring学习笔记(十九)——springboot Web开发和模板引擎thymeleaf语法使用
2)、SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来
不愿意做鱼的小鲸鱼
2022/09/26
8500
Spring学习笔记(十九)——springboot Web开发和模板引擎thymeleaf语法使用
springBoot——Web开发简介【七】
1. 所有 /webjars/ ,都去 classpath:/META-INF/resources/webjars/ 找资源** webjars:以jar包的方式引入静态资源 http://www.webjars.org/
思索
2024/08/16
1120
springBoot——Web开发简介【七】
SpringBoot之Web开发
测试:localhost:8080/webjars/jquery/3.3.1/dist/jquery.js
OY
2022/03/12
1.1K0
SpringBoot之Web开发
Spring boot 从0到0.1 part(1)
这里需要注意一点,去修改Spring boot版本,使其小于3.0.5(如果其他配置与我前面配置一致的话),否则会报错
用户9691112
2023/05/18
7260
Spring boot 从0到0.1 part(1)
Spring Boot保护Web应用程序
如果在类路径上添加了Spring Boot Security依赖项,则Spring Boot应用程序会自动为所有HTTP端点提供基本身份验证。端点“/”和“/home”不需要任何身份验证。所有其他端点都需要身份验证。
黑洞代码
2021/12/04
8930
Spring Boot保护Web应用程序
推荐阅读
相关推荐
SpringBoot 设置项目首页 & 指定路径跳转页面
更多 >
LV.3
这个人很懒,什么都没有留下~
目录
  • 一、开放的ASP.NET MVC代码
  • 二、从MvcHandler.ProcessRequest开始
    • (1)Controller的激活
    •   ①借助HttpConetxtWrapper封装HttpContext
    •   ②控制器工厂根据URL创建控制器
    •   ③调用控制器的Execute方法进入Action
    • (2)Action的触发
    •   ①从ControllerBase的Excute方法开始 
    •   ②根据URL获取Action名称并准备触发Action
    •   ③获取Controller与Action的描述信息和过滤器信息
    •   ④获取参数信息并开始真正执行Action:Filter->Action->Filter
    •   ⑤ActionResult闪亮登场:Filter->Result
    • (3)View的呈现
    •   ①约定大于配置的缘故
    •   ②找到ViewEngine视图引擎并获取ViewEngineResult
    •   ③加载ViewData/TempData等数据生成ViewContext
    •   ④开始Render:HTML页面的呈现
  • 三、一图胜千言,总体上概览
  • 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档