Cocoa and SOAP....the pain...its gone! (for now....)

Discussion in 'Mac Programming' started by MrCrispy, Jul 2, 2008.

  1. MrCrispy macrumors member

    MrCrispy

    Joined:
    Apr 10, 2008
    Location:
    Jacksonville, Florida
    #1
    ok, so I'm just experimenting with some development. I'm trying to connect to a very simple .NET web service I created. Basically, I feed it a number between 1 and 3 and it sends me back a string telling me what number I fed it. Like I said...simple.

    The documentation for dealing with web services is...limited..to say the least. I've pieced together what I can and have come up with the following:

    Code:
    - (IBAction)fetchResults:(id)sender
    {
    	NSString *ddlValue = [ddlNumb stringValue];
    	
    	NSLog(@"DDLValue = %@", ddlValue);
    	
    	WSMethodInvocationRef rpcCall;
    	NSURL *rpcURL = [NSURL URLWithString: @"http://test.dragonden.net/service.asmx"];
    	NSString *methodName = @"BringIt";
    	NSDictionary *params = [NSDictionary dictionaryWithObject: ddlValue forKey: @"x"];
    	NSDictionary *result;
    	
    	rpcCall = WSMethodInvocationCreate((CFURLRef) rpcURL, (CFStringRef) methodName, kWSXMLRPCProtocol);
    	
    	WSMethodInvocationSetParameters (rpcCall, (CFDictionaryRef) params, NULL);
    	
    	result = (NSDictionary *) (WSMethodInvocationInvoke(rpcCall));
    	
    	NSLog(@"result = %@", result);
    	
    	if (WSMethodResultIsFault ((CFDictionaryRef) result)) {
    		NSLog(@"result = %@", [result objectForKey: (NSString *) kWSFaultString]);
    		[lblResult setStringValue: @"Error"];
    	} else {
    		[lblResult setStringValue: [result objectForKey: (NSString *) kWSMethodInvocationResult]];
    	}
    		
    }
    
    It doesn't throw up any errors on the compile but when I run it and hit the button, the WSThingy results come back as

    Code:
    "/FaultCode" = -65794;
        "/FaultExtra" =     {
            domain = -1;
            error = -65795;
            msg = "Can't find a 'methodResponse' element";
        };
        "/FaultString" = "/CFStreamFault";
        "/kWSHTTPResponseMessage" = <NSCFType: 0x1a8900>;
        "/kWSResultIsFault" = 1;
    
    
    Searching for anything in that error block has proven to be fruitless. Can anyone see something that I'm missing? I'm hoping its something along the lines of "lol you noob. you forgot a ? at the end of your string. Go back to automator!!!11!!!!oneone"
     
  2. Sander macrumors 6502

    Joined:
    Apr 24, 2008
    #2
    I haven't tried consuming WebServices in Objective-C yet, but I notice you're using kWSXMLRPCProtocol. Are you sure this is OK? SOAP is not the same thing as XMLRPC.

    Have you successfully talked to your WebService from a different client (say, also written in .NET)? In other words, are you sure your WebService is OK?

    By the way, using the word "simple" in the same sentence as "SOAP"? You have got to read this :)
     
  3. MrCrispy thread starter macrumors member

    MrCrispy

    Joined:
    Apr 10, 2008
    Location:
    Jacksonville, Florida
    #4
    I did verify that the service was working. When I tried using the ksoapwhatevers protocol thingy (those are the official technical terms), it kept throwing back even more errors that didn't show up on any google search. One of the few articles I did find said that the kxmlrpc was the best way to go when dealing with .net services. Come to think of it though, he didn't really provide any working code for his tutorial either.

    According to those articles you posted, I should give up all hope and just buy a hampster. Admittedly, that's becoming a more and more enticing option.

    The geek in me won't let it go, however.

    P.S. After spending 3 hours trying to get the generated code working, the first person that says "just use the WSMakeStub thing. Its sooooo easy" gets a really dirty look from me
     
  4. MrCrispy thread starter macrumors member

    MrCrispy

    Joined:
    Apr 10, 2008
    Location:
    Jacksonville, Florida
    #5
    Ok, an update...

    I think I'm getting a bit closer. I'm getting a response from my service now.
    I made a few changes to the code. First, I went to the kWSSOAP2001Protocol and added a string to put in the appropriate headers.

    Still missing something though. Its not passing my value for the key X and so my service is kicking back the error code. At the same time, I'm having a bit of trouble figuring out how to pull just the result code out instead of the whole return string.

    So here's the latest code:
    Code:
    - (IBAction)fetchResults:(id)sender
    {
    	NSString *ddlValue = [ddlNumb stringValue];
    	
    	NSLog(@"DDLValue = %@", ddlValue);
    	
    	WSMethodInvocationRef rpcCall;
    	NSURL *rpcURL = [NSURL URLWithString: @"http://test.dragonden.net/service.asmx"];
    	NSString *methodName = @"BringIt";
    	NSDictionary *params = [NSDictionary dictionaryWithObject: ddlValue forKey: @"x"];
    	NSDictionary *result;
    	NSDictionary *headers = [NSDictionary	dictionaryWithObject:@"http://test.dragonden.net/BringIt" forKey:@"Soapaction"];
    	
    	rpcCall = WSMethodInvocationCreate((CFURLRef) rpcURL, (CFStringRef) methodName, kWSSOAP2001Protocol);
    	
    	
    	WSMethodInvocationSetParameters (rpcCall, (CFDictionaryRef) params, NULL);
    	WSMethodInvocationSetProperty(rpcCall, (CFStringRef) kWSHTTPExtraHeaders, (CFTypeRef) headers);
    	
    	
    	
    	result = (NSDictionary *) (WSMethodInvocationInvoke(rpcCall));
    	
    	NSLog(@"result = %@", result);
    	
    	if (WSMethodResultIsFault ((CFDictionaryRef) result)) {
    		NSLog(@"result = %@", [result objectForKey: (NSString *) kWSFaultString]);
    		[lblResult setStringValue: @"Error"];
    	} else {
    		[lblResult setStringValue: [result objectForKey: (NSString *) kWSMethodInvocationResult]];
    	}
    		
    }
    
    And the result I'm getting back now. I'm trying to just get the "dude, wtf" instead of the whole "{ bringit result etc"

    Code:
    2008-07-03 14:18:51.019 WebService[2184:10b] result = {
        "/Result" =     {
            BringItResult = "dude, wtf?";
        };
        "/kWSHTTPResponseMessage" = <NSCFType: 0x128640>;
        BringItResult = "dude, wtf?";
    }
    
    
    So close and yet so far...
     
  5. luckylefty01 macrumors member

    Joined:
    Apr 8, 2008
    #6
    I think you've actually got it. That response ( dictname { key = value} ) is just how %@ prints a dictionary (which is what result currently is).

    So I think all you need to do is

    Code:
    [[result objectForKey:@"/Result"] objectForKey:@"BringItResult"]; 
    or whatever to get the response out of the nested dictionaries.
     
  6. mcgomer macrumors newbie

    Joined:
    Feb 3, 2008
    #7
    Never give up!!

    I was more trying to let you know that others have tried and were having the same problems you were. Just to show that it wasnt a simple thing that you are doing wrong.

    Coming from .Net also, with Web Services being so easy to do there, I also would have thought that these things should be no brainers.

    Paul
     
  7. Sayer macrumors 6502a

    Sayer

    Joined:
    Jan 4, 2002
    Location:
    Austin, TX
    #8
    Apple initially made a lot of changes to AppleScript to support SOAP/RPC. The procedural api called Web Services Core is the actual implementation that sits under AppleScript's support I guess.

    What is lacking is a higher-level Cocoa-type framework to wrap up all the nitty-gritty mechanics.

    I guess it's just not much of a priority at Apple any more, and you can easily set up a web server to handle similar XML-based requests using any backend scripting/programming language you want.

    This is actually an area I am working in now with my new job. A Mac app talks to an embedded web server in a device that sends back straight XML (most of the time) and we parse out the bits we need. All done using standard http POST and GET functions.
     
  8. MrCrispy thread starter macrumors member

    MrCrispy

    Joined:
    Apr 10, 2008
    Location:
    Jacksonville, Florida
    #9
    Well, I've got it talking, reading the returned value. The problem is that regardless of what I've done, my program doesn't seem to be sending the value I'm setting for x. I'm spinning my wheels here now. I've a bunch of stuff I found on developer.apple.com but it doesn't seem to be helping anything. I am getting the "Dude, wtf" error message from the web service but at least its the one from the service. That's what's generated when it doesn't get the number 1, 2 or 3.

    Here's the latest code:

    Code:
    - (IBAction)fetchResults:(id)sender
    {
    	NSString *ddlValue = [ddlNumb stringValue];
    	
    	NSLog(@"DDLValue = %@", ddlValue);
    	
    	WSMethodInvocationRef rpcCall;
    	NSURL *rpcURL = [NSURL URLWithString: @"http://test.dragonden.net/service.asmx"];
    	NSString *methodName = @"BringIt";
    	NSString *nameSpace = @"http://test.dragonden.net/";
    	NSDictionary *params = [NSDictionary dictionaryWithObject:@"2" forKey: @"x"];
    	NSArray *paramOrder = [NSArray arrayWithObject:@"param"];
    	NSDictionary *result;
    	NSDictionary *headers = [NSDictionary dictionaryWithObject:@"http://test.dragonden.net/BringIt" forKey:@"Soapaction"];
    	
    	rpcCall = WSMethodInvocationCreate((CFURLRef) rpcURL, (CFStringRef) methodName, kWSSOAP2001Protocol);
    	
    	
    	WSMethodInvocationSetParameters (rpcCall, (CFDictionaryRef) params, (CFArrayRef)paramOrder);
    	WSMethodInvocationSetProperty(rpcCall, (CFStringRef) kWSHTTPExtraHeaders, (CFTypeRef) headers);
    	
    	// for good measure, make the call follow redirects.
        WSMethodInvocationSetProperty(rpcCall,    kWSHTTPFollowsRedirects, kCFBooleanTrue);
    	
    	WSMethodInvocationSetProperty(rpcCall, kWSSOAPMethodNamespaceURI, (CFStringRef) nameSpace);
    	
        // set debug props
        WSMethodInvocationSetProperty(rpcCall, kWSDebugIncomingBody,     kCFBooleanTrue);
        WSMethodInvocationSetProperty(rpcCall, kWSDebugIncomingHeaders, kCFBooleanTrue);
        WSMethodInvocationSetProperty(rpcCall, kWSDebugOutgoingBody,     kCFBooleanTrue);
        WSMethodInvocationSetProperty(rpcCall, kWSDebugOutgoingHeaders, kCFBooleanTrue);
    	
    	
    	
    	
    	result = (NSDictionary *) (WSMethodInvocationInvoke(rpcCall));
    	
    	NSLog(@"result = %@", result);
    	
    	if (WSMethodResultIsFault ((CFDictionaryRef) result)) {
    		NSLog(@"result = %@", [result objectForKey: (NSString *) kWSFaultString]);
    		[lblResult setStringValue: @"Error"];
    	} else {		
    		[lblResult setStringValue: [result objectForKey:@"BringItResult"]];
    	}
    		
    }
    
    
     
  9. MrCrispy thread starter macrumors member

    MrCrispy

    Joined:
    Apr 10, 2008
    Location:
    Jacksonville, Florida
    #10
    Wow...420+ views and still no suggestions on the last of my problems (at least my problems with this code. Gonna take a shrink to take care of the others).

    I spent the whole weekend working on this and let's just hope the hair I've pulled out will grow back.

    The problem seems to be with these two lines:
    Code:
    NSDictionary *params = [NSDictionary dictionaryWithObject:@"2" forKey: @"x"];
    
    and
    Code:
    WSMethodInvocationSetParameters (rpcCall, (CFDictionaryRef) params, (CFArrayRef)paramOrder);
    

    Something in there isn't sending the 2 along with the rest of the stuff.
     
  10. jimothyGator macrumors member

    jimothyGator

    Joined:
    Jun 12, 2008
    Location:
    Atlanta, GA
    #11
    SOAP Client

    There's a proposed renaming for SOAP, as it's now realized that's it's neither simple nor object-oriented. The proposed new name is Complex Remote Access Protocol, or CRAP.

    In the meantime, though, SOAP Client, a native Mac OS X client for SOAP testing, is now open source. I used this about two years ago when I was testing SOAP services, and it's pretty handy.

    http://ditchnet.org/soapclient/
     
  11. MrCrispy thread starter macrumors member

    MrCrispy

    Joined:
    Apr 10, 2008
    Location:
    Jacksonville, Florida
    #12
    I agree with the name change. I've been using the soap client and everything seems to work under that which has only served to frustrate me more.
     
  12. luckylefty01 macrumors member

    Joined:
    Apr 8, 2008
    #13
    Are you sure that param order is supposed to contain "params" and not the names of the actual params you're sending in order? i.e.

    Code:
    paramOrder = [NSArray arrayWithObject:@"x"];
    I did a bit of messing around with SOAP on OS X a while back, and that's more my recollection. I think it has to do with services needing the parameters specified in order, but NSDictionaries have no inherent order (thus one must be supplied somehow).
     
  13. MrCrispy thread starter macrumors member

    MrCrispy

    Joined:
    Apr 10, 2008
    Location:
    Jacksonville, Florida
    #14
    You Sir win the Internet! I KNEW it was something flat out stupid I was doing! A thousand thanks to you and everyone else who helped me through this exorcise in self torture.

    Here's the final code for anyone who was playing along at home:
    Code:
    - (IBAction)fetchResults:(id)sender
    {
    	NSString *ddlValue = [ddlNumb stringValue];
    	
    	NSLog(@"DDLValue = %@", ddlValue);
    	
    	WSMethodInvocationRef rpcCall;
    	NSURL *rpcURL = [NSURL URLWithString: @"http://test.dragonden.net/service.asmx"];
    	NSString *methodName = @"BringIt";
    	NSString *nameSpace = @"http://test.dragonden.net/";
    	NSDictionary *params = [NSDictionary dictionaryWithObject:ddlValue forKey: @"x"];
    	NSArray *paramOrder = [NSArray arrayWithObject:@"x"];
    	NSDictionary *result;
    	NSDictionary *headers = [NSDictionary dictionaryWithObject:@"http://test.dragonden.net/BringIt" forKey:@"Soapaction"];
    	
    	rpcCall = WSMethodInvocationCreate((CFURLRef) rpcURL, (CFStringRef) methodName, kWSSOAP2001Protocol);
    	
    	
    	WSMethodInvocationSetParameters (rpcCall, (CFDictionaryRef) params, (CFArrayRef)paramOrder);
    	WSMethodInvocationSetProperty(rpcCall, (CFStringRef) kWSHTTPExtraHeaders, (CFTypeRef) headers);
    	
    	// for good measure, make the call follow redirects.
        WSMethodInvocationSetProperty(rpcCall,    kWSHTTPFollowsRedirects, kCFBooleanTrue);
    	
    	WSMethodInvocationSetProperty(rpcCall, kWSSOAPMethodNamespaceURI, (CFStringRef) nameSpace);
    	
        // set debug props
        WSMethodInvocationSetProperty(rpcCall, kWSDebugIncomingBody,     kCFBooleanTrue);
        WSMethodInvocationSetProperty(rpcCall, kWSDebugIncomingHeaders, kCFBooleanTrue);
        WSMethodInvocationSetProperty(rpcCall, kWSDebugOutgoingBody,     kCFBooleanTrue);
        WSMethodInvocationSetProperty(rpcCall, kWSDebugOutgoingHeaders, kCFBooleanTrue);
    	
    	
    	
    	
    	result = (NSDictionary *) (WSMethodInvocationInvoke(rpcCall));
    	
    	NSLog(@"result = %@", result);
    	
    	if (WSMethodResultIsFault ((CFDictionaryRef) result)) {
    		NSLog(@"result = %@", [result objectForKey: (NSString *) kWSFaultString]);
    		[lblResult setStringValue: @"Error"];
    	} else {		
    		[lblResult setStringValue: [result objectForKey:@"BringItResult"]];
    	}
    		
    }
    
    
     
  14. luckylefty01 macrumors member

    Joined:
    Apr 8, 2008
    #15
    Can you ship it to me via UPS?

    Glad I could help. I did a bit of messing around with that a couple weeks ago and found it rather obtuse and poorly almost undocumented myself.

    If you happen to come across a good way to send complex structures (i.e. an employee structure with an NSString name and int ID members) please let me know:p. The only way I figured out was using WSSerializationOverride or whatever and basically creating the XML for the structure myself.
     
  15. MrCrispy thread starter macrumors member

    MrCrispy

    Joined:
    Apr 10, 2008
    Location:
    Jacksonville, Florida
    #16
    I saw something somewhere about dumping the results into a core data thingy. That's a ways to go for me. You can bet I'll probably be back here again when I try to tackle that hellish beast.
     
  16. HiRez macrumors 603

    HiRez

    Joined:
    Jan 6, 2004
    Location:
    Western US
    #17
    Another option might be to just call into another language that's better suited to dealing with that sort of thing, such as Python, Ruby, or Perl (or possibly even AppleScript). Those are all built into OS X and they're quite easy to call from a Cocoa app.
     
  17. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #18
    wsdl2java is pretty sweet for this sort of thing, too. It generates java classes for all of the types defined in the WSDL, then you can just use mutators to compose each type with primitives or other derived types. Making a particular web service call is not too much more difficult than calling anything else once you get the connection info set.

    I know this doesn't help much, but if you were to consider using another language I thought i'd toss that out there.

    -Lee
     

Share This Page