2011-03-23 128 views
2

我在使用另一家公司的webservice,他们有多个版本运行,每个新版本只添加了新的字段/对象,但改变了一些元素名称。将多个WSDL版本分散到同一个对象

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

具体在一个版本的搜索方法的返回值: <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. 是否有任何其他方式来从多个元素名称反序列化?

回答

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

所以很有可能在需要时手动修改wsdl.exe生成的类的请求/响应。

1

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

1

我在我的原始问题中提到了选项2:(这不是一个完整的例子,但应该相当明显,您需要修改以使其在您的情况下正常工作,任何人都可以在以后简化该)

解决方案描述在这里:手动使SOAP请求,但使用所有的Wsdl.exe用生成的类反序列化和保存数据,处理所述响应之后。

  • 不是一个简单的解决方案,但它比使用wsdl宽松得多。exe生成的方法调用,并且任何使用生成的类的方法仍然可以正常工作
  • 我相信它能够加载目标对象和源响应之间通用的任何元素,因此如果新版本只添加字段:
      从较新的版本
    1. 负荷将所有不包括新的字段中的数据从旧版本的
    2. 加载,新领域将是空的
  • 另一个好处是,你可以加载和从任何地方XML字符串(读从磁盘,上传到网页)到NormalizeSummaryVersion和其余的公关ocess将完全相同,从而导致与其兼容的对象。

设立的WebRequest是这样的:(我的是一个HTTPS Web服务使用基本身份验证,无法得到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数据:这是这种方法的主要缺点,我还没有找到一种可靠的方法来生成肥皂信封,但对于我的服务,它似乎并不关心在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; 
} 

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

ProcessLikeService提取物我想从soapenv:Envelope元素中反序列化XmlArray,并将其放入一个新的XmlDocument中,并将其转换回字符串。

NormalizeSummaryVersion后的GetData()XmlDocument processedDoc内部,从而将这个XML,不管是什么版本的SOAP响应来自:

<?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; 
} 

和通用Deserialize方法:

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(); 
} 
相关问题