簡體   English   中英

使用Web服務客戶端/ ClientBase檢測無效的XML響應

[英]Detecting invalid XML response with web service client/ClientBase

我們目前正在使用Web服務(IBM Message Broker)。 由於服務仍在開發中,在許多情況下它會返回無效的XML(是的,這將被修復,我承諾)。

使用由ClientBase<T>生成的svcutil生成的客戶端從.NET調用此服務時會出現此問題。 似乎使用的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);
    }
  }
}

附加到UnknownElement事件時,它會正確報告無效的XML(元素順序不匹配),但在使用ClientBase<T> ,這些(以及其他一些情況)將被忽略(就像不使用XmlSerializer的錯誤事件一樣) 。

我的問題是如何讓ClientBase<T>檢測無效的XML? 有沒有辦法掛鈎ClientBase<T>使用的XmlSerializer的故障事件?

目前,如果沒有意義,我們必須使用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) {}
}

隨附的驗證行為並不特別復雜:

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){}
}

您可以創建一些配置元素並通過配置應用行為,也可以通過在打開客戶端連接之前修改客戶端的通道工廠以編程方式執行此操作。 這是程序化方法:

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);

我會建議與Tragedian相同的實現,例如。 創建一個添加到服務端點的客戶端消息檢查器,該檢查器預先形成所有消息的模式驗證。

使用本地服務架構進行動態驗證

下面是動態加載最初從用於生成服務引用的服務中獲取的模式的示例。 這樣,您始終可以更新服務,而無需更改此代碼以使用架構驗證xml。

這使用您必須的服務引用加載解決方案上的現有架構(您可以使用文件資源管理器在項目內的ServiceReference文件夾中查看該架構信息。

using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Xml.Schema;

namespace ConsoleApplication1
{
    class Program
    {
        class XsdValidationInspector : IClientMessageInspector ... //omitted for clarity
        class XsdValiationBehavior : IEndpointBehavior ... //omitted for clarity

        static void Main(string[] args)
        {
            ContractDescription cd = ContractDescription.GetContract(typeof(ServiceReference1.IService1));

            WsdlExporter exporter = new WsdlExporter();

            exporter.ExportContract(cd);

            XmlSchemaSet set = exporter.GeneratedXmlSchemas;

            // Client implementation omitted for clarity sake.
            var client = <some client here>; //omitted for clarity

            var validation = new XsdValidationBehavior(new XmlSchemaSet { xmlSchema });

            client.ChannelFactory.Behaviours.Add(validation);
        }
    }
}

動態檢查服務端點模式

但是根據您關於不必更改硬編碼模式和/或我在下面添加的對象的注釋,您可以從服務端點動態自動獲取模式。 我建議你緩存這個。

您甚至可以使用它來識別服務端點是否已更改,例如。 當您第一次獲得服務的引用時,將其保存到磁盤並生成消息,然后服務可以每天從服務端點動態獲取架構,並檢查是否有任何修改或差異,並通知您或記錄任何錯誤。

請參閱下面的示例,了解如何執行此操作。

using System;
using System.IO;
using System.Net;
using System.Web.Services.Description;
using System.Text;
using System.Xml.Schema;

namespace ConsoleApplication1
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            //Build the URL request string
            UriBuilder uriBuilder = new UriBuilder(@"http://myservice.local/xmlbooking.asmx");
            uriBuilder.Query = "WSDL";

            HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uriBuilder.Uri);
            webRequest.ContentType = "text/xml;charset=\"utf-8\"";
            webRequest.Method = "GET";
            webRequest.Accept = "text/xml";

            //Submit a web request to get the web service's WSDL
            ServiceDescription serviceDescription;
            using (WebResponse response = webRequest.GetResponse())
            {
                using (Stream stream = response.GetResponseStream())
                {
                    serviceDescription = ServiceDescription.Read(stream);
                }
            }

            Types types = serviceDescription.Types;
            XmlSchema xmlSchema = types.Schemas[0];

            // Client implementation omitted for clarity sake.
            var client = some client here;

            var validation = new XsdValidationBehavior(new XmlSchemaSet { xmlSchema });

            client.ChannelFactory.Behaviours.Add(validation);

        }
    }
}

這樣您就不需要每次都重新生成模式,因為它總是會獲取最新的模式。

您可以配置svcutil以使用DataContractSerializer執行序列化

/serializer:DataContractSerializer

生成使用數據協定序列化程序進行序列化和反序列化的數據類型。

簡短形式:/ ser:DataContractSerializer

如果DataContractSerializer遇到錯誤排序的元素(有時對元素排序非常嚴格 )或其他問題,它將拋出異常。

現在,我對此並不是百分之百確定,但我相信派對正在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));
  }

正如您所看到的,沒有人將自己掛鈎到XmlSerializer.UnknownElement 但是,我們再說不過,因為XmlSerializer是通過參數傳遞的。 長話短說; 它來自replyMessageInfo.BodySerialize r或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建議很好。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM