Friday, November 14, 2008

Mac To WCF WebService - Passing Parameters

In my last posting I showed the process of creating a iPhone application which can be used to call a WCF WebService and return a valid result. The next stage in the evolution of this application is to modify the code to send values to the service which acts on those values and returns specific results.

Working on this I figured would be a simple matter of adding some structure to the generated proxy classes and away we go, but sadly Apple again let us down on the really bad implementation of the data structures. This of course leads me to describe the many. many extra functions you will need to do to get this element to work.

First lets review our WCF WebService, which was defined in my previous article, we have two simple public methods; "bool Ping()" and "string Generate(string noun1, string noun2)". I've already shown how to connect to the first, now lets focus on the second. Generate takes two string parameters, then concatenates these within a Joke which is returned to the User. On the Objective C side of the equation I was simply going to change the calling code in the pingTest method from:

-(IBAction)pingTest:(id)sender
{
NSLog(@"Pressed: ");
NSString *result = [[JokeGen Ping:@""] objectForKey:@"/Result"];
NSLog([result description]);
resultsField.text = [result objectForKey: @"PingResult"];
}

To
-(IBAction)pingTest:(id)sender
{
NSLog(@"Pressed: ");
NSString *result = [[JokeGen Genereate:@"hello"] objectForKey:@"/Result"];
NSLog([result description]);
resultsField.text = [result objectForKey: @"GenerateResult"];
}
-(IBAction)pingTest:(id)sender
{
NSLog(@"Pressed: ");
NSString *result = [[JokeGen Ping:@""] objectForKey:@"/Result"];
NSLog([result description]);
resultsField.text = [result objectForKey: @"GenerateResult"];
}


The rational being that all all I was doing was changing the parameters which went into the SOAP request and therefore everything should just work. Oh how wrong can one person be ..... :-) Make this change in the code worked fine for the service call, I got my string back from the WebService, but no parameters had been passed. Setting a break point with Visual Studio confirmed that the two values for noun1 and noun2 where null.

Luckily as part of my investigations I'd learned quite a bit about debugging the SOAP requests between both applications. Using svctraceviewer which I described previously seemed to give me some of the information but sadly not the actual XML messages sent and received. What I needed to do was to obtain the actual SOAP request being sent via the iPhone application and compare that to a working SOAP request from a .Net Client application.

First I reviewed the XML being sent from the iPhone in the Debug screen in XCode, I'd already managed to do that using extra settings in the Generate stub which is described in more detail here. This tells me that I was sending the following:

"/WSDebugInBody" = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Header><ActivityId CorrelationId=\"ecb10aea-3e99-4472-bed1-0100aecb517e\" xmlns=\"http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics\">574f4c95-a28f-4a8b-ab6e-8ad0f16152c4</ActivityId></s:Header><s:Body><GenerateResponse xmlns=\"http://tempuri.org/\"><GenerateResult>What do you call an Irish ?\n A: An</GenerateResult></GenerateResponse></s:Body></s:Envelope>";
"/WSDebugInHeaders" = {
"Cache-Control" = private;
Connection = close;
"Content-Length" = 434;
"Content-Type" = "text/xml; charset=utf-8";
Date = "Thu, 04 Dec 2008 18:59:03 GMT";
Microsoftofficewebserver = "5.0_Pub";
Server = "Microsoft-IIS/6.0";
"X-Aspnet-Version" = "2.0.50727";
"X-Powered-By" = "ASP.NET";
};
"/WSDebugOutBody" = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n <SOAP-ENV:Envelope\n\n xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\n xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\"\n\n SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"\n\n xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n\n <SOAP-ENV:Body>\n\n <Generate xmlns=\"http://tempuri.org/\">\n\n <parameters xsi:type=\"xsd:string\">hello</parameters>\n\n </Generate>\n\n </SOAP-ENV:Body>\n\n </SOAP-ENV:Envelope>\n\n";
"/WSDebugOutHeaders" = {
"Content-Type" = "text/xml";
Host = "192.168.1.106";
Soapaction = "http://tempuri.org/IJoke/Generate";
"User-Agent" = "Mac OS X; WebServicesCore.framework (1.0.0)";
};
"/WSHTTPMessageHeaders" = (
<NSCFType: 0x556730>
);
"/kWSHTTPResponseMessage" = <NSCFType: 0x543020>;
GenerateResult = "What do you call an Irish ?\n A: An";
}

Next I had to increase the logging for the Service and to do this I had to make additional changes to the services web.config file.

<system.diagnostics>
<trace autoflush="true" />
<sources>
<source name="System.ServiceModel.MessageLogging" switchValue="Verbose,ActivityTracing">
<listeners>
<add type="System.Diagnostics.DefaultTraceListener" name="Default">
<filter type="" />
</add>
<add name="ServiceModelMessageLoggingListener">
<filter type="" />
</add>
</listeners>
</source>
<source name="System.ServiceModel"
switchValue="Information, Verbose, ActivityTracing"
propagateActivity="true">
<listeners>
<add type="System.Diagnostics.DefaultTraceListener" name="Default">
<filter type="" />
</add>
<add name="ServiceModelTraceListener">
<filter type="" />
</add>
<add name="sdt"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData= "SdrConfigExample.e2e" />
</listeners>
</source>
</sources>
<sharedListeners>
<!-- This is where the messages will be logged -->
<add initializeData="Web_messages.svclog"
type="System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
name="ServiceModelMessageLoggingListener" traceOutputOptions="Timestamp">
<filter type="" />
</add>
<add initializeData="Web_tracelog.svclog"
type="System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
name="ServiceModelTraceListener" traceOutputOptions="Timestamp">
<filter type="" />
</add>
</sharedListeners>
</system.diagnostics>

And adding to the existing system.serviceModel tags..

<diagnostics wmiProviderEnabled="true">
<messageLogging logEntireMessage="true" logMalformedMessages="true"
logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="true" />
</diagnostics>

The above should produced two extra files Web_messages.svclog and Web_tracelog.svclog which can be opened by the the svctraceviewer application. The resulting values looked like the following, with the right panel showing the actual XML being passed e.g.:


Now all I needed to do compare the SOAL XML from the iPhone to a working version, so I created a small Client application in Visual Studio 2008 added the WebReference to my JokeGen application and executed the call. The code for this was very simple:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using JokeGenTestHarness.JokeGen;

namespace JokeGenTestHarness
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void btnPress_Click(object sender, EventArgs e)
{
using (JokeClient wcfJokeGen = new JokeClient())
{
txtResult.Text = wcfJokeGen.Generate(txtEntry.Text, txtEntry.Text);
}
}
}
}

with a simple WinForm which looked like this:



The results of this showed that there was a slight difference in the way the Body tag was being formed.
iPhone:
<SOAP-ENV:Body>
<Generate xmlns=\"http://tempuri.org/\">
<parameters xsi:type=\"xsd:string\">hello</parameters>
</Generate>
</SOAP-ENV:Body>

Windows Test App, assuming all the NameSpace and Header information is correct:
<s:Body>
<Generate xmlns="http://tempuri.org/">
<noun1>hello</noun1>
<noun2>hello</noun2>
</Generate>
</s:Body>

OK.. Now we are getting somewhere! All thats needed is for the parameters tag to be changed to a <noun1> and <noun2> and it should just work. How to do this will have to wait until my next posting.......