Backup Tool Project. Part 1: WCF Configuration

Who needs one more backup tool? There are dozens of them on the market – almost every self-respecting IT company already created a proprietary backup solution, and a lot of them are free. Of course, they have different functionality, and freeware tools are limited in their capabilities, but one still need to have a strong reason to once again reinvent the wheel.

However, creating a backup tool may be so exciting that you may want to share your experience with others. In this article I don’t describe the whole implementation process and don’t provide all code. My goal is to overview the fundamental solution and demonstrate the most interesting aspects of the project.

 ▎System Components. Modules Interaction.

The task was rather simple - to create a backup tool with ability to store data on the server on demand and on schedule, with support of encryption and compression, using .NET 4.0 as a technology. As a user interface, we decided to use Windows Forms. Since copying data should be executed on schedule, the application is located on Windows Service in order to be able to work permanently.

The tool consists of the following modules:

  1. Windows Service – the service permanently running in the background. It executes jobs on schedule and performs compression, encryption and transition of data to server.
  2. Client application – a Windows Forms application. The application creates/edits backup jobs, runs jobs and monitors their state.
  3. Server API – a mean of communication with data storage.

Windows Service (further referred to as the service) and Windows Forms (further referred to as the application) should interact with each other in the following ways:

Managing jobs settings. All work with jobs (create, edit, delete, start on schedule) is performed by the service. The service provides an interface for managing jobs. The application communicates with the service by means of interface, monitors the current state of all settings and executes them (if necessary). This approach ensures mobility in case the service and the application reside on different computers.

Configuration is based on an XML file. And the class of job settings should be extensible and support both saving settings as XML and in other storage locations. To implement the extensibility, a configuration module may be implemented as a MEF plug-in.

Commands to the service. The application should be able to manage the service via the following commands:

  • Run job
  • Suspend job
  • Resume job
  • Cancel job
  • Display job status in the log

Communication of the service and the applications is implemented through named pipes using WCF duplex mode: a bidirectional host is deployed on the service and the application connects to the service and performs the interaction.

 ▎Duplex WCF. Configuration on the service side.

Implementing duplex WCF is not as easy as we’d like it to be. The reason is that WCF is implemented as a general framework for different protocols, and the main of them is HTTP which is inherently one-way and doesn’t support working in both directions. In order to implement duplex WCF for HTTP and make conversion when changing protocols unnecessary, as well as for a number of other reasons, Microsoft implemented some additional settings. Being a non-trivial, the implementation requires a special expertise and profound knowledge of WCF.

Using named pipes is a special case that takes place when the service and the application reside on the same computer, i.e. on the workstation.

In the general case, the service may be located on a separate Windows server, and the service in this case is managed remotely. Using WCF for communication is a perfect solution – it’s enough to reconfigure App.config file in the application and the service, and we get a duplex HTTP channel!

Implementation of Duplex WCF has some distinctive features.

  1. Support of sessions is required.
  2. Client should keep connection with the service during its whole lifetime.

Point 2 is essential. There are a number of tasks which require calling the client from the server:

  • Displaying the current status of the job (start, finish, progress).
  • Getting information about system events.

WCF service in duplex mode cannot connect to the client and call the appropriate method just when needed. Therefore, a possible scenario is as follows:

  1. The application connects to the service (for instance, by calling the WCF service method Register). The call is "one-way", so the client does not need to wait for a response from the service.
  2. WCF service does not finish its work immediately in method Register, instead, it does the following:
  • Registers its copy of WCF-client in a pool of client connections (ClientConnectionPool).
  • Waits for commands from the Windows service in standby mode.

On certain events, Windows service, with the help of ClientConnectionManager, queries all clients contained in ClientConnectionPool and sends commands to provide specific messages. Upon receipt of a command from the Windows Service, the WCF service sends a message to the client and then goes into standby mode. The standby mode is being implemented by the kernel object Event. Method Register terminates on finishing either the client application or the service.

[ServiceContract(SessionMode = SessionMode.Required,
    CallbackContract = typeof(IClientCommunicationServiceCallback))]
public interface IClientCommunicationService
{
    /// 
    /// Register client to service to keep callback events
    /// 
    /// Client unique identifier. Differs for each connection
    [OperationContract(IsOneWay = true)]
    void Register(Guid clientId);

}

/// 
/// WCF service to organize calback operations to client
/// 
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class ClientCommunicationService: IClientCommunicationService, IDisposable
{
/// 
/// Register client to service to keep callback events
/// 
/// Client unique identifier. Differs for each connection
public void Register(Guid clientId)
{
        Contract.Requires(clientId != Guid.Empty);

        if (isDisposed)
        {
           throw new ObjectDisposedException(this.GetType().ToString());
        }

    this.clientId = clientId;
    // Add current service to ClientConnectionManager
    ClientConnectionManager.Current.AddService(this);

    bool exitNeeded = false;
    serviceCompletedEvent.Reset();

    try
    {
        while (!exitNeeded)
        {
            int eventId = WaitHandle.WaitAny(events);
            lock(syncObj)
            {
                switch (eventId)
                {
                    case 0:
                        // Remove current service from ClientConnectionManager
                        ClientConnectionManager.Current.RemoveService(this.clientId);
                        exitNeeded = true;
                        break;
                    case 1:
                        {
                            // Send message to client
                            // Process all messages from queue
                            while (jobInfoQueue.Count > 0)
                            {
                                object job = jobInfoQueue.Dequeue();
                                ...
                            }
                            updateJobStateEvent.Reset();
                        }
                        break;
                }
            }
        }

    }
    finally
    {
        serviceCompletedEvent.Set();
    }
}
}

Let’s consider the method Register in detail. The string Contract.Requires(clientId != Guid.Empty)uses code contracts for input validation. Checking isDisposed flag is a part of IDisposable pattern and is used to avoid calls to the object with method Dispose () already called (see paragraph 4 "Release services resources"). Each method of the WCF service should carry out such checks. In this article, for simplicity’s sake, such checks will be omitted.

The base class that ensures client and server interaction is singleton ClientConnectionManager. This class contains two main public methods for managing connection of the server to the client: AddService and RemoveService. Method AddService takes as a parameter a reference to an instance ClientCommunicationService and place it in the list ClientConnectionPool:

/// 
/// Add service into service client pool
/// 
public void AddService(ClientCommunicationService clientCommunicationService)
{
   lock(syncObject)
   {
       clientConnectionPool.Add(clientCommunicationService);
   }
}

/// 
/// Remove service by specified client Id. It is necessary when client closes connection to server.
/// 
/// Client Id
public void RemoveService(Guid clientId)
{
    lock(syncObject)
    {
        ClientCommunicationService service =
            clientConnectionPool.Where(cp => cp.ClientId == clientId).SingleOrDefault();
        if (service != null)
        {
            service.StopService();
            clientConnectionPool.Remove(service);
        }
    }
}

Pay attention to the operator lock (syncObject) - object clientConnectionPool is static and, therefore, is a shared resource. So every call to the object should be isolated from other threads.

In fact, registering duplex WCF is not an easy task. Requirement of sessions support ([ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] for service and [ServiceContract(SessionMode = SessionMode.Required) for interface ) is not a sufficient condition– how should the service initiate sending messages to the client? The service should be informed about interface of the client commands:

/// 
/// Callback interface to communicate with client application
/// 
public interface IClientCommunicationServiceCallback
{
    /// 
    /// Update status for job
    /// 
    /// Job info
    [OperationContract(IsOneWay = true)]
    void UpdateJobState(JobInfo jobInfo);

    /// 
    /// Update status for a list of jobs
    /// 
    /// A list of job infos
    [OperationContract(IsOneWay = true)]
    void UpdateJobsState(List jobInfos);

    /// 
    /// Update date for next run of jobs
    /// 
    /// Contains next run date for jobs
    [OperationContract(IsOneWay = true)]
    void UpdateJobsNextRun(List jobInfos);

    /// 
    /// On client update actions which happen on server.
    /// 
    /// Server action
    [OperationContract(IsOneWay = true)]
    void UpdateServerAction(ServerAction serverAction);
}

IClientCommunicationServiceCallback – this is the interface of the client. Where can we get it? There are a couple of tricks.

IClientCommunicationServiceCallback is a simple interface, and all its methods contain the attribute OperationContract and indicate that it operates one way

(IsOneWay=true), that is, when you call these methods, the service is not waiting for a response from the client:

The interface IClientCommunicationServiceCallback is inside the WCF service. Now we just need to teach the client to respond to the methods declared in the interface. A nice challenge, isn’t it?

To begin with, let’s create a client-side proxy class ClientCommunicationService:

Choose ClientCommunicationService from the list … oops, where is it?

Wait, why are we sure that it should be there? This is not a web application, and Visual Studio will not be able to determine automatically the services declared in the application. So, first we have to compile Windows Service, register it in the system and run via a Services snap.

In order to deploy WCF service inside the Windows Service, we need to:

  • configure WCF settings;
  • activate WCF at the start of the Windows service.

Below is an example of configuring duplex channel by means of protocol NamedPipes:


    
      
        
        
        
        
          
            
          
        
      
    
    
      
        
          
          
          
          
        
      
    
  

Address string "net.pipe://BackupClient/Services/ClientCommunicationService/" is the address of a named pipe. This address should be specified in the window «Add Service Reference» on generating proxy class for the WCF service.

Now let’s activate the WCF service itself:

protected override void OnStart(string[] args)
{
   // Run WCF Hosts
   clientCommunicationServiceHost = new ServiceHost(typeof(ClientCommunicationService));
   clientCommunicationServiceHost.Open();
}

Service inactivation code:

protected override void OnStop()
{
    // Close WCF hosts
    if (clientCommunicationServiceHost.State == CommunicationState.Opened)
    {
       clientCommunicationServiceHost.Close();
    }
}

Well, we’ve configured the service, set activation of WCF on the Windows service startup, compiled, run the Windows service, and now we need to generate a proxy class for the client... let’s try to do this.

Success! Now we can relax and have a cup of coffee. We need only few things to do.

 ▎WCF Configuration on The Client Side.

Let’s consider one more time the goals of interaction between the client and the server:

  1. Managing service inside the client;
  2. Displaying status of the service work in the client.

ClientCommunicationService fulfils the second task. It monitors commands from the WCF service and processes them upon receipt.

First we need to implement the interfaceIClientCommunicationServiceCallback, declared on the side of the WCF service. Methods UpdateJobState(), UpdateJobsNextRun(), UpdateJobsNextRun(), UpdateServerAction() implement server events handlers in client:

public class ClientCommunicationServiceCallback : IClientCommunicationServiceCallback
{
    #region Events

    public event EventHandler OnUpdateJobInfoList;
    public event EventHandler OnUpdateJobInfo;
    public event EventHandler OnUpdateJobNextRun;
    public event EventHandler OnServerAction;

    #endregion

    #region Implementation of IClientCommunicationServiceCallback

    public void UpdateJobState(JobInfo jobInfo)
    {
        if (OnUpdateJobInfo != null)
        {
            OnUpdateJobInfo(this, new JobInfoEventArgs(jobInfo));
        }
    }

    public void UpdateJobsState(List jobInfos)
    {
        if (OnUpdateJobInfoList != null)
        {
            OnUpdateJobInfoList(this, new JobInfoListEventArgs(jobInfos));
        }
    }

    public void UpdateJobsNextRun(List jobInfos)
    {
        if (OnUpdateJobNextRun != null)
        {
            OnUpdateJobNextRun(this, new JobNextRunEventArgs(jobInfos));
        }
    }

    public void UpdateServerAction(ServerAction serverAction)
    {
        if (OnServerAction != null)
        {
            OnServerAction(this, new ServerActionEventArgs(serverAction));
        }
    }

    #endregion

In fact, it is not obligatory to declare events here, you can write handlers directly in this class. The reason why I added the events is to divide communications from the UI.

Now let's see how connection is established between the client and the service:

Guid clientId = Guid.NewGuid();

// Create callback class and add event handlers on it
ClientCommunicationServiceCallback serviceCallback = new ClientCommunicationServiceCallback();
serviceCallback.OnUpdateJobInfoList += ServiceCallbackOnUpdateJobInfoList;
serviceCallback.OnUpdateJobInfo += serviceCallback_OnUpdateJobInfo;
serviceCallback.OnUpdateJobNextRun += serviceCallback_OnUpdateJobsNextRun;
serviceCallback.OnServerAction += serviceCallback_OnServerAction;
InstanceContext instanceContext = new InstanceContext(serviceCallback);
clientCommunicationService = new ClientCommunicationServiceClient(instanceContext);

// Register on server
clientCommunicationService.Register(clientId);

Variable clientId is a unique identifier for connection of the client to the server and can be used in communication of the client and the server. Class InstanceContext is used to create a context for class ClientCommunicationServiceCallback.

 ▎Client Termination.

On client termination the connection session on the service should be terminated. We remember that the WCF service keeps session throughout the entire duration of the client work.

There are two possible scenarios of termination - normal and emergency one. In the latter case, WCF detects disappearance of the client after a while, and on normal termination the client has to disconnect from the service on its own. Otherwise, firstly, resources are not freed immediately, and secondly, errors will occur on getting feedback from the client. And why do we need those errors if they can be avoided?

For normal termination of connection with the service, let’s call method Unregister:

try
{
    JobServiceController.Current.JobService.Unregister(clientId);
}
catch(CommunicationException)
{
}

We’ll consider the way it works in the article "Release services resources" which will be published soon.