此示例演示如何使用 Windows Communication Foundation (WCF) 平台来实现自定义编码器。该示例由一个客户端控制台程序 (.exe)、一个自承载的服务控制台程序 (.exe) 和一个压缩消息编码器库 (.dll) 组成。该服务实现定义“请求-答复”通信模式的协定。该协定由 ISampleServer 接口定义,该接口公开基本的字符串回送操作(Echo 和 BigEcho)。客户端向给定的操作发出同步请求,服务通过将该消息回送到客户端来进行回复。客户端和服务活动显示在控制台窗口中。提供该示例的目的在于演示如何编写自定义编码器并演示压缩消息对网络的影响。可以为压缩消息编码器添加计算消息大小和/或处理时间的方法。
此示例演示如何生成自定义消息编码器并将其集成到 WCF 应用程序中。库 GZipEncoder.dll 是同时用客户端和服务部署的。该示例还演示对消息进行压缩所带来的影响。GZipEncoder.dll 中的代码演示以下操作:
- 生成自定义编码器和编码器工厂。
- 为自定义编码器开发绑定元素。
- 使用自定义绑定配置集成自定义绑定元素。
- 开发自定义配置处理程序以允许对自定义绑定元素进行文件配置。
如上所述,在自定义编码器中实现了多个层。为了更好地阐释各层之间的关系,下面列出了经过简化的服务启动事件顺序的列表:
- 服务器启动。
- 读取配置信息。
- 服务配置对自定义配置处理程序进行注册。
- 创建并打开服务主机。
- 自定义配置元素创建并返回自定义绑定元素。
- 自定义绑定元素创建并返回消息编码器工厂。
- 服务配置对自定义配置处理程序进行注册。
- 接收消息。
- 消息编码器工厂返回消息编码器以读入消息并写出响应。
- 编码器层是作为类工厂实现的。对于自定义编码器,只有编码器类工厂才是必须公开的。在创建 ChannelFactory 对象时,绑定元素会返回工厂对象。消息编码器可以在缓冲模式或流模式下运行。此示例对缓冲模式和流模式均进行了演示。
在每个模式下,MessageEncoder 抽象类都随附了 ReadMessage 和 WriteMessage 方法。大部分编码工作都在这些方法中进行。此示例包装现有的文本和二进制消息编码器。这允许该示例将对消息网络表示形式的读写操作委托给内部编码器,并允许压缩编码器对结果进行压缩或解压缩。由于没有消息编码管线,因此这是在 WCF 中使用多个编码器的唯一模型。在对消息进行解压缩之后,所得到的消息将立即向上传递到堆栈,供通道堆栈进行处理。在压缩过程中,所得到的压缩消息都直接写入所提供的流中。
此示例使用帮助器方法(CompressBuffer 和 DecompressBuffer)执行从缓冲区到流的转换,以便使用 GZipStream 类。
经过缓冲处理的 ReadMessage 和 WriteMessage 类使用 BufferManager 类。编码器只能通过编码器工厂来访问。MessageEncoderFactory 抽象类提供了一个名为 Encoder 的属性和一个名为 CreateSessionEncoder 的方法,前者用来访问当前的编码器,后者用来创建支持会话的编码器。类似的编码器有序而可靠,可以用在通道支持会话的方案中。对于写入网络中的数据,此方案允许在每个会话中进行优化。如果这不是所需的,则基方法不应当重载。Encoder 属性提供了一种访问无会话编码器的机制,该属性的值由 CreateSessionEncoder 方法的默认实现返回。由于此示例通过包装现有的编码器来提供压缩,因此 MessageEncoderFactory 实现接受一个表示内部编码器工厂的 MessageEncoderFactory。
现在已经定义了编码器和编码器工厂,可以将它们与 WCF 客户端和服务一起使用。但是,必须将这些编码器添加到通道堆栈中。您可以从 ServiceHost 和 ChannelFactory 类派生类,并重写 OnInitialize 方法以手动添加该编码器工厂。还可以通过自定义绑定元素来公开编码器工厂。
若要创建新的自定义绑定元素,请从 MessageEncodingBindingElement。MessageEncodingBindingElement 公开了一种用来新建消息编码器工厂的方法 (CreateMessageEncoderFactory),实现该方法的目的在于返回匹配的消息编码器工厂的实例。另外,MessageEncodingBindingElement 还有一个指示寻址版本的属性。由于此示例包装现有的编码器,因此示例实现还会包装现有的编码器绑定元素、将内部编码器绑定元素作为构造函数的参数并通过一个属性来公开它。下面的示例代码演示 GZipMessageEncodingBindingElement 类的实现。
public sealed class GZipMessageEncodingBindingElement
: MessageEncodingBindingElement //BindingElement
, IPolicyExportExtension
{
//We use an inner binding element to store information
//required for the inner encoder.
MessageEncodingBindingElement innerBindingElement;
//By default, use the default text encoder as the inner encoder.
public GZipMessageEncodingBindingElement()
: this(new TextMessageEncodingBindingElement()) { }
public GZipMessageEncodingBindingElement(MessageEncodingBindingElement messageEncoderBindingElement)
{
this.innerBindingElement = messageEncoderBindingElement;
}
public MessageEncodingBindingElement InnerMessageEncodingBindingElement
{
get { return innerBindingElement; }
set { innerBindingElement = value; }
}
//Main entry point into the encoder binding element.
// Called by WCF to get the factory that creates the
//message encoder.
public override MessageEncoderFactory CreateMessageEncoderFactory()
{
return new
GZipMessageEncoderFactory(innerBindingElement.CreateMessageEncoderFactory());
}
public override MessageVersion MessageVersion
{
get { return innerBindingElement.MessageVersion; }
set { innerBindingElement.MessageVersion = value; }
}
public override BindingElement Clone()
{
return new
GZipMessageEncodingBindingElement(this.innerBindingElement);
}
public override T GetProperty<T>(BindingContext context)
{
if (typeof(T) == typeof(XmlDictionaryReaderQuotas))
{
return innerBindingElement.GetProperty<T>(context);
}
else
{
return base.GetProperty<T>(context);
}
}
public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
{
if (context == null)
throw new ArgumentNullException("context");
context.BindingParameters.Add(this);
return context.BuildInnerChannelFactory<TChannel>();
}
public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
{
if (context == null)
throw new ArgumentNullException("context");
context.BindingParameters.Add(this);
return context.BuildInnerChannelListener<TChannel>();
}
public override bool CanBuildChannelListener<TChannel>(BindingContext context)
{
if (context == null)
throw new ArgumentNullException("context");
context.BindingParameters.Add(this);
return context.CanBuildInnerChannelListener<TChannel>();
}
void IPolicyExportExtension.ExportPolicy(MetadataExporter
exporter, PolicyConversionContext policyContext)
{
if (policyContext == null)
{
throw new ArgumentNullException("policyContext");
}
XmlDocument document = new XmlDocument();
policyContext.GetBindingAssertions().Add(document.CreateElement(
GZipMessageEncodingPolicyConstants.GZipEncodingPrefix,
GZipMessageEncodingPolicyConstants.GZipEncodingName,
GZipMessageEncodingPolicyConstants.GZipEncodingNamespace));
}
}请注意,GZipMessageEncodingBindingElement 类实现 IPolicyExportExtension 接口,以使该绑定元素可以作为元数据中的策略导出,如下面的示例所示。
GZipMessageEncodingBindingElementImporter 类实现 IPolicyImportExtension 接口,该类导入 GZipMessageEncodingBindingElement 的策略。Svcutil.exe 工具可用于将策略导入到配置文件中以处理 GZipMessageEncodingBindingElement,应将下面的代码添加到 Svcutil.exe.config 中。
<configuration>
<system.serviceModel>
<extensions>
<bindingElementExtensions>
<add name="gzipMessageEncoding"
type=
"Microsoft.ServiceModel.Samples.GZipMessageEncodingElement,
GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /></bindingElementExtensions>
</extensions>
<client>
<metadata>
<policyImporters>
<remove type=
"System.ServiceModel.Channels.MessageEncodingBindingElementImporter,
System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<extension type=
"Microsoft.ServiceModel.Samples.GZipMessageEncodingBindingElementImporter,
GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</policyImporters>
</metadata>
</client>
</system.serviceModel>
</configuration>
现在压缩编码器具有一个匹配的绑定元素,可以通过编程方式将它挂钩到服务或客户端,方法是构造新的自定义绑定对象并向其中添加自定义绑定元素,如下面的示例代码所示。
ICollection<BindingElement> bindingElements = new List<BindingElement>(); HttpTransportBindingElement httpBindingElement = new HttpTransportBindingElement(); GZipMessageEncodingBindingElement compBindingElement = new GZipMessageEncodingBindingElement (); bindingElements.Add(compBindingElement); bindingElements.Add(httpBindingElement); CustomBinding binding = new CustomBinding(bindingElements); binding.Name = "SampleBinding"; binding.Namespace = "http://tempuri.org/bindings";
尽管对于大多数用户方案该操作已足够,但是,如果某个服务是由 Web 承载的,则支持文件配置是至关重要的。若要支持由 Web 承载的方案,必须开发一个自定义配置处理程序,以便允许在文件中配置自定义绑定元素。
可以在 .NET Framework 2.0 提供的配置系统的顶部为绑定元素生成一个配置处理程序。绑定元素的配置处理程序必须从 ApplyConfiguration 方法用来将 BindingElementExtensionElement 派生类的属性转换为要针对新创建的绑定元素设置的值。
下面的示例代码演示 GZipMessageEncodingElement 的实现。
public class GZipMessageEncodingElement : BindingElementExtensionElement
{
public GZipMessageEncodingElement()
{
}
//Called by the WCF to discover the type of binding element this
//config section enables
public override Type BindingElementType
{
get { return typeof(GZipMessageEncodingBindingElement); }
}
//The only property we need to configure for our binding element is
//the type of inner encoder to use. Here, we support text and
//binary.
[ConfigurationProperty("innerMessageEncoding",
DefaultValue = "textMessageEncoding")]
public string InnerMessageEncoding
{
get { return (string)base["innerMessageEncoding"]; }
set { base["innerMessageEncoding"] = value; }
}
//Called by the WCF to apply the configuration settings (the
//property above) to the binding element
public override void ApplyConfiguration(BindingElement bindingElement)
{
GZipMessageEncodingBindingElement binding =
(GZipMessageEncodingBindingElement)bindingElement;
PropertyInformationCollection propertyInfo =
this.ElementInformation.Properties;
if (propertyInfo["innerMessageEncoding"].ValueOrigin !=
PropertyValueOrigin.Default)
{
switch (this.InnerMessageEncoding)
{
case "textMessageEncoding":
binding.InnerMessageEncodingBindingElement =
new TextMessageEncodingBindingElement();
break;
case "binaryMessageEncoding":
binding.InnerMessageEncodingBindingElement =
new BinaryMessageEncodingBindingElement();
break;
}
}
}
//Called by the WCF to create the binding element
protected override BindingElement CreateBindingElement()
{
GZipMessageEncodingBindingElement bindingElement =
new GZipMessageEncodingBindingElement();
this.ApplyConfiguration(bindingElement);
return bindingElement;
}
} 此配置处理程序映射到服务或客户端的 App.config 或 Web.config 中的如下表示形式:
若要使用此配置处理程序,必须在 <system.ServiceModel> 元素中注册,如下面的示例配置所示。
在运行服务器时,操作请求和响应将显示在控制台窗口中。在该窗口中按 Enter 可以关闭服务器。
在运行客户端时,操作请求和响应将显示在控制台窗口中。在客户端窗口中按 Enter 可以关闭客户端。