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

 

一、问题的引出

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

二、预备知识


2.一、MVC处理的流程讲解web

网上有不少讲解ASP.Net 的管道机制的,都讲解的很好,你们能够找找看,今天我来点不同的,经过Reflector,Debug进源码一步一步调试给你们看,下面开始吧:redis

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对象。在该方中有两个特别重要的方法,GetControllerTypeGetControllerInstance方法。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.二、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分别调用了LoadTempDataSaveTempData方法,再分别转到这两个方法内部,以下所示:

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

三、实现分布式的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.一、把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.二、把TempData的值存入到NoSQL Memcached中实现真正的分布式

关于Memcached的安装和操做请参考个人这篇博客:

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

 MemcacheHelper:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Web;
 5 using Memcached.ClientLibrary;
 6 
 7 namespace WebDemo.Models
 8 {
 9     public static class MemcacheHelper
10     {
11         private static MemcachedClient mc;
12 
13         static MemcacheHelper()
14         {
15             //经过客户端来进行memcached的集群配置,在插入数据的时候,使用一致性哈希算法,将对应的value值存入Memcached
16             String[] serverlist = { "127.0.0.1:11211" };
17 
18             // 初始化Memcached的服务池
19             SockIOPool pool = SockIOPool.GetInstance("test");
20             //设置服务器列表
21             pool.SetServers(serverlist);
22             //各服务器之间负载均衡的设置比例
23             pool.SetWeights(new int[] { 1 });
24             pool.Initialize();
25             //建立一个Memcached的客户端对象
26             mc = new MemcachedClient();
27             mc.PoolName = "test";
28             //是否启用压缩数据:若是启用了压缩,数据压缩长于门槛的数据将被储存在压缩的形式
29             mc.EnableCompression = false;
30             
31         }
32         /// <summary>
33         /// 插入值
34         /// </summary>
35         /// <param name="key"></param>
36         /// <param name="value"></param>
37         /// <param name="expiry">过时时间</param>
38         /// <returns></returns>
39         public static bool Set(string key, object value,DateTime expiry){
40             return mc.Set(key, value, expiry);
41         }
42         /// <summary>
43         /// 获取值
44         /// </summary>
45         /// <param name="key"></param>
46         /// <returns></returns>
47         public static object Get(string key)
48         {
49             return mc.Get(key);
50         }
51     }
52 }
View Code

 引用对应的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差很少,本身能够尝试一下。

四、总结:

这篇文章花了很长时间,但愿对你有帮助,若是你们觉的还能够的话,帮忙点下推荐。若是对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/

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

相关文章
相关标签/搜索