在 WCF 4 调用中添加自定义消息头






4.82/5 (15投票s)
如何在 WCF 4 调用中添加自定义消息头
通常,我们希望将某些数据传递给部分或全部服务操作。这些数据通常是上下文数据,例如用户令牌,或用户或机器的环境偏好。
在简单的 Web 服务中,我们可以使用名为 “[SoapHeaderAttribute ("ServiceHeader", Direction=SoapHeaderDirection.In)]
” 的属性,并结合 Web 方法签名来传递自定义标头信息。
但在 WCF 中,我们不能使用相同的属性。
一种方法是将其作为附加的请求参数传递。但是,每个方法调用都需要重复地包含此参数。这并不是一个很干净的解决方案。而且,如果此参数的数据类型发生变化,所有的方法签名及其调用都需要更改。
传递该数据的一种好方法是使用消息标头。
在 WCF 中,要 along with 方法调用传递自定义标头信息,我们需要为客户端和服务实现自定义检查器,这些检查器将实现 BeforeSendRequest
和 AfterRecieveRequest
方法来注入自定义标头。
为了实现这一点,我们需要以下对象/类
- SOAP 标头
- 消息检查器
- 客户端上下文和服务器上下文类
- 自定义行为
让我们开始逐个创建这些类。
1. SOAP 标头
CustomHeader
类用于创建服务自定义标头,我们希望在方法调用时 along with 传递标头信息。CustomHeader
类包含我们希望 along with 方法调用传递的信息。您可以根据需要定义结构。
[DataContract]
public class ServiceHeader
{
[DataMember]
public string EmployeeID { get; set; }
[DataMember]
public string WindowsLogonID { get; set; }
[DataMember]
public string KerberosID { get; set; }
[DataMember]
public string SiteminderToken { get; set; }
}
public class CustomHeader : MessageHeader
{
private const string CUSTOM_HEADER_NAME = "HeaderName";
private const string CUSTOM_HEADER_NAMESPACE = "YourNameSpace";
private ServiceHeader _customData;
public ServiceHeader CustomData
{
get
{
return _customData;
}
}
public CustomHeader()
{
}
public CustomHeader(ServiceHeader customData)
{
_customData = customData;
}
public override string Name
{
get { return (CUSTOM_HEADER_NAME); }
}
public override string Namespace
{
get { return (CUSTOM_HEADER_NAMESPACE); }
}
protected override void OnWriteHeaderContents(
System.Xml.XmlDictionaryWriter writer, MessageVersion messageVersion)
{
XmlSerializer serializer = new XmlSerializer(typeof(ServiceHeader));
StringWriter textWriter = new StringWriter();
serializer.Serialize(textWriter, _customData);
textWriter.Close();
string text = textWriter.ToString();
writer.WriteElementString(CUSTOM_HEADER_NAME, "Key", text.Trim());
}
public static ServiceHeader ReadHeader(Message request)
{
Int32 headerPosition = request.Headers.FindHeader(CUSTOM_HEADER_NAME, CUSTOM_HEADER_NAMESPACE);
if (headerPosition == -1)
return null;
MessageHeaderInfo headerInfo = request.Headers[headerPosition];
XmlNode[] content = request.Headers.GetHeader<XmlNode[]>(headerPosition);
string text = content[0].InnerText;
XmlSerializer deserializer = new XmlSerializer(typeof(ServiceHeader));
TextReader textReader = new StringReader(text);
ServiceHeader customData = (ServiceHeader)deserializer.Deserialize(textReader);
textReader.Close();
return customData;
}
}
正如您所见,它是一个继承自 MessageHeader
类的类型。请注意 OnWriteHeaderContents
重写,它由 WCF 基础结构调用以序列化 SOAP 标头,以及我们稍后将使用的 ReadHeader
static
方法。
2. 消息检查器
SOAP 标头需要由使用者添加,由服务读取。为了做到这一点,我们需要一个类似下面的消息检查器
/// <summary>
/// This class is used to inspect the message and headers on the server side,
/// This class is also used to intercept the message on the
/// client side, before/after any request is made to the server.
/// </summary>
public class CustomMessageInspector : IClientMessageInspector, IDispatchMessageInspector
{
#region Message Inspector of the Service
/// <summary>
/// This method is called on the server when a request is received from the client.
/// </summary>
/// <param name="request"></param>
/// <param name="channel"></param>
/// <param name="instanceContext"></param>
/// <returns></returns>
public object AfterReceiveRequest(ref Message request,
IClientChannel channel, InstanceContext instanceContext)
{
// Create a copy of the original message so that we can mess with it.
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
Message messageCopy = buffer.CreateMessage();
// Read the custom context data from the headers
ServiceHeader customData = CustomHeader.ReadHeader(request);
// Add an extension to the current operation context so
// that our custom context can be easily accessed anywhere.
ServerContext customContext = new ServerContext();
if (customData != null)
{
customContext.KerberosID = customData.KerberosID;
customContext.SiteminderToken = customData.SiteminderToken;
}
OperationContext.Current.IncomingMessageProperties.Add(
"CurrentContext", customContext);
return null;
}
/// <summary>
/// This method is called after processing a method on the server side and just
/// before sending the response to the client.
/// </summary>
/// <param name="reply"></param>
/// <param name="correlationState"></param>
public void BeforeSendReply(ref Message reply, object correlationState)
{
// Do some cleanup
OperationContext.Current.Extensions.Remove(ServerContext.Current);
}
#endregion
#region Message Inspector of the Consumer
/// <summary>
/// This method will be called from the client side just before any method is called.
/// </summary>
/// <param name="request"></param>
/// <param name="channel"></param>
/// <returns></returns>
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
// Prepare the request message copy to be modified
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
ServiceHeader customData = new ServiceHeader();
customData.KerberosID = ClientContext.KerberosID;
customData.SiteminderToken = ClientContext.SiteminderToken;
CustomHeader header = new CustomHeader(customData);
// Add the custom header to the request.
request.Headers.Add(header);
return null;
}
/// <summary>
/// This method will be called after completion of a request to the server.
/// </summary>
/// <param name="reply"></param>
/// <param name="correlationState"></param>
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
#endregion
}
从上面的代码示例可以看出,我们使用 IClientMessageInspector
实现来处理在使用者端代码中添加标头,同时我们在服务端使用 IDispatchMessageInspector
来提取标头。有趣的是,MessageHeaders
集合的 FindHeader
方法以及 GetReaderAtHeader
方法都由同一个 Headers 集合提供。最后一个方法的返回值是一个 XmlDictionaryReader
,我们通过我们已经介绍过的 ReadHeader
static
方法来读取我们的自定义标头内容。
3. 客户端上下文和服务器上下文类
ClientContext
类用于在调用方法之前存储标头信息,因此当您想附加自定义标头数据时,您只需要为这个 ClientContext
类设置值。这些值会在 CustomMessageInspector
类的 BeforeSendRequest
方法中获取,并 along with 发送请求时一起发送。
/// <summary>
/// This class will act as a custom context in the client side to hold the context information.
/// </summary>
public class ClientContext
{
public static string EmployeeID;
public static string WindowsLogonID;
public static string KerberosID;
public static string SiteminderToken;
}
在服务端,一旦收到自定义标头,它将被存储在这个 ServerContext
类对象中,以便我们可以在收到请求后随时访问它。
/// <summary>
/// This class will act as a custom context, an extension to the OperationContext.
/// This class holds all context information for our application.
/// </summary>
public class ServerContext : IExtension<OperationContext>
{
public string EmployeeID;
public string WindowsLogonID;
public string KerberosID;
public string SiteminderToken;
// Get the current one from the extensions that are added to OperationContext.
public static ServerContext Current
{
get
{
return OperationContext.Current.Extensions.Find<ServerContext>();
}
}
#region IExtension<OperationContext> Members
public void Attach(OperationContext owner)
{
}
public void Detach(OperationContext owner)
{
}
#endregion
}
4. 自定义行为
服务将能够通过查询 IncomingMessageProperties
字典来读取通过自定义标头提供的 Key。
OperationContext.Current.IncomingMessageProperties["CurrentContext"];
当然,需要使用如下所示的自定义行为将自定义消息检查器插入 WCF 管道。
/// <summary>
/// This custom behavior class is used to add both client and server inspectors to
/// the corresponding WCF end points.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class CustomBehavior : Attribute, IServiceBehavior, IEndpointBehavior
{
#region IEndpointBehavior Members
void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint,
System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint,
System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
CustomMessageInspector inspector = new CustomMessageInspector();
clientRuntime.MessageInspectors.Add(inspector);
}
void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint,
System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
ChannelDispatcher channelDispatcher = endpointDispatcher.ChannelDispatcher;
if (channelDispatcher != null)
{
foreach (EndpointDispatcher ed in channelDispatcher.Endpoints)
{
CustomMessageInspector inspector = new CustomMessageInspector();
ed.DispatchRuntime.MessageInspectors.Add(inspector);
}
}
}
void IEndpointBehavior.Validate(ServiceEndpoint endpoint) { }
#endregion
#region IServiceBehavior Members
void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
BindingParameterCollection bindingParameters)
{
}
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription desc, ServiceHostBase host)
{
foreach (ChannelDispatcher cDispatcher in host.ChannelDispatchers)
{
foreach (EndpointDispatcher eDispatcher in cDispatcher.Endpoints)
{
eDispatcher.DispatchRuntime.MessageInspectors.Add(new CustomMessageInspector());
}
}
}
void IServiceBehavior.Validate(ServiceDescription desc, ServiceHostBase host) { }
#endregion
}
实现 IEndpointBehavior
接口,以修改、检查或扩展应用程序级别上端点范围的执行的某些方面,用于客户端或服务应用程序。
- 使用
AddBindingParameters
方法在运行时传递自定义数据,以使绑定能够支持自定义行为。 - 使用
ApplyClientBehavior
方法修改、检查或插入客户端应用程序中端点的扩展。 - 使用
ApplyDispatchBehavior
方法修改、检查或插入服务应用程序中端点范围执行的扩展。 - 使用
Validate
方法确认ServiceEndpoint
满足特定要求。这可用于确保端点具有某个配置设置已启用,支持特定功能以及其他要求。
实现 IServiceBehavior
以修改、检查或扩展应用程序级别上服务范围的执行的某些方面。
- 使用
ApplyDispatchBehavior
方法更改运行时属性值或插入自定义扩展对象,例如错误处理程序、消息或参数拦截器、安全扩展以及其他自定义扩展对象。 - 使用
Validate
方法在构造执行服务之前检查描述,以确认它能否正常执行。 - 使用
AddBindingParameters
方法将自定义信息传递给服务绑定元素,以便服务能够正确支持服务。
将行为添加到运行时
当您构造 ServiceHost
或客户端 ChannelFactory
时,运行时会反射服务类型,读取配置文件,并开始构建服务的内存描述。在 ServiceHost
中,此描述可通过 Description
属性(类型为 ServiceDescription
)提供给您。在 ChannelFactory
中,它可通过 Endpoint
属性(类型为 ServiceEndpoint
)提供;客户端描述仅限于目标端点。
ServiceDescription
包含服务的完整描述以及每个端点(ServiceEndpoint
),包括契约(ContractDescription
)和操作(OperationDescription
)。ServiceDescription
提供了一个 Behaviors
属性(类型为 IServiceBehavior
的集合),该属性建模了服务行为的集合。每个 ServiceEndpoint
还有一个 Behaviors
属性(类型为 IEndpointBehavior
的集合),该属性建模了各个端点行为。同样,ContractDescription
和 OperationDescription
各自拥有一个适当的 Behaviors
属性。
在 ServiceHost
和 ChannelFactory
构建过程中,这些行为集合会自动填充,包含在代码(通过属性)或配置文件中找到的任何行为(稍后详细介绍)。您也可以在构造后手动将行为添加到这些集合中。以下示例显示了如何将 CustomBehavior
添加到宿主作为服务行为。
WCFServiceClient ws = new WCFServiceClient();
ws.ChannelFactory.Endpoint.Behaviors.Add(new CustomBehavior());
通过属性添加行为
在 ServiceHost
/ChannelFactory
构建过程中,运行时会反射服务类型和配置文件,并自动将找到的任何行为添加到 ServiceDescription
的相应行为集合中。
/// <summary>
/// Summary description for WCFService
/// </summary>
[CustomBehavior]
public class WCFService : IWCFService
{
}
就是这样!享受通过指定行为传递自定义标头吧。
希望这能有所帮助!!!