将多个WSDL版本反序列化为同一对象

时间:2011-03-23 19:06:47

标签: c# wsdl webservice-client xml-deserialization

我正在使用其他公司的网络服务,他们有多个版本在运行,每个新版本只添加了新的字段/对象,但是更改了一些元素名称。

我希望能够使用具有相同代码的任何版本。

具体在一个版本中,搜索方法返回: <searchReturn><SummaryData_Version1Impl /><SummaryData_Version1Impl /></searchReturn>

并使用其他版本:<searchReturn><SummaryData_Version2Impl /><SummaryData_Version2Impl /></searchReturn>

所以现在由wsdl.exe生成的代理因为元素更改而无法同时使用它们。

  1. 最好的解决方案是让另一家公司修改他们的服务而不更改元素名称,但在这种情况下这是不太可能的
  2. 我认为我最好的办法就是手动发送和获取SOAP请求,然后修改元素名称,然后手动反序列化,到目前为止看起来它可能会起作用。 - 但需要相当多的工作
    • 我刚刚确认手动加载xml(在使用string.Replace更改元素名称后)会将任何版本的服务反序列化为所需的对象
  3. 或者通过修改生成的代理来做类似的事情:
    • 如果我可以在生成的代理尝试反序列化之前拦截并修改soap响应
    • 如果我可以在运行时修改服务的XmlTypeAttribute
  4. 我还想过拥有一系列接口,所以每个类都有旧class Data3 : IData3, IData2, IData1的接口,我认为这样可以让我至少向下抛出。并将每个版本放入不同的命名空间。
  5. 有一些鸭子打字技术我稍微调查了一下可能会起作用,但似乎不太可靠。
  6. 还有其他方法可以从多个元素名称反序列化吗?

3 个答案:

答案 0 :(得分:1)

没有办法做到这一点。不同的版本是不同的。没有办法提前知道它们有多相似。

答案 1 :(得分:1)

我在原始问题中提到的选项2可以工作:(这不是一个完整的例子,但应该相当明显,你需要修改它以使它在你的情况下工作,也标记这个wiki所以任何人可以在将来简化这一点)

解决方案在此处描述:手动制作Soap Request,但在处理完回复后,使用所有wsdl.exe生成的类来反序列化并保存数据。

  • 不是一个简单的解决方案,但它比使用wsdl.exe生成的方法调用要宽松得多,并且使用生成的类的任何方法仍然可以正常工作
  • 我相信它能够加载目标对象和源响应之间常见的任何元素,因此如果新版本只添加字段:
    1. 从较新版本加载将包含除新字段之外的所有数据
    2. 从旧版本加载,较新的字段将为null
  • 另一个好处是你可以从任何地方(从磁盘读取,上传到网页)加载和xml字符串到NormalizeSummaryVersion,其余的过程将完全相同,从而使得对象具有兼容性。

设置WebRequest是这样的:(我的是具有基本身份验证的https网络服务,无法使req.Credentials正常工作,因此我手动添加该标头)

WebRequest req = WebRequest.Create(url);
req.Headers.Add("SOAPAction", soapAction);
req.ContentType = "text/xml;";
req.Method = WebRequestMethods.Http.Post;
req.Headers.Add(HttpRequestHeader.Authorization, "Basic " + basicAuthEncoded);

然后向该流写入webmethod的xml数据:这是此方法的主要缺点,我还没有找到一种可靠的方法来生成soap信封,对于我的服务它似乎不太关心xmlns:ver中列出的版本,因此我使用此字符串并将SerializeObject(SearchCriteria)传递给它

//{0} is the soapAction
//{1} is the xml for that call

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ver="fake">
   <soapenv:Header/>
   <soapenv:Body>
      <ver:{0}>
         {1}
      </ver:{0}>
   </soapenv:Body>
</soapenv:Envelope>

注意:以下是我的概念证明代码,我确信它可以清理并简化相当数量。

有了这个,我就能从服务中读取xml响应。接下来,我调用NormalizeSummaryVersion重命名可能的节点名称差异,如果需要,还可以处理其中的任何其他节点或数据。

public string NormalizeSummaryVersion(string xmlString)
{
    xmlString = Regex.Replace(xmlString,"SummaryData_Version2_2Impl|SummaryData_Version3_3Impl|SummaryData_Version4_4Impl",
                                "SummaryData_Version1_1Impl");

    return xmlString;
}

所以现在节点有一个共同的名称和格式(额外或缺少的节点似乎并不重要,它只是忽略它们或使用这种反序列化方法将它们设置为默认值)

ProcessLikeServicesoapenv:Envelope元素中提取要反序列化的XmlArray,并将其放入新的XmlDocument中,然后将其转换回字符串。

因此,NormalizeSummaryVersion之后GetData() XmlDocument processedDoc内部将是此xml,无论Soap Response来自何种版本:

<?xml version="1.0" encoding="utf-16"?>
<searchReturn>
  <SummaryData_Version1_1Impl>
    <customerFirstName>first</customerFirstName>
    <customerLastName>last</customerLastName>
  </SummaryData_Version1_1Impl>
</searchReturn>

