Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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,
        };
    }
}
like image 358
Stephen Jennings Avatar asked Mar 18 '15 20:03

Stephen Jennings


3 Answers

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.

like image 93
knyu Avatar answered Nov 01 '22 05:11

knyu


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.

like image 21
Brian from state farm Avatar answered Nov 01 '22 06:11

Brian from state farm


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.

like image 1
Yaron Naveh Avatar answered Nov 01 '22 04:11

Yaron Naveh