扩展 WCF 第一部分






4.80/5 (28投票s)
关于在 WCF 客户端中使用外部配置文件的一篇文章
 
 
引言
Microsoft WCF 框架 (System.ServiceModel) 提供了几个扩展点,开发人员可以在需要时从这些点扩展该框架。在这篇第一部分的文章中,我将介绍扩展 WCF 框架的两个方面。
- 让我们考虑一个 WPF 桌面应用程序、一个 WPF 浏览器应用程序、一个 Windows 桌面应用程序或一个 .NET Web 应用程序,它们构建在面向服务的体系结构之上,并且所有后端服务都是 WCF 服务。现在,出于多种原因,您可能需要更改服务的服务器,从而更改服务 URL,或者您可能需要更改服务绑定/行为的配置。如果您在服务器上更改了任何 WCF 配置,您还需要为客户端提供相同的绑定/行为配置,以使客户端-服务交互正常工作。这对于 XBAP(WPF 浏览器应用程序)等典型客户端来说非常困难。我的 ExtendingWCFPartI.WcfExtentions.dll 组件为此提供了一个解决方案。您可以拥有一个外部配置文件(例如,在客户端应用程序启动之前从服务器下载),其中正确配置了服务终结点,然后使用该外部配置文件创建 WCF 服务代理实例。
- 在许多情况下,我们希望在 WCF 客户端中获得与 WCF 服务中完全相同的异常。ExtendingWCFPartI.WcfExtentions.dll 组件也提供了这一点。
背景
扩展 ChannelFactory<T>
此扩展用于服务 第一个 介绍项。通常,System.ServiceModel.ChannelFactory<T> 提供了以编程方式获取代理实例的选项,而不是使用通过 svcutil.exe (.NET Framework 工具) 生成的代理。我扩展了此类,创建了我自己的 ExtendingWCFPartI.WcfExtentions.ExtendedChannelFactory<T> 类,以提供 外部配置文件 功能。构造函数参数 "configurationPath" 应包含外部配置文件的完整路径。然后,我重写了 ChannelFactory<T>.CreateDescription() 方法来应用外部配置文件。外部配置文件与常规客户端的 app.config 或 web.config 文件外观完全相同。有关示例外部配置,您可以查看 ExtendingWCFPartI.Client.WcfClientConfiguration.xml 文件。
扩展 WCF 服务和 WCF 客户端行为
为了提供 第二个 介绍项,我们需要扩展服务、终结点和契约的行为。
public class ExtendedServiceErrorHandler:IErrorHandler
{
    #region IErrorHandler Members
    bool IErrorHandler.HandleError(Exception error)
    {
        if (error is FaultException)
        {
            return false; // Let WCF do normal processing
        }
        else
        {
            return true; // Fault message is already generated
        }
    }
    void IErrorHandler.ProvideFault(Exception error,
        MessageVersion version,
        ref Message fault)
    {
        if (error is FaultException)
        {
            // Let WCF do normal processing
        }
        else
        {
            // Generate fault message manually
            MessageFault messageFault = MessageFault.CreateFault(
                new FaultCode("Sender"),
                new FaultReason(error.Message),
                error,
                new NetDataContractSerializer());
            fault = Message.CreateMessage(version, messageFault, null);
        }
    }
    #endregion
}// end of class    
ExtendedServiceErrorHandler 应注入到 ChannelDispatcher 的 错误处理程序列表中。然后,当服务中抛出任何异常时,WCF 框架将使用我们实现的 IErrorHandler.ProvideFault 方法。在方法实现内部,我使用 NetDataContractSerializer 序列化 MessageFault ,然后将其传输到客户端。
public class ExtendedClientMessageInspector:IClientMessageInspector
{
    #region Methods
    /// Reads the soap message to find the
    /// node. If found than deserialize it using the
    /// NetDataContractSerializer to construct the exception.
    private static object ReadFaultDetail(Message reply)
    {
        const string detailElementName = "Detail";
        using (var reader = reply.GetReaderAtBodyContents())
        {
            // Find<detail>
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element && 
				reader.LocalName == detailElementName)
                {
                    break;
                }
            }
            // Did we find it?
            if (reader.NodeType != XmlNodeType.Element || 
			reader.LocalName != detailElementName)
            {
                return null;
            }
            // Move to the contents of <soap:Detail>
            if (!reader.Read())
            {
                return null;
            }
            // Deserialize the fault
            var serializer = new NetDataContractSerializer();
            try
            {
                return serializer.ReadObject(reader);
            }
            catch (FileNotFoundException)
            {
                // Serializer was unable to find assembly where exception is defined
                return null;
            }
        }
    }
    #endregion
    #region IClientMessageInspector Members
    /// Create a copy of the original reply to allow
    /// default processing of the message. Then its reads
    /// the copied reply to find Fault Detail using the ReadFaultDetail method
    void IClientMessageInspector.AfterReceiveReply
		(ref Message reply, object correlationState)
    {
        if (reply.IsFault)
        {
            // Create a copy of the original reply to allow 
            // default processing of the message
            var buffer = reply.CreateBufferedCopy(Int32.MaxValue);
            var copy = buffer.CreateMessage();  // Create a copy to work with
            reply = buffer.CreateMessage();     // Restore the original message
            var faultDetail = ReadFaultDetail(copy);
            var exception = faultDetail as Exception;
            if (exception != null)
            {
                throw exception;
            }
        }
    }
    object IClientMessageInspector.BeforeSendRequest
	(ref System.ServiceModel.Channels.Message request, 
	System.ServiceModel.IClientChannel channel)
    {
        return null;
    }
    #endregion
}// end of class    
现在我们需要扩展 IClientMessageInspector 以在客户端端恢复相同的异常。在 IClientMessageInspector.AfterReceiveReply 方法实现中,已使用 NetDataContractSerializer 反序列化故障异常并重新获取实际异常。
public class ExtendedServiceBehavior:Attribute, 
	IServiceBehavior, IEndpointBehavior, IContractBehavior
{
    private void ApplyDispatchBehavior(ChannelDispatcher dispatcher)
    {
        // Don't add an error handler if it already exists
        foreach (IErrorHandler errorHandler in dispatcher.ErrorHandlers)
        {
            if (errorHandler is ExtendedServiceErrorHandler)
            {
                return;
            }
        }
        dispatcher.ErrorHandlers.Add(new ExtendedServiceErrorHandler());
    }
    private void ApplyClientBehavior(ClientRuntime runtime)
    {
        // Don't add a message inspector if it already exists
        foreach (IClientMessageInspector messageInspector in runtime.MessageInspectors)
        {
            if (messageInspector is ExtendedClientMessageInspector)
            {
                return;
            }
        }
        runtime.MessageInspectors.Add(new ExtendedClientMessageInspector());
    }
} 
为了告诉 .NET 运行时使用我的 ExtendedServiceErrorHandler 在服务端的错误处理和 ExtendedClientMessageInspector 在客户端错误处理,我们需要这个 ExtendedServiceBehavior 类,它实现了服务、终结点和契约的行为。所有行为都有一个 AddBindingParameters 方法、一个 ApplyDispatchBehavior 方法、一个 Validate 方法和一个 ApplyClientBehavior 方法,但有一个例外:因为 IServiceBehavior 无法在客户端执行,所以它不实现 ApplyClientBehavior。有关更多详细信息,您可以阅读 MSDN 文档。
Using the Code
为简单起见,我已将 WCF 服务托管在控制台中。要运行示例代码并查看其工作原理,请按照以下步骤操作:
- 在 VS 中打开 "ExtendingWCFPartI" 应用程序后,右键单击ExtendingWCFPartI.Service项目,然后单击调试 -- > 启动新实例。这样做,您已确保服务在控制台主机中运行。
- 然后以调试模式运行 ExtendingWCFPartI.Client项目。
ExtendingWCFPartI.WcfExtentions.dll 是将在 WCF 客户端和 WCF 服务中使用的可重用组件。同时确保您已将自定义异常写入一个将在 WCF 客户端和 WCF 服务中使用的公共程序集。
在 WCF 服务中使用代码
首先,我将向您展示如何使用此组件来托管我们的 WCF 服务。您可以将 WCF 服务托管在 IIS、Windows 服务或控制台中,无论您在哪里托管它,只需将以下节添加到您的 app.config 或 web.config ServiceModel 节中。将 ExtendingWCFPartI.WcfExtentions.dll 的引用添加到您的服务主机应用程序。
<system.serviceModel>
    <extensions>
      <behaviorExtensions>
        <add name="ExtendedServiceBehavior" 
        	type="ExtendingWCFPartI.WcfExtentions.ServiceBehaviorExtension, 
        	ExtendingWCFPartI.WcfExtentions, Version=1.0.0.0, 
        	Culture=neutral, PublicKeyToken=null"/>
      </behaviorExtensions>
    </extensions>
