Server -IV-

galaxyPair

The previous post was about the auxiliaries to provide some basic interfaces, classes and messages. This post is about the 3rd project for our basic server solution. This project will allow to run the simple server and enable to connect a client. The RunServer project should be a WinForm project to have easy to handle starting point.

Program Start Entry Point

By default a entry point Program.cs is created. Here we find the Main() method where the application jumps in during startup. Here we call the mainForm object and register an event to handle exceptions.

using System;
using System.Windows.Forms;

namespace RunServer
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new mainForm());
        }

        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            Exception err = e.ExceptionObject as Exception;

                string msg = (err != null && !String.IsNullOrEmpty(err.Message)) ? err.Message : String.Empty;
                msg += "\r\n\r\nSee log for details";
                MessageBox.Show(msg, "DataServer application error",
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
}

User Interface

The Main Form provides a convenient way to  enter all needed settings and monitor the working server application. To provide a basic service allows connection from clients, we will have two tabs. One tab will be for service host settings and another to monitor all connected clients. This will be presented by the following code for mainForm.Desinger.cs:

namespace RunServer
{
    partial class mainForm
    {
        private System.ComponentModel.IContainer components = null;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.

        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.btnStart = new System.Windows.Forms.Button();
            this.tbSettings = new System.Windows.Forms.TabControl();
            this.tpConnection = new System.Windows.Forms.TabPage();
            this.dgvConnServiceHosts = new System.Windows.Forms.DataGridView();
            this.colCEnabled = new System.Windows.Forms.DataGridViewCheckBoxColumn();
            this.colCName = new System.Windows.Forms.DataGridViewTextBoxColumn();
            this.colCState = new System.Windows.Forms.DataGridViewTextBoxColumn();
            this.colCError = new System.Windows.Forms.DataGridViewTextBoxColumn();
            this.colCSettings = new System.Windows.Forms.DataGridViewButtonColumn();
            this.tpActiveUsers = new System.Windows.Forms.TabPage();
            this.btnDisconnect = new System.Windows.Forms.Button();
            this.dgvUsers = new System.Windows.Forms.DataGridView();
            this.clmSessionID = new System.Windows.Forms.DataGridViewTextBoxColumn();
            this.clmLogon = new System.Windows.Forms.DataGridViewTextBoxColumn();
            this.lblState = new System.Windows.Forms.Label();
            this.btnStop = new System.Windows.Forms.Button();
            this.timer1 = new System.Windows.Forms.Timer(this.components);
            this.lblCacheProgress = new System.Windows.Forms.Label();
            this.tbSettings.SuspendLayout();
            this.tpConnection.SuspendLayout();
            ((System.ComponentModel.ISupportInitialize)(this.dgvConnServiceHosts)).BeginInit();
            this.tpActiveUsers.SuspendLayout();
            ((System.ComponentModel.ISupportInitialize)(this.dgvUsers)).BeginInit();
            this.SuspendLayout();
            //
            // btnStart
            //
            this.btnStart.Location = new System.Drawing.Point(3, 12);
            this.btnStart.Name = "btnStart";
            this.btnStart.Size = new System.Drawing.Size(75, 23);
            this.btnStart.TabIndex = 0;
            this.btnStart.Text = "Start";
            this.btnStart.UseVisualStyleBackColor = true;
            this.btnStart.Click += new System.EventHandler(this.OnStart_Click);
            //
            // tbSettings
            //
            this.tbSettings.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
            | System.Windows.Forms.AnchorStyles.Left)
            | System.Windows.Forms.AnchorStyles.Right)));
            this.tbSettings.Controls.Add(this.tpConnection);
            this.tbSettings.Controls.Add(this.tpActiveUsers);
            this.tbSettings.Location = new System.Drawing.Point(3, 41);
            this.tbSettings.Name = "tbSettings";
            this.tbSettings.SelectedIndex = 0;
            this.tbSettings.Size = new System.Drawing.Size(726, 182);
            this.tbSettings.TabIndex = 1;
            //
            // tpConnection
            //
            this.tpConnection.Controls.Add(this.dgvConnServiceHosts);
            this.tpConnection.Location = new System.Drawing.Point(4, 22);
            this.tpConnection.Name = "tpConnection";
            this.tpConnection.Padding = new System.Windows.Forms.Padding(3);
            this.tpConnection.Size = new System.Drawing.Size(718, 156);
            this.tpConnection.TabIndex = 1;
            this.tpConnection.Text = "Connection Services";
            this.tpConnection.UseVisualStyleBackColor = true;
            //
            // dgvConnServiceHosts
            //
            this.dgvConnServiceHosts.AllowUserToAddRows = false;
            this.dgvConnServiceHosts.AllowUserToDeleteRows = false;
            this.dgvConnServiceHosts.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
            this.dgvConnServiceHosts.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
            this.colCEnabled,
            this.colCName,
            this.colCState,
            this.colCError,
            this.colCSettings});
            this.dgvConnServiceHosts.Dock = System.Windows.Forms.DockStyle.Fill;
            this.dgvConnServiceHosts.Location = new System.Drawing.Point(3, 3);
            this.dgvConnServiceHosts.Name = "dgvConnServiceHosts";
            this.dgvConnServiceHosts.RowHeadersVisible = false;
            this.dgvConnServiceHosts.Size = new System.Drawing.Size(712, 150);
            this.dgvConnServiceHosts.TabIndex = 2;
            this.dgvConnServiceHosts.VirtualMode = true;
            this.dgvConnServiceHosts.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.OnCellContentClick_ConnServices);
            this.dgvConnServiceHosts.CellValidating += new System.Windows.Forms.DataGridViewCellValidatingEventHandler(this.OnCellValidating_Connections);
            this.dgvConnServiceHosts.CellValueNeeded += new System.Windows.Forms.DataGridViewCellValueEventHandler(this.OnCellValueNeeded_ConnServices);
            //
            // colCEnabled
            //
            this.colCEnabled.HeaderText = "Enabled";
            this.colCEnabled.Name = "colCEnabled";
            this.colCEnabled.Width = 50;
            //
            // colCName
            //
            this.colCName.HeaderText = "Name";
            this.colCName.Name = "colCName";
            this.colCName.ReadOnly = true;
            //
            // colCState
            //
            this.colCState.HeaderText = "State";
            this.colCState.Name = "colCState";
            this.colCState.ReadOnly = true;
            //
            // colCError
            //
            this.colCError.HeaderText = "Error";
            this.colCError.Name = "colCError";
            //
            // colCSettings
            //
            this.colCSettings.HeaderText = "Edit Settings";
            this.colCSettings.Name = "colCSettings";
            //
            // tpActiveUsers
            //
            this.tpActiveUsers.Controls.Add(this.btnDisconnect);
            this.tpActiveUsers.Controls.Add(this.dgvUsers);
            this.tpActiveUsers.Location = new System.Drawing.Point(4, 22);
            this.tpActiveUsers.Name = "tpActiveUsers";
            this.tpActiveUsers.Size = new System.Drawing.Size(718, 156);
            this.tpActiveUsers.TabIndex = 2;
            this.tpActiveUsers.Text = "Active Users";
            this.tpActiveUsers.UseVisualStyleBackColor = true;
            //
            // btnDisconnect
            //
            this.btnDisconnect.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
            this.btnDisconnect.Location = new System.Drawing.Point(3, 138);
            this.btnDisconnect.Name = "btnDisconnect";
            this.btnDisconnect.Size = new System.Drawing.Size(75, 23);
            this.btnDisconnect.TabIndex = 3;
            this.btnDisconnect.Text = "Disconnect";
            this.btnDisconnect.UseVisualStyleBackColor = true;
            this.btnDisconnect.Click += new System.EventHandler(this.OnClick_Disconnect);
            //
            // dgvUsers
            //
            this.dgvUsers.AllowUserToAddRows = false;
            this.dgvUsers.AllowUserToDeleteRows = false;
            this.dgvUsers.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
            | System.Windows.Forms.AnchorStyles.Left)
            | System.Windows.Forms.AnchorStyles.Right)));
            this.dgvUsers.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
            this.dgvUsers.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
            this.clmSessionID,
            this.clmLogon});
            this.dgvUsers.Location = new System.Drawing.Point(0, 0);
            this.dgvUsers.Name = "dgvUsers";
            this.dgvUsers.ReadOnly = true;
            this.dgvUsers.RowHeadersVisible = false;
            this.dgvUsers.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
            this.dgvUsers.Size = new System.Drawing.Size(701, 132);
            this.dgvUsers.TabIndex = 0;
            this.dgvUsers.VirtualMode = true;
            this.dgvUsers.CellValueNeeded += new System.Windows.Forms.DataGridViewCellValueEventHandler(this.OnCellValueNeeded_Users);
            //
            // clmSessionID
            //
            this.clmSessionID.HeaderText = "Session";
            this.clmSessionID.MinimumWidth = 300;
            this.clmSessionID.Name = "clmSessionID";
            this.clmSessionID.ReadOnly = true;
            this.clmSessionID.Width = 300;
            //
            // clmLogon
            //
            this.clmLogon.HeaderText = "User Name";
            this.clmLogon.MinimumWidth = 100;
            this.clmLogon.Name = "clmLogon";
            this.clmLogon.ReadOnly = true;
            this.clmLogon.Width = 397;
            //
            // lblState
            //
            this.lblState.AutoSize = true;
            this.lblState.Location = new System.Drawing.Point(174, 17);
            this.lblState.Name = "lblState";
            this.lblState.Size = new System.Drawing.Size(47, 13);
            this.lblState.TabIndex = 2;
            this.lblState.Text = "Stopped";
            //
            // btnStop
            //
            this.btnStop.Enabled = false;
            this.btnStop.Location = new System.Drawing.Point(84, 12);
            this.btnStop.Name = "btnStop";
            this.btnStop.Size = new System.Drawing.Size(75, 23);
            this.btnStop.TabIndex = 0;
            this.btnStop.Text = "Stop";
            this.btnStop.UseVisualStyleBackColor = true;
            this.btnStop.Click += new System.EventHandler(this.OnStop_Click);
            //
            // timer1
            //
            this.timer1.Interval = 5000;
            this.timer1.Tick += new System.EventHandler(this.OnTimerTick);
            //
            // lblCacheProgress
            //
            this.lblCacheProgress.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
            this.lblCacheProgress.AutoSize = true;
            this.lblCacheProgress.Location = new System.Drawing.Point(12, 236);
            this.lblCacheProgress.Name = "lblCacheProgress";
            this.lblCacheProgress.Size = new System.Drawing.Size(0, 13);
            this.lblCacheProgress.TabIndex = 3;
            //
            // mainForm
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(733, 258);
            this.Controls.Add(this.lblCacheProgress);
            this.Controls.Add(this.lblState);
            this.Controls.Add(this.tbSettings);
            this.Controls.Add(this.btnStop);
            this.Controls.Add(this.btnStart);
            this.Name = "mainForm";
            this.Text = "RunServer";
            this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.OnClosing_MainForm);
            this.Load += new System.EventHandler(this.OnLoad_MainForm);
            this.tbSettings.ResumeLayout(false);
            this.tpConnection.ResumeLayout(false);
            ((System.ComponentModel.ISupportInitialize)(this.dgvConnServiceHosts)).EndInit();
            this.tpActiveUsers.ResumeLayout(false);
            ((System.ComponentModel.ISupportInitialize)(this.dgvUsers)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.Button btnStart;
        private System.Windows.Forms.TabControl tbSettings;
        private System.Windows.Forms.TabPage tpConnection;
        private System.Windows.Forms.Label lblState;
        private System.Windows.Forms.Button btnStop;
        private System.Windows.Forms.DataGridView dgvConnServiceHosts;
        private System.Windows.Forms.DataGridViewCheckBoxColumn colCEnabled;
        private System.Windows.Forms.DataGridViewTextBoxColumn colCName;
        private System.Windows.Forms.DataGridViewTextBoxColumn colCState;
        private System.Windows.Forms.DataGridViewTextBoxColumn colCError;
        private System.Windows.Forms.DataGridViewButtonColumn colCSettings;
        private System.Windows.Forms.TabPage tpActiveUsers;
        private System.Windows.Forms.DataGridView dgvUsers;
        private System.Windows.Forms.DataGridViewTextBoxColumn clmSessionID;
        private System.Windows.Forms.DataGridViewTextBoxColumn clmLogon;
        private System.Windows.Forms.Button btnDisconnect;
        private System.Windows.Forms.Timer timer1;
        private System.Windows.Forms.Label lblCacheProgress;
    }
}

So far, the code should be self explaining and obtain no secrets. Next we care about filling the UI with functionality. For this we have the mainForm.cs which is part of the Windows Form entity. There are all the actions assigned to functional elements from previous posts. The main funcionality is:

  • Edit Service Host Settings
  • Star/Stop Service Host
  • Monitor Service Host State (Started, Error Message)
  • Add Client Connection
  • Disconnect Client from Service Host
  • Monitor Connected Clients
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using ServiceAuxiliaries;
using System.Threading;
using ServiceAuxiliaries.Interfaces;

namespace RunServer
{
    public partial class mainForm : Form
    {
        #region Variables
        //the folder contains connection services dlls
        private readonly string m_ConnectionFolder = Path.Combine(Application.StartupPath, "Connections");
        //list of loaded connection services
        private readonly List<SaveSettingClasses.ConnServiceHostItem> m_ConnServiceHosts = new List<SaveSettingClasses.ConnServiceHostItem>();
        //list of all active sessions
        private readonly SortedList<string, string> m_ActiveSessions = new SortedList<string, string>();
        private MessageProcessor m_MessageProcessor;
        #endregion

        public mainForm()
        {
            InitializeComponent();
        }

        /// adds a new active session specified by ID
        private void AddSession(string aSessionID, string aLogon)
        {
            m_ActiveSessions[aSessionID] = aLogon;
            dgvUsers.RowCount = m_ActiveSessions.Count;
            dgvUsers.Refresh();
        }

