views:

412

answers:

1

I have wsdl from third party server. Ran svcutil and ended up wih a set of

XmlNode AMethod(object Request);

methods. There is a separate 100 page pdf describing response/request objects for each method

My thought was wrap web methods and use XmlSerializer to return strongly typed objects. Returned xml looks like this (i removed soap headers):

<Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xsi:type="ResponseExt" 
        xmlns="http://www.thirdparty.com/lr/"&gt;
  <Code>0</Code>
  <Message>SUCCESS</Message>
  <SessionId>session_token</SessionId>
</Response>

Looked simple. Created a class(from document/wire captures):

[XmlRoot("Response")]
//EDIT added XmlType
[XmlType("ResponseExt", Namespace = "http://www.thirdparty.com/lr/")]
public class MyClass {
    public string Code {get; set;}
    public string Message {get; set;}
    public string SessionId {get; set;}
}

Processing time:

//XmlNode node = xml from above
XmlSerializer serializer = new XmlSerializer(typeof(MyClass));
XmlNodeReader reader =  new XmlNodeReader(node);
Myclass myclass = serializer.Deserialize(reader) as MyClass

Last line is where it blows up with inner exception message: The specified type was not recognized: name='ResponseExt', namespace='http://www.thirdparty.com/lr/', at <Response xmlns=''>.
I can't figure out how to make Serializer happy and what exactly these two mean

xsi:type="ResponseExt" xmlns="http://www.thirdparty.com/lr/

As always any advice and pointer are appreciated


EDIT: Accepted answer below.

I was still getting exception, until i found this, hopefully it'll save someone some time. I started to work backwards. Captured xml on the wire. Deserialized to my created classes with correct attributes: worked like a charm. Tried again from webservice - exception. For some reason XmlSerializer doesn't recognize ResponseExt.

XmlSerializer serializer = new XmlSerializer(typeof(Response));
XmlNode node = (XmlNode)results[0];
XmlDocument doc = new XmlDocument();
doc.LoadXml(node.OuterXml); //reload node
XmlNodeReader reader = new XmlNodeReader(doc.FirstChild); //there is only one node
Response rsp = serializer.Deserialize(reader) as Response; //works

EDIT: underlying issue wsdl file was not complete. After spending 2 days on this and finding this (ugly) workaround, third-party vendor provided complete WSDL with all types that deserialize without errors.

+1  A: 

Why are you manually deserializing XML, when you have WSDL ?

If you have WSDL, use the svcutil.exe tool, or the wsdl.exe tool, to generate proxy classes and DTOs for the XML messages being sent and received on the wire.

The point of a web services toolkit, or "stack" is to provide this for you, so that you don't have to author classes and XML serialization code by hand.

Did you try this? Did you try to run the WSDL through one of those tools? Or did you try to "Add web reference" in Visual Studio?


After updating the question, I suggest that you modify the WSDL, rather than write custom code. You can produce a custom WSDL for the service, which will correctly generate the proxy classes you want. If you don't need all 100 methods (or however many there are), then leave them out. If you want a custom object from a method, then define a complexType that corresponds to that object. This is much simpler and more reliable than hand-authoring XML deserialization code for each method.


If you don't like that idea, and want to stick with manually writin the XML deserialization code, then you need to do two things:

  1. attach a namespace to the XmlRoot attribute.

  2. change the name of your class to ResponseExt, and derive it from a class called Response. Decorate that Response class with an XmlInclude attribute. This aligns the use of the Xml Serializer with the xsi:type used in the XML fragment.

It looks like this in code:

[XmlRoot("Response", Namespace="http://www.thirdparty.com/lr/")]
public class ResponseExt : Response {
}

[XmlRoot("Response", Namespace="http://www.thirdparty.com/lr/")]
[XmlInclude(typeof(ResponseExt))]
public class Response {
    public string Code {get; set;}
    public string Message {get; set;}
    public string SessionId {get; set;}
}

public class XsiType
{
    public static void Main(string[] args)
    {
        try
        {
            string filename = "XsiType.xml";
            XmlSerializer s1 = new XmlSerializer(typeof(Response));
            ResponseExt r = null;
            using(System.IO.StreamReader reader= System.IO.File.OpenText(filename))
            {
                r= (ResponseExt) s1.Deserialize(reader);
            }

            var builder = new System.Text.StringBuilder();
            var xmlws = new System.Xml.XmlWriterSettings { OmitXmlDeclaration = true, Indent= true };
            using ( var writer = System.Xml.XmlWriter.Create(builder, xmlws))
            {
                //s1.Serialize(writer, r, ns);
                s1.Serialize(writer, r);
            }
            string xml = builder.ToString();
            System.Console.WriteLine(xml);

        }
        catch (System.Exception exc1)
        {
            Console.WriteLine("Exception: {0}", exc1.ToString());
        }
    }
}

related: How can I force the use of an xsi:type attribute?

Cheeso
Cheeso, i did run the tools unfortunatly wsdl does not contain request/response information all methods take object and return XmlNode. There is a separate document describing each method. I updated the question to clarify the issue.
smvlad
Ok, I updated my answer.
Cheeso
updated the code with namespaces, renamed classes. still same exception
smvlad
not sure why. if I were you I'd simplify it and de-couple the deserialization from the message transfer. Once you can deserialize a message of the type you think you need to deserialize, *using a filesystem file*, then move that deser code back into the WCF proxy to deserialize from the XmlDocument. One way to move forward.
Cheeso