Like many others I wrote an app that was heavily dependent on asp.net SOAP web services that worked great in the simulator and then got hit with the surprise that the CoreServices library that contained all the web service functionality wasn't actually available on the iPhone. I'm still holding out hope this functionality will be added eventually.
All my code was dependant on receiving back the NSDictionary and NSArray object tree, and I didn't want to rewrite it all, so I created a wrapper to emulate the missing SOAP functionality by passing the request over REST. This allowed me to just swap out the method I was using for the webservice calls and keep the rest of the code the same. I thought I'd share it for anyone else in the same boat.
Fortunately asp.net already supports REST style requests (I didn't realize this before today), so no changes were needed on the web server side.
To demonstrate here the call to a simple method called "SayHello". You pass in name and it returns a single item called "string" that contains "Hello [yourname]". The code will work with more complex objects with child properties as well, and return them as NSDictionary and NSArray objects, but for simplicity I'm using "SayHello".
Code:
NSArray *keys = [NSArray arrayWithObjects:@"userName", nil];
NSArray *objects = [NSArray arrayWithObjects:@"jeremy", nil];
NSDictionary *params = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
NSDictionary *wsResponse=[WebServices callRestService:@"SayHello" :params];
NSString *responseString=[wsResponse objectForKey:@"string"];
callRestService is a helper method I created in a class called Webservices. This code was exactly the same before except the helper method was called callSoapService.
WebServices.m contains two methods. getRestUrl just appends all the method parameters into a url to make the REST request. callRestService uses the build-in NSXMLParser class to retrieve the xml and sends it to a custom class called XmlParser to handle the callbacks and create the NSDictionary result,
WebServices.m
Code:
+(id)callRestService: (NSString *) methodName : (NSDictionary *) params
{
NSURL *url=[WebServices getRestUrl: methodName : params];
XmlParser *xmlParser = [[XmlParser alloc] init];
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
[parser setDelegate:xmlParser];
[parser setShouldProcessNamespaces:NO];
[parser setShouldReportNamespacePrefixes:NO];
[parser setShouldResolveExternalEntities:NO];
[parser parse];
[parser release];
return xmlParser.result;
}
+(NSURL *)getRestUrl: (NSString *) methodName : (NSDictionary *) params
{
NSString *url=@"http://services.mywebsite.com/mywebservice.asmx/";
url=[url stringByAppendingString:methodName];
BOOL firstKey=TRUE;
for (NSString *key in params)
{
NSString *value=[params objectForKey:key];
if (firstKey) url=[url stringByAppendingString:@"?"]; else url=[url stringByAppendingString:@"&"];
url=[url stringByAppendingString:key];
url=[url stringByAppendingString:@"="];
url=[url stringByAppendingString:value];
firstKey=FALSE;
}
return [NSURL URLWithString:url];
}
The final step is to create the XmlParser handler. This class keeps track of each open and close tag to build an object tree containing NSMutableArray NSMutableDictionary and NSString objects.
XmlParser.h
Code:
#import <Foundation/Foundation.h>
@interface XmlParser : NSObject {
NSMutableDictionary *result;
NSString *currentElementName;
NSString *currentElementValue;
NSMutableArray *parentArray;
}
@property (nonatomic, retain) NSMutableDictionary *result;
@property (nonatomic, retain) NSString *currentElementName;
@property (nonatomic, retain) NSString *currentElementValue;
@property (nonatomic, retain) NSMutableArray *parentArray;
- (void)parserDidStartDocument:(NSXMLParser *)parser;
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict;
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName;
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string;
@end
XmlParser.m
Code:
#import "XmlParser.h"
@implementation XmlParser
@synthesize result;
@synthesize currentElementName;
@synthesize currentElementValue;
@synthesize parentArray;
- (void)parserDidStartDocument:(NSXMLParser *)parser
{
result=[[NSMutableDictionary alloc] init];
parentArray=[[NSMutableArray alloc] init];
[parentArray addObject:result];
currentElementName=@"";
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if (qName) {
elementName = qName;
}
currentElementValue=@"";
if (currentElementName!=@"")
{
id newParent=NULL;
if ([currentElementName isLike:@"*Array*"])
{
newParent=[[NSMutableArray alloc] init];
} else {
newParent=[[NSMutableDictionary alloc] init];
}
[parentArray addObject:newParent];
}
currentElementName=elementName;
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if (qName) {
elementName = qName;
}
if (currentElementName==@"")
{
//We're adding a container with children. Add it to the parent and remove this item fromt he parentArray
int currentIndex=[parentArray count]-1;
int parentIndex=currentIndex - 1;
id currentChild=[parentArray objectAtIndex:currentIndex];
id currentParent=[parentArray objectAtIndex:parentIndex];
if ([currentParent isKindOfClass:[NSMutableArray class]])
{
[currentParent addObject:currentChild];
} else {
[currentParent setObject:currentChild forKey:elementName];
}
[parentArray removeObjectAtIndex:currentIndex];
} else {
//We're adding a simple type element
int currentIndex=[parentArray count]-1;
id currentParent=[parentArray objectAtIndex:currentIndex];
if ([currentParent isKindOfClass:[NSMutableArray class]])
{
[currentParent addObject:currentElementValue];
} else {
[currentParent setObject:currentElementValue forKey:currentElementName];
}
}
currentElementName=@"";
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
currentElementValue=string;
}
@end
That's it. I'm sure it's not the cleanest code (This is my very first XCode app) and there may be a better way of doing this, but it saved me from having to rewrite all my code that was retrieving data from SOAP services. I've seen tons of other people with the same problem and no solutions so far. I hope this helps.