I've tried this, several times over, with WCF and .net 2.0 asp.net web services, and I can't get it to work. The iphone code works just fine, but I can't find out how to get the web service to accept calls over GET. Google turns up nothing particularly useful, and digging through the settings for the project, IIS, and other system settings doesn't turn anything up either. I figure I must just be overlooking something. All I can find is stuff saying "turn off POST and GET to your .net web services" as they are pretty insecure. I am in the process of writing an actual library for soap calls as opposed to rest, but I'm on a really tight schedule and if I can get this to work, I would rather use it.
Trilitech, thanks a ton for posting this information. It seems your efforts are appreciated by many. You're the first hit for the search string "soap iphone rest" in google .
Ive just forgotten to set the parser delegate. And change the weatherServiceURL to NSURL *weatherServiceURL = [NSURL URLWithString:@"http://litwinconsulting.com/webservices/weather.asmx"];
Ok, I could never get a web service or WCF service to accept HTTP GET requests. I did manage to figure out how to manually create a soap envelope and call a 2.0 web service. Here's what the service tells me it needs:
HTML Code:
POST /<my service name>/Service1.asmx HTTP/1.1
Host: <my service host>
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://heyWorld.org/HelloWorld"
<?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><HelloWorld xmlns="http://heyWorld.org/"><userName>string</userName></HelloWorld></soap:Body></soap:Envelope>
The above is from the default asp.net web service you get when creating a new project. I have changed the namespace associated with the function calls from tempuri.org, and added a string parameter to the default HelloWorld function that gets created for you.
The following obj-c creates the soap envelope, creates a NSURLRequest with the necessary content-type etc. (check out everything I do with the 'request' variable), and finally connects and sends the request. I put the NSMutableData variable into an NSString just so I could see it from the debugger, but you can also use this for your parser.
Code:
NSMutableString *sRequest = [[NSMutableString alloc]init];
//create soap envelope
[sRequest appendString:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[sRequest appendString:@"<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/\">"];
[sRequest appendString:@"<soap:Body>"];
[sRequest appendString:@"<HelloWorld xmlns=\"http://heyWorld.org/\">"];
[sRequest appendString:@"<userName>foo</userName>"];
[sRequest appendString:@"</HelloWorld>"];
[sRequest appendString:@"</soap:Body>"];
[sRequest appendString:@"</soap:Envelope>"];
NSURL *myWebserverURL = [NSURL URLWithString:@"http://<my host>/<my project>/Service1.asmx"];
[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[myWebserverURL host]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:myWebserverURL];
[request addValue:@"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
[request addValue:@"http://heyWorld.org/HelloWorld" forHTTPHeaderField:@"SOAPAction"];//this is default tempuri.org, I changed mine in the project
NSString *contentLengthStr = [NSString stringWithFormat:@"%d", [sRequest length]];
[request addValue:contentLengthStr forHTTPHeaderField:@"Content-Length"];
// Set the action to Post
[request setHTTPMethod:@"POST"];
// Set the body
[request setHTTPBody:[sRequest dataUsingEncoding:NSUTF8StringEncoding]];
// Create the connection
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
NSMutableData *myMutableData;
// Check the connection object
if(conn)
{
myMutableData=[[NSMutableData data] retain];
}
// Make this class the delegate so that the other connection events fire here.
[NSURLConnection connectionWithRequest:request delegate:self];
NSError *WSerror;
NSURLResponse *WSresponse;
// Execute the asp.net Service and return the data in an NSMutableData object
myMutableData = [NSURLConnection sendSynchronousRequest:request returningResponse:&WSresponse error:&WSerror];
//convert the mutabledata to an nsstring so I can see it with the debugger
NSString *theXml = [[NSString alloc]initWithBytes:[myMutableData mutableBytes] length:[myMutableData length] encoding:NSUTF8StringEncoding];
Be sure to replace <my host> and <my project> where appropriate (and anything else I have up there that's <my *>), and make sure you are using the namespace you have declared for the service (mine is http://heyWorld.org/ and my function is HelloWorld.)
Credit for the above discoveries and most of the code: Tom McCartan
Hi Guys,
What if my request xml is too long. Suppose i have 5000 records in a local table and that i want to send to server. Definitely i will not create Soap request like that as mentioned in above posts. What i will prefer is keeping all data in NSData object setting it as a setHTTPBody and then using POST send it to aspx page at server side. Then save whole xml locally over there and then read it in dataset that's all.
Hi Guys,
What if my request xml is too long. Suppose i have 5000 records in a local table and that i want to send to server. Definitely i will not create Soap request like that as mentioned in above posts. What i will prefer is keeping all data in NSData object setting it as a setHTTPBody and then using POST send it to aspx page at server side. Then save whole xml locally over there and then read it in dataset that's all.
Sounds like you answered your own question. Create a serialization class that knows what each of the records looks like that will create an NSString containing the xml that holds the data from your NSData object. You can the append that into the appropriate part of the envelope body and call the service that way.
Pseudo Code:
Code:
public class MyDataSerialization
{
/* maybe static, depends on you */
public string TurnDataToXml(NSData theData)
{
/* maybe static, depends on you */
foreach (dataUnit in theData)
{
//create the appropriate xml nodes in a string you are returning
}
return myStringOfXmlNodes;
}
}
I haven't looked at it yet, but another idea is to use the NSXml object to create your entire envelope programmatically. This is probably the better option as it is specific to what we are trying to do. Using the static strings above is just a simple way to show what we are all trying to accomplish. You can abstract the hell out of what's up there and make it as complex as you like.
Here's the wiki article about serialization. It even has an Objective-C sample (though it looks like they are talking about binary serialization as opposed to xml).
Thank you all for the code snippets, this is helping a lot.
I am a new iPhone developer being asked to put together a test app consuming a web service as a proof of concept for my vice president. Unfortunately I have little cocoa or iphone experience, and this is a fairly high-profile test. I really want it to succeed, but I need to make more rapid progress.
Could anyone post sample code for a complete app? I seem to learn the most from reading a whole app. I started with the original posters WebService.m, but am getting warning errors as I don't know how to create the corresponding .h file.
I am using that as a reference now. I've actually gotten the webservice call portion to work (took me a while to find one that responds to REST requests, went with the Flickr Echo test one). My issue now is in parsing the objects into a Dictionary object....the parse seems to work, but my resulting dictionary only has one entry in it, the top level tag and the body of the xml.
The one thing that has helped me parse the XML streams is to think of it as a water faucet.
As the flow of data comes pouring in from the data source, you redirect it to different pipes on the fly. Evaluating the elements to determine which pipe path you go down.
Store all the water/data in a bucket (storage variable) till you hit the end of the flow (end of the element).
Then dump the bucket into it's final container (class variable, dictionary object, what ever). Then go back up the pipe structure and watch the flow again, till the flow ends.
-------
Back to part of the SOAP discussion.
In my recent research, it is becoming very apparent that REST is growing in popularity and in support. Not just on the iPhone, but also the BlackBerry and Android.
For those of you with SOAP webservices now; You could use different tools to create a REST Wrapper around your SOAP... even if the SOAP is hosted by someone totally else. I was able to do this with .NET fairly quickly, capturing the SOAP calls via the SOAP extensions, and returning just the SOAP Message segment as a raw XML stream.
Granted I am just doing it a temporary solution, as I work on the iPhone product and another team works to add formal REST support to our services.
My suggestion would be to limit the amount of time and energy to stuff SOAP into your iPhone app, but to look at the core webservice to see if it can be converted to REST, or with at least a REST wrapper. Especially if it is going to be a widely used WebService by multiple platform.
As the flow of data comes pouring in from the data source, you redirect it to different pipes on the fly. Evaluating the elements to determine which pipe path you go down.
Store all the water/data in a bucket (storage variable) till you hit the end of the flow (end of the element).
Then dump the bucket into it's final container (class variable, dictionary object, what ever). Then go back up the pipe structure and watch the flow again, till the flow ends.
That was the key! Turns out it was parsing it all correctly the whole time, what I did not realize was that my parser was building a dictionary of dictionaries...once I fixed my reference call, I was able to get to the nested object I wanted!
Thanks to all for the samples, just took me a while to get what I was reading!
I am trying to post a file to a ASp page through HTTP.I am able to post file through c# client .But I am not able to post it through cocoa application written in Objective C.
I have used all information I got through google.For instance
I used NSUrl,NSUrlConnection ,NSData classes.Used encoding ..but I am not able to do this.
The posts in above forum is interesting that is why I thought I might get a solution with this forum.
My Asp website is normal with capable of accepting a posted file.
After doing a bit more work, I found the problem to be my web service not responding to the REST formatted request. I was thrown off a bit by the first post and made the bad assumption that I wouldn't need to make any changes to the SOAP web service.
Is this all correct thought process? Do I need to re-write my SOAP service to respond to a REST call?
My server is IIS V6x all written in Visual Studio.
OK -- so on the web service end of things, I created a new 'simple handler' in Visual Studio. Added in some code to look for the commandline parameters and now the two are talking. Pretty cool.
I actually spent a lot of time looking for REST examples and my head almost spun off.... ARGHHHH. Sometimes its a lot easier to just write something.
I'm still having some problems parsing through the multiple values in the XML that got returned... but that should be the easy part.
I'm sort of a noob here so apologies if this is a bit of a yawn. As seen below, there are quotes used in the XML but this seems to cause an error when compiled. Can anyone direct me toward a way to deal with the quotes as they are necessary in the SOAP request?
Thanks in advance.
Quote:
Originally Posted by nait
Ok, I could never get a web service or WCF service to accept HTTP GET requests. I did manage to figure out how to manually create a soap envelope and call a 2.0 web service. Here's what the service tells me it needs:
HTML Code:
POST /<my service name>/Service1.asmx HTTP/1.1
Host: <my service host>
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://heyWorld.org/HelloWorld"
<?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><HelloWorld xmlns="http://heyWorld.org/"><userName>string</userName></HelloWorld></soap:Body></soap:Envelope>
The above is from the default asp.net web service you get when creating a new project. I have changed the namespace associated with the function calls from tempuri.org, and added a string parameter to the default HelloWorld function that gets created for you.
The following obj-c creates the soap envelope, creates a NSURLRequest with the necessary content-type etc. (check out everything I do with the 'request' variable), and finally connects and sends the request. I put the NSMutableData variable into an NSString just so I could see it from the debugger, but you can also use this for your parser.
Code:
NSMutableString *sRequest = [[NSMutableString alloc]init];
//create soap envelope
[sRequest appendString:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[sRequest appendString:@"<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/\">"];
[sRequest appendString:@"<soap:Body>"];
[sRequest appendString:@"<HelloWorld xmlns=\"http://heyWorld.org/\">"];
[sRequest appendString:@"<userName>foo</userName>"];
[sRequest appendString:@"</HelloWorld>"];
[sRequest appendString:@"</soap:Body>"];
[sRequest appendString:@"</soap:Envelope>"];
NSURL *myWebserverURL = [NSURL URLWithString:@"http://<my host>/<my project>/Service1.asmx"];
[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[myWebserverURL host]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:myWebserverURL];
[request addValue:@"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
[request addValue:@"http://heyWorld.org/HelloWorld" forHTTPHeaderField:@"SOAPAction"];//this is default tempuri.org, I changed mine in the project
NSString *contentLengthStr = [NSString stringWithFormat:@"%d", [sRequest length]];
[request addValue:contentLengthStr forHTTPHeaderField:@"Content-Length"];
// Set the action to Post
[request setHTTPMethod:@"POST"];
// Set the body
[request setHTTPBody:[sRequest dataUsingEncoding:NSUTF8StringEncoding]];
// Create the connection
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
NSMutableData *myMutableData;
// Check the connection object
if(conn)
{
myMutableData=[[NSMutableData data] retain];
}
// Make this class the delegate so that the other connection events fire here.
[NSURLConnection connectionWithRequest:request delegate:self];
NSError *WSerror;
NSURLResponse *WSresponse;
// Execute the asp.net Service and return the data in an NSMutableData object
myMutableData = [NSURLConnection sendSynchronousRequest:request returningResponse:&WSresponse error:&WSerror];
//convert the mutabledata to an nsstring so I can see it with the debugger
NSString *theXml = [[NSString alloc]initWithBytes:[myMutableData mutableBytes] length:[myMutableData length] encoding:NSUTF8StringEncoding];
Be sure to replace <my host> and <my project> where appropriate (and anything else I have up there that's <my *>), and make sure you are using the namespace you have declared for the service (mine is http://heyWorld.org/ and my function is HelloWorld.)
Credit for the above discoveries and most of the code: Tom McCartan