The above silverlight application will allow you to do the following actions:
- Login
- Anonymous login
- Create a new user
- Logout
You can do multiple actions above during a silverlight session; there is no need to refresh the page for the actions to take effect within the silverlight application. But refresh the page once your account is created or when you are logged in to allow drupal to recognize the changes.
Drupal is an open source content management system (CMS) that allows users to add modules to their sites for great flexibility. These modules are typically written in PHP and use drupal's API to connect themselves with the framework. Silverlight (version 2.0 and greater) is a cross-platform browser plugin that allows users to write managed .NET code for the back end and use XAML as the front end. Since silverlight is a browser application and drupal is a framework for hosting data in browsers there can be data that is shared between the two using HTTP as the transport. In this example, I have chosen to use XML-RPC as drupal uses xml-rpc natively and silverlight can easily take advantage of this.
One thing to note, drupal has a module called services that is currently under development that will allow external applications (such as silverlight) to easily communicate through various interfaces, such as: XML-RPC, SOAP, REST and AMF. I am NOT using this module in my example.
Custom Module for Drupal:
As far as the drupal side of things go, I have a basic install of drupal 6.2 with a custom module that I have written to communicate with my silverlight application. I wrote the module based off of tips from Pro Drupal Development using chapters 2 and 19. There are two files that are needed to construct a drupal module: a .info file that contains information about the module and a .module file that is the actual custom module. My custom module is extremely simple in which it implements hook_xmlrpc to map xml-rpc methods to php callback functions. The xml-rpc methods are received from silverlight and then call the appropriate php function with the appropriate arguments. My php functions do various tasks, such as: checking the drupal database to see if a user exists so that user can login if the correct credentials are supplied,logging in an anonymous user, logging a user out and creating a fully authenticated user. Once the module is uploaded to the server, enable the module and your done! Well, done with the drupal side.
Silverlight application:
There are currently two methods in silverlight to communicate via a web service (in this case xml-rpc): WebClient and HttpWebRequest. HttpWebRequest contains all of the properties and methods that WebClient has and then some. But of course the extra goodies that HttpWebRequest provides come at a price; added complexity. WebClient is extremely easy to use and only requires one event and one property to be used, all though there are additional events and properties that can make life easier. Here are the events and properties that I have used in my application:
drupalDB = new WebClient();
drupalDB.UploadStringCompleted += new UploadStringCompletedEventHandler(drupalDB_UploadStringCompleted);
drupalDB.UploadProgressChanged += new UploadProgressChangedEventHandler(drupalDB_UploadProgressChanged);
drupalDB.UploadStringAsync(location, xmlData);
drupalDB is my WebClient and there are two events defined that will notify me when upload progress has changed (perfect for a progress bar) and completed. The property, UploadStringAsync takes in a Uri and a string as the data to upload to the web service. One thing to note here is that I am using UploadStringCompleted and not DownloadStringCompleted as WebClient does a GET for downloads and a POST for uploads and xml-rpc only accepts POSTs.
The data that I am uploading to the xml-rpc service is a string that is xml formatted that uses the standard xml-rpc definition:
string xmlData = "<?xml version=\"1.0\"?>"
+ "<methodCall>"
+ "<methodName>userlogin.logAnonIn</methodName>"
+ "<params>"
+ "<param>"
+ "<value>"
+ "</value>"
+ "</param>"
+ "</params>"
+ "</methodCall>";
There are two important things to note here. The tag <methodName> contains the xml-rpc method that the hook_xmlrpc function will map to a php function. The first part of that argument, userlogin is the name of my module and a standard format that drupal follows. The <param> tag contains a <value> tag where an argument can be passed to the hook_xmlrpc function. If multiple arguments are required then one needs to add additional <param><value>my data here</value></param> tags in between the <params> tag. Once the data is sent from silverlight to drupal and the appropriate mapping php function is processed, a return value is sent back to silverlight:
<methodResponse>
<params>
<param>
<value>
<string>return value</string>
</value>
</param>
</params>
</methodResponse>
The UploadStringCompleted event will fire once all of the above data is received. A popular way to parse the returned data is to use LINQ. LINQ is very powerful and very easy to use once you learn a little about it. I can parse the above data with the following LINQ statement:
XDocument document = XDocument.Parse(e.Result);
List<MethodToCall> methodsToCall = (from user in document.Descendants("value").Elements("string")
select new MethodToCall
{
MethodName = user.Value
}).ToList();
I place the result in an XDocument and use LINQ to parse the document for any node 'value' that has a child node 'string'. This is the standard format that xml-rpc sends. If there are multiple arguments then there will be multiple 'value' nodes. I call ToList() on the LINQ statement to create a generic list for multiple arguments that may be passed back.
With my particular application, since I can control what I send to and from the xml-rpc service, I decided to use reflection and pass back a string from drupal that I have defined as a method in my silverlight application. One thing to note here is that arguments are passed back as objects. If multiple arguments are passed back they are each an object, not an object[] so your method must take multiple arguments that are each objects instead of one argument of type object[].
After my method is called via reflection I simply update my user object as appropriate. And that's it! That is the basics of how to get drupal to talk to silverlight and vice versa. Of course, one needs to add some more logic and flare to a silverlight application to take advantage of the data it receives from drupal. I have added a few items that hopefully make the user experience that much better (and easier) in my user login application.
As a side note, once silverlight is configured on your web server all you need to display the silverlight application is the following html code placed wherever you would like:
<div style="width: 325px; height: 350px;">
<object data="data:application/x-silverlight," type="application/x-silverlight-2-b2" width="100%" height="100%">
<param name="source" value="/silverlight/SLApps/DrupalGameDemo.xap" />
<!-- Only need the below to show a link to get silverlight if it is not installed –>
<a href="http://go.microsoft.com/fwlink/?LinkID=115261" style="text-decoration: none;">
<img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style: none" />
</a>
</object>
</div>
The <object> tag contains a <param> tag that has the 'value' attribute of the location of your .xap file. The link in the above html code (<a> tag) is the link that will be displayed if a user does not have silverlight installed.
Feel free to try out the attached silverlight source (application is also displayed at the top of this page) and change things to your liking. Enjoy!
I have uploaded the custom module file as 'userlogin.txt'. (Rename it to .module) Remember to include a 'userlogin.info' file as well to complete your custom module. You can view the module file with notepad.
The LayoutTransformControl that I am using has been updated to the RTW bits. The latest code required additional code to update the transform at any time an animation was fired that would normally update the transform. Dave talks about why that is and one of the comments in his post points out a simple way to achieve this. Go grab this control as it's a great control to keep in your toolbox!