扩展 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 日:初始版本