        /// removes an active session by ID
        private void RemoveSession(string aSessionID)
        {
            if (m_ActiveSessions.Remove(aSessionID))
            {
                this.dgvUsers.RowCount = m_ActiveSessions.Count;
                this.dgvUsers.Refresh();
            }
        }

        /// Enumerates all assemblies in the Connections folder and loads them

        private void LoadConnectionServiceHosts()
        {
            string[] aDLLs = null;

            try
            {
                aDLLs = Directory.GetFiles(m_ConnectionFolder, "*.dll");
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            if (aDLLs.Length == 0)
                return;

            foreach (string item in aDLLs)
            {
                Assembly aDLL = Assembly.UnsafeLoadFrom(item);
                Type[] types = aDLL.GetTypes();

                foreach (Type type in types)
                {
                    try
                    {
                        //connection service must support IServiceHost interface
                        if (type.GetInterface("IServiceHost") != null)
                        {
                            object o = Activator.CreateInstance(type);

                            if (o is IServiceHost)
                            {

                                m_ConnServiceHosts.Add(new SaveSettingClasses.ConnServiceHostItem
                                {
                                    FileName = item,
                                    Enabled = true,
                                    Name = ((IServiceHost)o).Name,
                                    Type = type,
                                    Host = (IServiceHost)o,
                                    Parameters = new Dictionary<string, object>()
                                });
                            }
                        }
                    }
                    catch
                    {
                    }
                }
            }
            foreach (SaveSettingClasses.ConnServiceHostItem item in m_ConnServiceHosts)
            {
                item.Load(item.Host.DefaultSettings);
            }
        }

        /// refresh list of connection services in UI
        private void RefreshConnectionsView()
        {
            dgvConnServiceHosts.Rows.Clear();
            dgvConnServiceHosts.RowCount = m_ConnServiceHosts.Count;
            dgvConnServiceHosts.Refresh();
        }

        /// stores settings of data feeders and connection services to file
        private void SaveChanges()
        {
            foreach (SaveSettingClasses.ConnServiceHostItem connection in m_ConnServiceHosts)
            {
                connection.Save();
            }
        }

        /// updates enable property of UI controls
        private void EnableControls(bool enable)
        {
            btnStart.Enabled = enable;
            dgvConnServiceHosts.ReadOnly = !enable;
        }

        #region event handlers
        /// occurs when message router is adding a new session
        void OnRemovedSession_MsgRouter(object sender, MessageRouter.MessageRouter_EventArgs e)
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new Action<string>(o =>
                {
                    RemoveSession(o);
                }), e.ID);

            }
            else
                RemoveSession(e.ID);
        }

        /// occurs when message router is removing a session
        void OnAddedSession_MsgRouter(object sender, MessageRouter.MessageRouter_EventArgs e)
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new Action<string, string>((o1, o2) =>
                {
                    AddSession(o1, o2);
                }), e.ID, e.UserInfo.Login);

            }
            else
                AddSession(e.ID, e.UserInfo.Login);
        }

        private void OnLoad_MainForm(object sender, EventArgs e)
        {
            //load plugins found in DataFeeds, Connections folders
            try
            {
                LoadConnectionServiceHosts();
            }
            catch (Exception ex)
            {
                throw ex;
            }

            //update data feeders and connection services view
            RefreshConnectionsView();
        }

        private void OnStart_Click(object sender, EventArgs e)
        {
            var aInitializedConnectionServices = new List<SaveSettingClasses.ConnServiceHostItem>();

            lblState.Text = "Starting...";
            lblState.Refresh();
            EnableControls(false);
            SaveChanges();

            foreach (SaveSettingClasses.ConnServiceHostItem item in m_ConnServiceHosts)
            {
                item.Error = string.Empty;
                if (item.Enabled)
                {
                    if (item.Parameters == null && item.Host != null)
                        item.Parameters = item.Host.DefaultSettings;
                    aInitializedConnectionServices.Add(item);
                }
                else
                    item.State = "Disabled";
            }
            dgvConnServiceHosts.Refresh();
            if (MessageRouter.gMessageRouter == null)
            {
                MessageRouter.gMessageRouter = new MessageRouter();

                MessageRouter.gMessageRouter.AddedSession += OnAddedSession_MsgRouter;
                MessageRouter.gMessageRouter.RemovedSession += OnRemovedSession_MsgRouter;
            }
            lock (MessageRouter.gMessageRouter)
            {
                foreach (SaveSettingClasses.ConnServiceHostItem item in aInitializedConnectionServices)
                {
                    try
                    {
                        item.State = "Starting...";
                        item.Host.Start(item.Parameters);
                        item.State = "Started";
                    }
                    catch (Exception ex)
                    {
                        item.State = "Failed";
                        item.Error = ex.Message;
                    }
                }
            }

            if (m_MessageProcessor == null)
                m_MessageProcessor = new MessageProcessor();

            m_MessageProcessor.Start( aInitializedConnectionServices);

            dgvConnServiceHosts.Refresh();

            lblState.Text = "Started";
            btnStop.Enabled = true;
            timer1.Start();
        }

        private void OnStop_Click(object sender, EventArgs e)
        {
            if (m_MessageProcessor == null)
            {
                MessageBox.Show("RunServer already stopped", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);

                return;
            }
            this.timer1.Stop();
            lblState.Text = "Stopping";
            lblState.Refresh();
            try
            {
                m_MessageProcessor.Stop();
            }
            catch
            {
            }

            foreach (SaveSettingClasses.ConnServiceHostItem item in m_ConnServiceHosts)
            {
                try
                {
                    if (item.State == "Started")
                    {
                        item.Error = string.Empty;
                        item.State = "Stopping...";
                        item.Host.Stop();
                        item.State = "Stopped";
                    }
                }
                catch (Exception ex)
                {
                    item.Error = ex.Message;
                }
            }
            lblState.Text = "Stopped";
            btnStop.Enabled = false;
            EnableControls(true);
            dgvConnServiceHosts.Refresh();
        }

        private void OnCellValueNeeded_Users(object sender, DataGridViewCellValueEventArgs e)
        {
            if (e.RowIndex < 0 || e.RowIndex >= m_ActiveSessions.Count) return;

            string aSessionID = m_ActiveSessions.Keys[e.RowIndex];
            string aLogon = m_ActiveSessions[aSessionID];

            if (e.ColumnIndex == this.clmLogon.Index)
                e.Value = aLogon;
            else if (e.ColumnIndex == this.clmSessionID.Index)
                e.Value = aSessionID;
        }

        private void OnClosing_MainForm(object sender, FormClosingEventArgs e)
        {
            if (btnStop.Enabled)
            {
                if (MessageBox.Show("RunServer is currently running. Exit anyway?", Application.ProductName,
                    MessageBoxButtons.YesNo, MessageBoxIcon.Question) != System.Windows.Forms.DialogResult.Yes)
                {
                    e.Cancel = true;
                    return;
                }
            }
            lblState.Text = "Shutting down...";
            SaveChanges();
        }

        /// occurs on redrawing data feeds
        private void OnCellValueNeeded_Datafeeds(object sender, DataGridViewCellValueEventArgs e)
        {
        }

        private void OnCellContentClick_Datafeeds(object sender, DataGridViewCellEventArgs e)
        {
        }

        private void OnCellValidating_Datafeeds(object sender, DataGridViewCellValidatingEventArgs e)
        {
        }

        /// occurs on redrawing active sessions
        private void OnCellValueNeeded_ConnServices(object sender, DataGridViewCellValueEventArgs e)
        {
            if (e.RowIndex < 0 || e.RowIndex >= m_ConnServiceHosts.Count)
                return;

            if (e.ColumnIndex == colCEnabled.Index)
                e.Value = m_ConnServiceHosts[e.RowIndex].Enabled;
            else if (e.ColumnIndex == colCName.Index)
                e.Value = m_ConnServiceHosts[e.RowIndex].Name;
            else if (e.ColumnIndex == colCState.Index)
                e.Value = m_ConnServiceHosts[e.RowIndex].State;
            else if (e.ColumnIndex == colCError.Index)
                e.Value = m_ConnServiceHosts[e.RowIndex].Error;
            else if (e.ColumnIndex == colCSettings.Index)
                e.Value = "Edit Settings";
            else
                System.Diagnostics.Debug.Assert(false);
        }

        private void OnCellContentClick_ConnServices(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex == colCSettings.Index && e.RowIndex >= 0 && e.RowIndex < m_ConnServiceHosts.Count && !dgvConnServiceHosts.ReadOnly)             {                 ILogonControl aILogonControl = m_ConnServiceHosts[e.RowIndex].Host.LogonControl;                 if (aILogonControl is UserControl)                 {                     DlgLogonSettings dlg = new DlgLogonSettings();                     dlg.Init(aILogonControl);                     aILogonControl.Settings = m_ConnServiceHosts[e.RowIndex].Parameters;                     if (dlg.ShowDialog() == DialogResult.OK)                     {                         m_ConnServiceHosts[e.RowIndex].Parameters = aILogonControl.Settings;                     }                     m_ConnServiceHosts[e.RowIndex].Save();                 }                 else                     MessageBox.Show("The connection service does not support functionality to update settings", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);             }         }         private void OnCellValidating_Connections(object sender, DataGridViewCellValidatingEventArgs e)         {             if (e.ColumnIndex == colCEnabled.Index && e.RowIndex >= 0 && e.RowIndex < m_ConnServiceHosts.Count)
                m_ConnServiceHosts[e.RowIndex].Enabled = (bool)e.FormattedValue;
        }

        private void OnClick_Disconnect(object sender, EventArgs e)
        {
            List<string> aSessions = new List<string>();

            lock (m_ActiveSessions)
            {
                foreach (DataGridViewRow item in this.dgvUsers.SelectedRows)
                {
                    aSessions.Add(m_ActiveSessions.Keys[item.Index]);
                }
            }
            foreach (string item in aSessions)
            {
                lock (MessageRouter.gMessageRouter)
                {
                    IClientSession aIUserInfo = MessageRouter.gMessageRouter.GetClientSessionInfo(item);

                    if (aIUserInfo != null)
                    {
                        ThreadPool.QueueUserWorkItem(o =>
                        {
                            aIUserInfo.Disconnect();
                        }
                        );
                    }
                }
            }
        }

        private void OnTimerTick(object sender, EventArgs e)
        {
            List<string> aSessions = new List<string>();

            lock (m_ActiveSessions)
            {
                aSessions.AddRange(m_ActiveSessions.Keys);
            }
            foreach (string item in aSessions)
            {
                lock (MessageRouter.gMessageRouter)
                {
                    IClientSession aIUserInfo = MessageRouter.gMessageRouter.GetClientSessionInfo(item);

                    if (aIUserInfo != null)
                    {
                        ThreadPool.QueueUserWorkItem(o =>
                        {
                            try
                            {
                                aIUserInfo.Heartbeat();
                            }
                            catch
                            {
                            }
                        }
                        );
                    }
                }
            }
        }
        #endregion
    }
}

