一起谈.NET技术,如何实现对上下文(Context)数据的统一管理 [提供源代码下载]

在应用开发中,我们经常需要设置一些上下文(Context)信息,这些上下文信息一般基于当前的会话(Session),比如当前登录用户的个人信息;或者基于当前方法调用栈,比如在同一个调用中涉及的多个层次之间数据。在这篇文章中,我创建了一个称为ApplicationContext的组件,对上下文信息进行统一的管理。[Source Code从这里下载]

一、基于CallContext和HttpSessionState的ApplicationContext

如何实现对上下文信息的存储,对于Web应用来说,我们可以借助于HttpSessionState;对于GUI应用来讲,我们则可以使用CallConext。ApplicationContext完全是借助于这两者建立起来的,首先来看看其定义:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Runtime.Remoting.Messaging;
   4: using System.Web;
   5: namespace Artech.ApplicationContexts
   6: {
   7:     [Serializable]
   8:     public class ApplicationContext:Dictionary<string, object>
   9:     {
  10:         public const string ContextKey = "Artech.ApplicationContexts.ApplicationContext";
  11:         
  12:         public static ApplicationContext Current
  13:         {
  14:             get
  15:             {
  16:                 if (null != HttpContext.Current)
  17:                 {
  18:                     if (null == HttpContext.Current.Session[ContextKey])
  19:                     {
  20:                         HttpContext.Current.Session[ContextKey] = new ApplicationContext();
  21:                     }
  22:  
  23:                     return HttpContext.Current.Session[ContextKey] as ApplicationContext;
  24:                 }
  25:  
  26:                 if (null == CallContext.GetData(ContextKey))
  27:                 {
  28:                     CallContext.SetData(ContextKey, new ApplicationContext());
  29:                 }
  30:                 return CallContext.GetData(ContextKey) as ApplicationContext;                
  31:             }
  32:         }        
  33:     }
  34: }

为了使ApplicationContext定义得尽可能地简单,我直接让它继承自Dictionary,而从本质上讲ApplicationContext就是一个基于字典的上下文数据的容器。静态属性Current表示当前的ApplicationConext,如何当前存在HttpContext,则使用HttpConext的Session,否则使用CallConext。Session和CallConext的采用相同的Key:Artech.ApplicationContexts.ApplicationContext。你可以采用如下的方式对上下文数据进行设置和读取。

   1: //设置
   2: ApplicationContext.Current["UserName"] = "Foo";
   3: //读取
   4: var userName = ApplicationContext.Current["UserName"];

二、ApplicationContext在异步调用中的局限

在同步调用的情况下,ApplicationContext可以正常工作。但是对于异步调用,当前的上下文信息并不能被传播到另一个线程中去。接下来,我们将给出一个简单的例子,模拟通过ApplicationContext存贮用户的Profile信息,为此,我定义了如下一个Profile类,属性FirstName、LastName和Age代表三个Profile属性。

   1: using System;
   2: namespace Artech.ApplicationContexts
   3: {
   4:     [Serializable]
   5:     public class Profile
   6:     {
   7:         public string FirstName
   8:         { get; set; }
   9:         public string LastName
  10:         { get; set; }
  11:         public int Age
  12:         { get; set; }
  13:         public Profile()
  14:         {
  15:             this.FirstName = "N/A";
  16:             this.LastName = "N/A";
  17:             this.Age = 0;
  18:         }
  19:     }
  20: }

为了便于操作,我直接在ApplicationContext定义了一个Profile属性,返回值类型为Profile,定义如下:

   1: [Serializable]
   2: public class ApplicationContext : Dictionary<string, object>
   3: {
   4:     public const string ProfileKey = "Artech.ApplicationContexts.ApplicationContext.Profile";   
   5:  
   6:     public Profile Profile
   7:     {
   8:         get
   9:         {
  10:             if (!this.ContainsKey(ProfileKey))
  11:             {
  12:                 this[ProfileKey] = new Profile();
  13:             }
  14:             return this[ProfileKey] as Profile;
  15:         }
  16:     }
  17: }

