Sunday, September 21, 2008

Mac to WCF WebService - it never happened.

This article came about from my investigations into developing an iPhone application. As part of my test project I was looking for the iPhone to make a WebService call to my server for data updates. This would be the approach I've used for many .Net application. Other iPhone application also seem to have the same Service Orientated Architecture (SOA) design, so I figured "How hard could it be"?

First off the development was to be broken down into three parts;
1) Build a standard Cocoa application to run on on the Mac calling a generic webservice. Why do this, simply it's because there are good samples already out on the net.
2) Build a WCF Service serving up some content over basicHTTP protocol. I've chosen WCF rather than a standard WebService as I'm more comfortable in the environment and I figure that most Corporate customers would have lots of .Net coders who need to expose internal data using this method.
3) Link the existing Cocoa application to the WCF WebService.

If all this worked I was going then going to convert the Cocoa application to the iPhone.

--- 1) Cocoa application to read from a webservice -------
Building the Cocoa Application to read from a webservice was not difficult. As an example I chose the example from Aaron Hillegass's excellent book "Cocoa Programming for Mac OSX" (ISBN:0-321-50361-9) pp.345-352, where he connects to the Amazon website to get book lists. He used the built in framework to build the request to the URL and parsed the results into an itemNodes array:


// Get the string and precent-escape for insertion into URL
NSString *input = [searchField stringValue];
NSString *searchString = [input stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
//Create the URL
NSString *urlString = [NSString stringWithFormat:
@"http://ecs.amazonaws.com/onca/xml?"
@"Service=AWSECommerceService&"
@"AWSAccessKeyId=%@&"
@"Operation=ItemSearch&"
@"SearchIndex=Books&"
@"Keywords=%@&"
@"Version=2007-07-16",
AXWS_ID, searchString];
// Make the call
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReturnCacheDataElseLoad
timeoutInterval:30];
// Fetch the XML response
NSData *urlData;
NSURLResponse *response;
NSError *error;
urlData = [NSURLConnection sendSynchronousRequest:urlRequest
returningResponse:&response
error:&error];
//Parse the XML Response
[doc release];
doc = [[NSXMLDocument alloc] initWithData:urlData
options:0
error:&error];
[itemNodes release];
itemNodes = [[doc nodesForXPath:@"ItemSearchResponse/Items/Item"
error:&error] retain];



While the Table view columns have been set with identified attributes of the xPath to the data e.g. "ItemAttributes/Title".

--- 2) Window WCF application to serve content -------
Building a WCF application using visual studio 2008 is a dream, simply create the service using File/New Web Site and select WCF Service from the list of options. I choose the name "JokeGen" for the project and renamed the default 'service.c" and "IService.c" files to "Joke". By default this will create the service using the wsHttpBinding within the web.config file. This provides message encryption for any traffic and from a Window Client this is a great idea. Unfortunately we are dealing here with a Mac, so I'm going to downgrade this to basicHttpBinding, that way we won't have to deal with manually decrypting the traffic. To do this edit the web.config file and change the line:
endpoint address="" binding="wsHttpBinding" contract="IJoke"
to
endpoint address="" binding="basicHttpBinding" contract="IJoke"

One other suggestion I'd make would be to remove the dynamic assignment of IP address while debugging. This is a setting on the web project file, setting the "Use Dynamic ports" to false and set the Port number to 8888. I do this as I like to test directly on my development box and as I'm using a VM I want to keep the URL consistent. We still need however to create the application in IIS for the site to be visible outside of the server.

We create two simple functions; first is "Ping" which just returns a "true" for testing purposes. The second is Generate which takes two nouns as string and returns a joke created from these values.
IJoke.C

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

// NOTE: If you change the interface name "IService" here, you must also update the reference to "IService" in Web.config.
[ServiceContract]
public interface IJoke
{
[OperationContract]
bool Ping();

[OperationContract]
string Generate(string noun1, string noun2);

}


Joke.c

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

public class Joke : IJoke
{
public bool Ping()
{
return true;
}

public string Generate(string noun1, string noun2)
{
return String.Format("What do you call an Irish {0}?\n A: A {1}", noun1, noun2);
}
}


--- Changing the Mac application to the WCF service -------

Now we need to modify our previous application to point to the WCF Service. First off we need to check if the service is visible. The simplest way to do this if from your Mac browse to the Windows server (or VM in my case). First ping your Mac from the Windows server, then ping the Windows box from your Mac. Use IPConfig on the Windows box if your unsure what your external IP address would be or use the DNS name you have created. I received a "Safari can’t open the page “http://192.168.1.106:8080/JokeGen/Joke.svc” [The location of the WebSite in IIS] because it can’t find the server" which was a nice way of saying my Firewall was not co-operating. I also got a Username and Password login prompt so I needed to change the permissions for the virtual directory. Eventually you will see the standard service description page.

So now I went off looking for an example and this is what I found:
consuming_a_wsdl_web_service_from_cocoa__failed_attempt_1

Basically this post indicates that this task is going to be a lot harder than I first expected. I'll post again on the use of the WSMakeStubs application and maybe, just maybe be able to finish my dream of having a simple webservice that can be read from the Mac.