我实施蒂姆回答的细节
我需要为我目前工作的客户写这篇文章,所以我想我也不妨把它贴在这里。我希望它可以帮助某人。我创建了一个示例客户端和服务,用于尝试其中的一些想法。我已经清理了它并将其添加到 github。你可以在这里下载。
需要实现以下内容以允许以我需要的方式使用 WCF:
- WCF 不期望 SOAP 消息
- 能够完全按照要求格式化请求和响应消息
- 考虑处理的所有消息
- 传入的消息被路由到正确的操作来处理它们
1. 将 WCF 配置为不期望 SOAP 消息
第一步是通过 TextMessageEncoder 获取传入消息。这是通过在 textMessageEncoding 元素上使用带有 MessageVersion.None 设置的自定义绑定来实现的。
<customBinding>
<binding name="poxMessageBinding">
<textMessageEncoding messageVersion="None" />
<httpTransport />
</binding>
</customBinding>
2. 正确格式化消息
消息格式化程序是必需的,因为传入的消息拒绝被现有的 XML 格式化程序反序列化,而无需在消息合同上添加其他属性。这通常不是问题,但是通过 XSD.exe 运行我的客户端 ebXML 模式会生成一个 33000 行的 cs 文件,我不想以任何方式修改它。此外,人们将来会忘记重新添加属性,因此这也有望使维护变得更容易。
自定义格式化程序期望将传入消息转换为第一个参数的类型,并将返回类型转换为响应消息。它检查实现方法以确定构造函数中第一个参数和返回值的类型。
public SimpleXmlFormatter(OperationDescription operationDescription)
{
// Get the request message type
var parameters = operationDescription.SyncMethod.GetParameters();
if (parameters.Length != 1)
throw new InvalidDataContractException(
"The SimpleXmlFormatter will only work with a single parameter for an operation which is the type of the incoming message contract.");
_requestMessageType = parameters[0].ParameterType;
// Get the response message type
_responseMessageType = operationDescription.SyncMethod.ReturnType;
}
然后它简单地使用 XmlSerializer 序列化和反序列化数据。对我来说,其中一个有趣的部分是使用自定义 BodyWriter 将对象序列化为 Message 对象。下面是服务序列化程序的部分实现。客户端实现是相反的。
public void DeserializeRequest(Message message, object[] parameters)
{
var serializer = new XmlSerializer(_requestMessageType);
parameters[0] = serializer.Deserialize(message.GetReaderAtBodyContents());
}
public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
{
return Message.CreateMessage(MessageVersion.None, _responseMessageType.Name,
new SerializingBodyWriter(_responseMessageType, result));
}
private class SerializingBodyWriter : BodyWriter
{
private readonly Type _typeToSerialize;
private readonly object _objectToEncode;
public SerializingBodyWriter(Type typeToSerialize, object objectToEncode) : base(false)
{
_typeToSerialize = typeToSerialize;
_objectToEncode = objectToEncode;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartDocument();
var serializer = new XmlSerializer(_typeToSerialize);
serializer.Serialize(writer, _objectToEncode);
writer.WriteEndDocument();
}
}
3.处理所有传入的消息
为了指示 WCF 允许处理所有传入消息,我需要将 endpointDispatcher 上的 ContractFilter 属性设置为 MatchAllMessageFilter 的实例。这是我的端点行为配置中的一个片段。
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.ContractFilter = new MatchAllMessageFilter();
// Do more config ...
}
4. 选择正确的操作
这是通过创建一个实现 IDispatchOperationSelector 的类来实现的。在 SelectOperation 方法中,我检查传入的消息。这里我要寻找两件事:1. 彻底检查根元素的命名空间是否与服务合同的命名空间相同 2. 根元素的名称(注意使用 LocalName 删除任何命名空间前缀)
根元素的名称是返回值,它将映射到合约上具有匹配名称或操作属性具有匹配值的任何操作。
public string SelectOperation(ref Message message)
{
var messageBuffer = message.CreateBufferedCopy(16384);
// Determine the name of the root node of the message
using (var copyMessage = messageBuffer.CreateMessage())
using (var reader = copyMessage.GetReaderAtBodyContents())
{
// Move to the first element
reader.MoveToContent();
if (reader.NamespaceURI != _namespace)
throw new InvalidOperationException(
"The namespace of the incoming message does not match the namespace of the endpoint contract.");
// The root element name is the operation name
var action = reader.LocalName;
// Reset the message for subsequent processing
message = messageBuffer.CreateMessage();
// Return the name of the action to execute
return action;
}
}
把它包起来
为了更容易部署,我创建了一个端点行为来处理消息格式化程序、合同过滤器和操作选择器的配置。我也可以创建一个绑定来包装自定义绑定配置,但我认为这部分不太难记住。
对我来说一个有趣的发现是端点行为可以为端点中的所有操作设置消息格式化程序以使用自定义消息格式化程序。这节省了单独配置这些的需要。我从其中一个Microsoft 示例中选择了这个。
有用的文档链接
到目前为止,我找到的最好的参考资料是 Service Station MSDN 杂志文章(Google “site:msdn.microsoft.com service station WCF”)。
WCF Bindings in Depth - 关于配置绑定的非常有用的信息
使用自定义行为扩展 WCF - 我还没有找到关于调度程序集成点的最佳信息来源,它们包含一些非常有用的图表,这些图表说明了所有集成点以及它们在处理顺序中出现的位置。
Microsoft WCF 示例- 这里有很多其他地方没有很好地记录。我发现阅读其中一些非常有启发性的源代码。