[原创]WCF后续之旅(6): 通过WCF Extension实现Context信息的传递

在上一篇文章中,我们讨论了如何通过CallContextInitializer实现Localization的例子,具体的做法是将client端的culture通过SOAP header传到service端,然后通过自定义的CallContextInitializer设置当前方法执行的线程culture。在client端,当前culture信息是通过OperationContext.Current.OutgoingMessageHeaders手工至于SOAP Header中的。实际上,我们可以通过基于WCF的另一个可扩展对象来实现这段逻辑,这个可扩展对象就是MessageInspector。我们今天来讨论MessageInspector应用的另外一个场景:如何通过MessageInspector来传递Context信息。

1. 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数据。

2、Application Context

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

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> namespace Artech.ContextPropagation
{
[Serializable]
publicclassApplicationContext:Dictionary<string,object>
{
privateconststringCallContextKey="__ApplicationContext";
internalconststringContextHeaderLocalName="__ApplicationContext";
internalconststringContextHeaderNamespace="urn:artech.com";

privatevoidEnsureSerializable(objectvalue)
{
if(value==null)
{
thrownewArgumentNullException("value");
}

if(!value.GetType().IsSerializable)
{
thrownewArgumentException(string.Format("Theargumentofthetype\"{0}\"isnotserializable!",value.GetType().FullName));
}

}


publicnewobjectthis[stringkey]
{
get
{
returnbase[key];
}

set
{
this.EnsureSerializable(value);
base[key]=value;
}

}


publicintCounter
{
get
{
return(int)this["__Count"];
}

set
{
this["__Count"]=value;
}

}


publicstaticApplicationContextCurrent
{
get
{
if(CallContext.GetData(CallContextKey)==null)
{
CallContext.SetData(CallContextKey,
newApplicationContext());
}


returnCallContext.GetData(CallContextKey)asApplicationContext;
}

set
{
CallContext.SetData(CallContextKey,value);
}

}

}

}

由于此Context将会置于SOAP Header中从client端向service端进行传递,我们需要为此message header指定一个local name和namespace,那么在service端,才能通过此local name和namespace获得此message header。同时,在lcoal domain, client或者service,context是通过CallContext进行存取的,CallContext也是一个类似于disctionary的结构,也需要为此定义一个Key:

private const string CallContextKey = "__ApplicationContext"; internal const string ContextHeaderLocalName = "__ApplicationContext";
internal const string ContextHeaderNamespace = "urn:artech.com";

由于ApplicaitonContext直接继承自Dictionary<string,object>,我们可以通过Index进行元素的设置和提取,考虑到context的跨域传播,需要进行序列化,所以重写了Indexer,并添加了可序列化的验证。为了后面演示方面,我们定义一个context item:Counter。

Static类型的Current属性通过CallContext的SetData和GetData方法对当前的ApplicationContext进行设置和提取:

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> public static ApplicationContextCurrent
{
get
{
if(CallContext.GetData(CallContextKey)==null)
{
CallContext.SetData(CallContextKey,
newApplicationContext());
}


returnCallContext.GetData(CallContextKey)asApplicationContext;
}

set
{
CallContext.SetData(CallContextKey,value);
}

}

3、通过MessageInspector将AppContext置于SOAP header中

通过本系列第3部分对Dispatching system的介绍了,我们知道了在client端和service端,可以通过MessageInspector对request message或者reply message (incoming message或者outgoings message)进行检验。MessageInspector可以对MessageHeader进行自由的添加、修改和删除。在service端的MessageInspector被称为DispatchMessageInspector,相对地,client端被称为ClientMessageInspector。我们现在自定义我们自己的ClientMessageInspector。

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> namespace Artech.ContextPropagation
{
publicclassContextAttachingMessageInspector:IClientMessageInspector
{
publicboolIsBidirectional
{get;set;}

publicContextAttachingMessageInspector()
:
this(false)
{}

publicContextAttachingMessageInspector(boolisBidirectional)
{
this.IsBidirectional=IsBidirectional;
}


IClientMessageInspectorMembers#regionIClientMessageInspectorMembers

publicvoidAfterReceiveReply(refMessagereply,objectcorrelationState)
{
if(IsBidirectional)
{
return;
}


if(reply.Headers.FindHeader(ApplicationContext.ContextHeaderLocalName,ApplicationContext.ContextHeaderNamespace)<0)
{
return;
}


ApplicationContextcontext
=reply.Headers.GetHeader<ApplicationContext>(ApplicationContext.ContextHeaderLocalName,ApplicationContext.ContextHeaderNamespace);
if(context==null)
{
return;
}


ApplicationContext.Current
=context;
}


publicobjectBeforeSendRequest(refMessagerequest,IClientChannelchannel)
{
MessageHeader
<ApplicationContext>contextHeader=newMessageHeader<ApplicationContext>(ApplicationContext.Current);
request.Headers.Add(contextHeader.GetUntypedHeader(ApplicationContext.ContextHeaderLocalName,ApplicationContext.ContextHeaderNamespace));
returnnull;
}


#endregion

}

}


一般地,我们仅仅需要Context的单向传递,也就是从client端向service端传递,而不需要从service端向client端传递。不过回来应付将来潜在的需求,也许可能需要这样的功能:context从client端传向service端,service对其进行修改后需要将其返回到client端。为此,我们家了一个属性:IsBidirectional表明是否支持双向传递。

在BeforeSendRequest,我们将ApplicationContext.Current封装成一个MessageHeader, 并将此MessageHeader添加到request message 的header集合中,local name和namespace采用的是定义在ApplicationContext中常量:

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> public object BeforeSendRequest( ref Messagerequest,IClientChannelchannel)
{
MessageHeader
<ApplicationContext>contextHeader=newMessageHeader<ApplicationContext>(ApplicationContext.Current);
request.Headers.Add(contextHeader.GetUntypedHeader(ApplicationContext.ContextHeaderLocalName,ApplicationContext.ContextHeaderNamespace));
returnnull;
}


如何支持context的双向传递,我们在AfterReceiveReply负责从reply message中接收从service传回的context,并将其设置成当前的context:

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> public void AfterReceiveReply( ref Messagereply, object correlationState)
{
if(IsBidirectional)
{
return;
}


if(reply.Headers.FindHeader(ApplicationContext.ContextHeaderLocalName,ApplicationContext.ContextHeaderNamespace)<0)
{
return;
}


ApplicationContextcontext
=reply.Headers.GetHeader<ApplicationContext>(ApplicationContext.ContextHeaderLocalName,ApplicationContext.ContextHeaderNamespace);
if(context==null)
{
return;
}


ApplicationContext.Current
=context;
}

4、通过ContextInitializer实现对Context的接收

上面我们介绍了在client端通过ClientMessageInspector将context信息存储到request message header中,照理说我们通过可以通过DispatchMessageInspector实现对context信息的提取,但是考虑到我们设置context是通过CallContext来实现了,我们最好还是使用CallContextInitializer来做比较好一些。CallContextInitializer的定义,我们在上面一章已经作了详细的介绍了,在这里就不用多说什么了。

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> namespace Artech.ContextPropagation
{
publicclassContextReceivalCallContextInitializer:ICallContextInitializer
{
publicboolIsBidirectional
{get;set;}

publicContextReceivalCallContextInitializer()
:
this(false)
{}

publicContextReceivalCallContextInitializer(boolisBidirectional)
{
this.IsBidirectional=isBidirectional;
}


ICallContextInitializerMembers#regionICallContextInitializerMembers

publicvoidAfterInvoke(objectcorrelationState)
{
if(!this.IsBidirectional)
{
sr
分享到:
评论

你可能感兴趣的:(数据结构,asp.net,SOAP,asp,WCF)