我最近发现自己处于类似的位置,在阅读了 @carlosfigueira 的精彩回答后,我决定深入挖掘并弄清楚如何让他的示例从 IIS web.config 文件中运行。
事实证明,Carlos 在 Microsoft 有自己的博客,并且有几篇文章非常详细地介绍了这个主题。 this page 顶部的图表非常有帮助,并清楚地表明 MessageEncoder 是当服务通过 IIS 托管时,我最接近直接离线读取客户端消息的方式。
这基本上是使用 Carlos 详述的相同过程完成的,但是要在 web.config 文件中完成它,您必须创建一个派生自 BindingElementExtensionElement 的类,以便为 IIS 提供创建 BindingElement/Encoder/Factory 所需的挂钩如下例所示。
(我讨厌复制 Carlos 已经发布的内容,但我确实在弄清楚这一切的过程中稍微调整了他的代码。所以,与其偶然发布不适用于他的示例的代码,我将在这里重新发布我的调整)
第 1 步)创建您的 custom MessageEncoder(以及相关的 MessageEncoderFactory),它包装您的绑定通常使用的原始 MessageEncoder,并使包装 MessageEncoder 将时间戳添加到由原始/包装的 MessageEncoder 创建的 Message。
注意:这是三个类中的前两个,它们本质上将包装对象并通过将调用传递给包装/内部对象来实现其 WCF 接口(即。“return inner. property/method") 允许自定义 MessageEncoder 精确匹配我们实际上试图扩展/更改的 MessageEncoder 的行为。所以,虽然这是一个冗长的答案,但一旦你掌握了所有相关的部分,它就真的没那么复杂了。
using System;
using System.IO;
using System.ServiceModel.Channels;
namespace WCF.Sample
{
public class TimestampedMsgEncoder : MessageEncoder
{
public const String TimestampProp = "MsgReceivedTimestamp";
private MessageEncoder inner;
public TimestampedMsgEncoder(MessageEncoder inner) { this.inner = inner; }
public override Message ReadMessage(ArraySegment<Byte> buffer, BufferManager bufferManager, String contentType)
{
DateTime MsgReceived = DateTime.Now;
Message Msg = inner.ReadMessage(buffer, bufferManager, contentType);
Msg.Properties.Add(TimestampProp, MsgReceived);
return Msg;
}
public override Message ReadMessage(Stream stream, Int32 maxSizeOfHeaders, String contentType)
{
DateTime MsgReceived = DateTime.Now;
Message Msg = inner.ReadMessage(stream, maxSizeOfHeaders, contentType);
Msg.Properties.Add(TimestampProp, MsgReceived);
return Msg;
}
#region Pass-through MessageEncoder implementations
public override String ContentType { get { return inner.ContentType; } }
public override String MediaType { get { return inner.MediaType; } }
public override MessageVersion MessageVersion { get { return inner.MessageVersion; } }
public override Boolean IsContentTypeSupported(String contentType) { return inner.IsContentTypeSupported(contentType); }
public override void WriteMessage(Message message, Stream stream) { inner.WriteMessage(message, stream); }
public override ArraySegment<Byte> WriteMessage(Message message, Int32 maxMessageSize, BufferManager bufferManager, Int32 messageOffset) { return inner.WriteMessage(message, maxMessageSize, bufferManager, messageOffset); }
#endregion Pass-through MessageEncoder implementations
}
public class TimestampedMsgEncoderFactory : MessageEncoderFactory
{
protected readonly MessageEncoderFactory inner;
protected TimestampedMsgEncoderFactory() { }
public TimestampedMsgEncoderFactory(MessageEncoderFactory inner)
{
this.inner = inner;
}
public override MessageEncoder Encoder { get { return new TimestampedMsgEncoder(inner.Encoder); } }
public override MessageVersion MessageVersion { get { return inner.MessageVersion; } }
}
}
步骤 2) 创建一个派生自 MessageEncodingBindingElement 的类,该类将添加到您的 CustomBinding 中,并将(再次)包装一个“内部”对象,该对象是您通常绑定将使用的对象类型(在这种情况下为 TextMessageEncodingBindingElement BasicHttpBinding)。
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Configuration;
using System.Configuration;
namespace WCF.Sample
{
public class TimestampedTextMsgEncodingBindingElement : MessageEncodingBindingElement, IWsdlExportExtension, IPolicyExportExtension
{
private readonly TextMessageEncodingBindingElement inner;
public TimestampedTextMsgEncodingBindingElement(TextMessageEncodingBindingElement inner)
{
this.inner = inner;
}
public TimestampedTextMsgEncodingBindingElement()
{
inner = new TextMessageEncodingBindingElement();
}
public TimestampedTextMsgEncodingBindingElement(MessageVersion messageVersion, Encoding writeEnconding)
{
inner = new TextMessageEncodingBindingElement(messageVersion, writeEnconding);
}
public override MessageEncoderFactory CreateMessageEncoderFactory()
{
return new TimestampedMsgEncoderFactory(inner.CreateMessageEncoderFactory());
}
#region Pass-through MessageEncoderBindingElement implementations
public override BindingElement Clone()
{
return new TimestampedTextMsgEncodingBindingElement((TextMessageEncodingBindingElement)inner.Clone());
}
public override MessageVersion MessageVersion { get { return inner.MessageVersion; } set { inner.MessageVersion = value; } }
public Encoding WriteEncoding { get { return inner.WriteEncoding; } set { inner.WriteEncoding = value; } }
public Int32 MaxReadPoolSize { get { return inner.MaxReadPoolSize; } set { inner.MaxReadPoolSize = value; } }
public Int32 MaxWritePoolSize { get { return inner.MaxWritePoolSize; } set { inner.MaxWritePoolSize = value; } }
public XmlDictionaryReaderQuotas ReaderQuotas { get { return inner.ReaderQuotas; } set { inner.ReaderQuotas = value; } }
public override Boolean CanBuildChannelListener<TChannel>(BindingContext context)
{
return context.CanBuildInnerChannelListener<TChannel>();
//return inner.CanBuildChannelFactory<TChannel>(context);
}
public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
{
context.BindingParameters.Add(this);
return context.BuildInnerChannelListener<TChannel>();
//return inner.BuildChannelListener<TChannel>(context);
}
public void ExportContract(WsdlExporter exporter, WsdlContractConversionContext context)
{
((IWsdlExportExtension)inner).ExportContract(exporter, context);
}
public void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
{
((IWsdlExportExtension)inner).ExportEndpoint(exporter, context);
}
public void ExportPolicy(MetadataExporter exporter, PolicyConversionContext context)
{
((IPolicyExportExtension)inner).ExportPolicy(exporter, context);
}
#endregion Pass-through MessageEncoderBindingElement implementations
}
}
第 3 步)创建一个派生自 BindingElementExtensionElement 的类,该类将允许您使用我们刚刚在 web.config 文件中(如上)创建的 TimestampedTextMsgEncodingBindingElement 类。
这是我们创建的第一个(唯一的)类,其目的不是包装一些标准框架类以有效地扩展/更改它。尽管如此,它仍然是一个非常简单的类来实现。最小实现只有BindingElementType 属性和CreateBindingElement 方法。
但是,如果您想使用属性来自定义 web.config 文件中的 MessageEncoder 行为,那么它确实需要使用 ConfigurationProperty 属性修饰的特殊属性,以让 IIS 知道它们接受来自web.config 文件...然后必须相应地编组到内部 MessageEncoder。
(如果您知道您的消息编码器将始终使用相同的配置,那么对 UTF8 和 SOAP11 之类的东西进行硬编码可能更容易 - 我只是提供了一种实现属性的方法作为示例)
using System;
using System.Xml;
using System.Text;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Configuration;
using System.Configuration;
namespace WCF.Sample
{
public class TimestampedTextMsgEncodingExtension : BindingElementExtensionElement
{
private MessageVersion _MessageVersion = MessageVersion.Soap11;
private Encoding _Encoding = Encoding.UTF8;
private Int32 _MaxReadPoolSize = -1;
private Int32 _MaxWritePoolSize = -1;
private XmlDictionaryReaderQuotas _ReaderQuotas = null;
public override Type BindingElementType { get { return typeof(TimestampedTextMsgEncodingBindingElement); } }
protected override BindingElement CreateBindingElement()
{
TimestampedTextMsgEncodingBindingElement eb = new TimestampedTextMsgEncodingBindingElement(_MessageVersion, _Encoding);
if (_MaxReadPoolSize > -1) eb.MaxReadPoolSize = _MaxReadPoolSize;
if (_MaxWritePoolSize > -1) eb.MaxWritePoolSize = MaxWritePoolSize;
if (_ReaderQuotas != null) eb.ReaderQuotas = _ReaderQuotas;
return eb;
}
[ConfigurationProperty("messageVersion", DefaultValue = "Soap11", IsRequired = false)]
public String messageVersion
{
set
{
switch (value)
{
case "Soap11":
_MessageVersion = MessageVersion.Soap11;
break;
case "Soap12":
_MessageVersion = MessageVersion.Soap12;
break;
case "Soap11WSAddressing10":
_MessageVersion = MessageVersion.Soap11WSAddressing10;
break;
case "Soap12WSAddressing10":
_MessageVersion = MessageVersion.Soap12WSAddressing10;
break;
case "Soap11WSAddressingAugust2004":
_MessageVersion = MessageVersion.Soap11WSAddressingAugust2004;
break;
case "Soap12WSAddressingAugust2004":
_MessageVersion = MessageVersion.Soap12WSAddressingAugust2004;
break;
default:
throw new NotSupportedException("\"" + value + "\" is not a supported message version.");
}
}
}
[ConfigurationProperty("writeEncoding", DefaultValue = "Utf8TextEncoding", IsRequired = false)]
public String writeEncoding
{
set
{
switch (value)
{
case "Utf8TextEncoding":
_Encoding = Encoding.UTF8;
break;
case "Utf16TextEncoding":
_Encoding = Encoding.Unicode;
break;
case "UnicodeFffeTextEncoding":
_Encoding = Encoding.BigEndianUnicode;
break;
default:
_Encoding = Encoding.GetEncoding(value);
break;
}
}
}
[ConfigurationProperty("maxReadPoolSize", IsRequired = false)]
public Int32 MaxReadPoolSize { get { return _MaxReadPoolSize; } set { _MaxReadPoolSize = value; } }
[ConfigurationProperty("maxWritePoolSize", IsRequired = false)]
public Int32 MaxWritePoolSize { get { return _MaxWritePoolSize; } set { _MaxWritePoolSize = value; } }
[ConfigurationProperty("readerQuotas", IsRequired = false)]
public XmlDictionaryReaderQuotas ReaderQuotas { get { return _ReaderQuotas; } set { _ReaderQuotas = value; } }
}
}
第 4 步)更新您的 web.config 文件:
<configuration>
<system.web>
<compilation strict="false" explicit="true" targetFramework="4.5" />
<trust level="Full" />
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
<system.serviceModel>
<extensions>
<bindingElementExtensions>
<add name="timestampedTextMsgEncoding" type="WCF.Sample.TimestampedTextMsgEncodingExtension, WCFSample" />
</bindingElementExtensions>
</extensions>
<services>
<service behaviorConfiguration="WCF.Sample.SampleBehavior" name="WCF.Sample.Service">
<endpoint address="basic" contract="WCF.Sample.IService" binding="basicHttpBinding" bindingConfiguration="HttpNoAuth" />
<endpoint address="timestamp" contract="WCF.Sample.IService" binding="customBinding" bindingConfiguration="HttpNoAuthTimestampEncoding" />
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="HttpNoAuth" >
<security mode="None" >
<transport clientCredentialType="None" />
</security>
</binding>
</basicHttpBinding>
<customBinding>
<binding name="HttpNoAuthTimestampEncoding">
<timestampedTextMsgEncoding writeEncoding="Utf8TextEncoding" messageVersion="Soap11" />
<httpTransport authenticationScheme="None" />
</binding>
</customBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="WCF.Sample.SampleBehavior">
<useRequestHeadersForMetadataAddress />
<serviceMetadata httpGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
步骤 5) 使用代码更新您的 WCF 服务方法,以提取我们添加到传入消息的时间戳属性。由于我们在将客户端的消息发送到内部 MessageEncoder 之前记录了 DateTime,因此这本质上应该是我们必须获得 DateTime 的最早机会,因为 POST 消息的最后一位是从客户端传输的。
DateTime SOAPMsgReceived = (DateTime)System.ServiceModel.OperationContext.Current.IncomingMessageProperties[TimestampedMsgEncoder.TimestampProp];
最后两点。
如果您创建了一个新端点,请确保您的客户端在您进行测试时指向该端点的地址(我在这个问题上追了大约一个小时,然后才意识到我忽略了那个细节)。
上面的 web.config 示例几乎没有说明我在弄清楚如何使 customBinding 配置 (HttpNoAuthTimestampEncoding) 与 basicHttpBinding 配置的功能 (HttpNoAuth )。 customBinding 元素的文档提供了一点帮助,但主要是大量的网络搜索和反复试验。特别值得注意的是使用<httpTransport> 和<httpsTransport> 在HTTP 和HTTPS 之间切换。相比之下,basicHttpBinding 使用“security\@mode=None”和“security\@mode=Transport”。