Click here to Skip to main content
Click here to Skip to main content

Deploy .NET thick client application assemblies to a remote server

, 21 Mar 2008
Rate this:
Please Sign up or sign in to vote.
This demo shows a new way of deploying .NET thick client application assemblies to a remote server.

Introduction

This demo shows a new way of deploying .NET thick client application assemblies, like Windows Forms and Console applications, to a remote server, which makes deployment and maintenance a bit easier. This demo also concentrates on code security of .NET applications.

Background

During the days of VB, we used to prepare a setup file, and were forced to run the setup on each system.

With .NET, we definitely got options like application publisher and auto-update; but as per me, these options still have the downsides listed below:

  • Size of the application: If the size of the application is too big or complex, this will take a long time to update.
  • Users are notified while upgrading application: When auto-update happens, the user can see the progress and may cancel also; which can result in an unstable application.
  • Users are not allowed to work while upgrading: While the application is getting updated by auto-update, the user is not allowed to work. This may delay the user from performing urgent tasks.
  • Insecurity of the code: The application assemblies are physically located on the user's system; specifically, applications developed in .NET and JAVA. Anyone who has access to the user's system can use freely available Reflector tools and get into the code. This is not acceptable by companies, since the code is the asset of IT companies.

Due to code insecurity, I was always in search of an alternative application deployment method, which is easy for maintenance as well. This new way of application deployment should not restrict thick client applications which are developed using single-tier, two-tier, three-tier, or N-tier architecture. Also, thick client applications developed to support multi-cultures and multi-languages should not be restricted. In short, this new way of application deployment should allow deploying all varieties of .NET thick client applications.

I was aware of the CITRIX way of application deployment. But, it costs extra money, server, and maintenance. Hence, small and medium size projects cannot afford it, and is not advisable either.

'ClickOnce' Deployment

With .NET 2.0, Microsoft came with the idea of 'ClickOnce' deployment. 'ClickOnce' deployment allows developers to deploy .NET application assemblies to a remote server (including HTTP, HTTPS, FTP, shared drives, etc.).

Upon creation of the 'ClickOnce' deployment for .NET applications, the deployment wizard creates a 'setup.exe' and a '.application' file apart from the application folder. The 'setup.exe' file is responsible to check for a specific version of the .NET framework on the user's system. If the .NET framework is not installed on the user's system, 'setup.exe' will download and install it. The '.application' file is nothing but an XML file which contains the details of the deployed application like where the application has been deployed, the application name, version, public key, etc.

The user needs to execute the '.application' file to start the application on the user's system. The 'WinFXDocObj.exe' is responsible for reading the '.application' file, copy the application, and the dependency files to the user's system, and start the application. The 'WinFXDocObj.exe' copies application and dependency files from the deployed location to the user's system at 'C:\Documents and Settings\<user id>\Local Settings\Apps\2.0\<unique folders>\...'; the deployed application will be started from this path on the user's system.

As explained above, the 'ClickOnce' deployment still has the downsides listed below:

  • Application and dependency files are still getting copied to a unique folder on the local system of the user; which risks code security.
  • If the application has a new version, the new version gets copied to a new unique folder; instead of copying or overwriting to an existing unique folder. This creates multiple versions of the same application on the user's system and occupies more physical hard disk space.
  • Since the unique folder is created under each user's folder, if there is more than one user of the same system, the application gets deployed for each user, which occupies more physical hard disk space.

Requirement

I wanted to have an application deployment method, which meets the features listed below:

  • Should be very simple to implement
  • Should be easy to maintain
  • Should allow all variety of thick client applications
  • Should allow upgrading applications without user notification
  • Should have maximum code security
  • User should always be using the latest version of the application

What I mean by 'maximum code security' is that the application assembly and its dependent assemblies should run on a user's system virtually. This means that the application assembly and its dependent assemblies should exist on the user's system till the application is running. Once the user closes or terminates the application, all the dependent assemblies and the application assembly itself should be removed from user's system. And, this is the biggest difficultly for this new application deployment method.

Fortunately, I found the workaround for this difficultly, and this is what this article is all about.

Running the Demo

  • Extract the demo application ZIP file to a folder.
  • Open the command prompt.
  • Change the directory to the 'Client' folder under the un-zipped folder.
  • Execute 'AppClient.exe /setup'. This will register the .OSA file on the client's system. Once the .OSA file has been registered, the user can start execution of 'AppClient.exe' on the user's system by double-clicking on the .OSA file.
  • Open Explorer.
  • Change the directory to the 'Server' folder under the un-zipped folder.
  • Execute 'AppSrv.exe'. This will bring-up the application server.
  • Change the directory to the 'DemoApps' folder under the un-zipped folder.
  • Double-click on any of the .OSA files to start the demo application.
  • demo-win.osa is a simple .NET Windows Forms application which does not have any dependent assemblies.
  • demo-win-new.osa is a simple .NET Windows Forms application which has a dependent assembly.

Make it Work

I am always enthralled by .NET Remoting and Web-services. Since small and medium size projects do reside within the intranet, .NET Remoting is the best advised by me.

I am using a simple implementation of the server and the client application to accomplish my requirements. The server application is still the ordinary .NET Remoting application; but, the client application does all the magic to implement my idea.

Interface - IApplicationProvider

The interface 'IApplicationProvider' defines the functions required to communicate between the client and the server applications. Using these functions, the client application gets the required application assembly and its dependent assemblies from the server application.

namespace Company.Common
{
    public interface IApplicationProvider
    {
        bool Ping();

        bool Exists(string application);
        byte[] GetApplication(string application);
        byte[] GetDependency(string application, string dependency);
    }
}

Application - Server

As we will be deploying application assemblies and its dependent assemblies on a remote server within the intranet, we need some kind of server application which is responsible for providing the application assemblies and its dependent assemblies to the client application.

To achieve this, I have created a 'Server' application which uses the .NET Remoting architecture and implements the 'IApplicationProvider' interface. The configuration file of the server application has custom defined tags to store the settings and the physical location of the actual application assemblies and its dependent assemblies on the remote server.

<Company.AppSrv>
  <Applications>
    <add name=&quot;WinDemo&quot; location=&quot;.\WinDemo\WinDemo.exe&quot; />
    <add name=&quot;WinDemo_New&quot; location=&quot;.\WinDemoNew\WinDemoNew.exe&quot; />
  </Applications>
</Company.AppSrv>

Once the client application requests the server application for an application assembly or its dependent assemblies, the class which implements the 'IApplicationProvider' interface on the server application reads these details from the configuration file and returns the application assembly or its dependent assemblies to the client application.

So, the server application behaves exactly as a normal .NET Remoting server application.

Application - Client

The client application is responsible for getting a specific application assembly from a server application and execute it on the user's system virtually. This sounds very simple, but the magic of running an application assembly virtually on the user's system is implemented here.

Since the client application should execute any application assembly available on the remote server, instead of hard coding the specific application details within the client application, I have used an XML file format as the specific application configuration file and named these specific application files as OSA files.

The configuration file (.OSA) needs to be registered on the user's system. To register the configuration file (.OSA) on the user's system, we need to call 'AppClient.exe' by passing the '/setup' command line argument. The 'setup' option of 'AppClient.exe' updates the Registry of the user system with the application association details for the configuration file (.OSA). Once the configuration file is registered, the user is required to double-click on the .OSA file to start the application.

private static void Setup()
{
    RegistryKey regKey = null;
    string sAppName = @&quot;Company.AppClient&quot;;

    regKey = Registry.ClassesRoot.CreateSubKey(@&quot;.osa&quot;);
    if (regKey != null)
    {
        regKey.SetValue(string.Empty, sAppName, RegistryValueKind.String);
        regKey.SetValue(&quot;Content Type&quot;, &quot;application/x-osa&quot;, RegistryValueKind.String);
        regKey.Close();
    }

    regKey = Registry.ClassesRoot.CreateSubKey(sAppName);
    if (regKey != null)
    {
        regKey.SetValue(string.Empty, &quot;Company Application Client - Runtime&quot;, 
                        RegistryValueKind.String);
        regKey.Close();
    }

    regKey = Registry.ClassesRoot.CreateSubKey(string.Format(@&quot;{0}\DefaultIcon&quot;, sAppName));
    if (regKey != null)
    {
        regKey.SetValue(string.Empty, Path.GetFullPath(Application.ExecutablePath) + 
                        &quot;,0&quot;, RegistryValueKind.String);
        regKey.Close();
    }

    regKey = Registry.ClassesRoot.CreateSubKey(
             string.Format(@&quot;{0}\shell\open\command&quot;, sAppName));
    if (regKey != null)
    {
        regKey.SetValue(string.Empty, &quot;\&quot;&quot; + Path.GetFullPath(Application.ExecutablePath) + 
                        &quot;\&quot; \&quot;%1\&quot;&quot;, RegistryValueKind.String);
        regKey.Close();
    }
}

