§ July 3, 2006

PHP Webservices and C# / .NET SOAP Clients

Code Zulu Bind Maker is a project I started working on about 3 years ago. Its a stupid little app that configures game settings for Counter-Strike. When I wrote it, version 1.6 was in beta, and everybody played version 1.5 (which was ever so different). Anyway I run a website that supports the app, and of course, its written in PHP / MySql (my poison of choice on the web).

The reason for the boring and seemingly pointless background is that ever since I wrote the app, I always wanted to integrate the website and application, but never really thought of a "good" way to do it. A couple days ago, I had some spare time and sat down to see how easy it would be to create some PHP webservices that I could easily consume in C#. I had been considering expanding the community aspect of the bind maker website into the application so that they'd work seamlessly together. I also wanted to add a common place that gaming communities could spend their time (which I had previously planned for just the website, but was thinking of the possibilities of bringing that into the app as well), and a PHP webservice combined with a C# windows app seemed like the best option.

C# webservices (server side) are about as easy-as-it-gets to write. C#'s SOAP client is completely transparent (or I should say completely generated so you code just like you would use any other objects) and is completely brainless to use. Since I'd never done webservice work in PHP, I started asking Mr. Google how it worked, and what would you now but that I stumbled across the NuSOAP library for php. It sure beat the heck out of writing all my WSDL files by hand, wrapping and unwrapping stupidly simple SOAP messages, and hacking everything very poorly.

My first PHP webservice iteration was a simple "hello <name> " web method. It used a simple type (xsd:string) as its parameter and returned a simple type (again another xsd:string) as its return value and looked like this:
 /**
 * ProcessSimpleType method
 * @param string $who name of the person we'll say hello to
 * @return string $helloText the hello  string
 */
function ProcessSimpleType($who) {
	return "Hello $who";
}
NuSoap was great. It took care of all the nitty-gritty work that I had previously done myself. All it took was about 8 lines of code to set up the SOAP server and register the method and my types.
require_once("lib/nusoap/nusoap.php");
$namespace = "http://sanity-free.org/services";
// create a new soap server
$server = new soap_server();
// configure our WSDL
$server->configureWSDL("SimpleService");
// set our namespace
$server->wsdl->schemaTargetNamespace = $namespace;
// register our WebMethod
$server->register(
                // method name:
                'ProcessSimpleType', 		 
                // parameter list:
                array('name'=>'xsd:string'), 
                // return value(s):
                array('return'=>'xsd:string'),
                // namespace:
                $namespace,
                // soapaction: (use default)
                false,
                // style: rpc or document
                'rpc',
                // use: encoded or literal
                'encoded',
                // description: documentation for the method
                'A simple Hello World web method');
                
// Get our posted data if the service is being consumed
// otherwise leave this data blank.                
$POST_DATA = isset($GLOBALS['HTTP_RAW_POST_DATA']) 
                ? $GLOBALS['HTTP_RAW_POST_DATA'] : '';

// pass our posted data (or nothing) to the soap service                    
$server->service($POST_DATA);                
exit();
That was it. From visual studio I was then able to consume the webservice by adding a web reference

Add A Web Reference

This article was first written with visual studio 2005. Visual studio 2008 and 2010 no longer have the "Add Web Reference" menu item. Instead they have an option to add a service reference. Selecting this option ends up generating a WCF C# client proxy, but we'll want to use the asmx style web service client proxy, so we'll navigate to the add web reference dialog which is hidden deep inside Add Service Reference dialog.

Visual Studio 2008 and 2010 Only
Right click on the references folder, and select Add Service Reference

Add A Service Reference


Click on the advanced button

Advanced

Click Add A Web Reference

Finally click the Add Web Reference button

Add A Web Reference Dialog

Add A Web Reference

With the reference added, I put a button on my form, and added the following button click event handler:
private void button1_Click(object sender, EventArgs e) {
    SimpleService svc = new SimpleService();
    string s = svc.ProcessSimpleType("steve");
    MessageBox.Show(s);
}
Ran my application, pressed the button (which in turn called the webservice), and voilà I get a message box telling me Hello:


