Click here to Skip to main content
13,800,523 members
Click here to Skip to main content
Add your own
alternative version


58 bookmarked
Posted 22 Mar 2010
Licenced CPOL

The platform-independent code with Mono: Client-server application sample

, 24 Mar 2010
Rate this:
Please Sign up or sign in to vote.
This article shows how we can develop the platform-independent software with Mono usage


In this article I want to show how we can develop the platform-independent software with Mono usage . I don’t consider what is Mono. If Mono is unfamiliar to you, look through my previous article – "How we can write on C# in Linux: Implementing PanelApplet to Gnome Desktop". The first three parts describe the developed software. The last part describes how we can deploy this software in different operating system and I think it is the most important part of the article.

1. Requirements

There is a necessity to develop client-server application to monitore the list of processes on remote workstations. Both server and client software need to work on Linux and Windows operating systems. The used database can be Microsoft Sql Server or MySql.

2. Design

I describe here some design steps.

2.1 Architecture


There is one server which is running under Windows or Linux operation system. This server provides web service to clients that are running under Windows or Linux operating systems too. This service receives data from clients and put this data in a database. The database can be Microsoft Sql Server or MySql.

The server provides the ability to view received information using web-browser. This can be done with the help of using the IIS or Apache web servers.

2.2 Database schema

We can use free tools like Visio under Linux to develop schemas and diagrams. One of these tools is Dia. The process of the database schema development is shown at the picture:


There are two tables: Computers and Processes. The Computers table will store the data representing information about computers. This information includes computer name, IP address (to get it we’ll use .NET Framework built-in classes), user name and time of last activity. The Processes table will store the list of processes that includes the name of the process and process`s identifier (PID).

3. Implementation

Our solution will consist from following projects:

  • InfoCenter.Agent – client part of developed application
  • InfoCenter.Logic – business logic layer that is called from web-service
  • InfoCenter.Logic.Tests – tests for business logic layer
  • InfoCenter.Persistence – persistence layer. This layer interacts with database
  • Infocenter.Persistence.Tests – tests for persistence layer
  • InfoCenter.WebConsole – presentation layer. This is ASP.NET WebForms site.

I use MonoDevelop as IDE. When we'll develop server business logic we'll use Test-Driven Development methodology. We can use Synaptic Package Manager to install it under Ubuntu OS:


3.1 TDD methodology and NUnit tool

Test-driven development is a software development technique that relies on the repetition of a very short development cycle: first the developer writes a failing automated test case that defines a desired improvement or new function, then produces code to pass that test and finally refactors the new code to acceptable standards. Besides TDD allows us to confidently make new changes in old code: we can simply verify code by one click. Tools like NUnit help us to do this. NUnit is an open source unit testing framework for Microsoft .NET. It serves the same purpose as JUnit does in the Java world, and is one of many in the xUnit family.

We need to add the 'nunit.framework.dll' assembly to use this tool.

3.2 Server

3.2.1 Persistence layer

The server must save data to a database. If we want to support different databases without writing different Sql-scripts we should look at the object-relational mapping tools such as NHibernate.

Object-relation mapping and NHibernate

Object-relational mapping is a programming technique for converting data between incompatible type systems in relational databases and object-oriented programming languages. NHibernate is ORM tool that provides all the features required to quickly build an advanced persistence layer in code. It’s capable of loading and saving entire graphs of interconnected objects while maintaining the relationships between them. To use this tool we need to add Nhibernate.dll assembly and provide some additional meta-information about our database schema.


NHibernate mapping file for table Computers:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true">
  <class name="InfoCenter.Persistence.Entities.Computer, InfoCenter.Persistence" table="Computers" lazy="false">
    <id name="ID" column="ID">
      <generator class="native" />
    property name="Name" column="Name"/>
    <property name="Ip" column="Ip"/>
    <property name="UserName" column="UserName"/>
    <property name="OsVersion" column="OsVersion"/>
    <property name="RecentActivity" column="RecentActivity"/>

The Computer entity is below:

public class Computer
        private int _id;
        public int ID 
        private string _name;
	public string Name 

        private string _ip;
	public string Ip 
        private string _userName ;
	public string UserName 
        private string _osVersion;
	public string OsVersion 
        private DateTime _recentActivity;
	public DateTime RecentActivity 

The simpliest create, read, update, delete (CRUD) and search operations will be executed with the usage of Repository class:

public class Repository<T> : IRepository<T>
        private ISessionFactory _sessionFactory;
        private ISession Session
                return _sessionFactory.OpenSession();

        public Repository(ISessionFactory sessionFactory)
            _sessionFactory = sessionFactory;
        public void Add(T entity)
            using (ISession session = _sessionFactory.OpenSession())
            using (session.BeginTransaction())

        public void Update(T entity)
            using (ISession session = _sessionFactory.OpenSession())
            using (session.BeginTransaction())

        public void Remove(T entity)
            using (ISession session = _sessionFactory.OpenSession())
            using (session.BeginTransaction())

In Repository class we use Nhiberbate's inner classes to get the necessary data. Unfortunately we cannot use LinqToNhibernate features with Mono, because one of the necessary libraries is not implemented.

The Repository class is derived from IRepository interface:

public interface IRepository<T>
        void Add(T entity);
        void Update(T entity);

        void Remove(T entity);
        void Remove(DetachedCriteria criteria);

        long Count(DetachedCriteria criteria);

        bool Exists(DetachedCriteria criteria);
        //Collection by criteria
        ICollection<T> FindAll(DetachedCriteria criteria);
        ICollection<T> FindAll(DetachedCriteria criteria, params Order[] orders);
        ICollection<T> FindAll(DetachedCriteria criteria, int firstResult, int numberOfResults, params Order[] orders);

        //Single by criteria
        T FindFirst(DetachedCriteria criteria, Order order);
        T FindFirst(DetachedCriteria criteria);
        T FindOne(DetachedCriteria criteria);
Test cases

In the beginning we should init a test data:

public void Test_Construction()

    repository = new Repository<Computer>(SessionFactory);
Assert.IsNotNull(repository, "Cannot get repository");
    computer1 = new Computer();

    computer1.Name = "Computer1";
    computer1.Ip = "";
    computer1.OsVersion = "Os1";
    computer1.UserName = "User1";
computer1.RecentActivity = DateTime.Now;

Testing additing and retrieving data:

    public void CanAdd()

    public void CanGet()

DetachedCriteria criteria = DetachedCriteria.For<Computer>()
    Computer comp = repository.FindOne(criteria);

        Assert.NotNull(comp, "Query doesn't work");
        Assert.AreEqual(computer1.Name, comp.Name, "Name mismatch");
        Assert.AreEqual(computer1.Ip, comp.Ip, "Ip mismatch");

NUnit uses the attributes Test and TestFixtureSetUp. The results of run tests is shown below.


This is the main window of NUnit program under Windows.

3.2.2 Bussiness layer

This layer must get data from client, prepare it and call persistence layer methods. The main logic is located in PacketParser class. This class provides two methods:

  • AddInfo(). This method decompress input string from Agent. Then resulting data are deserialized into Agent.Entities.Computer object. Then this object is converted to Computer object and information about this computer is updated in database.
    public void AddInfo(string compressedInfo)
                string decompressed = Archiver.Decompress(compressedInfo);
                Agent.Entities.Computer info = (Agent.Entities.Computer) ObjectSerializer.XmlStrToObj<Agent.Entities.Computer>(decompressed);
    	    DetachedCriteria criteria = DetachedCriteria.For<Computer>()
    	    Computer oldEntry = computers.FindOne(criteria);
    	    if(oldEntry != null)
    		oldEntry.Name = info.Name;
    		oldEntry.Ip = info.Ip;
    		oldEntry.UserName = info.UserName;
    		oldEntry.OsVersion = info.OsVersion;
    		oldEntry.RecentActivity = DateTime.Now;
    		 Computer computer =  new Computer();
    		 computer.Name = info.Name;
    		 computer.Ip = info.Ip;
    		 computer.UserName = info.UserName;
    		 computer.OsVersion = info.OsVersion;
    		 computer.RecentActivity = DateTime.Now;		
  • AddProcesses(). This method adds processes from a computer into database.

Now take a look to test:

public void CanAddInfoFromAgent()
			DetachedCriteria criteria = DetachedCriteria.For<Computer>()
            //call expected method
            PacketParser pp = new PacketParser(computers, null);

	    Computer computer = computers.FindOne(criteria);

            Assert.IsNotNull(computer, "Can't get computer");
            Assert.AreEqual("ILYA", computer.Name, "Computer name is invalid");
            Assert.AreEqual("aidan", computer.UserName, "User name is invalid");
            Assert.AreEqual("Microsoft Windows NT 5.1.2600 Service Pack 2", computer.OsVersion, "OS name is invalid");
            Assert.AreEqual("", computer.Ip, "IP is invalid");

This test checks the AddInfo method. A GetInfo method returns Fake data. The result of all-run tests is shown below:


This picture shows the NUnit module in MonoDevelop 2.2 IDE under Ubuntu 9.10. I think it is very useful tool.

3.2.3 Presentation layer

We'll display data with the usage of ASP.NET WebForms features. Also we can use ASP.NET MVC but I think WebForms fits better for this example (Maybe I just want to simplify my job?). Implementation of our WebService is trivial:

    internal class InfoCenterService
    private PacketParser pp;

    IRepository<Computer> computers;
           IRepository<Process> processes;

    protected ISessionFactory sessionFactory;

    public InfoCenterService()
        sessionFactory = Initializer.GetSessionFactory(false);
                computers = new Repository<Computer>(sessionFactory);
                processes = new Repository<Process>(sessionFactory);
                 pp = new PacketParser(computers, processes);

 [WebMethod(Description = "This method insert information about computer")]
    public void AddInfo(string compressedXml)

    [WebMethod(Description="This method insert processes from a computer")]
    public void AddProcesses(string compName, string compressedXml)
    pp.AddProcesses(compName, compressedXml);

We just call the methods of bussines layer objects. The standard ASP.NET WebForms controls are used to show information from database:

<form id="form1" runat="server">
    <h2>Select a computer from the list below to view the processes</h2>
    <asp:DataList runat="server" id="computersList"
        Style="background-color: #d5d5ee;">
            <tr style="background:#AAA; color:White;">
                    <asp:LinkButton runat="server"
                        CommandArgument='<%#Eval("Name")%>' >
    <div><asp:Label runat="server" id="lblCurrentComp"></asp:Label></div>
    <asp:GridView runat="server" id="processesGrid" AutoGenerateColumns="false">
            <asp:BoundField DataField="Name" HeaderText="Name" />
            <asp:BoundField DataField="Pid" HeaderText="Pid" />

We use DataList and GridView controls to show table data.The code-behind logic is also trivial:

public partial class Default : System.Web.UI.Page
		IRepository<Computer> computers;
		IRepository<Process> processes;
		protected override void OnLoad (EventArgs e)
			ISessionFactory sessionFactory = Initializer.GetSessionFactory(false);
            computers = new Repository<Computer>(sessionFactory);
            processes = new Repository<Process>(sessionFactory);
			DetachedCriteria criteria = DetachedCriteria.For<Computer>()
			IList<Computer> list = new List<Computer>();
			foreach(Computer item in computers.FindAll(criteria))
			computersList.DataSource = list;
			base.OnLoad (e);
		protected void ComputersList_ItemCommand(object source, DataListCommandEventArgs e)
			string compName = e.CommandArgument.ToString();
			lblCurrentComp.Text = String.Format(@"<b>Processes on computer '{0}':</b>",compName);
			DetachedCriteria criteria = DetachedCriteria.For<Process>().CreateAlias("Host","c")
			IList<Process> list = new List<Process>();
			foreach(Process item in processes.FindAll(criteria))

			processesGrid.DataSource = list;

3.3 Client

3.3.1 Service or daemon

In windows operation system a computer program that runs in background is called service. Such program in Linux is called daemon. The special class exists in .NET Framework to realize the background program:

public class AgentService : System.ServiceProcess.ServiceBase
    private Container components;
    private Thread  listenerThread;
	private Listener listener;

    public AgentService()
      Logger.Debug("Service ctor");
		listener = new Listener();
      } catch (Exception e)
      Logger.Debug("Service ctor complete");

    // The main entry point for the process of service
    static void Main()
      ServiceBase[] services;
      Logger.Debug("Service entry point");
      services = new ServiceBase[] { new AgentService() };
    private void InitializeComponent()
      components = new System.ComponentModel.Container();
      this.ServiceName = "InfoCenter agent";
    protected override void OnStart(string[] args)
      Logger.Debug("Service OnStart");
      //listener.Run = true;
      listenerThread = new Thread(new ThreadStart(listener.Start));

    protected override void OnStop()
      Logger.Debug("Service OnStop");
      listener = null;
    protected override void OnContinue()
      Logger.Debug("Service OnContinue");;
    protected override void OnPause()
      Logger.Debug("Service OnPause");
    protected override void OnShutdown()
      Logger.Debug("Service OnShutdown");

This class provides us the ability to handle events such as Start, Stop, Pause Continue or Shutdown.There are some differences between service in Windows and daemon in Linux. We should register service in Windows system. To do this we can add Install class to our Agent:

    public partial class AgentInstaller : Installer
        private System.ServiceProcess.ServiceInstaller servInstaller;
        private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller;

        public AgentInstaller()

            this.servInstaller = new System.ServiceProcess.ServiceInstaller();
            this.serviceProcessInstaller = new System.ServiceProcess.ServiceProcessInstaller();
            // serviceInstaller1
            this.servInstaller.DisplayName = "InfoCenter Agent";
            this.servInstaller.ServiceName = "InfoAgent";
            this.servInstaller.StartType = System.ServiceProcess.ServiceStartMode.Automatic;
            this.servInstaller.Description = "Client part of InfoCenter";
            // serviceProcessInstaller1
            this.serviceProcessInstaller.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
            this.serviceProcessInstaller.Password = null;
            this.serviceProcessInstaller.Username = null;
            // ProjectInstaller
            this.Installers.AddRange(new System.Configuration.Install.Installer[] {



Later this class will be used by InstallUtil utillity.

3.3.2 Get system information

To get some system information we can use standard classes:

public static Computer Create()
    Computer computer = new Computer();

    computer.Name = Environment.MachineName;

    //get ip
    foreach (IPAddress ip in Dns.GetHostAddresses(computer.Name))
        if (((ip != IPAddress.Any) || (ip != IPAddress.Loopback)) &&
            (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork))
            computer.Ip = ip.ToString();

    computer.UserName = Environment.UserName;
    computer.OsVersion = Environment.OSVersion.ToString();

    return computer;

To get the list of processes we can use System.Diagnostics.Process.GetProcesses() method:

        public static IList<process> Processes()
            IList<Process> processes = new List<Process>();
            foreach (System.Diagnostics.Process process in System.Diagnostics.Process.GetProcesses())
                Process p = new Process();
                p.Pid = process.Id;
                p.Name = process.ProcessName;

            return processes;


3.3.3 Compress data

A list of processes can be long and it`s better for us to compress it. The following method compresses string representing the process list data using GZipStream class:

public static string Compress(string text)
    byte[] buffer = Encoding.UTF8.GetBytes(text);
    MemoryStream ms = new MemoryStream();
    using (GZipStream zip = new GZipStream(ms, CompressionMode.Compress, true))
        zip.Write(buffer, 0, buffer.Length);

    ms.Position = 0;

    byte[] compressed = new byte[ms.Length];
    ms.Read(compressed, 0, compressed.Length);

    byte[] gzBuffer = new byte[compressed.Length + 4];
    Buffer.BlockCopy(compressed, 0, gzBuffer, 4, compressed.Length);
    Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gzBuffer, 0, 4);

    return Convert.ToBase64String(gzBuffer);

3.3.4 Call web service

Web-service provides WSDL file and we can use wsdl tool from Mono to generate the necessary class:

wsdl InfoCenterService.wsdl

3.3.5 Logging

Also our service will log some information to file using the following class:

     static class Logger
    private static  string FullPath =System.AppDomain.CurrentDomain.BaseDirectory+"log.txt";

     public static void Debug (string str)
          DebugFormat (str);
     public static void DebugFormat (string str, params object [] args)
           using(StreamWriter writer = File.AppendText(FullPath))
               writer.WriteLine(DateTime.Now.ToLongTimeString()+": "+ str, args);
     public static void Fatal (string str)
           DebugFormat (str);

4 Deployment

In the beginning we should install Mono runtime on Linux and .NET Framework 2 (or higher) on Windows. The developed software works under Linux and Windows without necessity to build different versions for different operating systems. This is possible because assemblies that are built with Mono compiler are compatible with the implementation of Common Language Runtime from Microsoft. By the way the implementation of Mono exists for Windows.

To install Mono under Ubuntu we can use 'Synaptic Package Manager' which helps to download and install the software quickly from repositories. Also we can use terminal commands:

sudo apt-get install mono

The third way also exists – you just need to download source code, compile and install it . I used the third way. Note that Ubuntu ships with Mono pre-installed, but the version is often outdated by the time the Ubuntu release ships.

4.1 Database

With using NHibernate ORM tool we cannot worry about differences between various databases. All that we need is an appropriate configuration file for used database. This file is edited manually and there is no necessity to rebuild the application. I was able to test my code on two databases and below there are examples of the NHibernate configuration files.

4.1.1 Ms Sql Server config

<?xml version="1.0" encoding="utf-8" ?> 
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2"> 
    <property name="connection.provider"> 
    <property name="connection.driver_class"> 
    <property name="connection.connection_string"> 
      Server=;database=InfoCenter;user id=sa;password=1;persist security info=False; 
    <property name="dialect"> 
    <property name="show_sql"> 
    <property name="proxyfactory.factory_class"> 

There is a screenshot of Microsoft Sql Server Management Studio tool:

\ MsSqlServer.PNG

4.1.2 MySql config

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
    <property name="connection.provider">
    <property name="connection.driver_class">
	<property name="connection.connection_string">
			Database=InfoCenter;Server=localhost;User Id=root;Password=1234qwer!
	<property name="dialect">
	<property name="hbm2ddl.keywords">
    <property name="show_sql">
    <property name="proxyfactory.factory_class">

There is a screenshot of MySql Administrator tool:


Note: I need to put in the output directory some additional assemblies that are necessary for NHibernate.

4.2 Client

4.2.1 Windows

First, to run Agent under Windows we need to install it as Windows Service in operating system. To do this, we run the 'InstallUtil' utillity. This utillity is a part of Microsoft .NET Framework and you can find it in the framework directory. The output of this utility is here:


Then we can start our service with usage of Service Control Manager:


4.2.1 Linux

To run Agent under Linux we should use mono-service2 util:

mono-service2 Agent.exe

This util also provides some additional capabilities to service program:


4.3 Server

4.2.1 Run ASP.NET site on Windows and IIS

I think many developers are familiar with the deployment of ASP.NET application under Windows and Internet Information Service. The following picture shows our ASP.NET site on IIS 5.1:


There are results of our program work:

InfoCenterWin1.PNG InfoCenterWin2.PNG

4.2.2 Run ASP.NET site on Linux and Apache

First, we need to install web server software – Apache. For this run the terminal and type:

sudo apt-get install apache2

After installation we should deploy our ASP.NET web site to installed web server. I found the usefull information about this step here

Consider briefly:

  • Install ModMono with support for ASP.NET 2.0
    sudo apt-get install libapache2-mod-mono mono-apache-server2
  • When the installation is complete, restart Apache and activate the ModMono module by executing the following commands:
    sudo /etc/init.d/apache2 restart
    sudo a2enmod mod_mono
  • Deploy our site. First we need to create a configuration file for our site inside the directory "etc/apache2/sites-available/". To do this, execute the following command:
    gksu nautilus /etc/apache2/sites-available/

    This will open the directory in Nautilus:


Now right-click inside the window and create a new empty file and name it as "InfoCenter". Then open the file using a text editor and paste the following text inside it, save and close.

Alias /InfoCenter "/home/aidan/Projects/InfoCenter//InfoCenter.WebConsole" 
AddMonoApplications default "/InfoCenter:/home/aidan/Projects/InfoCenter//InfoCenter.WebConsole" 
<Location /InfoCenter> 
SetHandler mono 

Then run the following command in the terminal:

a2ensite InfoCenter
sudo /etc/init.d/apache2 restart

Now the InfoCenter site is available at this link: http://localhost/InfoCenter/Default.aspx. Lets have a look at our remote web-service page:



The developed software is a simple example of enterprise application for Intranet. Well, there can be mistakes and you may have some difficulties with its correct installing. However, I was able to run and test this application in my network.


  • 24th March 2010 - First version


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


About the Author

Ilya Builuk
Software Developer (Senior) Nokia
Germany Germany
Interested in design/development of framework functionality using the best patterns and practices.

You may also be interested in...


Comments and Discussions

Generallack of sql script Pin
Member 166982012-Apr-11 0:53
memberMember 166982012-Apr-11 0:53 
GeneralRe: lack of sql script Pin
Ilya Builuk17-Apr-11 1:04
memberIlya Builuk17-Apr-11 1:04 
GeneralHey, Ilya - great job. Pin
krogerma2-Jan-11 14:51
memberkrogerma2-Jan-11 14:51 
GeneralRe french Char set Pin
Ajay Kale New31-Aug-10 4:08
memberAjay Kale New31-Aug-10 4:08 
GeneralRe: Re french Char set Pin
Ilya Builuk2-Sep-10 10:56
memberIlya Builuk2-Sep-10 10:56 
GeneralSettimeout problem javascript Pin
Ajay Kale New31-Aug-10 0:22
memberAjay Kale New31-Aug-10 0:22 
GeneralRe: Settimeout problem javascript Pin
Ilya Builuk2-Sep-10 11:02
memberIlya Builuk2-Sep-10 11:02 
GeneralMy vote of 2 Pin
Graham Downs29-Mar-10 23:54
memberGraham Downs29-Mar-10 23:54 
GeneralGreat Job! Pin
klanglieferant29-Mar-10 21:12
memberklanglieferant29-Mar-10 21:12 
GeneralExcellent Pin
Sandeep Mewara25-Mar-10 10:11
mentorSandeep Mewara25-Mar-10 10:11 
5/5 Thumbs Up | :thumbsup:
Nicely explained...
GeneralWell done, thanks Pin
spoodygoon24-Mar-10 16:12
memberspoodygoon24-Mar-10 16:12 
GeneralExcellent! Pin
Cesar to Dnn24-Mar-10 4:08
professionalCesar to Dnn24-Mar-10 4:08 
GeneralRe: Excellent! Pin
Ilya Builuk24-Mar-10 4:23
memberIlya Builuk24-Mar-10 4:23 

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

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

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04 | 2.8.181215.1 | Last Updated 24 Mar 2010
Article Copyright 2010 by Ilya Builuk
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid