• MVC底层原理


     

    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:WindowsMicrosoft.NETFramework64v4.0.30319Config找到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:WindowsMicrosoft.NetassemblyGAC_MSILSystem.Web.Mvcv4.0_4.0.0.0__31bf3856ad364e35System.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:

     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差不多,自己可以尝试一下。

    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/

  • 相关阅读:
    HTML学习笔记
    "IIS无法启动"问题解决方法
    NET访问MySQl数据库中文乱码解决
    珍爱生命,远离肥胖,远离过劳死
    Bcp 命令注意事项
    阿里云万郁香:多样付费选择构筑成本最优的弹性体验
    性能提升40%!阿里云神龙大数据加速引擎获TPCxBB世界排名第一
    阿里云王志坤:强劲可靠、无处不在的云,为创新保驾护航
    发现新视界——视觉计算将如何改变生产方式
    Soul运维总监尤首智:企业如何从0到1建设云上运维体系
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/7055963.html
Copyright © 2020-2023  润新知