Hello Steve Message Box

That's great! Completely useless, but great!

Now to get a little more complicated... I had planned on using complex data types that would be consumed by both PHP and .NET clients (A PHP SOAP client for the website, and .NET client for the windows application). It may sound stupid to have a PHP client when most likely I'd have access to the method directly, but the server that is going to host the webservices is actually different than the "web presence" server I host my website on. Anyway, the test complex types I came up needed to look like simple structs (in C#) which would also be used in an array, and they looked something like this:
public struct MySoapObject {
    public string Author;
    public string Name;
    public string Description;
    public string Text;
    public int VoteTotal;
    public int VoteCount;
}

MySoapObject[] soapObjects = new MySoapObject[10];
so I added the definitions in PHP using NuSOAP like this:
$server->wsdl->addComplexType(
    'MySoapObject',
    'complexType',
    'struct',
    'all',
    '',
    array(
        'Author' => array('name'=>'Author','type'=>'xsd:string'),
        'Name' => array('name'=>'Name','type'=>'xsd:string'),
        'Description' => array('name'=>'Description','type'=>'xsd:string'),
        'Text' => array('name'=>'Text','type'=>'xsd:string'),
        'VoteTotal' => array('name'=>'VoteTotal','type'=>'xsd:int'),
        'VoteCount' => array('name'=>'VoteCount','type'=>'xsd:int')
    )
);

$server->wsdl->addComplexType(
    'MySoapObjectArray',
    'complexType',
    'array',
    '',
    'SOAP-ENC:Array',
    array(),
    array(array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'tns:MySoapObject[]')),
    'tns:MySoapObject'
);
I defined the method in PHP that would take in an array of MySoapObjects, process them, and then return one MySoapObject that it had "processed".

That method looked like this:
function ProcessMySoapObject($mySoapObjects) {
	$mso = $mySoapObjects[3];
	$mso['Name'] = "|||";
	return $mso;
}
The SOAP server registration:
$server->register(
    'ProcessMySoapObject',
    array('soapObjects'=>'tns:MySoapObjectArray'),
    array('return'=>'tns:MySoapObject'),
    $ns,
    false,
    'rpc',
    false,
    'Processes an array of MySoapObjects and returns one of them');


And I did exactly the same as I did before--and added the web reference in visual studio to my project. This time not only did Visual Studio add the Service class and web method, it added the 2 data types I had defined in PHP on my SOAP server (actually, it just built the MySoapObject, and the array was left out because arrays are an implied data type).

The object browser showing the structure of the MySoapObject

The object browser showing the method ProcessMySoapObject

I changed my button click event in my windows form and added the following code:
private void button1_Click(object sender, EventArgs e) {
    Services s = new Services();
    MySoapObject[] ms = new MySoapObject[10];
    for(int i = 0; i < ms.Length; i++) {
        ms[i] = new MySoapObject();
        ms[i].Author = "Steve";
        ms[i].Description = "The One that should be returned";
        ms[i].Name = i.ToString();
        ms[i].Text = "something something something " + i.ToString();
        ms[i].VoteCount = i * 2;
        ms[i].VoteTotal = (int)Math.Pow(ms[i].VoteCount, 2);
    }
    MySoapObject retn = s.ProcessMySoapObject(ms);
    string output = "";
    output += retn.Author + "\t\t\r\n";
    output += retn.Description + "\t\t\r\n";
    output += retn.Name + "\t\t\r\n";
    output += retn.Text + "\t\t\r\n";
    output += retn.VoteCount.ToString() + "\t\t\r\n";
    output += retn.VoteTotal.ToString() + "\t\t\r\n";
    MessageBox.Show(output);
}
Compiled, ran and clicked.

The image of the message box that was returned when I clicked the button.

Simple, Easy, Can't believe I never tried it before now!

This opened up a whole new world of opportunities in cross-platform distributed computing that I'd never exposed myself to before... what a wonderful thing!

Posted 1 week, 5 days ago on July 3, 2006

 Comments can be posted in the forums.

© 2003 - 2024 NullFX
Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License