现在的位置: 首页 > 综合 > 正文

通过添加HTTP Header实现上下文数据在WCF的自动传递

2012年02月10日 ⁄ 综合 ⁄ 共 5201字 ⁄ 字号 评论关闭

多年之前,我写了一篇通过WCF扩展实现上下文信息从客户端自动传递到服务端的文章,其实现机制很简单:将上下文信息存放到SOAP Header进行传递。那么对于非SOAP消息的RESTful服务就不使用了。为了解决这个问题,我们可以将存放上下文信息的地方从SOAP Header替换成HTTP Header。这篇为你消息讲述具体的实现[源代码从这里下载]。

目录
一、 Ambient Context
二、ApplicationContext
三、创建ContextSender将上下文附加到请求消息的HTTP Header
四、创建ContextReceiver从请求消息中接收上下文
五、创建自定义终结点行为
六、如何使用ContextPropagationBehavior
七、看看HTTP请求消息的结构

一、 Ambient Context

在一个多层结构的应用中,我们需要传递一些上下文的信息在各层之间传递,比如:为了进行Audit,需要传递一些当前当前user profile的一些信息。在一些分布式的环境中也可能遇到context信息从client到server的传递。如何实现这种形式的Context信息的传递呢?我们有两种方案:

  • 将Context作为参数传递:将context作为API的一部分,context的提供者在调用context接收者的API的时候显式地设置这些Context信息,context的接收者则直接通过参数将context取出。这虽然能够解决问题,但决不是一个好的解决方案,因为API应该只和具体的业务逻辑有关,而context 一般是与非业务逻辑服务的,比如Audit、Logging等等。此外,将context纳入API作为其一部分,将降低API的稳定性, 比如,今天只需要当前user所在组织的信息,明天可能需求获取当前客户端的IP地址,你的API可以会经常变动,这显然是不允许的。
  • 创建Ambient Context来保存这些context信息:Ambient Context可以在不同的层次之间、甚至是分布式环境中每个节点之间共享或者传递。比如在ASP.NET 应用中,我们通过SessionSate来存储当前Session的信息;通过HttpContext来存储当前Http request的信息。在非Web应用中,我们通过CallContext将context信息存储在TLS(Thread Local Storage)中,当前线程下执行的所有代码都可以访问并设置这些context数据。

二、ApplicationContext

介于上面所述,我创建一个名为ApplicationContext的Ambient Context容器,Application Context实际上是一个dictionary对象,通过key-value pair进行context元素的设置,通过key获取相对应的context元素。Application Context通过CallContext实现,定义很简单:

   1: public class ApplicationContext: Dictionary<string, string>

   2: {

   3:     public const string KeyOfApplicationContext = "__ApplicationContext";

   4:     private ApplicationContext()

   5:     { }

   6:     public static ApplicationContext Current

   7:     {

   8:         get

   9:         {

  10:             if (HttpContext.Current != null)

  11:             {

  12:                 if (HttpContext.Current.Session[KeyOfApplicationContext] == null)

  13:                 {

  14:                     HttpContext.Current.Session[KeyOfApplicationContext] = new ApplicationContext();

  15:                 }

  16:                 return (ApplicationContext)HttpContext.Current.Session[KeyOfApplicationContext];

  17:             }

  18:  

  19:             if (CallContext.GetData(KeyOfApplicationContext) == null)

  20:             {

  21:                 CallContext.SetData(KeyOfApplicationContext, new ApplicationContext());

  22:             }

  23:             return (ApplicationContext)CallContext.GetData(KeyOfApplicationContext);

  24:         }

  25:         set

  26:         {

  27:             CallContext.SetData("__ApplicationContext", value);

  28:         }

  29:     }

  30:  

  31:     public string Username

  32:     {

  33:         get{return this.GetContextValue("__UserName");}

  34:         set{this["__UserName"] = value;}

  35:     }

  36:     public string Department

  37:     {

  38:         get { return this.GetContextValue("__Department"); }

  39:         set { this["__Department"] = value; }

  40:     }

  41:     private string GetContextValue(string key)

  42:     {

  43:         if (this.ContainsKey(key))

  44:         {

  45:             return (string)this[key];

  46:         }

  47:         return string.Empty;

  48:     }

  49: }

