好听的歌

  我一直觉得看一篇文章再听一首好听的歌,真是种享受。于是,我在这里嵌入一首好听的歌,当然你觉得不想听的话可以点击停止,歌曲 from 王菲 《梦中人》;

-->

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

(1)Part 1:前奏

(2)Part 2:核心

(3)Part 3:管道

(4)Part 4:WebForm页面生命周期

(5)Part 5: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

    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创建控制器

        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方法看看:

        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究竟又是怎样被触发的呢?

    public interface IController
{
void Execute(RequestContext requestContext);
}

(2)Action的触发

  ①从ControllerBase的Excute方法开始 

    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

    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又是啥东东?我们先看看这个接口的定义:

    public interface IActionInvoker
{
bool InvokeAction(ControllerContext controllerContext, string actionName);
}

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

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

    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

    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方法看看:

        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类型吗?

    public ActionResult Result
{
get { return _result ?? EmptyResult.Instance; }
set { _result = value; }
}

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

        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

    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方法中去看看:

    private ResultExecutedContext InvokeActionResultFilterRecursive(IList<IResultFilter> filters, int filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult)
{
......
if (filterIndex > filters.Count - )
{
InvokeActionResult(controllerContext, actionResult);
return new ResultExecutedContext(controllerContext, actionResult, canceled: false, exception: null);
} IResultFilter filter = filters[filterIndex];
filter.OnResultExecuting(preContext);
......
int nextFilterIndex = filterIndex + ;
postContext = InvokeActionResultFilterRecursive(filters, nextFilterIndex, preContext, controllerContext, actionResult);
......
}

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

        protected virtual void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
{
actionResult.ExecuteResult(controllerContext);
}

(3)View的呈现

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

  ①约定大于配置的缘故

        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开发者的喜爱。

    public override void ExecuteResult(ControllerContext context)
{
......
ViewEngineResult result = null;
if (View == null)
{
result = FindView(context);
View = result.View;
}
......
}

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

        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

        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的构造函数中看到如下代码:

        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页面的呈现

        protected override ViewEngineResult FindView(ControllerContext context)
{
......
View.Render(viewContext, writer);
......
}

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

    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类中。

        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:

@{
ViewBag.Title = "Code in View";
Layout = "_LayoutPage1.cshtml";
}

  这个Layout的内容如下:

@{
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。有了上面的基本信息,下面看代码,先看入口点:

        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

        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()

    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方法:

        public override void WriteLiteral(object value)
{
Output.Write(value);
}

  这里的Output属性是:

        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/

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

ASP.Net请求处理机制初步探索之旅 - Part 5 ASP.Net MVC请求处理流程的更多相关文章

  1. ASP.Net请求处理机制初步探索之旅 - Part 1 前奏

    开篇:ASP.Net是一项动态网页开发技术,在历史发展的长河中WebForm曾一时成为了ASP.Net的代名词,而ASP.Net MVC的出现让这项技术更加唤发朝气.但是,不管是ASP.Net Web ...

  2. ASP.Net请求处理机制初步探索之旅 - Part 1 前奏(转)

        在读本文之前建议先阅读IIS架构:http://www.cnblogs.com/tiantianle/p/5079932.html     不管是ASP.Net WebForm还是ASP.Ne ...

  3. ASP.Net请求处理机制初步探索之旅 - Part 2 核心

    开篇:上一篇我们了解了一个请求从客户端发出到服务端接收并转到ASP.Net处理入口的过程,这篇我们开始探索ASP.Net的核心处理部分,借助强大的反编译工具,我们会看到几个熟悉又陌生的名词(类):Ht ...

  4. ASP.Net请求处理机制初步探索之旅 - Part 2 核心(转)

    开篇:上一篇我们了解了一个请求从客户端发出到服务端接收并转到ASP.Net处理入口的过程,这篇我们开始探索ASP.Net的核心处理部分,借助强大的反编译工具,我们会看到几个熟悉又陌生的名词(类):Ht ...

  5. ASP.Net请求处理机制初步探索之旅 - Part 3 管道

    开篇:上一篇我们了解了一个ASP.Net页面请求的核心处理入口,它经历了三个重要的入口,分别是:ISAPIRuntime.ProcessRequest().HttpRuntime.ProcessReq ...

  6. ASP.Net请求处理机制初步探索之旅 - Part 3 管道(转)

    开篇:上一篇我们了解了一个ASP.Net页面请求的核心处理入口,它经历了三个重要的入口,分别是:ISAPIRuntime.ProcessRequest().HttpRuntime.ProcessReq ...

  7. 【转载】ASP.Net请求处理机制初步探索之旅 - Part 3 管道

    开篇:上一篇我们了解了一个ASP.Net页面请求的核心处理入口,它经历了三个重要的入口,分别是:ISAPIRuntime.ProcessRequest().HttpRuntime.ProcessReq ...

  8. ASP.Net请求处理机制初步探索之旅 - Part 4 WebForm页面生命周期

    开篇:上一篇我们了解了所谓的请求处理管道,在众多的事件中微软开放了19个重要的事件给我们,我们可以注入一些自定义的业务逻辑实现应用的个性化设计.本篇,我们来看看WebForm模式下的页面生命周期. ( ...

  9. (转)Asp.Net 请求处理机制

    原文:http://www.cnblogs.com/cilence/archive/2012/05/28/2520712.html Asp.Net 请求处理机制   前言 我们都知道Web请求响应是基 ...

随机推荐

  1. 批量下载小说网站上的小说(python爬虫)

    随便说点什么 因为在学python,所有自然而然的就掉进了爬虫这个坑里,好吧,主要是因为我觉得爬虫比较酷,才入坑的. 想想看,你可以批量自动的采集互联网上海量的资料数据,是多么令人激动啊! 所以我就被 ...

  2. ubuntu安装vim时提示 没有可用的软件包 vim,但是它被其它的软件包引用了 解决办法

    ubuntu安装vim时提示 没有可用的软件包 vim-gtk3,但是它被其它的软件包引用了 解决办法 本人在ubuntu系统安装vim  输入 sudo apt-get install vim 提示 ...

  3. 【转】Android 底层开发的几点

    我干了3年Android sdk开发,觉得到了瓶劲没法更进一步,于是花了一年多点时间,大概摸到点门径.根据前辈的经验,Android底层完全入门需要两年. 先说下我的入门过程:第零步,下载源码,我下的 ...

  4. 我的Python学习之路 Python的初识与准备工作

    注:文笔不好,不喜勿喷,当个段子看看就好 一.初识Python 第一次听到Python是在2016年大概暑假 时候(即将大三),因为对黑客技术的蜜汁热爱(虽然自己并不会),在玄魂大大的公众微信号中看到 ...

  5. openswan-ipsec.conf配置说明

    Name ipsec.conf - IPsec configuration and connections Description The optional ipsec.conf file speci ...

  6. Content Negotiation in ASP.NET Web API

    本文描述Web API实现内容协商(content negotiation). The HTTP specification (RFC 2616) defines content negotiatio ...

  7. 软件工程:Wordcount程序作业

    由于时间的关系,急着交作业,加上这一次也不是那么很认真的去做,草草写了“Wordcount程序”几个功能,即是 .txt文件的读取,能计算出文件内容的单词数,文件内容的字符数,及行数. 这次选用C来做 ...

  8. 转载:Chrome调试折腾记_(1)调试控制中心快捷键详解!!!

    转载:http://blog.csdn.net/crper/article/details/48098625 大多浏览器的调试功能的启用快捷键都一致…按下F12;还是熟悉的味道;  或者直接 Ctrl ...

  9. nginx android app 慢网络请求超时

    最近遇到了android 在慢网络下面请求服务器报 java.net.SocketException: recvfrom failed: ECONNRESET (Connection reset by ...

  10. common.js js中常用方法

    //创建CSS样式段 //classid: CSS样式段ID//font: 字体//size: 字体大小//color: 字体颜色//style: 字体风格function FCMakeCSSClas ...