65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (15投票s)

2012年3月22日

CPOL

5分钟阅读

viewsIcon

184440

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

通常,我们希望将某些数据传递给部分或全部服务操作。这些数据通常是上下文数据,例如用户令牌,或用户或机器的环境偏好。

在简单的 Web 服务中,我们可以使用名为 [SoapHeaderAttribute ("ServiceHeader", Direction=SoapHeaderDirection.In)]” 的属性,并结合 Web 方法签名来传递自定义标头信息。

但在 WCF 中,我们不能使用相同的属性。

一种方法是将其作为附加的请求参数传递。但是,每个方法调用都需要重复地包含此参数。这并不是一个很干净的解决方案。而且,如果此参数的数据类型发生变化,所有的方法签名及其调用都需要更改。

传递该数据的一种好方法是使用消息标头。

在 WCF 中,要 along with 方法调用传递自定义标头信息,我们需要为客户端和服务实现自定义检查器,这些检查器将实现 BeforeSendRequestAfterRecieveRequest 方法来注入自定义标头。

为了实现这一点,我们需要以下对象/类

  1. SOAP 标头
  2. 消息检查器
  3. 客户端上下文和服务器上下文类
  4. 自定义行为

让我们开始逐个创建这些类。

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 的集合),该属性建模了各个端点行为。同样,ContractDescriptionOperationDescription 各自拥有一个适当的 Behaviors 属性。

ServiceHostChannelFactory 构建过程中,这些行为集合会自动填充,包含在代码(通过属性)或配置文件中找到的任何行为(稍后详细介绍)。您也可以在构造后手动将行为添加到这些集合中。以下示例显示了如何将 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
{
}

就是这样!享受通过指定行为传递自定义标头吧。

希望这能有所帮助!!!

© . All rights reserved.