How to Declare Complex Nested C# Type for Web Service - web-services

I would like to create a service that accepts a complex nested type. In a sample asmx file I created:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
// [System.Web.Script.Services.ScriptService]
public class ServiceNest : System.Web.Services.WebService
{
public class Block
{
[XmlElement(IsNullable = false)]
public int number;
}
public class Cell
{
[XmlElement(IsNullable = false)]
public Block block;
}
public class Head
{
[XmlElement(IsNullable = false)]
public Cell cell;
}
public class Nest
{
public Head head;
}
[WebMethod]
public void TakeNest(Nest nest)
{
}
}
When I view the asmx file in IE the test page shows the example SOAP post request as:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<TakeNest xmlns="http://schemas.intellicorp.com/livecompare/">
<nest>
<head>
<cell>
<block xsi:nil="true" />
</cell>
</head>
</nest>
</TakeNest>
</soap:Body>
</soap:Envelope>
It hasn't expanded the <block> into its number member.
Looking at the WSDL, the types all look good. So is this just a limitation of the post demo page creator?
Thanks.

But those elements ARE null. You need to construct them before they show up otherwise they are just null.

As Kevin pointed out the example POST XML indicates that those elements are nil. I should have simply tried to consume the web service. Once I did that I could see that the importer (either .NET, Java or Ruby) correctly created all the types. So really there is no question here after all.

The .NET code did not give up after a certain number of levels.
If you look at the code generated by "Add Web Reference", you'll find that there's a bool numberSpecified field. Only if the client sets that to true will the number be serialized.
If you look at the XML Schema, you'll see that the number element might be absent. If it were of a reference type, then that could be represented in the client by a null value. Since it's an int, this additional flag is necessary to indicate whether or not to serialize this optional value.

Related

Implementing Soap Webservice: Handling Complex object argument

My company has subscribed to a service offered by a travel reservation system whereby they call us back when any changes occur to a travel itinerary. My task is to stand up a SOAP endpoint to accept this callback. I am doing this with standard jax-ws stuff.
Here is the essentials of their soap message:
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:eb="http://www.ebxml.org/namespaces/messageHeader" xmlns:swse="http://wse.sabre.com/eventing"
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wse="http://schemas.xmlsoap.org/ws/2004/08/eventing">
<soap-env:Header>
<eb:MessageHeader eb:version="1.0"
soap-env:mustUnderstand="1">
<wse:MySubscription>7TZA</wse:MySubscription>
<swse:EventTopic>WSE.QUEUE.CCC.PNRCHNG</swse:EventTopic>
</eb:MessageHeader>
<wsa:Action>http://wse.sabre.com/EventSource/notification</wsa:Action>
<wsa:MessageID>721d8dc0-1307-4b27-a48b-a9ba7f7818c7</wsa:MessageID>
<wse:Identifer>7f9aad13-a8cd-4057-8c91-89ccfad64598</wse:Identifer>
<wsa:To>http://localhost:18888/</wsa:To>
</soap-env:Header>
<soap-env:Body>
<swse:CCC.PNRCHNG>
<swse:OWNPCC>N0V3</swse:OWNPCC>
<swse:HOMEPCC>W0H3</swse:HOMEPCC>
<swse:Locator>IGLYIZ</swse:Locator>
<swse:EventTimeStamp format="yyyy-MM-dd hh:mm:ss.fffffffff">2007-10-30 11:41:32.000986</swse:EventTimeStamp>
<swse:ChangeIndicators>
<swse:Indicator name="Ticketing">
<swse:hasChanged>Y</swse:hasChanged>
</swse:Indicator>
...
</swse:CCC.PNRCHNG>
</soap-env:Body>
I have a functioning SOAPHandler with a getHeaders() implementation wich is satisfying the mustUnderstand=1 requirement.
I have a #WebMethod that is successfully accepting a few of the top level parts of the payload. This is actually good enough for now, but I would like to understand how to write a #Webmethod that would accept the whole payload as a complex object.
I have a jibx-generated CCC.PNRCHNG class. But how would I write the #WebMethod to accept it? Below is the method that accepts the top level bits as separate #WebParams (Locator is all I really need right now)
#WebMethod(operationName="CCC.PNRCHNG", action="http://wse.sabre.com/EventSource/notification")
public Object onPnrEvent(
#WebParam(name="OWNPCC", targetNamespace=NS) String ownPcc,
#WebParam(name="HOMEPCC", targetNamespace=NS) String homePcc,
#WebParam(name="Locator", targetNamespace=NS) String locator
) {
try {
s_logger.info(locator);
}
catch(Exception e) {
s_logger.error(e);
}
return null;
}
It would be nice to get the full model so any advice there would be very much appreciated.
Best way would be to generate PortType based on WSDL with wsimport. If you don't have a WSDL, I wouldn't bother with writing a wrapped JAX-WS service.
On your class, add this annotation
#SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
Then write methods that accept the root element. Something like this:
#WebMethod(operationName="CCC.PNRCHNG", action="http://wse.sabre.com/EventSource/notification")
public Object onPnrEvent( #WebParam(name="CCC.PNRCHNG", targetNamespace=NS) PNRCHNG request) {
}

