Friday, May 02, 2008

Stand alone multiplatform Silverlight application

Recently we spoke about running Silverlight application as client side only stand alone application. I used WebBrowser, MSHTA and Silverlight OCX with a little bit interop to make it running. In comments, Laurent Bugnion show me his method, that was pretty similar to mine, except the fact, that he used managed code to access file system. But more interesting comment come from Christophe Lauer, who tried to run Silverlight as completely stand alone application, by using Cassini embedded  into windows application, that actually represents web server for Silverlight hosting.

Inspired by this idea, I decided to implement my own small web server suitable to Silverlight hosting and embed it into my application. Then, compile and run it on Linux. That’s the result – It’s working!

image

How to do this? Simple – we have a lot of handy classes in C# in order to make small web server, that only knows to dispatch static files with appropriate mime types. Here we go. First of all, we’ll create simple WinForms application, that has WebBrowser inside it. Then we’ll use TcpListener to create our server socket and give it’s local address to embedded web browser, that will create requests.

listener = new TcpListener(IPAddress.Any, port);
listener.Start();
BeginGetReponse(null);
webBrowser1.Url = new Uri(string.Format("http://localhost:{0}",port));

Now, we should start listening and one got request, response it

void BeginGetReponse(IAsyncResult ar)
       {
           try
           {
               if (ar == null)
               {
                   listener.BeginAcceptSocket(BeginGetReponse, null);
                   return;
               }
               Socket socket = listener.EndAcceptSocket(ar);
               if (socket.Connected)
               {
                   byte[] inbuffer =  new byte[1024];
                   int br = socket.Receive(inbuffer);
                   string req = Encoding.ASCII.GetString(inbuffer, 0, br);

How could we know what file to dispatch? See http header of cause. (Note – this is ugly, quick and dirty solution only for POC)

string s = req.Split(' ')[1];
                    string send = string.Empty;
                    string mime = "text/html";

                    if (s == "/")
                    {
                        send = Resources.Default;
                    }
                    else if (s == "/Page.xaml")
                    {
                        send = Resources.Page;
                        mime = "application/xaml+xml";
                    }

How we have to create the same header our self and response with content

const string httpHeader = "HTTP/1.1 200 OK\r\nServer: WeirdThing1.1\r\nContent-Type: {0}\r\nAccept-Ranges: bytes\r\nContent-Length: {1}\r\nConnection: Close\r\n\r\n{2}";

byte[] data = Encoding.ASCII.GetBytes(send);
byte[] resp = Encoding.ASCII.GetBytes(string.Format(httpHeader, mime, data.Length, send));
br = socket.Send(resp);

We done. Now it works in Windows, but will it work in Linux? Let’s see…

First of all we have to add libgluezilla package to be able to use WebBrowser in mono. This is Gecko engine, that clues base browser core into Winforms. Very cool. That’s all we need. Right now we can just run our application “as-is” by using mono prompt and it will work.

The only small problem, that Moonlight plugin does not know, that it can run on Gecko (it is not FireFox). But small woodoo with it’s sources solves the problem. And now we can either continue to run it as it (with mono prompt) or compile it by using NAnt to work natively.

From here, as you, probably understand, the sky is the limit – create 1 socket by using C++ and run it whenever Silverlight can run. Also, you can even use Windows 2008 core (it has small IIS inside) to run it.

Have a nice day and be good people. Source code for this article.

No comments: