SOAP Server Object Extension (SOE) - how to deserialize custom types?

5145
15
01-04-2011 10:23 AM
ErinBrimhall
Occasional Contributor II
So far I've had success writing a handful of SOE methods that take parameters that are simple types (string and string array) and return my own custom, multi-level types (e.g. Customer, with an array of Address objects, etc.).

Now, what I would like to do is write an SOE method that accepts my own complex type(s) as its parameters, e.g. something like CustomerSearch, that accepts a custom Query object that defines the criteria to filter results by.

Everything seems to be fine up until the point where I attempt to deserialize the incoming request parameter as an instance of the Query class. 


For example, inside the HandleSoapMessage method of my SOE class implementation, I try the following:

public void HandleSoapMessage(IMessage request, IMessage response)
{
    int parameterIndex = Utility.FindFieldIndex("inputQuery", request.Parameters, true);

    parameterIndex = Utility.FindFieldIndex("inputQuery", request.Parameters, true);
    Query query = (Query)request.Parameters.GetObject(parameterIndex, "http://www.mynamespace.com", "Query");

    .
    .
    .
}


parameterIndex correctly points to the first index in the set of request parameters where the "inputQuery" definition resides, but the following exception is thrown during the call to GetObject:

"Exception from HRESULT: 0x80043068"

A little searching reveals that the 0x80043068 code corresponds to the more usable message, "XML_SERIALIZE_E_CANT_MAP_XMLTYPE_TO_CLASS"

So, there was a problem mapping between the "Query" type defined in my WSDL and my concrete "Query" class.  I decided to start small by defining only a single string attribute off the Query object, but I still receive the same error.


I replaced my usage of "Query" throughout with an ESRI type, "PointN", and I was able to deserialize the incoming data as IPoint using the same GetObject method.  I also inspected the actual SOAP requests, comparing between the request that used "Query" and the request where I experimented with "PointN", and did not notice any obvious problem.  See request comparisons below (note that the sole string property on "Query" is called "Test":

<?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" xmlns:tns="http://www.mynamespace.com" xmlns:esri="http://www.esri.com/schemas/ArcGIS/10.0"><soap:Body><tns:CustomerSearch><ns1:inputQuery xmlns:ns1=""><ns1:Test>Test Text</ns1:Test></ns1:inputQuery></tns:CustomerSearch></soap:Body></soap:Envelope>


<?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" xmlns:tns="http://www.mynamespace.com" xmlns:esri="http://www.esri.com/schemas/ArcGIS/10.0"><soap:Body><tns:CustomerSearch><ns1:inputPoint xmlns:ns1=""><ns1:X>0</ns1:X><ns1:Y>0</ns1:Y></ns1:inputPoint></tns:CustomerSearch></soap:Body></soap:Envelope>



This post is getting long, so I will finish by saying that the "Query" definition in my SOE WSDL correctly matches the COM-visible "Query" class that implements the IXMLSerialize interface, with both the Deserialize and Serialize methods accounting for the dummy "Test" string attribute. 

I've also made doubly sure that my XmlSupport mapping file in the ArcGIS Server "XmlClassRegistry" folder (and sub-folder) correctly maps the "Query" XML type with the CSLID of the Query class.



I hope there is an SOE guru out there that can see where I am going wrong, or let me at least know if custom types as SOAP SOE input parameters is even supported.

Any help would be hugely appreciated!
15 Replies
TomTyndall
New Contributor III
I do have an entry in XmlSupportAdot.dat for MyCustomTypeCollection.

I modified the constructor on my collection class to also use a static string and I do have a change of behaviour - I'm now getting invalid cast exception. I'll have to play around with this more to see what is happening.

[ATTACH=CONFIG]19734[/ATTACH]

Since I have been able to get a working method that I can pass a custom object to, I also considered passing the collection as an attribute on a custom object but got bogged down in the wsdl syntax and the serialize/deserialize code for that scenario. Do you have any examples of what that looks like?

Thanks,
Tom
0 Kudos
ErinBrimhall
Occasional Contributor II
Are you able to actively debug your SOE as it runs?  My guess is that the cast-exception is stemming from this line:

MyCustomTypeCollection myTypeColl = (MyCustomTypeCollection)reqParams.GetObject(idx, c_ns_soe, "MyCustomTypeCollection");


It should be equivalent to what you already have, but you can also try using type attributes instead of hard-coding the "MyCustomTypeCollection" string.  E.g.:

typeof(MyCustomTypeCollection).Name


I bring this up since it was another difference I noticed after examining my code.  I'll post tomorrow an example of a custom type w/ a custom type collection attribute.
0 Kudos
TomTyndall
New Contributor III
No, I don't have a working debugger; ArcGIS Server is on a different machine than my development workstation. I have been logging for debugging.

Using the runtime type name without any other casting still doesn't work.
logger.LogMessage(ServerLogger.msgType.warning, "segment extraction", -1, typeof(MyCustomTypeCollection).Name);
object myTypeColl = reqParams.GetObject(idx, c_ns_soe, typeof(MyCustomTypeCollection).Name);


Logger output:  [ATTACH=CONFIG]19783[/ATTACH]

Comment out that GetObject call and it runs without problem.

My xml/soap comfort level is not that high and I get tangled up in namespace issues but I was thinking more about the change in behavior (throwing a invalid cast instead of the HRESULT: 0x80043068 error)when going from a passed in namespace

public MyCustomTypeCollection(string namespaceURI) : base(namespaceURI) { }


to a hardcoded one
public MyCustomTypeCollection() : base("http://www.myOrg.com/schemas/1.0") { }


In the "passed in" case ArcGIS Server is supplying the namespace when instantiating MyCustomTypeCollection. How is the namespace being set when it instantiates the MyCustomType's contained in that collection?


I order to get something up and running now I rewrote the service to take a string and I have the client build a delimited string of the MyCustomType payload data.

Thanks again for the help.
0 Kudos
TomTyndall
New Contributor III
I wanted to see what ArcGIS Server was actually passing into the MyCustomTypeCollection constructor and I believe the answer is nothing.

public MyCustomTypeCollection(string namespaceURI) : base(namespaceURI)
{
      SoapSOE1.logger.LogMessage(ServerLogger.msgType.warning, "collection constructor", -1, namespaceURI);
}


The construction fails (see image of log) and I'm assuming that is because Server is trying to invoke a no parameter constructor and not finding one; hence the behavior change outlined in the previous post when changing to a hard coded namespace.

[ATTACH=CONFIG]19787[/ATTACH]

I got that pattern from the CustomLayerInfos constructor in ESRI's FindNearFeaturesSoapSOE sample. That CustomLayerInfos object is a Server output though, not an input so apparently that idiom only applies to outputs of a soap service.

TT
0 Kudos
ErinBrimhall
Occasional Contributor II
Hi Tom,


public MyCustomTypeCollection(string namespaceURI) : base(namespaceURI) { }


...

In the "passed in" case ArcGIS Server is supplying the namespace when instantiating MyCustomTypeCollection. How is the namespace being set when it instantiates the MyCustomType's contained in that collection?


I haven't verified this but I always assumed the typeNamespace parameter of the GetObject method was ultimately passed all the way through to the collection type's constructor.  Regardless, I can definitely confirm that the parameterless-constructor w/ a static/hard-coded namespace value passed to the base constructor works fine in both AGS 10.0 and 10.1. 


No, I don't have a working debugger; ArcGIS Server is on a different machine than my development workstation. I have been logging for debugging.


This makes troubleshooting more challenging.  Even with the ability to develop and debug locally on the server, it still took me several days to work through all the deserialization issues in 10.1 and correctly implement the workaround (as described in NIM-083300). 

Are you performing the 64-bit regasm.exe assembly registration each time you rebuild and redeploy your SOE code?  I would also highly recommend developing on the server if that option is available to you.  The reasons for some of the tougher issues I ran into were not apparent until I was able to actively debug the SOE.

One last quick set of thoughts and things to try:


  • The fact that you can pass a single custom type object as an input parameter to an SOE method tells me that your namespaces, XML type-mapping file, and SOE assembly registration (i.e. w/ the 64-bit version of regasm.exe) are correct.



  • You are inspecting the SOAP message you send, but it might also be a good idea to inspect that message once it reaches the SOE.  The code snippet below will help support that:


  • public static string Read(this IMessage message)
    {
      string xml;
      XMLStreamClass xmlStream = new XMLStreamClass();
      message.WriteXML(xmlStream);
      xml = xmlStream.SaveToString();
    
      return xml;
    }
    



  • You may want to try just working with your collection type directly in the SOE, e.g. explicitly instantiate a collection object, add items to it, cast it, etc., just to make sure it is a valid class at runtime.  I ran into a situation where multiple versions of my assemblies were somehow being registered, which was causing type conflict errors that were being swallowed unless I was actively debugging the code. 



  • Add some logging inside the Deserialize method of the "MyCustomType" class.  This method will be called for each object that was passed in the collection, so writing to the log from here will give you an idea if the code is even making it this far or if the "invalid cast exception" is being thrown earlier.

0 Kudos
DanaFernandez
New Contributor

I am having same issue in 10.3, I tried the steps above and they do not fix the problem, does anyone have a fix for this in 10.3?

0 Kudos