XmlSerializer.Deserialize() behaves different between NECF 2.0 and NETCF 3.5

I am migrating an old NETCF 2.0 Application which uses webservices to NETCF 3.5. The webservice of the foreign server remains the same without changes. Also I newly generated Reference.cs using VS 2008 Command Prompt - and was excited to see the same result as using VS 2005.
My question relates to namespace definition at the root tag by a class which is defined by webservice. The java webservice serializes it with xmlns attribute and XmlSerializer.Deserialize() of NETCF 3.5 blames about this attribute throwing an InvalidOperationException. Using this XML and XmlSerializer.Deserialize() with NETCF 2.0 works as expected. The Object gets deserialized to memory.
Let a few code snippets make things clear.
Exception
InvalidOperationException-There is an error in XML document (2, 2).
InnerException: InvalidOperationException-<InstallDirective xmlns='java:my.foreign.namespace'> was not expected.
at System.Xml.Serialization.XmlSerializer.resolveDeserializingType(XmlReader reader, XmlSerializationReader serialReader, Boolean soap12)
at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle)
at System.Xml.Serialization.XmlSerializer.Deserialize(Stream stream)
at My.Neat.Software.Bug.Test()
generated Reference.cs (excerpt)
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="java:my.foreign.namespace")]
public partial class InstallDirective {
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
public System.Nullable<System.DateTime> Date;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
public JustAnotherThing JustAnotherThing;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
public System.Nullable<short> Oid;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
public string Filename;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
public string Version;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
public string Dir;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
public string Instructions;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("Scripts", IsNullable=true)]
public Script[] Scripts;
}
the XML from webservice
<?xml version="1.0" encoding="utf-8"?>
<InstallDirective xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="java:my.foreign.namespace">
<Date>2014-02-11T13:31:49.238+01:00</Date>
<JustAnotherThing>
<FileName>MyFunnyFile.txt</CabFileName>
<Checksum>e759af8bd5787e3f8d62245a7d6aa73d</Checksum>
<FileExists xsi:nil="true" />
<Name>MyFunnyFile</Name>
<Version>1.2</Version>
<Path xsi:nil="true" />
<DependsOn xsi:nil="true" />
<RegistryPath xsi:nil="true" />
</JustAnotherThing>
<Oid>1</Oid>
<Filename>MyFunnyFile.txt</Filename>
<Version>1.2</Version>
<Dir>\My\Path\To\Files</Dir>
<Instructions xsi:nil="true" />
<Scripts xsi:nil="true" />
</InstallDirective>
the code snippet which throws the exception
FileInfo directiveFile = new FileInfo(#"\tmp\install\funnyInstallDirective.xml");
XmlSerializer xmlSerializer = new XmlSerializer(typeof(InstallDirective));
TextReader reader = new StreamReader(directiveFile.OpenRead());
InstallDirective installDirective = (InstallDirective)xmlSerializer.Deserialize(reader); // BAM!
Thanks for your help!
Indeed the XmlSerializer implementation of .Net Compact Framework 2.0 and 3.5 differs. The solution to this "new" behaviour is easy as well. Because the XmlSerializer which - in my case - serializes XML as a "fragment", we need to declare the XmlRootAttribute. This is due to missing XmlRootAttribute annotation of the type to be serialized within the Reference.cs.
To be backward compatible with XML serialized by NETCF 2.0 implementation, we need to add the namespace by definition of InstallDirective (Reference.cs). Unfortunately I found no programmatically way to get this.
Before:
XmlSerializer xmlSerializer = new XmlSerializer(typeof(InstallDirective));
FileStream file = File.Create(#"\tmp\install\funnyInstallDirective.xml");
TextWriter writer = new StreamWriter(file);
xmlSerializer.Serialize(writer, installDirective);
After:
XmlRootAttribute att = new XmlRootAttribute(typeof(InstallDirective).Name);
att.Namespace = "java:my.foreign.namespace";
XmlSerializer xmlSerializer = new XmlSerializer(typeof(InstallDirective), att);
FileStream file = File.Create(#"\tmp\install\funnyInstallDirective.xml");
TextWriter writer = new StreamWriter(file);
xmlSerializer.Serialize(writer, installDirective);
Credits to ta.speot.is who answered the question here.
[UPDATE]
Of course you can beautify it by using the annotated XmlTypeAttribute of the web service type. It will look this way.
XmlTypeAttribute ta = (XmlTypeAttribute)Attribute.GetCustomAttribute(typeof(InstallDirective), typeof(XmlTypeAttribute));
XmlRootAttribute att = new XmlRootAttribute(typeof(InstallDirective).Name);
att.Namespace = ta.Namespace;
XmlSerializer xmlSerializer = new XmlSerializer(typeof(InstallDirective), att);

Should a web service response include empty values?

When invoking a web service I get a dynamic response in XML format.
So response could be :
<response>
<test1>test1</test1>
<test2>test1</test2>
<test3>test1</test3>
<test4>test1</test4>
</response>
or :
<response>
<test1>test1</test1>
<test2>test1</test2>
</response>
But I think the response should be static in order for the Java class to be unmarshalled correctly from the XML.
So instead of
<response>
<test1>test1</test1>
<test2>test1</test2>
</response>
This should be :
<response>
<test1>test1</test1>
<test2>test1</test2>
<test3></test3>
<test4></test4>
</response>
This means I can now handle the response and check for missing data.
Am I correct in my thinking?
Default Null Representation
By default a JAXB (JSR-222) implementation will treat a property as an optional element. As such null values are represented as the element being absent from the document.
Alternate Representation of Null
Alternatively you have null represented by including the xsi:nil="true" attribute on it. This is achieved by annotating your property with #XmlElement(nillable=true).
<date xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
Invalid Null Representation
An empty element is not a valid representation for null. It will be treated as an empty String which will be invalid for all non-String types.
<date/>
For More Information
http://blog.bdoughan.com/2012/04/binding-to-json-xml-handling-null.html
Update
SO test1 test1 is
a valid response but the fields test3 & test4 will be set to null ?
What happens is that nothing is done for the fields/properties that correspond to absent nodes. They will keep there default values, which are by default initialized to null.
Java Model (Root)
In the model class below I have initialed the fields to have values that are not null.
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Root {
#XmlElement
String foo = "Hello";
String bar = "World";
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
Demo
The document being marshalled <root/> does not have any elements corresponding to the mapped fields/properties in the model class.
import java.io.StringReader;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StringReader xml = new StringReader("<root/>");
Root root = (Root) unmarshaller.unmarshal(xml);
System.out.println(root.foo);
System.out.println(root.bar);
}
}
Output
We see that the default values were output. This shows that a set operation was not performed for the absent nodes.
Hello
World
Refer to JAXB Marshalling with null fields
and also What's the purpose of minOccurs, nillable and restriction?
Use #XmlElement(nillable = true) for those null/blank value fields to display; but pay particular attention to your date fields.

How can I instruct a JAXB Marshaller to scrub base64Binary data only for auditing?

I am working in a JAXWS/JAXB web service environment. JAXWS out of the box uses uses the JAXB to marshal/unmarshaler the web service payloads.
I also have a requirement to audit all request and response payloads.
I want a compact and concise marshaled representation of the payload for the audit (as a irrelevant side note - I am auditing using a java.util.concurrent.BlockingQueue and some consumer threads to put batches of audit data in the audit datasource).
I have binary content(mtom) included on some web service response payloads but I DO NOT want to marshal audit these because the serialized base64 would be too large.
So my need is to create a marshaller (exclusively for auditng) that in all cases will scrub binary content but then NOT scrub for the prime purpose of marshalling web service response payloads. I do XSD to Java xjc. I need to use the same XSD/JAXB namespace for both contexts/marshallers.
Java type converter:
<jxb:javaType name=""
parseMethod="com.xxx.xxx.ws.converter.XXXLongConverter.parseXXXLong"
printMethod="com.xxx.xxx.ws.converter.XXXLongConverter.printXXXLong" />
is will not work because 1. I would need to unregister the adapter http://docs.oracle.com/javase/6/docs/api/javax/xml/bind/Marshaller.html#setAdapter%28java.lang.Class,%20A%29
for the marshaller and I don't THINK I have a hook into that for JAXWS. 2. I can't be guaranteed the class name that JAXB will decide to create in order to unregister it.
I created my own XMLAdapter and used the annox jaxb plugin
but that didn't really work for the same reasons the above didn't work.
Update: I now tried manually and reflectively walking through payload(to be audited) prior to marshalling to scrub the binary data but that got to be too much pain for what it was worth.
I should also mention that for brevity of the audit I am using jersey JSON serialization supporting JAXB
but I don't think that takes away or adds to my base problem:
How can I scrub data in one marshaller/unmarshaller but not another but both whose origin is the same JAXB context?
UPDATE: Never figured out an elegate way to do this. Not really possible at this point with the frameworks as they are. UPDATE: Not true. Extending AttachmentMarshaller (I like this a lot and will use it) or creating a "need-aware" XmlAdapter would work for the audit specific marshaller as #Blaise answers below.
UPDATE: If I may take this a step further to round out my use case...I mentioned above that for brevity of the audit I'd like to do Json Serialization of the JSONJAXBContext using jersey apis, specifically using the JSONMarshaller but the interface does not define setAdapter and setAttachmentMarshaller. Coming out of JSONJAXBContext.createJSONMarshaller() is a JSONMarshallerImpl implementation which do define these this methods. I will grudgingly cast to impl so I can set my custom attachment marshaller.
How can I scrub data in one marshaller/unmarshaller but not another
but both whose origin is the same JAXB context?
You could set your own implementation of AttachemntMarshaller and set it on the Marshaller that you are using for auditing.
Root
Below is a sample domain object with a byte[] property that by default will be represented as an element of type base64Binary.
package forum8914008;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Root {
byte[] bytes;
}
Demo
The demo code below first marshals the object to XML, and then marshals it a second time with a custom impplementation of AttachmentMarshaller set.
package forum8914008;
import javax.activation.DataHandler;
import javax.xml.bind.*;
import javax.xml.bind.attachment.AttachmentMarshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Root root = new Root();
root.bytes = "Hello World".getBytes();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
marshaller.setAttachmentMarshaller(new AttachmentMarshaller() {
#Override
public boolean isXOPPackage() {
return true;
}
#Override
public String addMtomAttachment(DataHandler arg0, String arg1,
String arg2) {
return "fake";
}
#Override
public String addMtomAttachment(byte[] arg0, int arg1, int arg2,
String arg3, String arg4, String arg5) {
return "fake";
}
#Override
public String addSwaRefAttachment(DataHandler arg0) {
return "fake";
}
});
marshaller.marshal(root, System.out);
}
}
Output
Below is the output from running the demo code. The first XML document could grow to be quite large if the byte[] was big. The second XML document would stay the same size.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<bytes>SGVsbG8gV29ybGQ=</bytes>
</root>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<bytes>
<xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="fake"/>
</bytes>
</root>
How can I scrub data in one marshaller/unmarshaller but not another
but both whose origin is the same JAXB context?
You could support this use case with an XmlAdapter.
XmlAdapter (ByteArrayAdapter)
The following XmlAdapter is used to convert a byte[] to a byte[]. In its default state it will return the original byte[], it also has a audit state where it will return an empty byte[].
package forum8914008;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class ByteArrayAdapter extends XmlAdapter<byte[], byte[]> {
private boolean audit;
public ByteArrayAdapter() {
this(false);
}
public ByteArrayAdapter(boolean audit) {
this.audit = audit;
}
#Override
public byte[] marshal(byte[] bytes) throws Exception {
if(audit) {
return new byte[0];
}
return bytes;
}
#Override
public byte[] unmarshal(byte[] bytes) throws Exception {
return bytes;
}
}
package-info
The #XmlJavaTypeAdapter annotation is used tp register the XmlAdapter. When used at the package level it will apply to all properties of the specified type in that package (see: http://blog.bdoughan.com/2012/02/jaxb-and-package-level-xmladapters.html).
#XmlJavaTypeAdapter(value=ByteArrayAdapter.class, type=byte[].class)
package forum8914008;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
Root
Below is a sample domain object with a byte[] property that by default will be represented as an element of type base64Binary.
package forum8914008;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Root {
byte[] bytes;
}
Demo
The demo code below first marshals the object with the default state of the ByteArrayAdapter which will return the real byte[] and the marshals the object a second time with a stateful ByteArrayAdapter set which will convert all byte[] values to an empty byte[].
package forum8914008;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Root root = new Root();
root.bytes = "Hello World".getBytes();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
marshaller.setAdapter(new ByteArrayAdapter(true));
marshaller.marshal(root, System.out);
}
}
Output
Below is the output from running the demo code. The XmlAdapter would apply to all mapped fields/properties of type byte[].
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<bytes>SGVsbG8gV29ybGQ=</bytes>
</root>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<bytes></bytes>
</root>