</system.serviceModel>
ExtendingWCFPartI.WcfExtentions.ServiceBehaviorExtension 已用于告知 WCF 框架在其 CreateBehavior() 方法中使用 ExtendedServiceBehavior 。
<system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="CustomServiceBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
          <serviceThrottling maxConcurrentCalls="16" maxConcurrentSessions="16" />
          <ExtendedServiceBehavior />
        </behavior>
      </serviceBehaviors>
    </behaviors>
</system.serviceModel>
现在创建一个服务行为,如上面的示例。<ExtendedServiceBehavior> 确保此行为配置将使用 ExtendedServiceBehavior。
<system.serviceModel>
    <services>
      <service behaviorConfiguration="CustomServiceBehavior" 
      	name="ExtendingWCFPartI.Service.CustomerService">
        ...
        ...
        Your service endpoint's configuration goes here.
      </service>
    </services>
</system.serviceModel>
您只需要做这些来托管具有 引言 中所述两种功能的 WCF 服务。
在 WCF 客户端中使用代码
将 ExtendingWCFPartI.WcfExtentions.dll 的引用添加到您的客户端应用程序。现在创建外部配置文件。它可能看起来像这样:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <extensions>
      <behaviorExtensions>
        <add name="ExtendedServiceEndpointBehavior" 
		type="ExtendingWCFPartI.WcfExtentions.ServiceBehaviorExtension, 
		ExtendingWCFPartI.WcfExtentions, Version=1.0.0.0, 
		Culture=neutral, PublicKeyToken=null"/>
      </behaviorExtensions>
    </extensions>
    <bindings>
        .. tips: provide the same binding which has been used at service
    </bindings>
    <behaviors>
      <endpointBehaviors>
        <behavior name="CustomServiceEndpointBehavior">
          <ExtendedServiceEndpointBehavior />
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <client>
      <endpoint address="https://:8036/Services/CustomerService"
          binding="wsHttpBinding" bindingConfiguration="CustomWSHttpBinding"
          behaviorConfiguration="CustomServiceEndpointBehavior"
          contract="ExtendingWCFPartI.Common.Services.ICustomerService" 
		name="CustomerServiceEndPoint" />
    </client>
  </system.serviceModel>
</configuration>
现在将此外部配置文件保存在硬盘上的任何位置,并使用 .xml 或 .config 扩展名,例如 "C:\Temp\MyAppServices.xml"。
我提供了一个非常简单的接口 ExtendingWCFPartI.WcfExtentions.WcfClientHelper 来获取代理实例。
public static T GetProxy<T>(string externalConfigPath)
{
    var channelFactory = new ExtendedChannelFactory<T>(externalConfigPath);
    channelFactory.Open();
    return channelFactory.CreateChannel();
} 
在客户端代码中,使用此接口调用您的服务方法:
var externalConfigPath = @"C:\Temp\MyAppServices.xml";
var proxy = WcfClientHelper.GetProxy<IMyWCFService>(externalConfigPath);
proxy.CallServiceMethod(); 
关注点
WCF 框架中还有许多其他非常有趣且有用的扩展点。我将在下一篇文章中尽力介绍更多示例。
总而言之,我发现 WCF 框架设计得非常好,程序员几乎拥有完成任何事情的所有选项。
历史
- 2009 年 11 月 7 日:初始版本


