Linked .Net Users Group

Installing client applications on Terminal Server has become more and more common for a few years now.

It holds many benefits, including:
  • Cutting down hardware costs (less hardware needed)
  • Only on installation is needed for numerous clients (the number of clients that can run on a terminal server is subject to the client's resources consumption)
  • Better application maintenance
  • and many more...
When installing client applications on a terminal server we need to take the following considerations into account:

App.Config

The app.config file is now shared by many instances of the same program. If the file holds instance or user dedicated information, for example, styles selected by the user or special program behavior, you will have to move the data to a database, or to the user's folder as an xml or other config file.

Saving files locally

In saving files locally you need to take into consideration that all of the program's instances run from the same exe file located in the same path. Because of this you will have to save the files to the user specific folder at the user's profile at "Documents and Settings" folder or to any other specific folder defined at run time.

Logging

Out of the box (using log4net), all instances of the program will wirte to the same log which may be an undesirable outcome.

If your logging is done directly to a table at a database, you may want to add a field indicating under which user the application is currently running and you will be able to filter the table rows by that user easily.

You are in a harder situation if you log directly to a file in the file system. The solution to this may be to create the log file under the user's profile at the "Documents and Settings" folder.

In our applications we are using log4net which also gives us the ability to change to log file name at runtime.

This can be done by the following example:

//This must be defined before the LogManager.GetLogger of log4net
log4net.GlobalContext.Properties["LogName"] = MyUserName;

Place the property in the app.config by setting the RollingFileAppender’s File like shown below:
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender"
<
File type="log4net.Util.PatternString" value="Logs/%property{LogName}/log.log"/> 
<
param name="AppendToFile" value="true" /> 

WCF

When using WCF for cases you wish the client to serve also as a host for incoming TCP calls from the server, you will have to set the port dynamically (this can be done by leaving out the host's port number in the app.config file).

In this case, the server does not have any knowledge of the port it needs to send the message to beacuse the client's host port was set dynamically.

I can think of a least two solutions for this:

First solution

Using WCF duplex communication (the client does not need to set a WCF host). In this solution, the client will call a published WCF method at the server. The method will receive client id, or, IP and port and will keep it in memory along with the callback channel.

When the time comes, the server will send the data back to the client using the callback channel found by the client id.

Sample Code:

The interface (implemented at the server):

[ServiceContract(CallbackContract=typeof(IMyCallback))]
public interface IMyDuplex
{
    [OperationContract(IsOneWay = true)]
    voidGetAsyncData(stringMachineID);
}
The callback interface (implemented at the client):
public interface IMyCallback
{
    [OperationContract(IsOneWay = true)]
    void GetAsyncData(MyObject data);
}

The implementation of IMyDuplex interface at the server that accepts the first call from the client and keeps the callback channel in memory for when it needs to send data to the client:

public static void GetAsyncData(string machineID)
{
    IMyCallback callback = OperationContext.Current.GetCallbackChannel<IMyCallback>();

    if(!string.IsNullOrEmpty(machineID))
    {
        if(!DuplexDictionary.ContainsKey(machineID))
            DuplexDictionary.Add(machineID, callback);
    }
}

Later, when it is time for the server to send the data to the client it fetches the callback from the DuplexDictionary by the client id and sends the data to the client:

DuplexDictionary[machineID].GetAsyncData(data);

Second solution

When Duplex communication is not possible, the client will have to fetch an available port and open a host at that port. It will send the server it's IP and port that was dynamically created.

The server will open a proxy to the client by the IP and port that was sent to it and use it for any messages it may wish to send to the client.


About the author

Guy has extensive development experience from the days of Visual Basic 4 up to C# and .Net 3.5.

He has worked in the past as a developer/team leader and later became a software projects manager.

Current role is software development manager and architect of a number of access and authorization softwares.

Guy's blog: http://blogs.microsoft.co.il/blogs/guy

Rate this article:

5 user(s) on-line: 0 registered and 5 guests

This web site uses Kentico CMS, the content management system for ASP.NET developers.