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

WCF generated proxy throws InvalidOperationException due to multiple types with same name in WSDL

我正在使用 Visual Studio 2013 从 this WSDL file 生成 WCF 服务代理。但是,一旦我尝试调用 setSalesItemsV3 方法,WCF 就会从 System.Xml.dll.

深处抛出一个 InvalidOperationException

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

这是内部异常:

Message: The top XML element 'start' from namespace '' references distinct types WsdlDuplicateName.SalesItemService.hsSimpleDate and System.DateTime. Use XML attributes to specify another XML name or namespace for the element or types.

我不是阅读 WSDL 的专家,但我看过它,唯一引用名称 "start" 的部分是一些 <wsdl:part> 元素 name="start"

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

但是,这些部分在完全不同的消息中,所以我不明白为什么会有任何混淆。我已经通过几个在线 WSDL 验证器 运行 了 WSDL 文件,他们似乎同意了。

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

class Program
{
    static void Main(string[] args)
    {
        SalesServiceClient client = new SalesServiceClient();
        var date = ToSimpleDate(new DateTime());

        // throws InvalidOperationException
        // Message == "There was an error reflecting 'start'."
        client.setSalesItemsV3(1, 1, null, date, date);
    }

    static hsSimpleDate ToSimpleDate(DateTime time)
    {
        return new hsSimpleDate
        {
            year = time.Year,
            month = time.Month,
            day = time.Day,
        };
    }
}

为了演示问题,让我们看一下生成的 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 元素 'start' 引用了不同的类型 ” 序列化程序异常。

如果我们有这个选项:

(b) the changes I can make to the generated proxies to make the serializer happy

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

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

如果您将对此服务器的引用添加为 Web 引用(添加 -> 服务引用... -> Advanced... -> Add Web Reference...) 和编写相同的web方法调用,不会出现序列化问题。实际上,就我而言,我在测试示例中收到了另一种服务器异常,但它会解决您的直接序列化问题。

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

希望这会有所帮助。

UPDATE:要找到真正导致此问题的原因,我们需要深入研究 XmlReflectionImporter 源代码。首先,我们的 WSDL 使用 XSD 模式来定义命名空间:http://www.w3.org/2001/XMLSchema for xsd and http://services.hotschedules.com/api/services/SalesService for tnsXmlReflectionImporter 使用 NameTable(这是 Hashtable 的包装器)来存储“accessors”。访问器是一对 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 是正确的并且将通过验证,但是由于 XmlReflectionImporter class 中 ReconcileAccessor 的这种通用实现,我们得到了一个异常。不确定这是否是 XmlReflectionImporter 的确切问题,或者可能是更高抽象层上的另一个问题。并且,"Web Reference" 生成的源未使用 XmlReflectionImporter

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

您的问题可能是您使用 WSDL 的方式。在我工作的地方,我们有较旧的服务,要求我们使用 wsdl.exe 从 WSDL 生成 class 文件。我们还使用 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 错误,因为没有提供安全性。

我看到几个选项:

  1. 使用“add web reference”代替 "add service reference"。我已经验证它有效。这将回退到经典的 asp.net 服务代理,它不像 wcf 那样闪亮,但可以完成工作。

  2. 因为只有 6 种方法(有些看起来是虚拟的),您可以将 wsdl 导入 6 次到 6 个不同的代理(可能更少)。每次更改 wsdl 以仅包含一个操作(只需删除其他操作标签,不要打扰 messages/schema)。

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

  4. (未测试)我相信您可以重构 WSDL,这样每条消息将包含一个名为 "parameter" 的部分,而不是部分元素,该部分将指向包装器 xsd键入所有原始零件。您将为每条消息构建一个包装器。您可以将 wcf 配置为将其视为裸参数,而不发出虚拟包装器元素,因此在线路上它看起来是一样的。

  5. 当然如果你能换服务器那是最好的。

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