现在我们来看看ApplicationContext在一个简单的Windows Form应用中的使用情况。在如右图(点击看大图)所示的一个Form中,我们可以进行Profile的设置和获取。其中“Get [Sync]”和“Get [Async]”按钮分别模拟对存贮于当前ApplicationContext中的Profile信息进行同步异步方式的获取,通过点击Save按钮将设置的Profile信息保存到当前的ApplicationContext之中。

“Save”、“Clear”、“Get [Sync]”和“Get [Async]”响应的事件处理程序如下面的代码所示:

   1: using System;
   2: using Artech.ApplicationContexts;
   3: namespace WindowsApp
   4: {
   5:     public partial class ProfileForm : System.Windows.Forms.Form
   6:     {
   7:         public ProfileForm()
   8:         {
   9:             InitializeComponent();
  10:         }
  11:  
  12:         private void buttonSave_Click(object sender, EventArgs e)
  13:         {
  14:             ApplicationContext.Current.Profile.FirstName = this.textBoxFirstName.Text.Trim();
  15:             ApplicationContext.Current.Profile.LastName = this.textBoxLastName.Text.Trim();
  16:             ApplicationContext.Current.Profile.Age = (int)this.numericUpDownAge.Value;
  17:         }
  18:  
  19:         private void buttonClear_Click(object sender, EventArgs e)
  20:         {
  21:             this.textBoxFirstName.Text = string.Empty;
  22:             this.textBoxLastName.Text = string.Empty;
  23:             this.numericUpDownAge.Value = 0;
  24:         }
  25:  
  26:         private void buttonSyncGet_Click(object sender, EventArgs e)
  27:         {
  28:             this.textBoxFirstName.Text = ApplicationContext.Current.Profile.FirstName;
  29:             this.textBoxLastName.Text = ApplicationContext.Current.Profile.LastName;
  30:             this.numericUpDownAge.Value = ApplicationContext.Current.Profile.Age;
  31:         }
  32:  
  33:         private void buttonAsyncGet_Click(object sender, EventArgs e)
  34:         {
  35:             GetProfile getProfileDel = () =>
  36:                 {
  37:                     return ApplicationContext.Current.Profile;
  38:                 };
  39:             IAsyncResult asynResult = getProfileDel.BeginInvoke(null, null);
  40:             Profile profile = getProfileDel.EndInvoke(asynResult);
  41:             this.textBoxFirstName.Text = profile.FirstName;
  42:             this.textBoxLastName.Text = profile.LastName;
  43:             this.numericUpDownAge.Value = profile.Age;
  44:         }
  45:  
  46:         delegate Profile GetProfile();       
  47:     }
  48: }

运行上面的程序,你会发现你设置的Profile信息,可以通过点击“Get [Sync]”按钮显示出来,。而你点击“Get [Async]”按钮的时候,却不能显示正确的值。具体的结果如下图(点击看大图)所示。三张截图分别模拟的点击“Save”、Get [Sync]”和“Get [Async]”按钮之后的显示。

上面演示的是ApplicationContext在Windows Form应用中的使用,实际上在ASP.NET应用中,你依然会得到相同的结果。通过ApplicaticationContext的定义我们可以知道,ApplicationContext对象最终保存在CallContext或者HttpSessionState中。Windows Form应用采用的是前者,而Web应用则采用后者。

也就是说,无论是CallContext还是HttpContext(HttpSessionState最终依附于当前的HttpContext),都不能自动实现数据的跨线程传递。至于原因,需要从两种不同的CallContext说起。

三、LogicalCallContext V.S. IllogicalCallContext