最后我能够使用通用的XmlDeserialize方法来获取我想要的对象。 (我对所有这些的主要调用实际上返回GetData(xmlString).searchReturn,因为

[XmlRoot("searchReturn")]
public class SearchReturn
{
    [XmlElement("SummaryData_Version1_1Impl", typeof(SummaryData))]
    public SummaryData[] searchReturn;
}

public SearchReturn GetData(string xmlString)
{
    System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
    doc.LoadXml(xmlString);


    System.Xml.XmlNode DataNode = doc.SelectSingleNode("//searchReturn");

    System.Xml.XmlDocument processedDoc = new System.Xml.XmlDocument();
    processedDoc.AppendChild(processedDoc.ImportNode(DataNode, true));


    SearchReturn data = Deserialize<SearchReturn>(processedDoc);
    return data;
}

通用反序列化方法:

public static T Deserialize<T>(XmlDocument xml)
{
    XmlSerializer s = new XmlSerializer(typeof(T));

    using (XmlReader reader = new XmlNodeReader(xml))
    {
        try
        {
            return (T)s.Deserialize(reader);
        }
        catch (Exception)
        {
            throw;
        }
    }
    throw new NotSupportedException();
}

答案 2 :(得分:1)

我现在认为最好的选择是使用SoapExtension 并设置SoapExtensionAttribute来触发在修改响应所需的任何方法上使用它。

  1. 修改生成的代码并将属性[ModifyResponseExtensionAttribute]添加到需要修改的任何方法中,在您的情况下,您可能需要多个SoapExtension类
  2. 将以下类添加到项目中:

    public class ModifyResponseExtension : SoapExtension
    {
        Stream inStream;
        Stream outStream;
    
        // Save the Stream representing the SOAP request or SOAP response into
        // a local memory buffer.
        public override Stream ChainStream(Stream stream)
        {
            inStream = stream;
            outStream = new MemoryStream();
            return outStream;
        }
    
        //This can get properties out of the Attribute used to enable this
        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            return null;
        }
    
        //This would have default settings when enabled by config file
        public override object GetInitializer(Type WebServiceType)
        {
            return null;
        }
    
        // Receive the object returned by GetInitializer-- set any options here
        public override void Initialize(object initializer)
        {
        }
    
        //  If the SoapMessageStage is such that the SoapRequest or
        //  SoapResponse is still in the SOAP format to be sent or received,
        //  save it out to a file.
        public override void ProcessMessage(SoapMessage message)
        {
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeSerialize:
                    break;
                case SoapMessageStage.AfterSerialize:
                    //This is after the Request has been serialized, I don't need to modify this so just copy the stream as-is
                    outStream.Position = 0;
                    Copy(outStream, inStream);
                    //Not sure if this is needed (MSDN does not have it) but I like closing things
                    outStream.Close();
                    inStream.Close();
                    break;
                case SoapMessageStage.BeforeDeserialize:
                    //This is before the Response has been deserialized, modify here
                    //Could also modify based on something in the SoapMessage object if needed
                    ModifyResponseMessage();
                    break;
                case SoapMessageStage.AfterDeserialize:
                    break;
            }
        }
    
        private void ModifyResponseMessage()
        {
            TextReader reader = new StreamReader(inStream);
            TextWriter writer = new StreamWriter(outStream);
    
            //Using a StringBuilder for the replacements here
            StringBuilder sb = new StringBuilder(reader.ReadToEnd());
    
            //Modify the stream so it will deserialize with the current version (downgrading to Version1_1 here)
            sb.Replace("SummaryData_Version2_2Impl", "SummaryData_Version1_1Impl")
                .Replace("SummaryData_Version3_3Impl", "SummaryData_Version1_1Impl")
                .Replace("SummaryData_Version4_4Impl", "SummaryData_Version1_1Impl");
            //Replace the namespace
            sb.Replace("http://version2_2", "http://version1_1")
                .Replace("http://version3_3", "http://version1_1")
                .Replace("http://version4_4", "http://version1_1");
    
            //Note: Can output to a log message here if needed, with sb.ToString() to check what is different between the version responses
    
            writer.WriteLine(sb.ToString());
            writer.Flush();
    
            //Not sure if this is needed (MSDN does not have it) but I like closing things
            inStream.Close();
    
            outStream.Position = 0;
        }
    
        void Copy(Stream from, Stream to)
        {
            TextReader reader = new StreamReader(from);
            TextWriter writer = new StreamWriter(to);
            writer.WriteLine(reader.ReadToEnd());
            writer.Flush();
        }
    }
    
    // Create a SoapExtensionAttribute for the SOAP Extension that can be
    // applied to an XML Web service method.
    [AttributeUsage(AttributeTargets.Method)]
    public class ModifyResponseExtensionAttribute : SoapExtensionAttribute
    {
        private int priority;
    
        public override Type ExtensionType
        {
            get { return typeof(ModifyResponseExtension); }
        }
    
        public override int Priority
        {
            get { return priority; }
            set { priority = value; }
        }
    }
    
  3. 因此,在需要时可以手动修改wsdl.exe生成的类的请求/响应。