由于WSDL中具有相同名称的多个类型,WCF生成的代理会抛出InvalidOperationException

时间:2015-03-18 20:27:40

标签: wcf wsdl svcutil.exe proxy-classes

我使用Visual Studio 2013从this WSDL file生成WCF服务代理。但是,只要我尝试调用setSalesItemsV3方法,WCF就会从InvalidOperationException深处抛出System.Xml.dll

此示例项目演示了问题:https://github.com/jennings/WsdlDuplicateNameProblem

这是内在的例外:

  

消息:顶级XML元素' start'来自命名空间''引用不同类型WsdlDuplicateName.SalesItemService.hsSimpleDate和System.DateTime。使用XML属性为元素指定另一个XML名称或命名空间。

我没有阅读过WSDL的专家,但是我已经看过了它,并且唯一引用该名称的部分&#34; start&#34;是一些带有<wsdl:part>的{​​{1}}元素:

name="start"

但是,这些部分是完全不同的信息,所以我不明白为什么会有任何混淆。我通过几个在线WSDL验证器运行WSDL文件,它们似乎没问题。

以下是重现问题所需的项目中唯一的代码(除了生成的代理)。

<wsdl:message name="setSalesItems">
  <wsdl:part name="start" type="xsd:dateTime"></wsdl:part>
</wsdl:message>

<wsdl:message name="setSalesItemsV3">
  <wsdl:part name="start" type="tns:hsSimpleDate"></wsdl:part>
</wsdl:message>

3 个答案:

答案 0 :(得分:9)

为了演示这个问题,我们来看一下生成的Reference.cs:

public partial class getSalesItemsV3 {
  // skipped
  [System.ServiceModel.MessageBodyMemberAttribute(Namespace="", Order=2)]
  public WsdlDuplicateName.SalesItemService.hsSimpleDate start;  
  // skipped
}

public partial class setSalesItems {
  // skipped
  [System.ServiceModel.MessageBodyMemberAttribute(Namespace="", Order=3)]
  public System.DateTime start;
  // skipped
}

请注意,这些元素具有相同的名称(start)和MessageBodyMember属性("",空命名空间)声明的相同名称空间。这导致“顶级XML元素'从命名空间开始''引用不同类型”序列化器异常。

如果我们有这个选项:

  

(b)我可以对生成的代理进行的更改   序列化快乐

...我们可以手动设置元素startendreturn的命名空间(它们都会导致麻烦)。我自己做了并把结果here。您可以将其粘贴到Reference.cs中,序列化程序异常将消失。

