Thursday, 22 August 2013

Consuming PHP service in C#

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. 
$namespace = "";
// create a new soap server
$server = new soap_server();
// configure our WSDL
// set our namespace
$server->wsdl->schemaTargetNamespace = $namespace;
// register our WebMethod
                // method name:
                // parameter list:
                // return value(s):
                // namespace:
                // soapaction: (use default)
                // style: rpc or document
                // use: encoded or literal
                // 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.                
                ? $GLOBALS['HTTP_RAW_POST_DATA'] : '';

// pass our posted data (or nothing) to the soap service                    
That was it. From visual studio I was then able to consume the webservice by adding 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

Click on the advanced button

Finally click the Add Web Reference button

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");
Ran my application, pressed the button (which in turn called the webservice), and voilĂ  I get a message box telling me Hello: 

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: 
        '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')

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:
    '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).

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";
Compiled, ran and clicked.

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! 

Wednesday, 14 August 2013

Duplicate type with name 'Dynamics.Ax.application.' in assembly 'Dynamics.Ax.application,

Duplicate type with name 'Dynamics.Ax.application.' in assembly 'Dynamics.Ax.application, version=6.0.947.280, culture...

The following should resolve the problem:

  • Full compile AX
  • Stop the AOS
  • Truncate the SysXPPAssembly table in SQL (the table is used to contain the assemblies and to share between multiple AOS instances)
  • Delete the DLL and netmodule files in your AOS's bin\xppIL directory(C:\Program Files\Microsoft Dynamics AX\60\Server\MicrosoftDynamicsAX\bin\XppIL directory).
  • Restart the AOS
  • Perform full CIL generation