1、问题的引出

我相信大家在项目中都使用过TempData,TempData是一个字典集合,一般用于两个请求之间临时缓存数据或者页面之间传递消息。也都知道TempData是用Session来实现的,既然是用Session来实现的,那么模式就是线程模式,这样的Session是没法用到分布式系统中的,那么在多台机器上部署,怎么做到Session在多台机器中共存,这就涉及到分布式存储。那该如何实现TempData的分布式存储?在讲如何实现时,先给大家说说ASP.Net MVC 的管道机制,本人能力有限,说的不对的地方,还请大家能指出来,共同进步。

2、预备知识

2.1、MVC处理的流程讲解

网上有很多讲解ASP.Net 的管道机制的,都讲解的很好,大家可以找找看,今天我来点不一样的,通过Reflector,Debug进源码一步一步调试给大家看,下面开始吧:

1)俗话说的好,工欲善其事必先利其器,下面我们在VS2012上装Reflector

选择"扩展和更新",在弹出来的对话框中安装我们的利器

安装完成之后会在VS上面出现如下的菜单:

点击该菜单,选择下面的选项:

在弹出来的对话中勾选所有以system.web开头的dll,生成PDB文件,因为只有生成它,我们才能调试源码,如下图的勾选情况:

OK,装好之后我们就开始探索的旅程了~~~~~~~~

2)窥探ASP.Net MVC请求处理流程

Part 1

这里先附上一张一次请求 http://localhost:42132/Home/Index/1  处理响应的整体流程图:

看不明白的不要着急,下面会通过调试的方式详细介绍请求处理响应的流程,动动你的小手,下面开始划重点了~~

我们上网时,在浏览器地址输入网址:Http://www.cnblogs.com,按下回车,一张网页就呈现在我们眼前。这究竟发生了什么?对于一名优秀的Programmer来说,我想有必要一下熟悉浏览器--->服务器请求的过程。

1)ASP.Net

ASP.NET是运行在公共语言运行时刻时(CLR)上的应用程序框架。他用来在服务器端构建功能强大的web应用程序。当浏览器请求 ASP.NET 文件时,IIS 会把该请求传递给服务器上的 ASP.NET 引擎,ASP.NET 引擎会逐行地读取该文件,并执行文件中的脚本,最后,ASP.NET 文件会以纯 HTML 的形式返回浏览器。

客户端浏览器和服务器之间的请求响应是通过Socket进行通信,基于HTTP协议,客户端发送一次HTTP请求,服务器接收到请求,处理之后向浏览器回应响应报文。那么什么是HTTP协议呢?

2)Http协议

当浏览器寻找到Web服务器地址后,浏览器将帮助我们把对服务器的请求转换为一系列参数(消息)发给Web服务器,浏览器和Web服务器的对话中,需要使用双方都能理解语法规范进行通信,这种程序之间进行通信的语法规定,我们称之为协议。浏览器与服务器之间的协议是应用层协议,当前遵循的协议是HTTP/1.1。HTTP/1.1协议时Web开发的基础,这是一个无状态协议,客户端浏览器和服务器通过Socket通信进行请求和响应完成一次会话。每次会话中,通信双方发送的数据称为消息,分为两种:请求消息和响应消息。

对于消息而言,一般他有三部分组成,并且消息的头和消息体之间用一个空行进行分隔:

下面用Fiddler我们可以清晰看到浏览器和服务器之间的通信内容:

注意:在请求头和请求体之间是有一空行的,是Http协议规定的。

如果想更加详细的了解Http协议的内容,可以参考下面的两篇文章:

http://www.cnblogs.com/EricaMIN1987_IT/p/3837436.html

http://www.cnblogs.com/wxisme/p/6212797.html

了解了什么是HTTP协议之后,我们在回到先前提出的那个问题,浏览器的请求怎样到达服务器?

 3)Http.sys和TCP.sys组件

我们知道要访问一个网站,必须要其部署在相应服务器软件上(如IIS),于IIS相关的内核驱动程序有两个:一个是TCP.sys和Http.sys,所谓的TCP,是用来定义在网络上数据传输方式的协议,它是一个位于OSI七层协议栈的传输层的协议。HTTP协议是一个定义在应用层的协议,它定义了数据交互的谓词数据的格式等,但是传输层上是使用TCP协议进行数据包传送。了解了以上内容有助于理解http.sys和TCP.sys之间的关系:TCP.sys位于Windows通信的最底层,凡是使用TCP协议传输的HTTP协议数据包都会被tcp.sys完成组包后再交给http.sys进行处理。当请求的数据包包含一个HTTP请求时,就会有tcp.sys转给http.sys进行处理,http.sys在内核态上处理完HTTP请求后,IIS就会把HTTP请求对应的HTTP上下文对象转到对应的应用程序进程中,由对应的w3wp.exe进程对请求进行处理。由于IIS本身只能处理静态页面比如html、htm等,对于动态的页面比如cshtml,IIS本身是无法处理的,那么怎样能让IIS能够支持ASP.Net动态也的处理呢?答案就是采用ISAPI。ISAPI可以理解为是IIS的一种扩展插件,当IIS发现某种服务器上的资源自给无法处理时,就会按照配置信息把请求转给对应的ISAPI的扩展来执行;IIS会等待ISAPI的执行结果,然后把结果传给客户的浏览器。

4)IIS服务器扩展

ISAPI(服务器应用编程接口),它为开发人员提供了强大的可编程能力,只要按照标准接口开发不同类型的Web应用程序的ISAPI扩展程序,就能实现对IIS功能上的扩展,从而使IIS可以处理不同类型的客户端请求。IIS管理器提供了应用程序配置功能,可以对不同的客户端请求配置不同的ISAPI扩展程序ISAPI扩展程序通常以DLL形式存在,可以被IIS加载并调用。有了基于ISAPI的扩展扩展程序,IIS服务器就可以根据客户端请求的资源扩展名,来决定应由哪个ISAPI扩展程序来处理客户端请求,然后就可以将请求转发给合适的ISAPI扩展程序。

5)IIS中处理程序映射

Part 2

1)整体把握ASP.Net MVC和ASP.Net WebForm处理流程的差异

ASP.Net是一项动态网页开发技术,在历史发展的长河中WebForm曾一时成为了ASP.Net的代名词,而ASP.Net MVC的出现让这项技术更加唤发朝气。但是,不管是ASP.Net WebForm还是ASP.Net MVC在请求处理机制上大部分都是相同的,只是在请求处理管道上的处理事件做了不同的操作。

2)ASP.Net MVC的管道机制

第一个进入ASP.Net管道的是:PipelineRuntime.ProcessRequestNotification方法

当你在浏览器中输入http://localhost:42132/Home/Index/1按回车之后,请求首先会到达PipelineRuntime.ProcessRequestNotification方法,如下图所示:

注意调用堆栈信息,我们的请求到达ASP.Net管道时,首先会经过PipelineRuntime类中的ProcessRequestNotification方法,至于该方法里面的参数暂时可以忽略,抄起你的小手,划重点了,在该方法内部,又调用了ProcessRequestNotificationHelper方法,下面转到该方法内部,如下图所示:

在该方法内部,调用了InitializeRequestContext方法,主要用来初始化请求上下文,我们接着转到该方的内部,如下图所示:

注意InitializeRequestContext方法内部的这段代码  context = new HttpContext(wr, false);     实例化HttpContext对象,接下来我们看看,在new HttpContext对象的时候都做了些神马:

在该方法内部又调用了Init方法,进行Httprequest和HttpResponse对象进行分装,如下图所示:

好了,实线收回到  ProcessRequestNotificationHelper方法中,在该方法中回执行  HttpRuntime.ProcessRequestNotification(wr, httpContext);  ,如下图所示:

在该方中,第一个参数和第二个参数,就是我们上面实例化的对象,转到该方的内部,你会看到不一样的世界,如下图所示:

在该方法的内部又调用了  HttpRuntime.ProcessRequestNotificationPrivate 方法,在该方法的内部try中 EnsureFirstRequestInit 方法,确保网站第一次被访问时,调用了Global文件中了Application_Start方法,不信你看:

全局事件中例如Application_Start方法如何保证只执行一次?在_theApplicationFactory.EnsureAppStartCalled(context);方法中,判断_appOnStartCalled标志,如果是false则调用FireApplicationOnStart方法触发Application_Start方法,然后更改_appOnStartCalled标志。

注意了,重点来了,赶快抄起你的小手,划重点了,通过HttpApplicationFactory.GetApplicationInstance方法来创建APPlication对象,其实这里的application对象就是Global实例对象,有图有真相。我们来详细了解一下HttpApplicationFactory是怎么来创建application对象的,下面我们转到GetApplicationInstance方法内部,如下图所示:

在转到GetNormalApplicationInstance方法内部,窥探一下application对象是如何生成的,如下图所示:

通过查看这段代码,它首先维护着一个HttpApplication池(_freeList,本质上就是一个Stack栈),然后判断可用的HttpApplication实例的数量(_numFreeAppInstances)是否大于0?如果存在可用的,则从池中出栈,然后将可用数量减1。最后,再判断可用的数量是否小于最低限制的数量,如果小于那么则将最低限制的数量设置为目前可用的数量。那么,如果目前HttpApplication池暂时没有可用的实例呢?

代码 state = (HttpApplication) HttpRuntime.CreateNonPublicInstance(this._theApplicationType);    内部通过反射创建了application对象,注意了,重点来了,赶快抄起你的小手,划重点了,在GetNormalApplicationInstance方法,内部application对象(也就是Global对象),调用了InitInternal方法,该方法的功能整体上是这样的:创建系统配置文件和用户配置文件中的HttpModule对象,如下图所示:

在HttpApplication.InitInternal方法的内部,又调用了 this.InitModules(),在该方法中,首先通过读取Web.config配置文件中关于HttpModule的信息,然后将其传递给HttpModule的集合,如下图所示:

那在ASP.NET中已经预定了哪些HttpModule,我们通过 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config找到web.config文件

然后,又调用了InitModulesCommon方法,遍历上面这个_moduleCollection集合,分别对其每一个HttpModule执行其对应的Init方法。

现在我们把视线收回到 HttpApplication.InitInternal()方法内部,在该方法内部又调用了this._stepManager.BuildSteps(this._resumeStepsWaitCallback); 它完成了19个请求处理管道事件的注册工作。如下图所示:

从上面的代码可知,ApplicationStepManager对象的BuildSteps方法被调用,完成HttpApplication 19个管道事件的注册。这个方法很重要,它将创建各种HttpApplication.IExecutionStep保存到一个数组列表 _execSteps 中:如上图中 steps.CopyTo(this._execSteps)。这样做的目的在于:便于在后面的BeginProcessRequest方法内部调用ResumeSteps方法依次执行这些对象的Execute()方法,完成各个事件的执行。打起精神,抄起你的小手,划重点了,在完成HttpApplication 19个管道事件的注册后,开始依次跑管道事件,在执行每个管道事件的时候,会触发HttpModule中各个事件对应的执行方法,下面列出部分方法被触发执行的情况,如下图所示:

来来来,打起精神,抄起你的小手,重点来了!!!重点来了!!!重点来了!!!重要的事情说三遍!

看见没,URLRoutingModule,它是一个实现了IHttpModule接口,重写了Init方法,在该方法内部,第七个管道事件上没注册了 OnApplicationPostResolveRequestCache方法,如下图所示:

也就是说,我们的ASP.Net MVC 网站已经进入到第七个管道事件 PostResolveRequestCache ,我们的MVC就是通过这种方法来实现的。下面 我们转到该方法内部,看看到底干了些神马,如下图所示:

在说明该方法时,我们先补充一些关于HttpModule和HttpHandler,首先附上一张管道事件的图片,如下图所示:

我们再来理解一下什么是HttpModule和HttpHandler,他们有助我们在ASP.NET页面处理过程的前后注入自定义的代码逻辑的原理。首先他们之间主要的差别在于:

(1)整体把握:

ASP.NET 请求处理过程是基于管道模型的,这个管道模型是由多个HttpModule和HttpHandler组成,ASP.NET 把http请求依次传递给管道中各个HttpModule,最终被HttpHandler处理,处理完成后,再次经过管道中的HTTP模块,把结果返回给客户端。我们可以在每个HttpModule中都可以干预请求的处理过程。

注意:在http请求的处理过程中,只能调用一个HttpHandler,但可以调用多个HttpModule。 
当请求到达HttpModule的时候,系统还没有对这个请求真正处理,但是我们可以在这个请求传递到处理中心(HttpHandler)之前附加一些其它信息,或者截获的这个请求并作一些额外的工作,也或者终止请求等。在HttpHandler处理完请求之后,我们可以再在相应的HttpModule中把请求处理的结果进行再次加工返回客户端。

(2)IHttpModule

比如我们的MVC中的URLRoutingModule,就是实现了IHttpModule接口,重写了里面的Init方法。

IHttpModule定义如下:

public interface IHttpModule
{
void Dispose();
void Init(HttpApplication context);
}

Init 方法:系统初始化的时候自动调用,这个方法允许HTTP模块向HttpApplication 对象中的事件注册自己的事件处理程序。URLRoutingModule就是这样实现的

(3)IHandler

HttpHandler是HTTP请求的处理中心,真正地对客户端请求的服务器页面做出编译和执行,并将处理过后的信息附加在HTTP请求信息流中再次返回到HttpModule中。
    HttpHandler与HttpModule不同,一旦定义了自己的HttpHandler类,那么它对系统的HttpHandler的关系将是“覆盖”关系。
    IHttpHandler接口声明
    public interface IHttpHandler
    {
        bool IsReusable { get; }
        public void ProcessRequest(HttpContext context); //请求处理函数
    }

(注:该部分参考来源:ivan.yu的.net空间)关于更详细的介绍可以参考这位前辈的文章:http://www.cnblogs.com/yuanyuan/archive/2010/11/15/1877709.html,讲解的非常详细。

后面我会,结合HttpModule和HttpHandler讲解几个实战的例子。

好了,回到URLRoutingModule中Init方法在第七个管道事件上注册的  OnApplicationPostResolveRequestCache方法,我们的MVC在第七个事件主要做的事情是创建一个MVCHandler存入到HttpContext对象的ReMapHandler属性中,但是对于静态文件是不需要经过MVC处理的。下面我们来看看在该方法内部是如何实现的, RouteData routeData = this.RouteCollection.GetRouteData(context);通过该方法获取到封装的路由信息的RouteData实例。也就是当请求到达UrlRoutingModule的时候,UrlRoutingModule会触发注册的事件方法,在该方法内部通过 GetRouteData方法 ,根据URL到路由表里面查找匹配URL规则的路由,若匹配,把请求交给IRouteHandler,即MVCRouteHandler。我们可以看下GetRouteData的源码,如下图所示:

注意了,重点来了,抄起你的小手,开始划重点,在GetRouteData方法红色框中标注的代码,会返回RouteData对象,那我们看看,RouteData对象中到底有些神马,如下图所示:

注意了这里把MVCRouteHandler对象赋值给了RouteHandler了,最终返回,把值赋值给routeData变量。接着我们把视线收回到第七个管道事件注册的方法中,

接着,会判断一下routeData是否为NUll,routeData是不为null的,所以接下来,通过routeData.RouteHandler拿到了MVCRouteHandler对象,重点来啦,赶快抄起小手!!!接下来继续执行,当执行到IHttpHandler  HttpHandler=routeHandler.GetHttpHandler(requestContext)时,我们的MVCHandler就诞生了,最后把创建的MVCHandler对象,存入到了RemapHandler不信,如下图所示:

不信,如下图所示:

那我们的MVCHandler创建好了,之后该怎么执行呢?很简单,继续执行下面的管道事件呗,接着到第八个管道事件了,在第八个管道事件,先检查HttpContext里面的remapHandler,发现不为空,直接略过执行后面的是那件,在第十一和管道事件和第十二个管道事件之间调用MVCHandler的BeginProcessRequest方法,不信如下图所示:

在该方法内部,会执行ProcessRequestInit方法,进行处理请求的初始化工作,如下图所示:

看到没,我们的控制器的名字:Home,注意了,重点来啦!!!重点来啦!!!重点来啦!!!重要的事情说三遍!!!

this.ControllerBuilder.GetControllerFactory()方法拿到Controller Factory对象,然后,调用CreateController方法,拿到对应的controller对象。下面我们看看能不是如何实现了,不要忘了这篇文章讲的是如何实现跨越Session的分布式的TempData。CreateController方法中有两个参数,一个是RequestContext对象,通过他我们能拿到请求的先关信息,第二个参数是一个string类型的controller名称,它的值来源于URL,如下图所示:

首先要注意,我们的Controller Factory就是DefaultControllerFactory对象(作用:为请求提供服务的controller实例),在该方法中,通过反射去创建对应的controller对象。在该方中有两个特别重要的方法,GetControllerType和GetControllerInstance方法。GetControllerType方法方法,返回Type类型,为请求匹配对应的controller类。GetControllerInstance方法返回是IController类型,作用是根据指定的controller类型创建类型的实例。重写GetControllerInstance方法可以实现对创建controller实例的过程进行控制,最常见的就是依赖注入,这里我们暂且不讲。那么GetControllerInstance又是如何来获取实例呢? 下面我们转到GetControllerInstance方法内部,如下图所示:

看到没,它是通过ControllerActivator来拿到controller实例的,转到内部,如下图所示:

看到没,这段代码是不是很熟悉,是不是有点像我们使用autofac的影子。好了我们总结一下Controller对象的创建过程:首先当我们的DefaultControllerFactory类接收到一个controller实例的请求时,在DefaultControllerFactory类内部通过GetControllerType方法来获取controller的类型,然后把这个类型传递给GetControllerInstance方法以获取controller实例,所以在GetControllerInstance方法中就需要有某个东西来创建controller实例,这个创建的过程就是controller被激活的过程。那我们的controller对象创建完毕,接下来就是要调用Controller里面的Execute方法,执行对应的Action方法。接着我们把视线收回到MVCHandler中的BeginProcessRequest方法内部,在该方法内部又执行了 asyncController.BeginExecute(this.RequestContext, asyncCallback, asyncState);方法,为什么会执行Controller里面的ExecuteCore方法呢??首先我们补充一点关于IController的知识:

(1)我们添加的Controller都是一个继承自抽象类System.Web.MVC.Controller,该类又继承自ControllerBase,ControllerBase又实现了IController接口,在该接口中只有一个方法,就是Execute方法,当请求送到了一个实现了IController接口的Controller类时,Execute方法就会被调用。如下所示:

public interface IController
{
void Execute(RequestContext requestContext);
}
ControllerBase实现了Execute方法,如下所示:

注意到没有,this.ExecuteCore()和 Protected abstract void ExecuteCore(),我们再看一下Controller的实现,你就会明白下面的执行流程了,如下图所示:

看到没,我们的Controller类实现了ControllerBase中的ExecuteCore这个抽象方法。注意下1和3是在执行Action方法前和后执行的,后面会讲解到底是什么,继续看我们MVC执行的流程

注意:Controller中的一切对请求的处理都是从Execute方法开始的!!!,下面我们转到BeginExecute方法的内部,如下图所示:

来来来,抄起小手,划重点了,注意到没有,return后面的AsyncRequestWrapper.Begin方法了吗?在第三个参数中有这样一句代码:this.BeginExecuteCore,这里的this值的就是Controller,F11自然会进入到该方法,如下图所示:

首先要明白,当Controller Factory创建好了一个类的实例后,MVC框架则需要一种方式来调用这个实例的Action方法,如果创建了controller是继承Controller抽象类的话,那么则是有Action Invoker来完成调用action方法的任务,MVC默认使用的是ControllerActionInvoker类。然后我们看看代码的具体实现:首先,通过路由数据获取Action名称,例如请求URL为:http://xxx.com/Home/Index,这里获取的Action名称即为Index。然后,通过IActionInvoker invoker = this.ActionInvoker;拿到Action的激活器。那么问题来了,这个ActionInvoker又是啥东东?我们先看看这个接口的定义代码如下:

public interface IActionInvoker
{
bool InvokeAction(ControllerContext controllerContext, string actionName);
}
我们发现原来是一个叫做ControllerActionInvoker的类实现了IActionInvoker接口,ControllerActionInvoker类如下图所示:

接着执行: asyncInvoker.BeginInvokeAction(this.ControllerContext, actionName, asyncCallback, asyncState);,转到内部,如下图所示:

在该方法的内部,主要是获取Controller与Action的描述信息和过滤器信息。获取参数信息后并开始真正执行Action,在action方法执行完之后,开始View的呈现,

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

在该方法内部,找到视图引擎,找到视图,执行视图生成HTML,下面我们一步一步来看看,如何执行的。先检查是否传入指定的视图名称,如果没有传入,则取Action方法的名字作为待会要读取的视图名字,代码如下:this.ViewName=context.RouteData.GetRequiredString("action");接着找到对应的视图引擎,代码如下result=this.FindView(context)。在FindView方法内部,循环视图引擎集合,看看哪个视图引擎可以找到对应的视图,就返回哪个视图引擎的结果,此结果中就包含视图接口对象,找到了RazorViewEngine对象,调用视图引擎的FindView方法,但这个方法在RazorViewEngine类中没有,而是在父类的父类中定义(继承关系:RazorViewEngine-->BuildManagerViewEngine-->VirtualPathProviderViewEngine),获取控制器名称、视图的路径,同时还获得了母版页的路径,最终返回ViewEngineResult,然后获取返回的ViewEngineResult里的View对象,然后调用它的Render方法来生成HTML代码并写入到Response中,代码如下:TextWriter  writer=context。HttpContext.Response.Output;ViewContext viewContext=new  ViewContext(context,view,ViewData,TempData,writer); View.Render(viewContext,writer);最后生成HTML。大家可能通过文字来不是好理解,下面我在附上一张我自己画的流程图,是根据我自己调试代码理解的,如下图所示:(想要下面流程图的可以提下,到时候发给你)

到这里我们ASP.Net MVC 的一次请求处理响应的流程就结束了,好了,不是很理解的话,不要紧,下去可以通过代码调试的方法,自己好好调试调试,慢慢理解。来来来,把思路整理一下,回到我的TempData。通过上面流程的讲解,大家知道在执行action方法之前和之后都会分别执行PossiblyLoadTempData()和PossiblySaveTempData(),如下图所示:

从中可以看到在请求开始时就去取TempData,在Action调用结束后去保存TempData。为什么要再去保存一遍呢?

2.2、TempData源码的讲解

TempData是什么

(1)可以存储一次,只能读取一次,如果第二次读取,将不会有tempdata数据,这样就起到了临时变量的作用

(2) 是一个string object的字典。
(3) action执行前后,都会对temp进行操作

(4)一般用于两个请求之间临时缓存数据或者页面之间传递消息

TempData源码分休

public TempDataDictionary TempData
{
  get
  {
   if ((this.ControllerContext != null) && this.ControllerContext.IsChildAction)
       {
    return this.ControllerContext.ParentActionViewContext.TempData;
        }

   if (this._tempDataDictionary == null)
    {
    this._tempDataDictionary = new TempDataDictionary();
     }
  return this._tempDataDictionary;
}
set
{
this._tempDataDictionary = value;
}
}

step1:先来看看上面提到了两个方法内部是如何实现的

他们内部又调用了Load和Save方法,转到定义,如下图所示:

这两个方法内部又通过,TempDataprovider分别调用了LoadTempData和SaveTempData方法,再分别转到这两个方法内部,如下所示:

public interface ITempDataProvider
{
IDictionary<string, object> LoadTempData(ControllerContext controllerContext);
void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values);
}

注意:它是一个接口。里面是这两个方法,肯定有子类实现该接口中的两个方法,通过调试源码,你就会知道上面Load和Save方法中最后一个参数,tempDataProvider就是SessionStateTempDataProvider,不信我们来看下源码:

// Generated by .NET Reflector from C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Web.Mvc\v4.0_4.0.0.0__31bf3856ad364e35\System.Web.Mvc.dll
namespace System.Web.Mvc
{
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc.Properties; public class SessionStateTempDataProvider : ITempDataProvider
{
internal const string TempDataSessionStateKey = "__ControllerTempData"; public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
{
HttpSessionStateBase session = controllerContext.HttpContext.Session;
if (session != null)
{
Dictionary<string, object> dictionary = session["__ControllerTempData"] as Dictionary<string, object>;
if (dictionary != null)
{
session.Remove("__ControllerTempData");
return dictionary;
}
}
return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
} public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
HttpSessionStateBase session = controllerContext.HttpContext.Session;
bool flag = (values != null) && (values.Count > 0);
if (session == null)
{
if (flag)
{
throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);
}
}
else if (flag)
{
session["__ControllerTempData"] = values;
}
else if (session["__ControllerTempData"] != null)
{
session.Remove("__ControllerTempData");
}
}
}
}

看到没,我们的SessionStateTempDataProvider类实现了ITempDataProvider接口,重写了Load和Save方法。

从图中可知,SessionStatesTempDataProvider暴露了LoadTempData和SaveTempData两个方法。

其中从SaveTempData中session["__ControllerTempData"] = (object) values;可以看出,TempData是存储在Session中的。

其中LoadTempData方法中session.Remove("__ControllerTempData");就说明了从session中获取tempdata后,对应的tempdata就从session中清空了

原来每次取完TempData后都会从Session中清空,如果TempData未曾使用,那当然要重新保存到Session中啊。这就回答了为什么要再去保存一遍的问题。

那问题来了,我们要实现分布式的TempData,在MVC哪个地方注入呢?我们再来回顾一下MVC的管道和action方法执行前后发现:PossiblyLoadTempData和PossiblySaveTempData是在调用Controller中对应的action方法时执行的,并且Controller中有 TempDataProvider属性,代码如下:

public ITempDataProvider TempDataProvider
        {
            get
            {
                if (this._tempDataProvider == null)
                {
                    this._tempDataProvider = this.CreateTempDataProvider();
                }
                return this._tempDataProvider;
            }
            set
            {
                this._tempDataProvider = value;
            }
        }

所以注入点我们就找到,在创建Controller Factory中创建Controller实例的时候,把我们自定义的DataProvider类,赋值给TempDataProvider就可以了,下面我们来实现一把分布式的tempData

3、实现分布式的TempData

准备工作:首先我们新建一个MVC项目,新建一个文件夹Infrastructure文件夹,在这个文件下添加一个类:继承自DefaultControllerFactory的MyControllerFactory类即我们自定义的Controller Factory,代码如下:

 1 public class MyControllerFactory:DefaultControllerFactory
2 {
3 public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
4 {
5 var iController= base.CreateController(requestContext, controllerName);
6
7 var controller = iController as Controller;
8 controller.TempDataProvider = new CrossSessionTempData2();
9
10
11 return iController;
12 }
13 }

3.1、把TempData的值存入到cache中

 1 namespace System.Web.Mvc
2 {
3 using System;
4 using System.Collections.Generic;
5 using System.Web;
6 using System.Web.Mvc.Properties;
7
8 public class SessionStateTempDataProvider : ITempDataProvider
9 {
10 internal const string TempDataSessionStateKey = "__ControllerTempData";
11
12 public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
13 {
14 var cache = controllerContext.HttpContext.Cache;
15 if (cache != null)
16 {
17 Dictionary<string, object> dictionary =cache["__ControllerTempData"] as Dictionary<string, object>;
18 if (dictionary != null)
19 {
20 cache .Remove("__ControllerTempData");
21 return dictionary;
22 }
23 }
24 return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
25 }
26
27 public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
28 {
29 if (controllerContext == null)
30 {
31 throw new ArgumentNullException("controllerContext");
32 }
33 var cache = controllerContext.HttpContext.Cache;
34 bool flag = (values != null) && (values.Count > 0);
35 if (cache == null)
36 {
37 if (flag)
38 {
39 throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);
40 }
41 }
42 else if (flag)
43 {
44 cache ["__ControllerTempData"] = values;
45 }
46 else if (cache ["__ControllerTempData"] != null)
47 {
48 cache .Remove("__ControllerTempData");
49 }
50 }
51 }
52 }

TempData的值存入到cache中之文件依赖

接着我们需要自定义一个实现了ITempDataProvider接口的DataProvider类,代码如下:(2017年6月20日18:13:07 代码修改)

 1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Web;
5 using System.Web.Caching;
6 using System.Web.Mvc;
7
8 namespace CrossSessionTempData.Infrastructure
9 {
10 public class CrossSessionTempData2 : ITempDataProvider
11 {
12
13 internal const string TempDataSessionStateKey = "__ControllerTempData";
14
15 public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
16 {
17 var cache = controllerContext.HttpContext.Cache;
18
19 if (cache != null)
20 {
21 Dictionary<string, object> dictionary = cache[TempDataSessionStateKey] as Dictionary<string, object>;
22 if (dictionary != null)
23 {
24 cache.Remove(TempDataSessionStateKey);
25 return dictionary;
26 }
27 }
28 return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
29 }
30
31 /// <summary>Saves the specified values in the temporary data dictionary by using the specified controller context.</summary>
32 /// <param name="controllerContext">The controller context.</param>
33 /// <param name="values">The values.</param>
34 /// <exception cref="T:System.InvalidOperationException">An error occurred the session context was being retrieved.</exception>
35 public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
36 {
37 if (controllerContext == null)
38 {
39 throw new ArgumentNullException("controllerContext");
40 }
41 var cache = controllerContext.HttpContext.Cache;
42 bool flag = values != null && values.Count > 0;
43 if (cache == null)
44 {
45 if (flag)
46 {
47 throw new InvalidOperationException("");
48 }
49 }
50 else
51 {
52 CacheDependency dp = new CacheDependency(controllerContext.HttpContext.Server.MapPath("/Data/123.txt"));
53 if (flag)
54 {
55
56
57
58 cache.Insert(TempDataSessionStateKey, values, dp);
59
60 return;
61 }
62 63 if (cache[TempDataSessionStateKey] != null)
64 {
65 cache.Remove(TempDataSessionStateKey);
66 }
67 }
68 }
69 }
70 }

添加一个controller,代码如下:

我们在Index中设置TempData的值,然后再List中读取。按说我们只有概念文件依赖是存在缓存中发TempData的值才会消失,下面我们运行一把,看看运行结果:

先访问:http://localhost:42913/Default/Index

在执行Index action方法之前会执行LoadTempData方法,如下图所示:

接着,设置TempData的值,如下图所示:

接着执行Save方法,如下图所示:

看到没,把TempData的值存入到Cache中了,接着我方访问以下http://localhost:42913/Default/List,TempData的值就会显示出来:

首先也会执行LoadTempData方法

再执行List里面的代码,在执行SaveTempData方法, 返回视图:

不管怎么刷新,值依然存在,但是只要我们修改依赖文件1.txt值立马就消失了。

保存,再次刷新页面,数据就丢失了。

3.2、把TempData的值存入到NoSQL Memcached中实现真正的分布式

关于Memcached的安装和操作请参考我的这篇博客:

ASP.Net MVC4+Memcached+CodeFirst实现分布式缓存

MemcacheHelper:

 

引用对应的dll:

自定义的我们的DataProvider:

 1  public class CrossSessionTempData2 : ITempDataProvider
2 {
3
4 internal const string TempDataSessionStateKey = "__ControllerTempData";
5
6 public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
7 {
8
9 Dictionary<string, object> dictionary = MemCaheHelper.Get(TempDataSessionStateKey) as Dictionary<string, object>;
10 if (dictionary != null)
11 {
12 MemCaheHelper.Set(TempDataSessionStateKey, dictionary, DateTime.MinValue);
13 return dictionary;
14 }
15 return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
16 }
17
18 public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
19 {
20 if (controllerContext == null)
21 {
22 throw new ArgumentNullException("controllerContext");
23 }
24
25 bool flag = values != null && values.Count > 0;
26 if (flag)
27 {
28
29 MemCaheHelper.Set(TempDataSessionStateKey, values,DateTime.Now.AddMinutes(1));
30 return;
31 }
32
33 if (MemCaheHelper.Get(TempDataSessionStateKey) != null)
34 {
35 MemCaheHelper.Set(TempDataSessionStateKey,values,DateTime.MinValue);
36 }
37
38
39 }
40 }

运行效果完美!!!!至此,我们的分布式TempData的功能已经实现。后面我会的代码提供给大家。其实我们也可以把值存入到redis中,原理和MemCached差不多,自己可以尝试一下。

4、总结:

这篇文章花了很长时间,希望对你有帮助,如果大家觉的还可以的话,帮忙点下推荐。如果对TempData还是不太了解,可以参考这位园友的文章TempData知多少

附件下载:

分布式TempData代码

MemCached

Reflector注册机(最好安装8.5版本的)

流程图

参考文章:

木碗城主:http://www.cnblogs.com/OceanEyes/archive/2012/08/13/aspnetEssential-1.html

Edison Chou:http://www.cnblogs.com/edisonchou/p/3855969.html

MIN飞翔:http://www.cnblogs.com/EricaMIN1987_IT/p/3837436.html

Liam Wang:http://www.cnblogs.com/willick/p/3331521.html

一线码农:http://www.cnblogs.com/huangxincheng/p/5663725.html

作者:郭峥

出处:http://www.cnblogs.com/runningsmallguo/

MVC底层原理的更多相关文章

  1. ASP.NET MVC底层原理与框架

    前言 鄙人有一毛病,喜欢钻研原理性的东西,感觉只知道怎么用还不太够,更想知道如何实现的以及为什么会这样. 暑假的时候做积分系统是第一次接触MVC,感觉MVC就是一个框架,分为Module ,view和 ...

  2. 窥探ASP.Net MVC底层原理 实现跨越Session的分布式TempData

    1.问题的引出 我相信大家在项目中都使用过TempData,TempData是一个字典集合,一般用于两个请求之间临时缓存数据或者页面之间传递消息.也都知道TempData是用Session来实现的,既 ...

  3. Spring MVC 底层原理

    参考博客:http://www.cnblogs.com/xiaoxi/p/6164383.html Spring MVC处理的流程: 具体执行步骤如下: 1 首先用户发送请求给前端控制器,前端控制器根 ...

  4. 深入源码分析SpringMVC底层原理(二)

    原文链接:深入源码分析SpringMVC底层原理(二) 文章目录 深入分析SpringMVC请求处理过程 1. DispatcherServlet处理请求 1.1 寻找Handler 1.2 没有找到 ...

  5. 漫画 | Spring AOP的底层原理是什么?

    1.Spring中配置的bean是在什么时候实例化的? 2.描述一下Spring中的IOC.AOP和DI IOC和AOP是Spring的两大核心思想 3.谈谈IOC.AOP和DI在项目开发中的应用场景 ...

  6. Neo4j图数据库简介和底层原理

    现实中很多数据都是用图来表达的,比如社交网络中人与人的关系.地图数据.或是基因信息等等.RDBMS并不适合表达这类数据,而且由于海量数据的存在,让其显得捉襟见肘.NoSQL数据库的兴起,很好地解决了海 ...

  7. <转>ASP.NET学习笔记之理解MVC底层运行机制

    ASP.NET MVC架构与实战系列之一:理解MVC底层运行机制 今天,我将开启一个崭新的话题:ASP.NET MVC框架的探讨.首先,我们回顾一下ASP.NET Web Form技术与ASP.NET ...

  8. Spring MVC执行原理

    spring的MVC执行原理 1.spring mvc将所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责对请求 进行真正的处理工作. 2.DispatcherSer ...

  9. spring的MVC执行原理

    spring的MVC执行原理 1.spring mvc将所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责对请求 进行真正的处理工作. 2.DispatcherSer ...

随机推荐

  1. 解题:SHOI 2014 概率充电器

    题面 显然就是在求概率,因为期望乘的全是1....然后就推推推啊 设$fgg[i]$表示这个点父亲没给他充上电的概率,$sgg[i]$表示这个点子树(和它自己)没给他充上电的概率,然后这个点没充上电的 ...

  2. mybatis异常invalid comparison: java.util.Date and java.lang.String

    原文链接:http://blog.csdn.net/wanghailong_qd/article/details/50673144 mybatis异常invalid comparison: java. ...

  3. c++中的 Stl 算法(很乱别看)

    #include <iostream> #include <vector> #include <functional> #include <algorithm ...

  4. Nginx 连接限制和访问控制Nginx 连接限制和访问控制

    Nginx 连接限制和访问控制   前言 Nginx自带的模块支持对并发请求数进行限制, 还有对请求来源进行限制.可以用来防止DDOS攻击.阅读本文须知道nginx的配置文件结构和语法. 连接限制 l ...

  5. centos7搭建ELK Cluster集群日志分析平台

    应用场景:ELK实际上是三个工具的集合,ElasticSearch + Logstash + Kibana,这三个工具组合形成了一套实用.易用的监控架构, 很多公司利用它来搭建可视化的海量日志分析平台 ...

  6. 五大常见的MySQL高可用方案

      1. 概述 我们在考虑MySQL数据库的高可用的架构时,主要要考虑如下几方面: 1.1 如果数据库发生了宕机或者意外中断等故障,能尽快恢复数据库的可用性,尽可能的减少停机时间,保证业务不会因为数据 ...

  7. iOS设置tableViewCell之间的间距(去掉UItableview headerview黏性)

    经常在项目中遇到自定义cell的情况,而且要求cell之间有间距,但是系统没有提供改变cell间距的方法,怎么办? 方法1:自定义cell的时候加一个背景View,使其距离contentView的上下 ...

  8. python中的__call__

    如果python中的一个类定义了 __call__ 方法,那么这个类它的实例就可以作为函数调用,也就是实现了 () 运算符,即可调用对象协议 下面是一个简单的例子: class TmpTest: de ...

  9. FPGA学习笔记. DDS

    DDS原理 直接数字式频率合成器(Direct Digital Synthesizer) 频率计算公式 Fout = FW * Fclk / 2^N Fout 输出频率, Fw 频率控制字, N 位数 ...

  10. 恶意代码分析实战-确认EXE什么时候编译的

    场景 确认开源的后门在中毒机器上是什么版本,具有什么功能. 思路 1.查看样本PE里的编译时间 2.对照开源后门里组件的编译时间 技术点 查看NT头-TimeDateStamp struct IMAG ...