加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > asp.Net > 正文

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

发布时间:2020-12-16 09:05:26 所属栏目:asp.Net 来源:网络整理
导读:多年之前,我写了一篇通过WCF扩展实现上下文信息从客户端自动传递到服务端的文章,其实现机制很简单:将上下文信息存放到SOAP Header进行传递。那么对于非SOAP消息的RESTful服务就不使用了。为了解决这个问题,我们可以将存放上下文信息的地方从SOAP Header

多年之前,我写了一篇通过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>
   3:     const string KeyOfApplicationContext = "__ApplicationContext";
   5:     { }
   7:     {
   9:         {
  11:             {
  14:                     HttpContext.Current.Session[KeyOfApplicationContext] = new ApplicationContext();
  16:                 return (ApplicationContext)HttpContext.Current.Session[KeyOfApplicationContext];
  18:? 
  20:             {
  22:             }
  24:         }
  26:         {
  28:         }
  30:? 
  32:     {
  34:         set{this["__UserName"] = value;}
  36:     string Department
  38:         get { "__Department"); }
  40:     }
  42:     {
  44:         {
  46:         }
  48:     }
  
void AfterReceiveReply(ref Message reply,1)">object correlationState) { }
   5:     {
   7:         if (!request.Properties.Keys.Contains(HttpRequestMessageProperty.Name))
   9:             requestProperty = new HttpRequestMessageProperty();
  11:         else
  13:             requestProperty = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
  15:         foreach(var context in ApplicationContext.Current)
  17:             requestProperty.Headers.Add(context.Key,context.Value.ToString());
  19:         null;
  21: }

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

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

void AfterInvoke(object BeforeInvoke(InstanceContext instanceContext,IClientChannel channel,Message message)
foreach (string key in requestProperty.Headers.Keys)
   9:             if(key.StartsWith("__"))
  11:                 ApplicationContext.Current[key] = requestProperty.Headers[key];
  13:         }            
  15:     }
class ContextPropagationBehavior: IEndpointBehavior
void ApplyClientBehavior(ServiceEndpoint endpoint,System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
   7:     }
   9:     {
  11:         {
  13:         }
  15:     void Validate(ServiceEndpoint endpoint) { }
class ContextPropagationBehaviorElement : BehaviorExtensionElement
   4:     {
   6:     }
protected override object CreateBehavior()
  11:     }
   1: [ServiceContract]
   3: {
   5:     [WebGet]
   7: }

而服务类型很简单。

public ApplicationContext GetContext()
   7: }

假设我们采用自我寄宿的方式,我们创建的自定义终结点行为通过如下的配置应用到服务的终结点上。而从配置上我们也可以看到,我们并没有采用基于SOAP的消息交换,而是采用JSON的消息编码方式。

<configuration>
   8:           webHttp defaultOutgoingResponseFormat="Json" />
  15: Artech.ContextPropagation.Lib,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null"  16:         17:       18:     services  19:       service ="Service.ContextTestService"  20:         endpoint address="http://127.0.0.1/testService" behaviorConfiguration="contextPropagation"
   4:            5:                6:                    7:                     contextPropagation    8:                        9:                   12:         client  13:             =http://127.0.0.1/testservice
  15:                 ="Service.Interface.IContextTest" ="contextTestService"   16:           17:           18:               19:                   20:               21:         >       
   1: ApplicationContext.Current.Username = "Zhan San";
   3: using (ChannelFactory<IContextTest> channelFactory = new ChannelFactory<IContextTest>("contextTestService"))
   5:     IContextTest proxy = channelFactory.CreateChannel();
   7:     foreach (var item in context)
   9:         Console.WriteLine("{0,-20}:{1}",item.Key,item.Value);
  11: }

输出结果充分地证明了客户端设置的上下文被成功地传播到了服务端。

2: __Department :IT

七、看看HTTP请求消息的结构

为了更加清楚地证实客户端设置的当前上下文是否存在于请求消息中,我们可以通过Fildder查看整个HTTP请求消息(你需要将IP地址127.0.0.1替换成你的主机名)。整个HTTP请求消息如下所示,从中我们可以清楚地看到两个上下文项存在于HTTP Header列表中。