I'm writing some Objective-C (actually Objective-C++) code to create a Cocoa framework to call into a Web Service (hosted in ASP.NET) using WSMethodInvocationInvoke(), based on the example from here:
http://developer.apple.com/internet/webservices/webservicescoreandcfnetwork.html

This works great for Web Service calls which take simple parameters (basically key-value pairs of strings), for example:

NSDictionary *params = [NSDictionary dictionaryWithObject:@"test" forKey:@"GroupName"];

To call a method like so (leaving off some of the outer <soap:Envelope> tags and such):

  <soap:Body>
    <ListSubGroups xmlns="foo">
      <GroupName>test</GroupName>
    </ListSubGroups>
  </soap:Body>

And I can get responses with simple parameters (such as a returned string) as well, like so:

NSDictionary *result = (NSDictionary *)WSMethodInvocationInvoke (soapReq);
  NSString *resultField = [result objectForKey:@"ListSubGroupsResult"];

To parse a response like:

  <soap:Body>
    <ListSubGroupsResponse xmlns="foo">
      <ListSubGroupsResult>Return Value Here</ListSubGroupsResult>
    </ListSubGroupsResponse>
  </soap:Body>


Things get more complicated, though, when dealing with array parameters (string[] in the C# source code of the Web Service). I've figured out how to process a return value (output parameter) which is an array (of strings), like so:

  <soap:Body>
    <ListSubGroupsResponse xmlns="foo">
      <ListSubGroupsResult>
        <string>Group1</string>
        <string>Group2</string>
      </ListSubGroupsResult>
    </ListSubGroupsResponse>
  </soap:Body>

With code like this:

NSDictionary* resultField = [result objectForKey:@"ListSubGroupsResult"];
  id names = [resultField valueForKey:@"string"];
  NSString* Items = nil;
  int count = 0;
  if ([names isKindOfClass:[NSString class]])
  {
    count = 1;
    Items = (NSString*)names;
  }
  else if ([names isKindOfClass:[NSArray class]])
  {
    count = [(NSArray*)names count];
    Items = [(NSArray*)names componentsJoinedByString:@"\t"];
  }


However, I'm stymied trying to generate a call to a function that takes an array as an input parameter, like this:

  <soap:Body>
    <MethodWithArrayParam xmlns="foo">
      <Data>
        <string>Value1</string>
        <string>Value2</string>
      </Data>
    </MethodWithArrayParam >
  </soap:Body>

If I try to duplicate what I get back in the return parameter case (above), by creating an NSArray and adding it to a NSDictionary with the key "string", like so:

  NSArray* values; // init not shown
  // ...
NSDictionary* valuesDict = [NSDictionary dictionaryWithObject:values forKey:@"string"]; NSDictionary *params = [NSDictionary dictionaryWithObject: valuesDict forKey:@"Data"];

Then the calling markup ends up like this (leaving off some of the extraneous xsi:type attributes and such):

  <soap:Body>
    <MethodWithArrayParam xmlns="foo">
      <Data>
        <string>
          <item_0>Value1</item_0>
          <item_1>Value2</item_1>
        </string>
      </Data>
    </MethodWithArrayParam >
  </soap:Body>

And the Web Service doesn't recognize those tags, so I get a fault response. I can get rid of the intermediate NSDictionary, but the NSArray is still serialized as <item_0>, <item_1>, etc. And AFAIK an NSDictionary can't have more than one key named "string" to generate the multiple <string> tags.

I can't find any combination of NSArray, NSDictionary, or any other Cocoa objects that will serialize properly. Again, what the Web Service wants is something like:

      <Data>
        <string>Value1</string>
        <string>Value2</string>
        <string>Value3</string>
      </Data>

Which ends up as a string[] (array of string) parameter in the C# code of my ASP.NET Web Service.

Here's a live, public example of a similar Web Service method with the same kind of input array parameter (from someone else's site, found via Google):
http://www.chemspider.com/Search.asmx?op=CSID2ExtRefs


I found this old post which seems to describe the same problem, but there were no responses:
http://lists.apple.com/archives/cocoa-dev/2004/Mar/msg01729.html
http://www.cocoabuilder.com/archive/message/cocoa/2004/3/26/102571

Now, as that post suggests, I've tried using a workaround using WSMethodInvocationAddSerializationOverride(), similar to the one in this post:
http://lists.apple.com/archives/Macnetworkprog/2009/Apr/msg00063.html

The serialization function is:

static CFStringRef _arrayToXML(WSMethodInvocationRef invocation, CFTypeRef obj, void *info)
  {
    if (obj != nil && (CFGetTypeID(obj) == CFArrayGetTypeID()))
    {
      NSArray* array = (NSArray*)obj;
NSMutableString* result = [[NSMutableString alloc] initWithFormat:@"<%s>", "%@"];
      for (int i = 0; i < [array count]; i++)
[result appendFormat:@"<string>%@</string>", [array objectAtIndex:i]];

      [result appendFormat:@"</%s>", "%@"];
      return (CFStringRef)result;
    }

    return NULL;
  }

Which allows the entry in the parameter dictionary to be an NSArray, and it generates the right markup.

This basically works, but it doesn't properly handle certain things, such as XML control characters (less-than signs and ampersands) in the strings in the array. Those obviously need to be replaced with entities. There are also other subtle encoding issues with UTF-16 characters.

Now, I can do the entity replacements myself, and otherwise try to roll my own code to handle all the vagaries of properly encoding the data into the XML markup, in the serialization override function. However, it seems like there must be an easier way to do this.


So, I have two questions:

1. Is there any Cocoa object, or combination of objects, that I can use to generate the required XML without having to call WSMethodInvocationAddSerializationOverride()?

2. Is there something built-in I can call in my serialization override function to properly encode the data, escaping XML control characters with entities and such, in the same way that the default serialization works?

For the latter, I've poked around in here:
http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Archiving/Concepts/serializations.html

And looked at NSArchiver, NSEncoder, et al, but I can't seem to figure out how to hook those into my custom serialization.


I suppose I could just change the Web Service method itself (or add an alternate method) to take a single string parameter, and make it tab- delimited or something, but the Web Service is already published and in use and I don't really want to have to change it. I could also just roll my own XML generation for the entire SOAP message, but that seems like overkill just to get this one last part working. Anyway, I really want to figure out how to call the function as-is with WSMethodInvocationInvoke().

I'm using Xcode 3.2 on Snow Leopard on a MacBook Pro. (I'm pretty new to Cocoa and Objective-C, so my code is leaking like a sieve, but I just want to get it functional first, then I'll try to clean up the memory.)

Apologies if there's a more appropriate forum for this question; please redirect me.

Thanks,
Dan

_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to