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".
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,
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.
#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.
Last edited by Trilitech; 08-21-2008 at 11:07 PM.
Reason: Fixed error
Thanks for the post. Just wondering, if you're interacting more, like sending data like you would normally do in the SOAP envelope, how do you do that in this scenario?
I too am keen to see how things go with more data going back and forth, before I decide on this route vs plain XML and manual parsing.
One thing I'm going to need to resolve is binary (base64) going back and forth as well at some point. Judging by the apparent lack of .NET <> Cocoa SOAP success, I may well be in for an uphill struggle.
realberen - Currently it doesn't handle sending complex objects as part of the message, it can just receive them. (that's all I needed for my app, so I didn't take it that far). The REST handling is built in on the ASP.NET side, so if you can figure out the parameter names it expects for these complex objects, I don't see any reason why you couldn't create a generic method to convert your XCode objects into those parameters.
wtyphoon - Definitely no base 64 support either. Although you should receive the string value back and be able to decode it in the XCode.
As far as whether it's better to take this approach or just call rest and handle it manually for each web service call, it was a no-brainer for me. It's a royal pain to deal with three different methods to receive the open tag, close tag and contents, and keep track of all your nesting to and build out your object tree. The missing "built-in" classes provide you with a nice object tree instead of the series of methods which is FAR easier to convert into your custom objects. That's what I set out to replicate here.
I'm still absolutely shocked Apple would leave this core functionality on a platform made for developing web connected mobile apps. I'm still holding out hope this will eventually be added. In theory, once it is, I can just swap out this code for the built-in libraries without having to change all my code that converts the result into my custom objects.
Just create button via Interface Builder.
Create the IBOutlet and IBAction.
Then have IBAction method call the WS and use the UITextfield value property for the textfield data.
#import "myViewController.h"
@implementation myViewController
@synthesize lblUserName, lblPW;
-(IBAction) callMyWebService: (id) sender {
NSURL *theURL;
NSArray *keys = [NSArray arrayWithObjects:@"userName",@"passWord", nil];
NSArray *keyvalues = [NSArray arrayWithObjects:lblUserName.text,lblPW.text, nil];
theURL = [WebServiceHelper generateWebServiceHTTPGetURL: @"http://www.mywebsite.com/mywebservice.asmx": @"mywebservicemethod": keys: keyvalues];
// The rest of the code you will have to use NSXMLParser to parse the WS XML
}
I hope the above will help you... maybe some syntax errors... wrote it by hand....
Hi Trilitech,
I read your whole post.It wrks great. My app need to send more complex data for now i am sending xml as a string parameter in url. But it always says bad url. When i construct same url and send it through browser it wrks and send response but from NSURLRequest object it becomes bad url. i am also using UTF8String encoding.
Any idea??
i need a code that connect to a login.asp page. when i have the request from the page that it ask me for a username and password.
when i connect successful that the view switch and i can show my status of an order.
is that possible?
-(IBAction)showandanswerid)sender {
buttonViewController *ansicht2 = [[buttonViewController alloc] initWithNibName:@"Login" bundle:nil];
[self view:ansicht2]; here is the error: buttonViewController may not respond to -view
[ansicht2 release];
What does the error exactly means? I have try very much eventualities to fit the error.
Is there no sample code or tutorial out there where i can look at?
First off, thanks for the code samples. They have helped me tremendously. I'm a .NET web developer in transition. :-)
I have my application calling a .NET web service. The service returns only an integer value wrapped in "<int></int>". That is the full response from the service. Example: <int>456</int>
The parser returns the value correctly when I check it in a breakpoint during debugging (value is at currentElementValue), but I can't seem to get the value into something usable within the application.
I'm assuming that I need to use objectAtIndex somehow, but all of my efforts have failed.
I too am a .NET Developer and recently in past few months been staying up ever night learning objective-C and SDK very well...
To answer your question:
I am assuming you are familiar with the XMLParser, since that seems to be best (and probably only way) to parse XML...
See below... it may help you....
Code:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
// NSLog(@"found this element: %@", elementName);
currentElement = [elementName copy];
if ([elementName isEqualToString:@"int"]) {
myInt = [[NSMutableString alloc] init]; // Notice I am alloc as a NSmutablestring, you can change this to int (not not alloc it)
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
if ([elementName isEqualToString:@"int"]) {
// Below is a hack, because the vars do not get saved between methods
finalMyInt = @"";
finalMyInt = [finalNearByLatitudes stringByAppendingString:nearbyLatitudes];
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
if ([currentElement isEqualToString:@"int"]) {
[MyInt appendString:string];
}
// NSLog(@"found characters: %@", string);
}
Wow, I feel like I completely overlooked that in the previously provided sample code. I tried something similar to that but it didn't work, so I gave up on that method. I wish I had kept on with it now.
I really appreciate the reply. Thanks, it's just what I needed to see.
I'm new to iphone programming (also coming from .net). I'm really struggling to get this working. I'm trying to test with a public webservice: TimeService Web Service