ApplicationContext本质上是个字典,静态属性Current用于设置和获取当前ApplicationContext。具体来说,根据应用类型的不同,我们分别将当前ApplicationContext存放在SessionState和CallContext中。而UserName和Department是为了编程方便而实现的两个原生的上下文元素。需要注意的是:字典元素的Key均以字符串”__”作为前缀。

三、创建ContextSender将上下文附加到请求消息的HTTP Header

实现上下文从客户端到服务端的自动传递需要解决两个问题:客户端将当前上下文附加到请求消息中,服务端则从请求消息获取上下文信息并作为当前的上下文。对于前者,我创建了一个自定义的ClientMessageInspector:ContextSender。在BeforeSendRequest方法中,我们将所有上下文元素置于请求消息的HTTP Header之中。

   1: public class ContextSender: IClientMessageInspector

   2: {

   3:     public void AfterReceiveReply(ref Message reply, object correlationState) { }

   4:     public object BeforeSendRequest(ref Message request, IClientChannel channel)

   5:     {

   6:         HttpRequestMessageProperty requestProperty;

   7:         if (!request.Properties.Keys.Contains(HttpRequestMessageProperty.Name))

   8:         {

   9:             requestProperty = new HttpRequestMessageProperty();

  10:         }

  11:         else

  12:         { 

  13:             requestProperty = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];

  14:         }

  15:         foreach(var context in ApplicationContext.Current)

  16:         {

  17:             requestProperty.Headers.Add(context.Key, context.Value.ToString());

  18:         }

  19:         return null;

  20:     }

  21: }

四、创建ContextReceiver从请求消息中接收上下文

对于服务端,请求消息的接收,以及对当前上下文的设定,实现在一个自定义CallContextInitializer中。该自定义CallContextInitializer起名为ContextReceiver,定义如下。而上下文的获取和设置实现在BeforeInvoke方法中,确保在服务操作在执行的时候当前上下文信息已经存在。在这里通过判断Header名称是否具有”__”前缀确实是否是基于上下文HTTP Header。

   1: public class ContextReceiver: ICallContextInitializer

   2: {

   3:     public void AfterInvoke(object correlationState) { }

   4:     public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)

   5:     {

   6:         HttpRequestMessageProperty requestProperty = (HttpRequestMessageProperty)message.Properties[HttpRequestMessageProperty.Name];

   7:         foreach (string key in requestProperty.Headers.Keys)

   8:         { 

   9:             if(key.StartsWith("__"))

  10:             {

  11:                 ApplicationContext.Current[key] = requestProperty.Headers[key];

  12:             }

  13:         }            

  14:         return null;

  15:     }

  16: }

五、创建自定义终结点行为

为了将上面创建的两个自定义对象,ContextSender和ContextReceiver,最终应用到WCF的消息处理运行时框架中,我们创建了如下所示的自定义的终结点行为:ContextPropagationBehavior。而ContextSender和ContextReceiver的应用分别实现在方法ApplyClientBehavior和ApplyDispatchBehavior方法中。

   1: public class ContextPropagationBehavior: IEndpointBehavior

   2: {

   3:     public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { }

   4:     public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)

   5:     {

   6:         clientRuntime.MessageInspectors.Add(new ContextSender());

   7:     }

   8:     public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)

   9:     {

  10:         foreach (var operation in endpointDispatcher.DispatchRuntime.Operations)

  11:         {

  12:             operation.CallContextInitializers.Add(new ContextReceiver());

  13:         }

  14:     }

  15:     public void Validate(ServiceEndpoint endpoint) { }

  16: }

为了使ContextPropagationBehavior能够需要通过配置的方式进行使用,我们定义它对应的BehaviorExtensionElement:ContextPropagationBehaviorElement。

 

抱歉!评论已关闭.