但似乎问题的根本原因是此服务(http://services.hotschedules.com/api/services/SalesService?wsdl)旨在通过WebServices使用(此问题是某种不兼容性)。

如果您将此服务器的引用添加为Web引用(添加 - &gt; 服务引用... - &gt; 高级... - &gt; 添加Web引用... )并编写相同的Web方法调用,不会出现序列化问题。实际上,在我的情况下,我在测试示例中收到了另一种服务器异常,但它将解决您的直接序列化问题。

可以找到代码的镜像副本,但使用Web服务引用(并且不需要对生成的文件进行任何更改)here

希望这会有所帮助。

更新:要找到实际导致此问题的原因,我们需要深入研究XmlReflectionImporter源代码。首先,我们的WSDL使用XSD架构来定义名称空间:xsd http://www.w3.org/2001/XMLSchema tns http://services.hotschedules.com/api/services/SalesService XmlReflectionImporter使用NameTable(这是Hashtable的包装)来存储“访问者”。访问者是一对NamespaceName

让我们看看抛出异常的源代码:

private Accessor ReconcileAccessor(Accessor accessor, NameTable accessors)
{
   // initial check skipped
   // look for accessor by name and namespace, add to accessors hash if not found and return
   Accessor accessor1 = (Accessor) accessors[accessor.Name, accessor.Namespace];
   if (accessor1 == null)
   {
        accessor.IsTopLevelInSchema = true;
        accessors.Add(accessor.Name, accessor.Namespace, (object) accessor);
        return accessor;
   }

   // accessor ("start" in our case) found!

   // check if mappings is the same and return accessor. This is not our case, we have two accessors with the same name but different mappings (despite that this mappings is have the same type)!
   if (accessor1.Mapping == accessor.Mapping)
     return accessor1;

    // next I skipped some  reconciliations for MembersMapping and ArrayMapping. Please note that it performed by types, for example:
    // if (accessor.Mapping is ArrayMapping) { /* some logic */}

   // Our mapping is not MembersMapping or ArrayMapping and we finally got there:      
   throw new InvalidOperationException(Res.GetString("XmlCannotReconcileAccessor", (object) accessor.Name, (object) accessor.Namespace, (object) XmlReflectionImporter.GetMappingName((Mapping) accessor1.Mapping), (object) XmlReflectionImporter.GetMappingName((Mapping) accessor.Mapping)));

   // Resource definition is: XmlCannotReconcileAccessor=The top XML element '{0}' from namespace '{1}' references distinct types {2} and {3}. Use XML attributes to specify another XML name or namespace for the element or types.
    // using this resource template you can see that string representations of mappings are "WsdlDuplicateName.SalesItemService.hsSimpleDate" and "System.DateTime".
}

因此,主要的协调逻辑是我们不能拥有两个具有相同名称但名称空间不同的访问者MembersMappingArrayMapping类型可能存在一些例外情况,但不是我们的情况。

我相信这是某种错误。 WSDL是正确的并且将通过验证,但是由于来自ReconcileAccessor类的XmlReflectionImporter的这种通用实现,我们得到了一个例外。不确定这是XmlReflectionImporter的确切问题,还是在更高的抽象层上可能存在另一个问题。而且,“Web引用”生成的源不使用XmlReflectionImporter

另一件值得一提的是:生成器为MessageBodyMemberAttribute设置了Namespace=""值,这有效地打破了对帐过程。所以,我认为存在一些不一致或不兼容的问题。

答案 1 :(得分:1)

您的问题可能是您使用WSDL的方式。在我工作的地方,我们有旧的服务,要求我们使用wsdl.exe从WSDL生成类文件。我们还使用SoapUI来测试我们的服务。在不更改任何WSDL或生成的代码的情况下,我可以向该系统发出请求。

Fiddler抓住:

外向

POST http://services.hotschedules.com/api/services/SalesService HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 4.0.30319.18444)
VsDebuggerCausalityData: uIDPo7vfNRAHy8VFtfrdjickfDQAAAAAVvkpSjtKpEyy02P7sVr8C51Xoz163FNKvwhRT+6uA+wACQAA
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
Host: services.hotschedules.com
Content-Length: 536
Expect: 100-continue
Proxy-Connection: Keep-Alive

<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><setSalesItemsV3 xmlns="http://services.hotschedules.com/api/services/SalesService"><concept xmlns="">1</concept><storeNum xmlns="">1</storeNum><start xmlns=""><day>1</day><month>1</month><year>1</year></start><end xmlns=""><day>1</day><month>1</month><year>1</year></end></setSalesItemsV3></soap:Body></soap:Envelope>

入站

HTTP/1.1 500 Internal Server Error
Server: Apache-Coyote/1.1
Content-Type: text/xml;charset=UTF-8
Content-Length: 366
Date: Thu, 26 Mar 2015 16:51:22 GMT
Proxy-Connection: Keep-Alive
Connection: Keep-Alive

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><soap:Fault><faultcode xmlns:ns1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">ns1:InvalidSecurityToken</faultcode><faultstring>Error in SecurityHeader:  An invalid security token was provided</faultstring></soap:Fault></soap:Body></soap:Envelope>

我从系统收到500错误,因为没有提供安全保护。

答案 2 :(得分:1)

我看到了几个选项:

  1. 使用“add web reference”代替“添加服务引用”。我已经确认它有效。这将回归到经典的asp.net服务代理,它不像wcf那样闪亮但会完成工作。

  2. 因为只有6种方法(有些似乎是假的)你可以将wsdl导入6个不同的代理(可能更少)。每次chnage wsdl只包含一个操作(只需删除ootehr操作标记,不要打扰消息/模式)。

  3. 更改wsdl中的参数名称(start - &gt; start1,start2 ...),然后在运行时构建一些更改回来的message inspector(start1,start2 - &gt; start)

  4. (未经测试)我相信你可以重构WSDL,这样每个消息都会有一个名为“参数”的部分代替部分元素,它将指向包含所有原始部分的包装器xsd类型。你将为每条消息构建一个包装器。您可以配置wcf将其视为裸参数,而不是发出虚拟包装元素,因此它看起来是相同的。

  5. 当然,如果您有能力更改最佳服务器。

  6. 每个选项都有其优点和缺点。有些会产生运行时开销(#3),有些会使设计时间复杂化。它还取决于您是否希望更改此WSDL,并且您需要多次重新导入它。