CallContext定义在System.Runtime.Remoting.Messaging.CallContext命名空间下,是类似于方法调用的线程本地存储区的专用集合对象,并提供对每个逻辑执行线程都唯一的数据槽。数据槽不在其他逻辑线程上的调用上下文之间共享。当 CallContext 沿执行代码路径往返传播并且由该路径中的各个对象检查时,可将对象添加到其中。CallContext定义如下:

   1: [Serializable, ComVisible(true), SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)]
   2: public sealed class CallContext
   3: {
   4:     
   5:     public static void FreeNamedDataSlot(string name);
   6:     public static object GetData(string name);
   7:     public static Header[] GetHeaders();    
   8:     public static object LogicalGetData(string name);
   9:     public static void LogicalSetData(string name, object data);
  10:     public static void SetData(string name, object data);
  11:     public static void SetHeaders(Header[] headers);
  12:    
  13:     public static object HostContext { get; [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)] set; }   
  14: }

CallContext具有如下两种不同的类型:

  • LogicalCallContext:LogicalCallContext 类是在对远程应用程序域进行方法调用时使用的 CallContext 类的一个版本。CallContext 是类似于方法调用的线程本地存储的专用集合对象,并提供对每个逻辑执行线程都唯一的数据槽。数据槽不在其他逻辑线程上的调用上下文之间共享。当 CallContext 沿执行代码路径往返传播并且由该路径中的各个对象检查时,可将对象添加到其中。当对另一个 AppDomain 中的对象进行远程方法调用时,CallContext 类将生成一个与该远程调用一起传播的 LogicalCallContext。只有公开 ILogicalThreadAffinative 接口并存储在 CallContext 中的对象被在 LogicalCallContext 中传播到 AppDomain 外部。不支持此接口的对象不在 LogicalCallContext 实例中与远程方法调用一起传输。
  • IllogicalCallContext:IllogicalCallContext和LogicalCallContext 相反,仅仅是存储与当前线程的TLS中,并不能随着跨线程的操作执行实现跨线程传播。

HttpContext本质上也通过CallContext存储的,不过HttpContext本身是作为IllogicalCallContext的形式保存在CallContext,这也正是为何基于HttpSessionState的ApplicationContext也不能解决多线程的问题的真正原因。

四、让CallContext实现跨线程传播

也就是说,如果想让CallContext的数据被自动传递当目标线程,只能将其作为LogicalCallContext。我们有两种当时将相应的数据存储为LogicalCallContext:调用CallContext的静态方法LogicalSetData,或者放上下文类型实现ILogicalThreadAffinative接口。

也就说,在ApplicationContext的Current方法中,我们只需要将CallContext.SetData(ContextKey, new ApplicationContext());替换成CallContext.LogicalSetData(ContextKey, new ApplicationContext());即可:

   1: [Serializable]
   2: public class ApplicationContext : Dictionary<string, object>
   3: {
   4:     //其他成员
   5:     public static ApplicationContext Current
   6:     {
   7:         get
   8:         {
   9:             //...
  10:             if (null == CallContext.GetData(ContextKey))
  11:             {
  12:                 CallContext.LogicalSetData(ContextKey, new ApplicationContext());
  13:             }
  14:             return CallContext.GetData(ContextKey) as ApplicationContext;
  15:         }
  16:     }
  17: }

或者说,我们直接让ApplicationContext实现ILogicalThreadAffinative接口。由于该ILogicalThreadAffinative没有定义任何成员,所有我们不需要添加任何多余的代码:

   1: [Serializable]
   2: public class ApplicationContext : Dictionary<string, object>, ILogicalThreadAffinative
   3: {
   4:     //...
   5: }

现在再次运行我们上面的Windows Form应用,点击“Get [Async]”按钮后将会得到正确的Profile显示,有兴趣的读者不妨下载实例代码试试。但是当运行Web应用的时候,依然有问题,为此我们需要进行一些额外工作。

五、通过ASP.NET扩展解决Web应用的异步调用问题

在上面我们已经提过,ASP.NET管道将当前的HttpContext的存储与基于当前线程的CallContext中,而存贮的形式是IllogicalCallContext而非LogicalCallContext,说在非请求处理线程是获取不到当前HttpContext的。针对我们ApplicationContext就意味着:在Web应用中,主线程实际上操作的是当前HttpContext的Session,而另外一个线程中则是直接使用CallConext。

