我们目前正在使用Web服务(IBM Message Broker).由于服务仍在开发中,在许多情况下它会返回无效的XML(是的,这将被修复,我承诺).
使用由svcutil使用生成的客户端从.NET调用此服务时会出现此问题ClientBase<T>.似乎XmlSerializer使用的不是无效的XML元素.
以下是无法报告错误的示例,只返回部分初始化的元素:
using System;
using System.Diagnostics;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
[Serializable]
public class Program
{
[XmlElement(Order = 0)]
public string One { get;set; }
[XmlElement(Order = 1)]
public string Two { get;set; }
static void Main(string[] args)
{
var ser = new XmlSerializer(typeof(Program));
ser.UnknownElement += (o, e) => {
Console.WriteLine("Unknown element: {0}", e.Element.Name);
};
using (var input = new StringReader(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<Program>
<Two>Two</Two>
<One>One</One>
</Program>"))
{
var p = (Program)ser.Deserialize(input);
Debug.Assert(p.One != null);
}
}
}
Run Code Online (Sandbox Code Playgroud)
附加到UnknownElement事件时,它会正确报告无效的XML(元素顺序不匹配),但在使用时ClientBase<T>,这些(以及其他一些情况)将被忽略(就好像不使用故障事件一样XmlSerializer).
我的问题是如何ClientBase<T>检测无效的XML?有没有一种方法挂接到的故障事件XmlSerializer所用ClientBase<T>?
目前,如果没有意义,我们必须使用SoapUI手动检查响应.
谢谢
因此,开箱即用的WCF不相信XML验证.它将XML视为一种消息格式,读取看似正确的信息并忽略其余信息.这具有服务将接受的非常自由的优点.
当元素的排序开始变得重要时,就会遇到麻烦.可以认为结构的排序不应该是重要的,您可以指示数据本身中的信息排序(例如,日期,时间或索引属性).在你无关紧要的情况下,排序实际上并不重要,因为你可以阅读和理解信息而不管它出现的顺序.我相信你的实际案例更有效,所以我不会进一步研究这一点.
为了验证XML结构,您需要访问WCF管道中的消息.最简单的方法是使用一个IClientMessageInspector验证消息并使用行为将其附加到客户端的操作.
假设您要对XSD进行XML模式验证,您可以创建一个这样的检查器:
class XsdValidationInspector : IClientMessageInspector
{
private readonly XmlSchemaSet _schemas;
public XsdValidationInspector(XmlSchemaSet schemas)
{
this._schemas = schemas;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
// Buffer the message so we can read multiple times.
var buffer = reply.CreateBufferedCopy();
// Validate the message content.
var message = buffer.CreateMessage();
using (var bodyReader
= message.GetReaderAtBodyContents().ReadSubTree())
{
var settings = new XmlReaderSettings
{
Schemas = this._schemas,
ValidationType = ValidationType.Schema,
};
var events = new List<ValidationEventArgs>();
settings.ValidationEventHandler += (sender, e) => events.Add(e);
using (var validatingReader
= XmlReader.Create(bodyReader, settings))
{
// Read to the end of the body.
while(validatingReader.Read()) { }
}
if (events.Any())
{
// TODO: Examine events and decide whether to throw exception.
}
}
// Assign a copy to be passed to the next component.
reply = buffer.CreateMessage();
}
public object BeforeSendRequest(
ref Message request,
IClientChannel channel) {}
}
Run Code Online (Sandbox Code Playgroud)
随附的验证行为并不特别复杂:
class XsdValiationBehavior : IEndpointBehavior
{
private readonly XmlSchemaSet _schemas;
public XsdValidationBehavior(XmlSchemaSet schemas)
{
this._schemas = schemas;
}
public void AddBindingParameters(
ServiceEndpoint endpoint,
BindingParameterCollection bindingParameters) {}
public void ApplyClientBehavior(
ServiceEndpoint endpoint,
ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(
new XsdValidationInspector(this._schemas));
}
public void ApplyDispatchBehavior(
ServiceEndpoint endpoint,
EndpointDispatcher endpointDispatcher) {}
public void Validate(ServiceEndpoint endpoint){}
}
Run Code Online (Sandbox Code Playgroud)
您可以创建一些配置元素并通过配置应用行为,也可以通过在打开客户端连接之前修改客户端的通道工厂以编程方式执行此操作.这是程序化方法:
var schemaMarkup = @"<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'>
<xsd:element name='Program'>
<xsd:complexType>
<xsd:sequence>
<xsd:element name='One' minOccurs='1' maxOccurs='1'/>
<xsd:element name='Two' minOccurs='1' maxOccurs='1'/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>";
var schema = new XmlSchema();
using (var stringReader = new StringReader(schemaMarkup));
{
var events = new List<ValidationEventArgs>();
schema.Read(stringReader, (sender, e) => events.Add(e));
// TODO: Check events for any errors.
}
var validation = new XsdValidationBehavior(new XmlSchemaSet { schema });
client.ChannelFactory.Behaviours.Add(validation);
Run Code Online (Sandbox Code Playgroud)
现在,我不是 100% 确定这一点,但我确实相信聚会正在文件 XmlSerializerOperationFormatter.cs(System.ServiceModel) 上进行,
即在DeserializeBody中:
private object DeserializeBody(XmlDictionaryReader reader, MessageVersion version, XmlSerializer serializer, MessagePartDescription returnPart, MessagePartDescriptionCollection bodyParts, object[] parameters, bool isRequest)
{
try
{
if (reader == null)
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError((Exception) new ArgumentNullException("reader"));
if (parameters == null)
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError((Exception) new ArgumentNullException("parameters"));
object obj = (object) null;
if (serializer == null || reader.NodeType == XmlNodeType.EndElement)
return (object) null;
object[] objArray = (object[]) serializer.Deserialize((XmlReader) reader, this.isEncoded ? XmlSerializerOperationFormatter.GetEncoding(version.Envelope) : (string) null);
int num = 0;
if (OperationFormatter.IsValidReturnValue(returnPart))
obj = objArray[num++];
for (int index = 0; index < bodyParts.Count; ++index)
parameters[((Collection<MessagePartDescription>) bodyParts)[index].Index] = objArray[num++];
return obj;
}
catch (InvalidOperationException ex)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError((Exception) new CommunicationException(System.ServiceModel.SR.GetString(isRequest ? "SFxErrorDeserializingRequestBody" : "SFxErrorDeserializingReplyBody", new object[1]
{
(object) this.OperationName
}), (Exception) ex));
}
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,没有人将自己挂在XmlSerializer.UnknownElement. 不过,话又说回来,我们不能真的这么说,因为XmlSerializer是通过参数传递的。长话短说; 它来自replyMessageInfo.BodySerializer 或requestMessageInfo.BodySerializer属于 的一部分的属性XmlSerializerOperationFormatter.cs,这些来自 XmlSerializerOperationFormatter 构造函数。
几步之遥,并且.. 好吧 20983832972389 步进一步,因为源代码是疯狂的。基本上,它导致了这样一个事实:我没有看到任何应用于 XmlSerializer 的内容,这有点表明您刚才所说的内容。
可能的解决方案:
1)作为XmlSerializerOperationBehavior基础并编写您自己的“自定义序列化器”。这是如何编写自定义序列化器的完美示例:http://code.google.com/p/protobuf-net/source/browse/trunk/protobuf-net/ServiceModel/
您也许可以重用 XmlSerializerOperationBehavior 中的某些部分。也许添加某种错误报告。
2) 我从来不喜欢通过 XmlSerializer 进行 Xml 验证。
XmlSerializer 的目的是序列化/反序列化对象,仅此而已。部分构造的对象是一场噩梦。我强烈建议(以及我自己一直遵循的XmlSerializer用法)是根据模式实际验证 XML,然后反序列化。
抛开一切不谈,@CodeCaster 的建议很好。