To play the magic, the client application gets an OSA file as a command line argument. The client application reads this OSA file, and prepares the URL which points to the server application where the specific application has been deployed. With the help of this URL, the client application gets an instance of the 'IApplicationProvider' interface and gets a specific application assembly by calling the underlying functions. The client application loads the specific application assembly into its current application domain, and invokes an 'EntryPoint' function which starts execution of the specific application assembly within the memory of the client application.

In the demo, the client application also has the capacity to pass command line arguments to the application assembly while invoking the 'EntryPoint' function.

Assembly asmApp = AppDomain.CurrentDomain.Load(btApp);

if (asmApp.EntryPoint.GetParameters().Length > 0)
{
    asmApp.EntryPoint.Invoke(null, BindingFlags.InvokeMethod, null, 
           new object[] { this._fdOSA.Param.Split(' ') }, null);
}
else
{
    asmApp.EntryPoint.Invoke(null, BindingFlags.InvokeMethod, null, new object[] { }, null);
}

Each dependency required by the application assembly calls the 'AssemblyResolve' event of the current application domain. This event requests the server application for the dependent assembly, loads it into the current application domain, and returns the handle to the application assembly.

private Assembly AssemblyResolve(object sender, ResolveEventArgs args)
{
    if (this._fdOSA == null)
    {
        return null;
    }

    string[] sData = args.Name.Split(new char[] { ',' }, 
                     StringSplitOptions.RemoveEmptyEntries);
    byte[] btData = this._appProvider.GetDependency(this._fdOSA.AppName, 
                                                    sData[0].Trim());
    Assembly asmReturn = AppDomain.CurrentDomain.Load(btData);
    return asmReturn;
}

The instance of the client application is up and running till the user closes an application or an application assembly is terminated.

Application Configuration File - OSA

Now, there must be question(s) in your mind; why is the configuration file (.OSA) used and registered on the user's system? Below are the advantages of doing this:

  • The user should not know about which executable is going to run the application. The user only needs to double-click on the configuration file (.OSA), like how we do for a text file, Word document, etc.
  • When there is a change in the application, configuration, or when moving applications to another server, we need to update only the configuration files (.OSA) and give it to the user.
  • One more step forward, instead of giving configuration files (.OSA) physically to the users, we can have a web-page which has a link to the configuration files (.OSA). We can ask the user to browse this web-page and to click on the link of the configuration file (.OSA) to start the application. By doing this, the configuration file also exists on the server, so we can modify it without the user getting disturbed. We can also allow users to access multiple applications from a single web-page.

In short, we do not have dependencies during re-configuration or re-deployment of application assemblies. Whenever the user is starting an application by using a configuration file, the user is working on the latest version of the application. And, all these magic occurs virtually, so the code is secure too.

Scope

This is the simplest (like skeleton) demo of .NET thick client applications deployment on a remote server. It requires lots of enhancements and improvements to meet corporate needs, like:

  • .NET Remoting Security, so a desired set of users and user groups can access a server application.
  • Proper error handing on both client and server applications.

Declarations

This demo only concentrates on .NET thick client applications. .NET thin client applications like ASP.NET cannot be deployed using this method of application deployment.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Indrajeet Valera
Team Leader
India India
No Biography provided

Comments and Discussions

 
QuestionDoes anyone has this code? PinmemberMember 102855413-Jan-14 5:55 
GeneralPffft... PinmemberPIEBALDconsult21-Mar-08 4:38 
GeneralRe: Pffft... PinmemberMaxGuernsey21-Mar-08 7:37 
QuestionClickOnce ? PinmemberBillWoodruff11-Mar-08 2:30 
AnswerRe: ClickOnce ? PinmemberIndrajeet Valera21-Mar-08 1:24 
GeneralNice PinmemberMaxGuernsey10-Mar-08 15:18 
GeneralRe: Nice PinmemberIndrajeet Valera21-Mar-08 1:26 
GeneralRe: Nice PinmemberMaxGuernsey21-Mar-08 7:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 21 Mar 2008
Article Copyright 2008 by Indrajeet Valera
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid