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

扩展 WCF 第一部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (28投票s)

2009年11月7日

CPOL

5分钟阅读

viewsIcon

87778

downloadIcon

2190

关于在 WCF 客户端中使用外部配置文件的一篇文章

Codes

引言

Microsoft WCF 框架 (System.ServiceModel) 提供了几个扩展点,开发人员可以在需要时从这些点扩展该框架。在这篇第一部分的文章中,我将介绍扩展 WCF 框架的两个方面。

  1. 让我们考虑一个 WPF 桌面应用程序、一个 WPF 浏览器应用程序、一个 Windows 桌面应用程序或一个 .NET Web 应用程序,它们构建在面向服务的体系结构之上,并且所有后端服务都是 WCF 服务。现在,出于多种原因,您可能需要更改服务的服务器,从而更改服务 URL,或者您可能需要更改服务绑定/行为的配置。如果您在服务器上更改了任何 WCF 配置,您还需要为客户端提供相同的绑定/行为配置,以使客户端-服务交互正常工作。这对于 XBAP(WPF 浏览器应用程序)等典型客户端来说非常困难。我的 ExtendingWCFPartI.WcfExtentions.dll 组件为此提供了一个解决方案。您可以拥有一个外部配置文件(例如,在客户端应用程序启动之前从服务器下载),其中正确配置了服务终结点,然后使用该外部配置文件创建 WCF 服务代理实例。
  2. 在许多情况下,我们希望在 WCF 客户端中获得与 WCF 服务中完全相同的异常。ExtendingWCFPartI.WcfExtentions.dll 组件也提供了这一点。

背景

扩展 ChannelFactory<T>

此扩展用于服务 第一个 介绍项。通常,System.ServiceModel.ChannelFactory<T> 提供了以编程方式获取代理实例的选项,而不是使用通过 svcutil.exe (.NET Framework 工具) 生成的代理。我扩展了此类,创建了我自己的 ExtendingWCFPartI.WcfExtentions.ExtendedChannelFactory<T> 类,以提供 外部配置文件 功能。构造函数参数 "configurationPath" 应包含外部配置文件的完整路径。然后,我重写了 ChannelFactory<T>.CreateDescription() 方法来应用外部配置文件。外部配置文件与常规客户端的 app.configweb.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 服务托管在控制台中。要运行示例代码并查看其工作原理,请按照以下步骤操作:

  1. 在 VS 中打开 "ExtendingWCFPartI" 应用程序后,右键单击 ExtendingWCFPartI.Service 项目,然后单击调试 -- > 启动新实例。这样做,您已确保服务在控制台主机中运行。
  2. 然后以调试模式运行 ExtendingWCFPartI.Client 项目。

ExtendingWCFPartI.WcfExtentions.dll 是将在 WCF 客户端和 WCF 服务中使用的可重用组件。同时确保您已将自定义异常写入一个将在 WCF 客户端和 WCF 服务中使用的公共程序集。

在 WCF 服务中使用代码

首先,我将向您展示如何使用此组件来托管我们的 WCF 服务。您可以将 WCF 服务托管在 IIS、Windows 服务或控制台中,无论您在哪里托管它,只需将以下节添加到您的 app.configweb.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 日:初始版本
© . All rights reserved.