To get all settings right in a nice overview, thy are provided in a simple Dialog:

namespace RunServer
{
    partial class DlgLogonSettings
    {
        /// Required designer variable.
         private System.ComponentModel.IContainer components = null;

        /// Clean up any resources being used.
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        private void InitializeComponent()
        {
            this.btnCancel = new System.Windows.Forms.Button();
            this.btnOk = new System.Windows.Forms.Button();
            this.SuspendLayout();
            //
            // btnCancel
            //
            this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
            this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
            this.btnCancel.Location = new System.Drawing.Point(287, 221);
            this.btnCancel.Name = "btnCancel";
            this.btnCancel.Size = new System.Drawing.Size(75, 23);
            this.btnCancel.TabIndex = 0;
            this.btnCancel.Text = "Cancel";
            this.btnCancel.UseVisualStyleBackColor = true;
            //
            // btnOk
            //
            this.btnOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
            this.btnOk.Location = new System.Drawing.Point(206, 221);
            this.btnOk.Name = "btnOk";
            this.btnOk.Size = new System.Drawing.Size(75, 23);
            this.btnOk.TabIndex = 0;
            this.btnOk.Text = "Ok";
            this.btnOk.UseVisualStyleBackColor = true;
            this.btnOk.Click += new System.EventHandler(this.btnOk_Click);
            //
            // DlgLogonSettings
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(374, 256);
            this.Controls.Add(this.btnOk);
            this.Controls.Add(this.btnCancel);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
            this.MaximizeBox = false;
            this.Name = "DlgLogonSettings";
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
            this.Text = "Edit Settings";
            this.Load += new System.EventHandler(this.DlgLogonSettings_Load);
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.Button btnCancel;
        private System.Windows.Forms.Button btnOk;
    }
}

This Dialog contains a simple logic to check the entered values, by using the ILogonControl interface from ServiceAuxiliaries project.

using System;
using System.Drawing;
using System.Windows.Forms;
using ServiceAuxiliaries.Interfaces;

namespace RunServer
{
    /// Control to configure connections

    public partial class DlgLogonSettings : Form
    {
        private ILogonControl m_Settings;

        public DlgLogonSettings()
        {
            InitializeComponent();
        }

        public void Init(ILogonControl aSettings)
        {
            m_Settings = aSettings;
        }

        private void btnOk_Click(object sender, EventArgs e)
        {
            try
            {
                m_Settings.ValidateSettings();
                this.DialogResult = DialogResult.OK;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void DlgLogonSettings_Load(object sender, EventArgs e)
        {
            UserControl aUICtrl = (UserControl)m_Settings;

            this.Controls.Add(aUICtrl);
            aUICtrl.Location = new Point(0, 0);
            this.Size = new Size(aUICtrl.Width + 20, aUICtrl.Height + 80);
            aUICtrl.Anchor = AnchorStyles.Top & AnchorStyles.Left & AnchorStyles.Bottom & AnchorStyles.Left;
        }
    }
}

This Settings Dialog will host a control which acts like a template to enter all needed values.  Its inherited from UserControl which is  part of Windows Forms as well as the ILogonControl interface from ServiceAuxiliaries project. For now this would be the last element of the Windows Forms user interface.

namespace WcfServiceHost
{
    partial class LogonControl
    {
        private System.ComponentModel.IContainer components = null;

        /// Clean up any resources being used.
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Component Designer generated code
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.

        private void InitializeComponent()
        {
            this.label1 = new System.Windows.Forms.Label();
            this.numPort = new System.Windows.Forms.NumericUpDown();
            this.label2 = new System.Windows.Forms.Label();
            this.txtAddress = new System.Windows.Forms.TextBox();
            this.label3 = new System.Windows.Forms.Label();
            this.label4 = new System.Windows.Forms.Label();
            this.numDTPort = new System.Windows.Forms.NumericUpDown();
            ((System.ComponentModel.ISupportInitialize)(this.numPort)).BeginInit();
            ((System.ComponentModel.ISupportInitialize)(this.numDTPort)).BeginInit();
            this.SuspendLayout();
            //
            // label1
            //
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(2, 7);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(48, 13);
            this.label1.TabIndex = 0;
            this.label1.Text = "Address:";
            //
            // numPort
            //
            this.numPort.Location = new System.Drawing.Point(54, 29);
            this.numPort.Maximum = new decimal(new int[] {
            65535,
            0,
            0,
            0});
            this.numPort.Minimum = new decimal(new int[] {
            1,
            0,
            0,
            0});
            this.numPort.Name = "numPort";
            this.numPort.Size = new System.Drawing.Size(68, 20);
            this.numPort.TabIndex = 1;
            this.numPort.Value = new decimal(new int[] {
            1,
            0,
            0,
            0});
            //
            // label2
            //
            this.label2.AutoSize = true;
            this.label2.Location = new System.Drawing.Point(21, 33);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(29, 13);
            this.label2.TabIndex = 0;
            this.label2.Text = "Port:";
            //
            // txtAddress
            //
            this.txtAddress.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
            | System.Windows.Forms.AnchorStyles.Right)));
            this.txtAddress.Location = new System.Drawing.Point(54, 3);
            this.txtAddress.Name = "txtAddress";
            this.txtAddress.Size = new System.Drawing.Size(286, 20);
            this.txtAddress.TabIndex = 0;
            this.txtAddress.Text = "localhost";
            //
            // label3
            //
            this.label3.AutoSize = true;
            this.label3.Location = new System.Drawing.Point(3, 54);
            this.label3.Name = "label3";
            this.label3.Size = new System.Drawing.Size(47, 13);
            this.label3.TabIndex = 4;
            this.label3.Text = "DT Port:";
            //
            // label4
            //
            this.label4.AutoSize = true;
            this.label4.Location = new System.Drawing.Point(128, 54);
            this.label4.Name = "label4";
            this.label4.Size = new System.Drawing.Size(87, 13);
            this.label4.TabIndex = 5;
            this.label4.Text = "(design time port)";
            //
            // numDTPort
            //
            this.numDTPort.Location = new System.Drawing.Point(54, 52);
            this.numDTPort.Maximum = new decimal(new int[] {
            65535,
            0,
            0,
            0});
            this.numDTPort.Name = "numDTPort";
            this.numDTPort.Size = new System.Drawing.Size(68, 20);
            this.numDTPort.TabIndex = 6;
            //
            // LogonControl
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.Controls.Add(this.numDTPort);
            this.Controls.Add(this.label4);
            this.Controls.Add(this.label3);
            this.Controls.Add(this.txtAddress);
            this.Controls.Add(this.numPort);
            this.Controls.Add(this.label2);
            this.Controls.Add(this.label1);
            this.Name = "LogonControl";
            this.Size = new System.Drawing.Size(343, 78);
            ((System.ComponentModel.ISupportInitialize)(this.numPort)).EndInit();
            ((System.ComponentModel.ISupportInitialize)(this.numDTPort)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.NumericUpDown numPort;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.TextBox txtAddress;
        private System.Windows.Forms.Label label3;
        private System.Windows.Forms.Label label4;
        private System.Windows.Forms.NumericUpDown numDTPort;
    }
}

Now the entered values become parsed and set, to be provided to service host:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using ServiceAuxiliaries.Interfaces;

namespace WcfServiceHost
{
    public partial class LogonControl : UserControl, ILogonControl
    {
        public LogonControl()
        {
            InitializeComponent();
        }

        public void ValidateSettings()
        {
            if (txtAddress.Text.Trim().Length == 0)
            {
                throw new Exception("Invalid Address");
            }
        }

        public Dictionary<string, object> Settings
        {
            get
            {
                Dictionary<string, object> aDict = new Dictionary<string, object>();

                aDict["ip"] = txtAddress.Text.Trim();
                aDict["port"] = numPort.Value.ToString();
                aDict["design_time_port"] = numDTPort.Value.ToString();

                return aDict;
            }
            set
            {
                SuspendLayout();
                try
                {
                    txtAddress.Text = string.Empty;
                    numPort.Value = 1005;
                    numDTPort.Value = 0;
                    if (value.ContainsKey("ip"))
                        txtAddress.Text = value["ip"].ToString();
                    if (value.ContainsKey("port"))
                    {
                        int iVal;

                        if (Int32.TryParse(value["port"].ToString(), out iVal))
                            numPort.Value = iVal;
                    }
                    if (value.ContainsKey("design_time_port"))
                    {
                        int iVal;

                        if (Int32.TryParse(value["design_time_port"].ToString(), out iVal))
                            numDTPort.Value = iVal;
                    }
                }
                catch
                {
                }
                finally
                {
                    ResumeLayout();
                }
            }
        }
    }
}

Useful classes for convenience

Now the user interface is complete – but there are still some gaps to fill in mainForm.cs code. At least we need to provide some essential functionality about messaging and several helper classes for saving entered values and data. Lets start with a small SaveSettingClasses.cs class which is located in RunServer project:

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using ServiceAuxiliaries;

namespace RunServer
{
    public class SaveSettingClasses
    {
        public class DisplayItem
        {
            public string Id { get; set; }
            public string FileName { get; set; }
            public bool Enabled { get; set; }
            public string Name { get; set; }
            public Type Type { get; set; }
            public Dictionary<string, object> Parameters { get; set; }
            public string State { get; set; }
            public string Error { get; set; }

            public DisplayItem()
            {
                Id = Guid.NewGuid().ToString();
                State = "Stopped";
                Enabled = true;
                Error = string.Empty;
            }

            /// saves parameters to file

            public void Save()
            {
                if (Parameters != null)
                {
                    SavedSettings settings = new SavedSettings(Parameters, Enabled);
                    string file = Path.Combine(Path.GetDirectoryName(FileName),
                                               Path.GetFileNameWithoutExtension(FileName) + ".set");
                    settings.Save(file);
                }
            }

            /// loads parameters from file
            public void Load(Dictionary<string, object> aDefaultParams)
            {
                string file = Path.Combine(Path.GetDirectoryName(FileName),
                                           Path.GetFileNameWithoutExtension(FileName) + ".set");
                SavedSettings settings = SavedSettings.Load(file);

                if (settings != null)
                {
                    Enabled = settings.Enabled;
                    Parameters = settings.Parameters;
                }
                else
                    Parameters = aDefaultParams;
            }
        }

        public class ConnServiceHostItem : DisplayItem
        {
            public IServiceHost Host;
        }

        /// serializable class to store/load objects in file
        [Serializable]
        public class SavedSettings
        {
            public bool Enabled = false;
            public Dictionary<string, object> Parameters;

            public SavedSettings(Dictionary<string, object> aParameters, bool aEnabled)
            {
                Parameters = aParameters;
                Enabled = aEnabled;
            }

            public static SavedSettings Load(string fileName)
            {
                try
                {
                    if (File.Exists(fileName))
                    {
                        using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
                        {
                            BinaryFormatter formatter = new BinaryFormatter();

                            return (SavedSettings)formatter.Deserialize(fs);
                        }
                    }
                }
                catch
                {
                    System.Diagnostics.Debug.Assert(false);
                }
                return null;
            }

            public void Save(string fileName)
            {

                try
                {
                    using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None))
                    {
                        BinaryFormatter formatter = new BinaryFormatter();

                        formatter.Serialize(fs, this);
                    }
                }
                catch
                {
                    System.Diagnostics.Debug.Assert(false);
                }
            }
        }
    }
}

The central message dispatcher

Last but not least the MessageDispatcher.cs takes a central role in this server application. There a message queues for request and response messages is provided handle incoming and outgoing messages. Also, we find some essential events assigned  to “Start” and “Stop” methods.

using ServiceAuxiliaries;
using System;
using System.Collections.Generic;
using System.Threading;

namespace RunServer
{
    public class MessageDispatcher
    {
        #region Variables
        private readonly Dictionary<string, IServiceHost> m_SessionManagersByName = new Dictionary<string, IServiceHost>();
        private readonly Queue<RequestMessage> m_InMessages = new Queue<RequestMessage>();
        private readonly Dictionary<string, Queue<ResponseMessage>> m_OutMessages = new Dictionary<string, Queue<ResponseMessage>>();

        private TimeZoneInfo m_EST;
        #endregion

        #region Public
        public TimeZoneInfo TimeZoneInfo
        {
            get
            {
                if (m_EST == null)
                {
                    foreach (TimeZoneInfo item in TimeZoneInfo.GetSystemTimeZones())
                    {
                        if (item.StandardName == "Eastern Standard Time")
                        {
                            m_EST = item;
                            break;
                        }
                    }
                }

                return m_EST;
            }
        }

        public void Start(List<SaveSettingClasses.ConnServiceHostItem> aSessionManagers)
        {
            foreach (SaveSettingClasses.ConnServiceHostItem item in aSessionManagers)
            {
                if (item.State == "Started")
                    m_SessionManagersByName.Add(item.Host.Name, item.Host);
            }

            MessageRouter.gMessageRouter.RouteRequest += OnRouteRequest;
            MessageRouter.gMessageRouter.RemovedSession += OnRemovedSession;
        }

        public void Stop()
        {
            MessageRouter.gMessageRouter.RouteRequest -= OnRouteRequest;
            MessageRouter.gMessageRouter.RemovedSession -= OnRemovedSession;

            m_SessionManagersByName.Clear();

            lock (m_InMessages)
            {
                m_InMessages.Clear();
            }
            lock (m_OutMessages)
            {
                m_OutMessages.Clear();
            }
        }
        #endregion

        #region event handlers
        private void OnRouteRequest(object sender, MessageRouter.MessageRouter_EventArgs args)
        {
            lock (m_InMessages)
            {
                args.Request.User = args.UserInfo;
                m_InMessages.Enqueue(args.Request);
                if (m_InMessages.Count == 1)
                    ThreadPool.QueueUserWorkItem(o => ThreadFunc_RequestWorker());
            }
        }

        void OnRemovedSession(object sender, MessageRouter.MessageRouter_EventArgs e)
        {
        }
        #endregion

        #region Manage Messages Methods

        private void ThreadFunc_RequestWorker()
        {
            bool bContinue = true;

            while (bContinue)
            {
                RequestMessage aRequest = null;

                try
                {
                    lock (m_InMessages)
                    {
                        if (m_InMessages.Count > 0)
                        {
                            aRequest = m_InMessages.Peek();
                        }
                    }
                    if (aRequest == null)
                        break;

                    try
                    {
                        lock (m_InMessages)
                        {
                            if (m_InMessages.Count > 0)
                                m_InMessages.Dequeue();

                            if (m_InMessages.Count == 0)
                                bContinue = false;
                        }
                    }
                    catch
                    {
                    }
                }

                catch (Exception e)
                {
                    try
                    {
                        aRequest.User.SendError(e);
                    }
                    catch
                    {
                    }
                }
            }
        }

        private void ThreadFunc_ResponseWorker(Queue<ResponseMessage> aResponses)
        {
            ResponseMessage aResponse;
            bool bContinue = true;

            while (bContinue)
            {
                try
                {
                    aResponse = null;
                    lock (aResponses)
                    {
                        if (aResponses.Count > 100)
                        {
                            //trim all older messages (keep only one item of each type)
                            var items = aResponses.ToArray();
                            var types = new List<Type>();
                            Type t;
                            aResponses.Clear();
                            for (int i = items.Length - 1; i > -1; i--)
                            {
                                t = items[i].GetType();
                                if (!types.Contains(t))
                                {
                                    aResponses.Enqueue(items[i]);
                                    types.Add(t);
                                }
                            }
                            continue;
                        }
                        else if (aResponses.Count > 0)
                        {
                            aResponse = aResponses.Peek();
                        }
                    }

                    try
                    {
                        if (aResponse != null)
                            aResponse.User.Send(aResponse);
                    }
                    finally
                    {
                        lock (aResponses)
                        {
                            if (aResponses.Count > 0)
                                aResponses.Dequeue();

                            if (aResponses.Count == 0)
                                bContinue = false;
                        }
                    }
                }
                catch (Exception e)
                {
                }
            }
        }

        private void PushResponses(string aID, IEnumerable<ResponseMessage> aCollection)
        {
            bool bStart;
            Queue<ResponseMessage> aResponses;

            lock (m_OutMessages)
            {
                if (!m_OutMessages.TryGetValue(aID, out aResponses))
                {
                    aResponses = new Queue<ResponseMessage>();
                    m_OutMessages.Add(aID, aResponses);
                }
            }
            lock (aResponses)
            {
                bStart = (aResponses.Count == 0);
                foreach (ResponseMessage item in aCollection)
                {
                    aResponses.Enqueue(item);
                }
            }
            if (bStart)
                ThreadPool.QueueUserWorkItem(o => ThreadFunc_ResponseWorker(aResponses));
        }

        private void PushResponse(ResponseMessage aResponse)
        {
           List<ResponseMessage> aList = new List<ResponseMessage>();
           PushResponses(aResponse.User.ID, new ResponseMessage[] { aResponse });
        }
        #endregion

    }
}

All request and response messages become enqueued and dequeued and presented to their consumers. All this is done to handle multiple threads which bring a huge benefit for asynchronous broker apis. To buld and run the app, the only thing to do is creating a folder named “Connections” in your target path e.g. …\Debug, and copy the WcfServiceHost.dll to the …\Debug\Connetioncs folder. Than the RunServer.exe should start.

Finally the last pieces were put together to get a working server application example. Now we could connect clients and exchange messages between them. This will lead me to the next topic – a simple client. If you have suggestions or comments, feel free to let me know. To get a preview about upcoming topics or what was posted in past, please have a look here: Content++.

CcuYraMXIAE5Yn6

 

quantocracy-badge-130

 


9 thoughts on “Server -IV-

  1. Glad to hear you like it!
    Currently there is no github active, but maybe later…
    For now it like in the early 90′ computer game magazines,
    were you have to wait until the next readout – and type in the code by hand;-)))

    Like

    1. I’m glad you like it!
      I will continue for sure, after I finished the code review and finalized some other stuff.
      Than, after some vacation, I will find the time to cover the next topic.
      I had planed to continue with a simple client example to show how to connect to server.
      So stay tuned!

      Like

Leave a comment