那么如果我们们能够将存储与当前HttpContext的Session中的ApplicationContext作为LogicalCallContext拷贝到CallContext中,那么在进行异步调用的时候,就能自动传递到另外一个线程之中了。此外,由于ASP.NET采用线程池的机制处理HTTP请求,我们需要将当前CallContext的数据进行及时清理,以免被另外一个请求复用。我们可以有很多方式实现这样的功能,比如在Global.asax中定义响应的事件处理方法,自定义HttpApplication或者自定义HttpModule。

如果自定义HttpModule,我们可以注册HttpApplication的两个事件:PostAcquireRequestState和PreSendRequestContent,分别实现对当前ApplicationContext的拷贝和清理。具体定义如下:

   1: using System.Runtime.Remoting.Messaging;
   2: using System.Web;
   3: namespace Artech.ApplicationContexts
   4: {
   5:     public class ContextHttpModule:IHttpModule
   6:     {
   7:  
   8:         public void Dispose(){}
   9:         public void Init(HttpApplication context)
  10:         {
  11:             context.PostAcquireRequestState += (sender, args) =>
  12:                 {
  13:                     CallContext.SetData(ApplicationContext.ContextKey, ApplicationContext.Current);
  14:                 };
  15:             context.PreSendRequestContent += (sender, args) =>
  16:             {
  17:                 CallContext.SetData(ApplicationContext.ContextKey, null);
  18:             };
  19:         }
  20:     }
  21: }

我们只需要通过如下的配置将其应用到我们的程序之中即可:

   1: xml version="1.0"?>
   2: <configuration>
   3:   <system.web>
   4:     <httpModules>
   5:       <add name="ContextHttpModule" type="Artech.ApplicationContexts.ContextHttpModule,Artech.ApplicationContexts.Lib"/>
   6:     httpModules>
   7:   system.web> 
   8: configuration>

作者:Artech

出处:http://artech.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

时间: 2024-10-24 14:50:02

一起谈.NET技术,如何实现对上下文(Context)数据的统一管理 [提供源代码下载]的相关文章

如何实现对上下文(Context)数据的统一管理 [提供源代码下载]

在应用开发中,我们经常需要设置一些上下文(Context)信息,这些上下文信息一般基于当前的会话(Session),比如当前登录用户的个人信息:或者基于当前方法调用栈,比如在同一个调用中涉及的多个层次之间数据.在这篇文章中,我创建了一个称为ApplicationContext的组件,对上下文信息进行统一的管理.[Source Code从这里下载] 一.基于CallContext和HttpSessionState的ApplicationContext 如何实现对上下文信息的存储,对于Web应用来说

一起谈.NET技术,使用VS2010的Database项目模板统一管理数据库对象

Visual Studio 2010 有一个数据库项目模板:Visual Studio Database Project(以下简称VSDP),VS 2003/2005/2008也有类似的项目,在VS2010上的得到了很大的加强,现在还具备了智能感知,构建时验证和自动部署功能,VSDP是针对典型的数据库开发任务而设计的,可以对原有数据库反向工程,添加表,存储过程和其他数据库项目,而且有选择性地将修改部署到目标数据库中.他的主要特性有: 1.模型对比(Schema Compare) 在项目的维护和升

在应用开发中实现对上下文(Context)数据的统一管理

在应用开发中,我们经常需要设置一些上下文(Context)信息,这些上下文信息一般基于当前的会话 (Session),比如当前登录用户的个人信息:或者基于当前方法调用栈,比如在同一个调用中涉及的多 个层次之间数据.在这篇文章中,我创建了一个称为ApplicationContext的组件,对上下文信息进行统一 的管理 一.基于CallContext和HttpSessionState的ApplicationContext 如何实现对上下文信息的存储,对于Web应用来说,我们可以借助于HttpSess

一起谈.NET技术,ASP.NET页面间数据传递的方法

00.引言 Web页面是无状态的, 服务器对每一次请求都认为来自不同用户,因此,变量的状态在连续对同一页面的多次请求之间或在页面跳转时不会被保留.在用ASP.NET 设计开发一个Web系统时, 遇到一个重要的问题是如何保证数据在页面间进行正确.安全和高效地传送,Asp.net 提供了状态管理等多种技术来解决保存和传递数据问题,以下来探讨.NET 下的解决此问题的各种方法和各自的适用场合. 1.ASP.NET页面间数据传递的各种方法和分析 1.1 使用Querystring 方法 QueryStr

一起谈.NET技术,浅析五大ASP.NET数据控件

ASP.NET数据控件综述: 1. 前3个(GridView 控件,DetailsView 控件,FormView 控件)用于呈现多条记录,后面2个(Repeater 控件,DataList 控件)用于呈现单条数据明细,即常用的记录明细. 2. GridView和DetailsView控件的布局固定,自定义数据显示的布局功能有限,一般适合布局简单的数据呈现. 3. DataList, Repeater和FormView数据控件都有很强的自定义布局能力,如果数据呈现需要较为复杂的布局方案,这3个控

一起谈.NET技术,ASP.NET 4过滤数据新控件QueryExtender

在ASP.NET 4中的一个新的控件是QueryExtender.QueryExtender控件是为了简化LinqDatasource或EntityDataSource控件返回的数据过滤而设计的,它主要是将过滤数据的逻辑从数据控件中分离出来.使用QueryExtender是十分容易的事,只需要简单往页面上增加一个QueryExtender控件,指定其数据源是哪个控件并设置过滤条件就可以了.比如,当在页面中显示产品的信息时,你可以使用该控件去显示那些在某个价格范围的产品,也可以搜索用户指定名称的产

一起谈.NET技术,Xml日志记录文件最优方案(附源代码)

Xml作为数据存储的一种方式,当数据非常大的时候,我们将碰到很多Xml处理的问题.通常,我们对Xml文件进行编辑的最直接的方式是将xml文件加载到XmlDocument,在内存中来对XmlDocument进行修改,然后再保存到磁盘中.这样的话我们将不得不将整个XML document 加载到内存中,这明显是不明智的(对于大数据XML文件来说,内存将消耗很大,哥表示鸭梨很大).下面我们将要讲的是如何高效的增加内容(对象实体内容)到xml日志文件中. (一)设计概要 总体来说,我们将(通过代码)创建

WCF技术剖析之二十九:换种不同的方式调用WCF服务[提供源代码下载]

我们有两种典型的WCF调用方式:通过SvcUtil.exe(或者添加Web引用)导入发布的服务元数据生成服务代理相关的代码和配置:通过ChannelFactory<TChannel>创建服务代理对象.在这篇文章中,我们采用一种独特的方式进行服务的调用.从本质上讲,我们只要能够创建于服务端相匹配的终结点,就能够实现正常的服务调用.在WCF客户端元数据架构体系中,利用MetadataExchangeClient可以获取服务的元数据,而利用MetadataImporter将获取的元数据导入成Serv

一起谈.NET技术,WPF 企业内训全程实录(中)

摘要 WPF企业内训全程实录由于文章比较长,所以一共拆分成了三篇,上篇WPF企业内训全程实录(上)主要讲了基础,这篇作为该实录的中篇,起着承上启下的作用,主要讲解开发模式.团队协作及应用框架.其实如果大家仔细看目录,可以发现我安排的顺序是首先讲解最基本的概念和基础内容.然后过渡到开发模式及框架.最后结合其他技术和项目实际运用,这也是学习并应用一门技术最好的流程.上篇实际上主要有两个侧重点:一则就是理清脉络--历史渊源.概念引入及基本阐述:二则是讲解WPFBasic--主要讲解WPF的每个知识点,