10

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,
        };
    }
}
Stephen Jennings
  • 12,494
  • 5
  • 47
  • 66
  • are you sure your use of tns:hsSimpleDate is correct? The error message could indicate a interface mismatch... have you tried to pass in null instead (just to see if it works)? – Jocke Mar 18 '15 at 20:36
  • @Jocke The WSDL was made by a different company; if it's invalid, I can discuss it with them. But, I just don't know enough about WSDL to say whether it's valid or not. – Stephen Jennings Mar 18 '15 at 20:46
  • I was sloppy in my comment: I guess their wsdl is fine (and that you are not the only one using it), but you have your own implementation of ToSimpleDate() - and that could be the issue. Or that your should not pass it in as both param 4 and 5... to steSalesItemsV3(). And since it is a reference type you could try to just pass in null. Perhaps they can provide you with a demo client project? – Jocke Mar 18 '15 at 20:51
  • @Jocke The issue has nothing to do with their service. The exception is thrown during serialization to the SOAP message, before any data has been transferred to the service. Therefore, the parameters I'm passing don't matter because nothing is validating them (yet). – Stephen Jennings Mar 18 '15 at 21:46
  • The wsdl works fine from SOAPUI, but the reference.cs which is created from servicereference looks a little unusual (at least not like most of the generated files I compared to). You can fiddle the namespaces for the start property (and then end property) to be something consistent other than "" and it then accepts those properties, but then hits a similar exception for return.. It's failing to distinguish between the various identically named properties used in different methods, but for the life of me I don't know why I'm afraid. It's namespace related, but I can't see the actual problem. – mcr Mar 18 '15 at 22:48
  • Other people have noted the same error elsewhere, but no clear answers I could see : http://forums.asp.net/t/1876605.aspx?Serice+Reference+call+Wierd+Error http://stackoverflow.com/questions/10532271/the-xml-element-named-name-from-namespace-references-distinct-types https://social.msdn.microsoft.com/Forums/en-US/e3405d68-9d48-4600-8fa0-1587aa380c47/sgenexe-error-the-top-xml-element-from-namespace-references-distinct-types?forum=asmxandxml – mcr Mar 18 '15 at 22:50

3 Answers3

11

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.

knyu
  • 996
  • 6
  • 8
  • 1
    This should not be an issue of "Add Web Reference" vs. "Add Service Reference". In general, those should be compatible. If there is a specific incompatibility (which I doubt) then it should be specifically noted. In general, do not go running to "Add Web Reference", which uses legacy technology that should not be used in new development. – John Saunders Mar 26 '15 at 18:03
  • Agreed, but in my VS2013 there's no problem with serializer in case of Web Reference, but the problem exists if I use Service Reference. Anyway, I'm agreed with that this is not recommended way. I was looked for incompatibility description related to issue described, but no luck. We can't change 3rd-party service contract and need some kind of workaround. If it is possible to change the service contract it would be a much better solution. But, again, it's not possible considering options provided by @Stephen Jennings. – knyu Mar 26 '15 at 18:19
  • setSalesItems and setSalesItemsV3 are completely different message types. On these separate messages, why does it matter that two members have the same name and namespace? Is this disallowed by WSDL, or is the serializer just doing something wrong? – Stephen Jennings Mar 26 '15 at 19:12
  • Update an answer. WSDL is correct, but XmlReflectionImporter.ReconcileAccessor throws an exception. – knyu Mar 26 '15 at 20:34
  • Oh, nice, a dive into the reference source. I did add namespaces to a similarly non-functional web service and it started working. I'm almost certain adding some namespaces to the generated code will be the best solution here, so I'll try that when I have a moment and get back to you. – Stephen Jennings Mar 26 '15 at 23:08
  • Thanks. Sorry for a little bit confusing answer :) My assumptions were: 1) WSDL is correct; 2) no problem to access service from another clients; 3) We can't change "empty namespaces" for generated sources (I tried svcutil and .svcmap options); 4) XmlReflectionImporter consider all accessors must have a correct namespace. You can take a look at this blog post related to a similar issue (but not exact the same): http://goo.gl/FoVceL. – knyu Mar 27 '15 at 08:18
  • Running into same issue with a 3rd party service. I tried changing the namespaces as shown above to the `WrapperNamespace` but now it will silently not deserialize responses. In my case, there are 4 different types it uses for a single property (it's a generic `return` property). UGGGGH. – kamranicus Jan 07 '16 at 21:26
  • Alright, I missed the part where you didn't need to change namespace for array types. I removed the namespace from the array types and now it deserializes properly. Glad I wasted the entire day on this BS. :( I hate XML. – kamranicus Jan 07 '16 at 21:56
  • 3
    hello from 2019... this issue still exists and msft should have addressed by now for those of us who cannot control the WSDLs we must consume. Apparently there is some common tool for generating WSDL's in php that loves that generic 'return' property name and .net chokes reflecting on that XML. Wonderful. – Roger Apr 15 '19 at 16:16
1

Your problem might be how you are using the WSDL. Where I work we have older services that requires us to use wsdl.exe to generate the class files from the WSDL. We also use SoapUI to test our services. Without changing any of the WSDL or generated code I can make a request to that system.

Fiddler captures:

Outbound

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>

Inbound

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>

I received a 500 error from the system for not supplying security.

Brian from state farm
  • 2,825
  • 12
  • 17
1

I see a few options:

  1. use "add web reference" instead of "add service reference". I have verified it works. this will fallback into classic asp.net service proxy which is not as shiny as wcf but will do the work.

  2. since there are only 6 methods (some seem dummy) you could import the wsdl 6 times into 6 different proxies (probably less). each time chnage the wsdl to contain only one operation (just delete the otehr operation tags, don't bother with messages/schema).

  3. change the parameter names in the wsdl (start --> start1, start2...) and then in runtime build some message inspector that changes back (start1,start2-->start).

  4. (not tested) I beleive you can refactor the WSDL such that instead of part elements each message will have one part called "parameter" which will direct to a wrapper xsd type with all of the original parts. you will build one wrapper per message. you can configure wcf to treat this as bare parameters and not emit the dummy wrapper element so on the wire it looks the same.

  5. of course if you are in position to change the server that is best.

Every option has its pros and cons. Some will have a runtime overhead (#3) and some will complicate the design time. It also depends if you expect this WSDL to change and you will need to reimport it many times.

Yaron Naveh
  • 23,560
  • 32
  • 103
  • 158