Passing serialized object through web service vs. Passing the object

I have a webservice in C#.NET with the following namespace:
[WebService (Namespace = "http://enterpriseName/wsName")]
The web service contains a WebMethod GetServiceObject and a class MyObject.
This web method returns a string whose content is a serialized instance of MyObject.
[WebMethod (MessageName = "GetServiceObjectXML" Description = "Get ServiceObject from Server to Client")]
public string GetServiceObjectXML ()
This method returns the following XML:
<? Xml version = "1.0" encoding = "utf-16"?>
<ServiceObject Xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Services>
<service>
<id>3</id>
<date>02/08/2010</date>
</service>
</Services>
</ServiceObject>
The problem that I encounter is that when I call this method from the client side and try to deserialize this xml to class MyObject and I get a NULL object.
After that I created a new WebMethod with the following signature:
[WebMethod (MessageName = "GetServiceObject" Description = "Get ServiceObject from Server to Client")]
public MyObject GetServiceObject ()
When I call this method from the client side I get the object filled correctly and I can also serialize the object without problems, but the result of serialization is the following xml:
<? Xml version = "1.0" encoding = "utf-16"?>
<ServiceObject Xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Services Xmlns="http://enterpriseName/wsName">
<service>
<id>3</id>
<date>02/08/2010</date>
</service>
</Services>
</ServiceObject>
which is different from the xml generated by the WebMethod GetServiceObjectXML.
How can I get around this, since I intend to use both methods on the same webservice and in the same customer?
The obvious answer would be, fix GetServiceObjectXML() to return the same XML as GetServiceObject(). The difference seems to be that the object as serialized by the framework has a different XML namespace specified. Whatever method you're using to serialize the object into XML in GetServiceObjectXML() isn't doing that.