XmlNode in Generated Typed Class
I generated a C# (or VB.NET) class using an XSD file supplied from a Java-based web service. The schema includes a "body" element that can itself be more in-line XML as defined in a second XSD file (or in others). The generated class declares this body element as an XmlNode rather than an object.
Why isn't the body element declared as an object like the other XML elements? Do I have to serialize the object based on the second schema and then deserialize it into an XMLDocument or XmlFragment to get it into this XmlNode? I've seen examples on similar manipulations on XmlDocument types, but I'd prefer to work with C# types as much as possible and then serialize the complete object when it comes time to store it as an XML string.
I have googled and searched MSDN for help but this doesn't seem to be common.
XSD fragment from first:
<xs:complexTypename="bodyType"mixed="true">
<xs:sequence>
<xs:anynamespace=http://blah.blah.com/secondprocessContents="skip"minOccurs="0"maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>Class definition (C#) fragment:
PublicClass bodyType<System.Xml.Serialization.XmlTextAttribute(), _ System.Xml.Serialization.XmlAnyElementAttribute()> _
Public Any()As System.Xml.XmlNode
EndClass...
Public header As headerType
Public bodyAs bodyType
What wrong with adding WSDL url as web reference?
These elements are not directly referenced in the WSDL. The WSDL only specifies a string parameter in one of the method calls. The string is an escaped xml string.
The code that I'm currently concerned with is generating the XML string for clients. The XML string will be inserted into a database and then picked up by a system service and sent off to the web service. The response will come back later to our web service that matches the remote web service.
Note that the web service is SOAP/RPC; it's essentially a legacy web service that I can't change. Yes, we've got legacy web services already.
I guess the real question is...
If we have hierarchical document schemas, defined in separate files, what's the best way to add the component documents to the enveloping document in CSharp or VB.NET code?
I really want to use a strongly typed data type (the generated class definition) because is makes the client interface so easy: create an instance and set the values.
In this case, setting the value of a subelement seems to be difficult or I'm missing something.
Maybe the case where the subelement can be one of several types is more difficult than if it were strongly typed itself?
I'm confused. What does the XML instance look like exacly that you are trying to serialize? And what is the desired C# class you'd like to serialize into?
There are two XSchema definition files from a vendor that describe the XML content for a single parameter in a SOAP/RPC web service. One XSD describes a wrapper, let's call it A, for the other, B, (I didn't design these) and the wrapper indicates a sequence of <xs:any/> elements where XML defined by the second schema should be.
The goal is to have a reasonably simple object to use to build the string parameter for the SOAP/RPC call.
A contains:
<xs:complexType name="body" mixed=true>
<xs:sequence>
<xs:any namespace="http://temp.uri/B" processContents=skip minOccurs="0" maxOccurs="unbounded"/>
<xs:sequence>
</xs:complexType>
B contains a complex set of many elements with the outermost element <document>.
So, I would expect two classes A.vb and B.vb as a result of running XSD.EXE. We get those, but in A.vb, the reference to B seems to be as an XMLNode rather than an object of type B.
What we get in A.vb is:
Public Class body
'<remarks/>
<System.Xml.Serialization.XmlTextAttribute(), _
System.Xml.Serialization.XmlAnyElementAttribute()> _
Public Any() As System.Xml.XmlNode
End Class
So we cannot write something like:
Dim a As A = new A
a.body.document.somevalue = "text value here"
In order to serialize a complete object A with B as a part of it, it seems that we have to serialize A with no B object in it, then find the body node and then serialize a separate object B into that node's innerXML. That seems too difficult.
It appears that XSDObjectGen does something similar but we found a way to create a strongly typed reference using a schema derived from an actual, though incomplete, message using XsdInference at gotdotnet. I copied the reference to B generated by the latter technique into the A class generated by the latter and we have a working solution.
This is what we get in A.vb for the reference to the body element when we infer the schema from a message:
Public Class body
'*********************** document element ***********************
<XmlElement(Type:=GetType(b.document), ElementName:="document", IsNullable:=False, Form:=XmlSchemaForm.Qualified, Namespace:="http://vendorUri/B"), _
EditorBrowsable(EditorBrowsableState.Advanced)> _
Public __document As b.document
<XmlIgnore()> _
Public Property document() As csr.document
Get
If __document Is Nothing Then __document = New csr.document
document = __document
End Get
Set(ByVal Value As csr.document)
__document = Value
End Set
End Property
'*********************** Constructor ***********************
Public Sub New()
End Sub
End Class
Now, I can write something like the above sample and I get a complete object out of it and serializing A includes serializing B.
XSD and XSDObjectGen should yield a strongly typed reference to Class B within the definition for Class A given the two XSD files.
--Peter
The contract
<xs:any namespace="http://temp.uri/B" processContents="skip" minOccurs="0" maxOccurs="unbounded"/> says that <body> can contain zero or more gloabl elements from the namespace
"http://temp.uri/B", therefore to ensure you comply with this contract you should implement it as a collection property, not a singleton.
Public Class body
<XmlElement(Type:=GetType(Document), ElementName:="document", IsNullable:=False, Form:=XmlSchemaForm.Qualified, Namespace:="http://vendorUri/B")> _
Public Documents As New List(Of Document)
End Class
Here we're also assuming there is only one global element defined in namespace B. So if you happen to know that there's always only one <document> element in the body, then you can further simplify this to a singleton, but you'd better get that in writing from your web service provider and perhaps have them change the WSDL to match that new contract.
Note that the XmlSerializer can serialize more things than xsd.exe can generate so it is common that you need to tweak the classes generated by xsd.exe. In this case xsd.exe didn't do a very good job of analyzing namespace B, my guess is that it has a pretty brain dead mapping of <xsd:any> that assumes absolutely anything is possible, which is why it mapped it to XmlNode.
Chris.
Yes. It's funny that it can be easily set by making a reference to the second class and that inferring the XSD from a sample message, though incomplete, correctly creates two classes with the appropriate reference. I guess when generating the classes from each separate file, there are no smarts in the tools to look for the additional class file or xsd and work it in.
The good news, is that we can safely assume one document element in each message. After reviewing the B.xsd, it looks like it could be any number of elements or subelements given the hiearchy (or lack thereof) but I don't think we'll ever see more than one.
The WSDL does not expose the structure of the content of the SOAP/RPC string parameter so updating WSDL is just not helpful.
The vendor on this also requires particular namespace aliases to appear on each element, rather than seeing what is defined above so I think its a brain-dead implementation on the vendor side anyway.
I don't know if these problems are common for Axis implementations or if it is just this vendor's software team.
We've gotten little documentation except for the wsdl and the xsd files and everything seems very loose so we'll have to go with the flow.
Thanks for your comments.
--Peter
We finally used the XML to XSD tool found on the web to convert an existing message (that referenced all elements) to get single XSD file that we then used to generate classes from. That generated two classes based on the fact that there were two namespaces in the XML.
I saw how the inner XSD was referenced in this class and copied only that reference into the class generated from the original XSD.
That worked.
--Peter