Search code examples
wcfwsdlsvcutil.exeproxy-classes

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


I'm using Visual Studio 2013 to generate a WCF service proxy from this WSDL file. However, as soon as I try to call the setSalesItemsV3 method, WCF throws an InvalidOperationException from deep in System.Xml.dll.

This sample project demonstrates the problem: https://github.com/jennings/WsdlDuplicateNameProblem

This is the inner exception:

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.

I'm no expert at reading WSDL, but I've looked at it and the only sections that reference the name "start" are a few <wsdl:part> elements with 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>

But, the parts are in completely different messages, so I don't see why there should be any confusion. I've run the WSDL file through several online WSDL validators and they seem to be okay with it.

Below is the only code in the project necessary to reproduce the problem (besides the generated proxy).

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

Solution

  • To demonstrate the problem let’s take a look into generated 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
    }
    

    Please note that these elements have the same name (start) and the same namespace declared by the MessageBodyMember attribute ("", empty namespace). This cause "The top XML element 'start' from namespace '' references distinct types" serializer exception.

    If we have this option:

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

    ... we can set namespaces for elements start, end and return (they all cause troubles) manually. I did it by myself and put the result here. You can paste it into your Reference.cs and serializer exception will gone.

    But it seems that the root cause of your issue is that this service (http://services.hotschedules.com/api/services/SalesService?wsdl) is intended to be used through WebServices (and this problem is some kind of incompatibilities).

    If you add reference to this server as a Web Reference (Add -> Service Reference... -> Advanced... -> Add Web Reference...) and write the same web method call, no problems with serialization will occur. Actually, in my case I received another kind of server exceptions in my test example, but it will solve your immediate serialization problem.

    The mirror copy of your code, but using Web Service Reference (and not requires any changes in generated files) can be found here.

    Hope this will help.

    UPDATE: To found what is actually cause this problem we need to deep delve in XmlReflectionImporter source code. First, our WSDL using XSD schemas to define namespaces: http://www.w3.org/2001/XMLSchema for xsd and http://services.hotschedules.com/api/services/SalesService for tns. XmlReflectionImporter using NameTable (this is a wrapper for Hashtable) to store "accessors". Accessor is a pair of Namespace and Name.

    Let's see source code that throws exception:

    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".
    }
    

    So, the main reconciliation logic is we can't have two accessors with the same name but different namespaces! There're may be some exceptions for MembersMapping and ArrayMapping types, but it is not our case.

    I believe that this is some kind of a bug. The WSDL is correct and will pass validation, but due to this generic implementation of ReconcileAccessor from XmlReflectionImporter class we got an exception. Not sure if this is exact problem of XmlReflectionImporter, or may be there's another problem on a higher abstract layer. And, source generated by "Web Reference" is not using XmlReflectionImporter.

    Another thing is worth to mention: generator puts a Namespace="" value for MessageBodyMemberAttribute, what is effectively break the reconciliation process. So, I believe there's some inconsistency or incompatibility.