WebApp & DeskApp Communication

The last several days I have investigated how to get desktop application to communicate with a web application. I would have to assume that this I not a very popular approach because there just doesn.t seem to be very much put in place to allow this. Sure, you can create a web service to act as the middle-man, but I don.t want to. I just want to be able to send a small fragment of data from a .NET desktop application to an ASP.NET web application to indicate that it is done processing. In the end of my research I have only found one approach that REALLY does what I want it to do.

The biggest problem with any approach that I have found is that there isn.t a good way to launch a desktop application from a web-page. I.ve been trying to come up with a solution that wouldn.t require the user to actually install the desktop application. My solution to this problem has been to use .NET.s ClickOnce deployment model. As long as the user is using internet explorer this seems to work great. Unfortunately this approach doesn.t work quite as well on non-IE browsers.

For example, I found that in the Google Chrome browser, when I attempt to open the web-deployed ClickOnce .application, chrome just wants to download the file instead of opening it. Once chrome downloads the file (if the ClickOnce app is web-deployed) .NET complains that the app doesn.t have all the files that it needs to run.

ONE solution to this non-IE browser problem is to change your ClickOnce deployment method to .available online and offline. so that ClickOnce actually installs the application on the client machine. Unfortunately the process gets a little more complicated for the user since they have to tell chrome to download the file and also double-click the downloaded file so that it will open.

Another solution is simply to require the user to install and launch the desktop application separately/manually. Now I believe this can be automated to some degree (a COM control, for example – I have seen web-embedded COM controls open installer applications) but I haven.t played with this approach as much. My goal has been to get a desktop application launching from a web application with as little end-user-effort and user privileges as possible (non-administrators). Perhaps I will investigate this more later.

The COM & Process.Start() approach

I haven.t seen a practical use for COM for quite a while. But, in this case I found that it would work to embed a C# COM object in my webpage to manage the initialization and .communication. with my desktop application.

I asked two of my (more experienced) C# colleagues whether or not they had experience with C# COM and he told me he didn.t even know you could use C# to create COM-visible objects. A third colleague told me I shouldn.t even bother with .old technology like COM.. Perhaps this is a sign that this isn.t a popular concept.

The only real reason I would want to use COM is to have more of the control of system resources that C# allows. For example, using C# COM I can makes calls to theSystem.Diagnostics.Process class. One of the big questions I had was just how much access the COM object would have to the file system. I tried executing (usingSystem.Diagnostics.Process.Start method) applications from different directories such as C:\Users\Sean\Documents\Projects\MyProject\bin\Debug\MyProject.exe and c:\MyProject\bin\debug\MyProject.exe and those didn.t work. The only directory I seemed to be able to work with was %TMP%.

However, even though my COM object had very limited access to the file system I was still able to view all the currently running processing on the file system.

When I think about it that makes some sense as I have previously seen web/com-based virus scanners be able to look at actively running processes. And to help verify my findings, the web/com-based virus scanners weren.t able to fix the viruses they were only able to identify them. They required that you download the virus scanning software separately to actually fix the viruses.

So, I can use COM to view my list of active processes and even watch for when the processes close, but I can.t rely on it being able to launch executables from the file system because COM has very limited access to the file system.

I stumbled onto a small section in the System.Diagnostics.Process MSDN docs that explicitly explained that the Process class can start ClickOnce web applications. It explained that all you have to do is call Process.Start(.http://myserver.com/ClickOnceDeploy/MyApp.application.). I found that you can even pass variables to the ClickOnce application

As it turns out, even though you can use the Process class to start the ClickOnce applications you can.t actually hold onto the application instance using the same Process instance.  After investigating a bit further I believe this is due to the way that ClickOnce works. It seems that the call to the .application file actually spawns off a separate instance of the .real. application. This makes quite a bit of sense given that ClickOnce manages updates. The .application file seems to simply be the manifest of the application (information on version number, files, author, etc) as well as an indicator to .NET of what should actually be downloaded/launched.

So waiting for the original Process.Start(..application.) call to complete using the .WaitForExit() method won.t actually work. This also means that if the process doesn.t ever exit then I never get an execute code; I also can.t watch the standard output or error output of the process, etc.

It seemed that whatever process that class was holding onto never actually exited, so I believe that it may be a reference to some .NET runtime process. I could figure this out easily enough by finding the PID that is referenced by the Process instance and using TaskMan to see which process it actually is. Maybe later.

So in order to solve THIS problem I will use the Process class to list all currently running processes (we know this is something we can do in COM because I proved it earlier) and wait for the actual process to start, at which point I can use the Process class to wait for the process to exit. Here is a small chunk of code that I used to do that:

int tryCount = 0;
Process foundProcess = null;

while (tryCount < 5 && foundProcess == null)
{
	if (tryCount != 0)
		System.Threading.Thread.CurrentThread.Join(1000);

	Process[] foundProcesses = Process.GetProcessesByName("MyClickOnceApp");

	// Something should happen when there are multiple returned processes...
	if (foundProcesses.Length == 1)
		foundProcess = foundProcesses[0];
}

Unfortunately, even though I have an instance of the Process class that actually does reference the true process/PID of the desktop application; I can.t read the standard output or error streams. This is due to the fact that the process wasn.t started with RedirectStandardOutput or RedirectStandardError or UseShellExec. None of these properties can be set on processes that are already running and there doesn.t seem to be a way to tell a ClickOnce application to start with these properties either.

ClickOnce Con: Very limited control over the way the ClickOnce applications are started.

The reality is that there is just no way to get information from the ClickOnce application via standard methods.

I decided to look into sending data to the ClickOnce desktop application though. There seems to be some rules with this:

  • You cannot pass arguments to a ClickOnce application from the command line. For example, if you run: C:\>MyClickOnceApp.application myArg1 myArg2 the application just won.t receive the arguments. You can only pass arguments to the ClickOnce app via a web-start, for example:http://mydomain.com/MyClickOnce.application?Param1=Value1&Param2=Value2
  • In order to pass arguments to the click once in web-start form, you have to deploy the application with the option .Allow URL parameters to be passed to this application. turned on.
    • I found several articles describing that you can use the MageUI utility to change this option after the application has been deployed, but I did not have success with this. I had to turn the option on in the visual studio properties prior to deploying the application.

All-in-all this approach has seemed very limited. I was able to launch a desktop application from the COM control using the ClickOnce web-start deployment method, but I wasn.t able to return data from the desktop application to the COM control because the COM control couldn.t read the standard or error output from the application. The only thing I could do was pass information to the desktop application.

When embedding COM controls like this in your web pages keep flow-code as a consideration. I had many troubles wiring up JavaScript functions to handle the events from the control because I assumed that the order of code did not matter (like almost all other scripting languages). For example, I had to place my <OBJECT> element in the HTML/ASPX page before I actually referenced it in my JavaScript. So, as a rule of thumb: Embed any JavaScript that interacts with your embedded objects AFTER the object is initialized on the page.
The Silverlight & Sockets Approach

As I said earlier one of my colleagues said I should ditch the COM concept altogether. Specifically, he mentioned the possibility of using Silverlight, which caught my attention. I decided to take a look at Silverlight just to see whether or not this was a more viable approach the COM. There are other options, such as Flash, but Silverlight was something new and it kept the scope of my research inside C#/.NET technology (which is also why I chose to look at COM . being that you can create COM controls using C#).

Setting up my environment for Silverlight was reasonably straight-forward. Fortunately, the Silverlight technology is still so new that there is a large quantity of (even new) documentation available for it (for example, I found this to be a great 1st resource: http://silverlight.net/GetStarted/). Once I installed the necessary tools, toolKITS and service packs it was as easy as 1-2-3 to get a Silverlight control hosted on a webpage (a couple visual studio project templates get installed with the Silverlight toolkit).

I would recommend installing Microsoft.s Expression Blend like the Silverlight .Getting Started. guide recommends. It is a VERY handy tool for designing UI.s and I must say I was quite impressed with it. It has an unusually high price tag of $499.00 on it, but if you are going to be doing some serious Silverlight development, I think you.ll find that the visual return of your Silverlight controls from Expression Blend will be worth the investment. Like I say. .A developer is only as good as the tools s/he uses..

Upon creating a new Silverlight project you don.t get much. Just a typical blank Page_Load() method implementation and an ASPX page that embeds the control on the webpage. The first thing I tried doing is placing some .Hello World.-type text on the screen. To do that all I needed to do was create a Silverlight textbox/label and call this.MyLabel.Text = .Hello World.; from the Page_Load() method. I won.t delve into too many details on my Silverlight learning curve as there are plenty of white-papers, guides and examples on the web about Silverlight development (if I can pick it up in 10 minutes than it can.t be too hard).

Right from the start I tried running Process.Start() to see whether or not I can run my ClickOnce application in the same way as the com control. Unfortunately Silverlight doesn.t provide access to the System.Diagnostics.Process class. To start the ClickOnce application I had to open a separate window in the browser with my ClickOnce web-deployed URI (for my test I used a HyperlinkButton control). Doing this started the process just as easily as the COM/Processs.Start() approach. I would never be able to identify when the process exits (like I did with my COM/Process.Start() approach), but because of the method of communicating back to the Silverlight control from the desktop application I don.t really need to be worried about identifying when the process has ended because I can rely on the process telling me.

Communicating to the Silverlight control from the desktop application in this case is a little trickier, but in my opinion the benefits were worth the effort. In simplified terms, the concept is to create a listener socket on the desktop application (the server, so-to-speak) and to connect to the .server. from the Silverlight control in the webpage. After a connection between the desktop application and the Silverlight control has been established you can receive data from the server on the client.

Using sockets in Silverlight is a bit different than typical C# applications. One particular difference is that the Socket class in Silverlight doesn.t have the same method as the standard (out-of-the-box) .NET Socket class. For example, you have to create a SocketAsyncEventArgs class to pass onto the socket.ConnectAsync() method (notice that it is .ConnectAsync(). instead of .Connect().). Here is a code-snippet used to connect to the socket listener from within Silverlight:

DnsEndPoint endPoint = new DnsEndPoint("127.0.0.1", 4444);
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.UserToken = socket;
args.RemoteEndPoint = endPoint;
args.Completed += new EventHandler<SocketAsyncEventArgs>(args_Completed);
socket.ConnectAsync(args);

One of the limitations here is that Silverlight does not provide you any way to listen for socket connections. They only provide a way to connect to other socket connections (with the above code).

With that being the case, this approach to communicating with the desktop application yields yet another problem: We cannot guarantee that the desktop application will have initialized the socket listener when the socket.ConnectAsync() method has been called in the Silverlight control. Additionally, Silverlight sockets don.t have any form of a wait timeout built in. In other words, if you the listener isn.t initialized in the desktop when the ConnectAsync() method is called, it will just return back as !SocketError.Successs.

The solution to this problem is (yes, you guessed it) to create a loop (sort of) until a connection has been made. The best way I found to do this that wouldn.t cause overlap in connection attempts (due to the fact that the call is asynchronous) is to wait for the first connection attempt to return !Success and then make a call to the connect method again after disposing all of the appropriate connection resources. Like so:

private void tryConnect()
{
	DnsEndPoint endPoint = new DnsEndPoint("127.0.0.1", 4530);
	Socket socket = new Socket(AddressFamily.InterNetwork,
	SocketType.Stream, ProtocolType.Tcp);

	SocketAsyncEventArgs args = new SocketAsyncEventArgs();
	args.UserToken = socket;
	args.RemoteEndPoint = endPoint;
	args.Completed += new EventHandler<SocketAsyncEventArgs>(args_Completed);

	socket.ConnectAsync(args);

	this._connectTryCount++;
}

void args_Completed(object sender, SocketAsyncEventArgs e)
{
	Socket socket = (Socket)e.UserToken;
	e.Completed -= new EventHandler<SocketAsyncEventArgs>(args_Completed);

	if (e.SocketError == SocketError.Success)
	{
	}
	else
	{
		socket.Close();
		e.Dispose();
		socket.Dispose();

		if (_connectTryCount < _TRY_TIMEOUT)
		{
			// Wait for one second after the connect attempt
			System.Threading.Thread.CurrentThread.Join(3000);

			// Recursively try to connect
			tryConnect();
		}
	}
}

It seems that there are special security restrictions when communicating with SOCKS in Silverlight controls (kind of like a firewall); the .server. needs to publish a .Client Access Policy. so that Silverlight knows whether the ports you are trying to communicate on are allowed.

I won.t go into this as I didn.t make any .great. discoveries on this front. Check my references at the bottom for an article on how to set this up and get it working.

After I got the Silverlight .client. to connect to the desktop .server. things went quite smooth. On the server I waited for connections and opened a stream to write to the client when I needed to send data/messages back. Score one for two-way communication between the desktop app and webpage.

The next issue was how I would be able to notify the webpage code that the Silverlight control had received messages. I ended up venturing down the same avenue as I did with COM and captured the Silverlight control events using JavaScript. To do this, though, you need to prepare your Silverlight control in a couple of ways:

  • Mark your class with [ScriptableType] and [ScriptableMember] (almost feels like WCF).
  • Make the following call when your Silverlight control starts (in the App class): HtmlPage.RegisterScriptableObject(“MyClass”, myClassInstance);

Once the Silverlight control was prepared as a scriptable object and the types/members were appropriately marked I was able to wire up to the events of the object even easier than in the COM version:

function sivlerlightOnLoad(sender, e) {
	var silverlightControlHost = document.getElementById("MySilverlightObject");
	silverlightControlHost.Content.MyClass.SomeEvent = someEvent;
}

function someEvent(sender, e) {
	alert("SomeEvent fired and caught in javascript!");
}

Note that when you haven.t properly setup your Silverlight class as a scriptable object, silverlightControlHost.Content.MyClass would return an error. That is because Javascript does not have access to the .MyClass property as it is not setup as a scriptable type/object.

Notice though that there is a silverlightOnLoad() method? This isn.t wired up automatically. The <asp:Silverlight> element doesn.t allow you to wire up the Silverlight control.s OnLoad event in javascript either. To wire up events in javascript to a Silverlight control you have to load the control in the old-fashioned <OBJECT> element. When you specify the <OBJECT> element you can add a <PARAM> element as a child of the <OBJECT> and specify the OnLoad event, like so:

<object data="data:application/x-silverlight," type="application/x-silverlight-2" width="50%" height="50%" id="MySilverlightObject">
	<param name="source" value="ClientBin/MySilverlightTest.xap"/>
	<param name="onload" value="silverOnLoad" />
</object>

Now, in this case (unlike the case of the COM control) it doesn.t matter whether or not you have your javascript code before or after the OBJECT element. I believe this is because the Silverlight control is loaded after the DOM has finished loading, though I haven.t verified this.

Summary

By far, I have found that the Silverlight/SOCKS approach was better. It provided the ability to have two-way communication between my web app and desktop application AND as an added bonus I have much more graphic capbility with the Silverlight control. I suppose I could apply the same SOCKS usage to the COM control, but what can I say, I.m a sucker for new and cool technology (and there were far less problems with the Silverlight control than with the COM).

References

ClickOnce Deployment Overview
http://msdn.microsoft.com/en-us/library/142dbbz4(VS.80).aspx

Pushing Data to a Silverlight Client with Sockets: Part I
http://weblogs.asp.net/dwahlin/archive/2008/04/10/pushing-data-to-a-silverlight-client-with-sockets-part-i.aspx

Pushing Data to a Silverlight Client with Sockets: Part II
http://weblogs.asp.net/dwahlin/archive/2008/04/13/pushing-data-to-a-silverlight-client-with-sockets-part-ii.aspx

Creating a Silverlight Client Access Policy Socket Server
http://weblogs.asp.net/dwahlin/archive/2008/06/08/creating-a-silverlight-2-client-access-policy-socket-server.aspx

Silverlight Events in JavaScript and JavaScript Events in Silverlight
http://blogs.microsoft.co.il/blogs/alex_golesh/archive/2008/07/28/quick-silverlight-tip-silverlight-events-in-javascript-and-javascript-events-in-silverlight.aspx

How to Handle an ActiveX Event in JavaScript
http://stackoverflow.com/questions/150814/how-to-handle-an-activex-event-in-javascript

 

Leave a Reply

Your email address will not be published.

Humanity Verification *Captcha loading...