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 > );
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,代码如下:

 public class MyControllerFactory:DefaultControllerFactory
{
public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
var iController= base.CreateController(requestContext, controllerName); var controller = iController as Controller;
controller.TempDataProvider = new CrossSessionTempData2(); return iController;
}
}

3.1、把TempData的值存入到cache中

 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)
{
var cache = controllerContext.HttpContext.Cache;
if (cache != null)
{
Dictionary<string, object> dictionary =cache["__ControllerTempData"] as Dictionary<string, object>;
if (dictionary != null)
{
cache .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");
}
var cache = controllerContext.HttpContext.Cache;
bool flag = (values != null) && (values.Count > );
if (cache == null)
{
if (flag)
{
throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);
}
}
else if (flag)
{
cache ["__ControllerTempData"] = values;
}
else if (cache ["__ControllerTempData"] != null)
{
cache .Remove("__ControllerTempData");
}
}
}
}

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

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

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Caching;
using System.Web.Mvc; namespace CrossSessionTempData.Infrastructure
{
public class CrossSessionTempData2 : ITempDataProvider
{ internal const string TempDataSessionStateKey = "__ControllerTempData"; public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
{
var cache = controllerContext.HttpContext.Cache; if (cache != null)
{
Dictionary<string, object> dictionary = cache[TempDataSessionStateKey] as Dictionary<string, object>;
if (dictionary != null)
{
cache.Remove(TempDataSessionStateKey);
return dictionary;
}
}
return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
} /// <summary>Saves the specified values in the temporary data dictionary by using the specified controller context.</summary>
/// <param name="controllerContext">The controller context.</param>
/// <param name="values">The values.</param>
/// <exception cref="T:System.InvalidOperationException">An error occurred the session context was being retrieved.</exception>
public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
var cache = controllerContext.HttpContext.Cache;
bool flag = values != null && values.Count > ;
if (cache == null)
{
if (flag)
{
throw new InvalidOperationException("");
}
}
else
{
CacheDependency dp = new CacheDependency(controllerContext.HttpContext.Server.MapPath("/Data/123.txt"));
if (flag)
{ cache.Insert(TempDataSessionStateKey, values, dp); return;
}
if (cache[TempDataSessionStateKey] != null)
{
cache.Remove(TempDataSessionStateKey);
}
}
}
}
}

添加一个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:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Memcached.ClientLibrary; namespace WebDemo.Models
{
public static class MemcacheHelper
{
private static MemcachedClient mc; static MemcacheHelper()
{
//通过客户端来进行memcached的集群配置,在插入数据的时候,使用一致性哈希算法,将对应的value值存入Memcached
String[] serverlist = { "127.0.0.1:11211" }; // 初始化Memcached的服务池
SockIOPool pool = SockIOPool.GetInstance("test");
//设置服务器列表
pool.SetServers(serverlist);
//各服务器之间负载均衡的设置比例
pool.SetWeights(new int[] { });
pool.Initialize();
//创建一个Memcached的客户端对象
mc = new MemcachedClient();
mc.PoolName = "test";
//是否启用压缩数据:如果启用了压缩,数据压缩长于门槛的数据将被储存在压缩的形式
mc.EnableCompression = false; }
/// <summary>
/// 插入值
/// </summary>
/// <param name="key">建</param>
/// <param name="value">值</param>
/// <param name="expiry">过期时间</param>
/// <returns></returns>
public static bool Set(string key, object value,DateTime expiry){
return mc.Set(key, value, expiry);
}
/// <summary>
/// 获取值
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static object Get(string key)
{
return mc.Get(key);
}
}
}

引用对应的dll:

自定义的我们的DataProvider:

  public class CrossSessionTempData2 : ITempDataProvider
{ internal const string TempDataSessionStateKey = "__ControllerTempData"; public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
{ Dictionary<string, object> dictionary = MemCaheHelper.Get(TempDataSessionStateKey) as Dictionary<string, object>;
if (dictionary != null)
{
MemCaheHelper.Set(TempDataSessionStateKey, dictionary, DateTime.MinValue);
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");
} bool flag = values != null && values.Count > ;
if (flag)
{ MemCaheHelper.Set(TempDataSessionStateKey, values,DateTime.Now.AddMinutes());
return;
} if (MemCaheHelper.Get(TempDataSessionStateKey) != null)
{
MemCaheHelper.Set(TempDataSessionStateKey,values,DateTime.MinValue);
} }
}

运行效果完美!!!!至此,我们的分布式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/

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

窥探ASP.Net MVC底层原理 实现跨越Session的分布式TempData的更多相关文章

  1. 在ASP.Net MVC 中如何实现跨越Session的分布式TempData

    Hi,guys!Long time no see! 1.问题的引出 我相信大家在项目中都使用过TempData,TempData是一个字典集合,一般用于两个请求之间临时缓存数据或者页面之间传递消息.也 ...

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

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

  3. MVC底层原理

    窥探ASP.Net MVC底层原理 实现跨越Session的分布式TempData 1.问题的引出 我相信大家在项目中都使用过TempData,TempData是一个字典集合,一般用于两个请求之间临时 ...

  4. ASP.NET MVC程序传值方式:ViewData,ViewBag,TempData和Session

    转载原地址 http://www.cnblogs.com/sunshineground/p/4350216.html 在ASP.NET MVC中,页面间Controller与View之间主要有以下几种 ...

  5. Asp.Net MVC 请求原理分析

    分析Asp.Net MVC的请求过程,我们从以下几方面看: 配置:IIS网站的配置可以分为两个块:全局 Web.Config 和本站 Web.Config . Asp.Net Routing属于全局性 ...

  6. Asp.net MVC使用Model Binding解除Session, Cookie等依赖

    上篇文章"Asp.net MVC使用Filter解除Session, Cookie等依赖"介绍了如何使用Filter来解除对于Session, Cookie的依赖.其实这个也可以通 ...

  7. Spring MVC 底层原理

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

  8. ASP.NET MVC判断基于Cookie的Session过期

    当我们第一次请求访问时,可以看到Response的Set-Cookie里添加了ASP.NET_SessionId的值,以后再访问时可以看到Resquest里的Cookie已经包含这个Key.   Se ...

  9. ASP.NET MVC 原理

    我出了份卷子做面试题,其中之一就是要求说说ASP.NET MVC的原理.感觉太空泛了,谁能说得准呢? 但是,如果站在我这个面试官立场,面试题好多时并不要求有标准答案,可能也没有什么标准答案,主要是通过 ...

随机推荐

  1. Spark之UDAF

    import org.apache.spark.sql.{Row, SparkSession} import org.apache.spark.sql.expressions.{MutableAggr ...

  2. aspectj eclipse4.6下载地址

    http://www.eclipse.org/ajdt/downloads/#46zips

  3. 内核线程的进程描述符task_struct中的mm和active_mm

    task_struct进程描述符中包含两个跟进程地址空间相关的字段mm, active_mm, struct task_struct { // ... struct mm_struct *mm; st ...

  4. c/c++ 线性表之单向循环链表

    c/c++ 线性表之单向循环链表 线性表之单向循环链表 不是存放在连续的内存空间,链表中的每个节点的next都指向下一个节点,最后一个节点的下一个节点不是NULL,而是头节点.因为头尾相连,所以叫单向 ...

  5. shell 函数用法

    近期在学习shell编程方面的知识,写的不怎么好,请大家多多指点,下面给大家分享一下shell函数的用法. 我们为什么要用shell函数? 简单的说,函数的作用就是把程序多次调用相同的代码部分定义成一 ...

  6. IPerf——网络测试工具介绍与源码解析(2)

    对于IPerf源码解析,我是基于2.0.5版本在Windows下执行的情况进行分析的,提倡开始先通过对源码的简单修改使其能够在本地编译器运行起来,这样可以打印输出一些中间信息,对于理解源码的逻辑,程序 ...

  7. 4.12Python数据处理篇之Matplotlib系列(十二)---绘图风格的介绍

    目录 目录 前言 (一)不同风格 1.说明: 2.使用: 3.代码使用: (二)例子演示 1.dark_background 2.bmh 3.fivethirtyeight 4.ggplot 5.gr ...

  8. March 09th, 2018 Week 10th Friday

    All good things must come to an end. 好景无常. Love is when the other person's happiness is more importa ...

  9. TestFlight的使用--再也不用担心环境打错了

    转赞请注明出处:http://www.cnblogs.com/zhanggui/p/7039651.html 一.前言 在iOS开发过程中,难免会遇到各种Bug.因此你会去解决所有的Bug,然后提交到 ...

  10. Nunit单元测试入门学习随笔(一)

    Nunit单元测试 一.插件安装与项目关联 选择工具~扩展和更新 点击联机~搜索Nunit安装图内三个插件 新建单元测试项目 勾选项目引用 二.Nunit学习 1.了解单元测试 单元测试在我的理解是测 ...