Blogger Widgets
  • Sharing Photos using SignalR
  • TFS Extenstion - allows copy work items between projects
  • Displaying jquery progressbar with ajax call on a modal dialog
  • Managing windows services of a server via a website
  • Exploring technologies available to date. TechCipher is one place that any professional would like to visit, either to get an overview or to have better understanding.

Search This Blog

Wednesday, 28 November 2012

Combining ASP.NET MVC + SignalR + KnockoutJs with WCF Dual HTTP service

Managing windows services for a server via a website seems like a good option instead of having to remote desktop or remote accessing services.

A simple ASP.NET website should suffice this requirement, Is that correct ?
No, here is a simple scenario to prove that.

User wants to start a windows service using the website, now there are many things that can go wrong :
  • Service taking longer time to process the request
  • Service was unable to start due to some failure
  • Users display shows invalid service state

To fix this issue polling every 10(sec) can be done to find the correct state of the service. But what if this clients polling be replaced with service polling only when the state of the service is changed.

Now lets dig deep into and find out how this can be done. First lets look at the architecture of "OnlineServiceManagement"


OnlineServiceManagement has got 2 parts
  • WCF Window service with Dual HTTP binding enabled
  • ASP.NET MVC website with SignalR and KnockoutJS

WCF Window service with Dual HTTP binding enabled

Note - This application uses nlog for logging
First define contracts at least one that has dual binding enabled
 [ServiceContract]
    public interface IWinServiceNotificationHandler
    {
        [OperationContract(IsOneWay = true)]
        void ServiceStatusChanged(String ServiceName);
    }

     [ServiceContract(
        Name = "WinServiceProviderService",
        SessionMode = SessionMode.Required,
        CallbackContract = typeof(IWinServiceNotificationHandler))]
    public interface IWinServiceProvider
    {
        [OperationContract]
        string Ping();

        [OperationContract]
        ServiceControllerStatus GetServiceStatus(String Identification, String ServiceName);

        [OperationContract]
        ServiceControllerStatus StartService(String Identification, String ServiceName);

        [OperationContract]
        ServiceControllerStatus StopService(String Identification, String ServiceName);

        [OperationContract]
        ServiceControllerStatus RestartService(String Identification, String ServiceName);
    }

Now implement contract behaviour
 [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Single, InstanceContextMode = InstanceContextMode.PerCall)]
    public class WinServiceProvider : IWinServiceProvider
    {
        private static List _callbackList = new List();
        private static Dictionary _services = new Dictionary();
        public static readonly object _locker_ = new object();

        public string Ping()
        {
            return "I can ping";
        }

        private void RegisterClient(String Identification)
        {
            ServiceLogger.Logger.Info("Started " + System.Reflection.MethodInfo.GetCurrentMethod().Name + "Identifiction as '" + Identification + "'");
            // Subscribe the guest to the beer inventory
            IWinServiceNotificationHandler client = OperationContext.Current.GetCallbackChannel();
            if (!_callbackList.Contains(client))
            {
                _callbackList.Add(client);
            }
            ServiceLogger.Logger.Info("Completed " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
        }

        private void RegisterServiceNames(String ServiceName, ServiceControllerStatus Status)
        {
            ServiceLogger.Logger.Info("Started " + System.Reflection.MethodInfo.GetCurrentMethod().Name + "for Service '" + ServiceName + "'");

            if (!_services.ContainsKey(ServiceName))
            {
                _services.Add(ServiceName, Status);
            }
            else
            {
                _services[ServiceName] = Status;
            }

            ServiceLogger.Logger.Info("Completed " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
        }

        public static void ResetStatuses()
        {
            ServiceLogger.Logger.Info("Started " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
            try
            {
                lock (_locker_)
                {
                    string[] serviceNames = _services.Keys.ToArray();
                    foreach (string serviceName in serviceNames)
                    {
                        ServiceController sc = ServiceController.GetServices().Where(s => s.ServiceName == serviceName).FirstOrDefault();
                        _services[serviceName] = sc.Status;
                    }
                }
            }
            catch (Exception ex)
            {
                ServiceLogger.Logger.Error(ex);
            }
            ServiceLogger.Logger.Info("Completed " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
        }

        public static void NotifyClients(String ServiceName)
        {
            ServiceLogger.Logger.Info("Started " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
            ServiceLogger.Logger.Info("Found {0} callback clients", _callbackList.Count);
            try
            {
                _callbackList.ForEach(
                    delegate(IWinServiceNotificationHandler callback)
                    { callback.ServiceStatusChanged(ServiceName); });

                ResetStatuses();
            }
            catch (Exception ex)
            {
                ServiceLogger.Logger.ErrorException("Exception", ex);
            }
            ServiceLogger.Logger.Info("Completed " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
        }

        public static string HasServiceStatusChanged()
        {
            ServiceLogger.Logger.Info("Started " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
            string retStatusChanged = string.Empty;
            try
            {
                lock (_locker_)
                {
                    string[] serviceNames = _services.Keys.ToArray();
                    foreach (string serviceName in serviceNames)
                    {
                        ServiceController sc = ServiceController.GetServices().Where(s => s.ServiceName == serviceName).FirstOrDefault();
                        if (sc != null)
                        {
                            if (_services[serviceName] != sc.Status)
                            {
                                ServiceLogger.Logger.Info("Status changed for '{0}'", serviceName);
                                retStatusChanged = serviceName;
                                break;
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                ServiceLogger.Logger.Error(ex);
            }
            ServiceLogger.Logger.Info("Completed " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
            return retStatusChanged;
        }

        public ServiceControllerStatus GetServiceStatus(String Identification, String ServiceName)
        {
            ServiceLogger.Logger.Info("Started " + System.Reflection.MethodInfo.GetCurrentMethod().Name + "Identifiction as '" + Identification + "'");
            RegisterClient(Identification);
            ServiceControllerStatus retStatus = ServiceControllerStatus.StopPending;
            ServiceController sc = ServiceController.GetServices().Where(s => s.ServiceName == ServiceName).FirstOrDefault();
            if (sc != null)
            {
                retStatus = sc.Status;
            }
            RegisterServiceNames(ServiceName, sc.Status);
            //NotifyClients(ServiceName);
            ServiceLogger.Logger.Info("Completed " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
            return retStatus;
        }

        public ServiceControllerStatus StartService(String Identification, String ServiceName)
        {
            ServiceLogger.Logger.Info("Started " + System.Reflection.MethodInfo.GetCurrentMethod().Name + "Identifiction as '" + Identification + "'");
            RegisterClient(Identification);
            ServiceControllerStatus retStatus = ServiceControllerStatus.StopPending;
            ServiceController sc = ServiceController.GetServices().Where(s => s.ServiceName == ServiceName).FirstOrDefault();
            if (sc != null)
            {
                sc.Start();
                retStatus = sc.Status;
                //while (retStatus != ServiceControllerStatus.Running)
                //{
                //    System.Threading.Thread.Sleep(1000);
                //}
            }
            RegisterServiceNames(ServiceName, sc.Status);
            //NotifyClients(ServiceName);
            ServiceLogger.Logger.Info("Completed " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
            return retStatus;
        }

        public ServiceControllerStatus StopService(String Identification, String ServiceName)
        {
            ServiceLogger.Logger.Info("Started " + System.Reflection.MethodInfo.GetCurrentMethod().Name + "Identifiction as '" + Identification + "'");
            RegisterClient(Identification);
            ServiceControllerStatus retStatus = ServiceControllerStatus.StopPending;
            ServiceController sc = ServiceController.GetServices().Where(s => s.ServiceName == ServiceName).FirstOrDefault();
            if (sc != null)
            {
                sc.Stop();
                retStatus = sc.Status;
                //while (retStatus != ServiceControllerStatus.Stopped)
                //{
                //    System.Threading.Thread.Sleep(1000);
                //}
            }
            RegisterServiceNames(ServiceName, sc.Status);
            //NotifyClients(ServiceName);
            ServiceLogger.Logger.Info("Completed " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
            return retStatus;
        }

        public ServiceControllerStatus RestartService(String Identification, String ServiceName)
        {
            ServiceLogger.Logger.Info("Started " + System.Reflection.MethodInfo.GetCurrentMethod().Name + "Identifiction as '" + Identification + "'");
            RegisterClient(Identification);
            ServiceControllerStatus retStatus = ServiceControllerStatus.StopPending;
            ServiceController sc = ServiceController.GetServices().Where(s => s.ServiceName == ServiceName).FirstOrDefault();
            if (sc != null)
            {
                retStatus = StopService(Identification, ServiceName);
                retStatus = StartService(Identification, ServiceName);
            }
            RegisterServiceNames(ServiceName, sc.Status);
            //NotifyClients(ServiceName);
            ServiceLogger.Logger.Info("Completed " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
            return retStatus;
        }
    }
Now define endpoint configuration in app.config for windows service
endpoint address="" binding="wsDualHttpBinding"                  contract="WinServiceManger.Contract.IWinServiceProvider" 

That's it main components of windows service are completed. Windows service exposes some operations for website consumption and also has "IWinServiceNotificationHandler" for implementing call back to clients.

ASP.NET MVC website with SignalR and KnockoutJS

Using the default MVC template for creating the project should be sufficient. Now lets look into the details of website. Include following dependencies :
Dlls
  • SignalR
  • NLog

Scripts
  • JQuery
  • JQuery Templates Plugin
  • KnockoutJS
  • Json

Service references
  • WinServiceManger


We are using KnockoutJS that follows MVVM pattern in which Model comes first. So lets define our Model first
public class WindowsServicesModel
    {
        public List Services { get; set; }
        public WindowsServicesModel(String Identification)
        {
            LoadServices(Identification);
        }

        private void LoadServices(String Identification)
        {
            try
            {
                Services = new List()
                {
                    new WinService
                    {
                        Name = "ASP.NET State Service",
                        ServiceName = "aspnet_state",
                        Description="Provides support for out-of-process session states for ASP.NET. If this service is stopped, out-of-process requests will not be processed. If this service is disabled, any services that explicitly depend on it will fail to start.",
                        CSSClass = "one",
                        Status= ""
                    },
                    new WinService
                    {
                        Name = "Background Intelligent Transfer Service",
                        ServiceName = "BITS",
                        Description="Transfers files in the background using idle network bandwidth. If the service is disabled, then any applications that depend on BITS, such as Windows Update or MSN Explorer, will be unable to automatically download programs and other information.",
                        CSSClass = "two",
                        Status= ""
                    },
                    new WinService
                    {
                        Name = "Bluetooth Support Service",
                        ServiceName = "bthserv",
                        Description="The Bluetooth service supports discovery and association of remote Bluetooth devices.  Stopping or disabling this service may cause already installed Bluetooth devices to fail to operate properly and prevent new devices from being discovered or associated.",
                        CSSClass = "three",
                        Status= ""
                    }
                };

                foreach (WinService winService in Services)
                {
                    WinServiceManger.ServiceControllerStatus status = Global.WinServiceMangerClient.GetServiceStatus(Identification, winService.ServiceName);
                    switch (status)
                    {
                        case WinServiceManger.ServiceControllerStatus.Running:
                            winService.Status = "Running";
                            break;
                        case WinServiceManger.ServiceControllerStatus.ContinuePending:
                            winService.Status = "Continue Pending";
                            break;
                        case WinServiceManger.ServiceControllerStatus.Paused:
                            winService.Status = "Paused";
                            break;
                        case WinServiceManger.ServiceControllerStatus.PausePending:
                            winService.Status = "Pause Pending";
                            break;
                        case WinServiceManger.ServiceControllerStatus.StartPending:
                            winService.Status = "Start Pending";
                            break;
                        case WinServiceManger.ServiceControllerStatus.Stopped:
                            winService.Status = "Stopped";
                            break;
                        default:
                            winService.Status = "Unknown";
                            break;
                    }
                }
            }
            catch (Exception ex)
            {
                WebLogger.Logger.ErrorException("WindowsServicesModel.LoadServices Exception : ", ex);
            }
        }
    }
We have our model ready now, so lets define our View to be a KnockoutJS template that display list of services available.
 
Now write javascript code for updating View that is defined earlier
var koHelper = {
    ViewModel: {},
    LoadViewModel: function (resultData) {
        //var strViewModel = document.getElementById(viewModelFieldId).value;
        //var clientViewModel = ko.utils.parseJson(resultData);
        var clientViewModel = resultData;
        this.ViewModel = clientViewModel;
        ko.applyBindings(this.ViewModel);
    },
    getWindowsServices: function () {
        $.ajax({
            url: "/Home/GetWindowsServices/",
            type: 'post',
            //data: "{'customerID':'1' }",
            contentType: 'application/json',
            success: function (result) {
                koHelper.LoadViewModel(result);
                var dt = new Date();
                koHelper.UpdateStatus("Autoupdate service status by Signal-R");
            },
            error: function (jqXHR, textStatus, errorThrown) {
                var errorMessage = '';
                $('#message').html(jqXHR.responseText);
            }
        });
    },
    StartService: function (data, event) {        
        $.ajax({
            url: "/Home/StartService/",
            type: 'post',
            data: JSON.stringify( { 'ServiceName': data.ServiceName } ),
            contentType: 'application/json',
            dataType: 'json',
            success: function (result) {
                if (result != null) {
                    koHelper.LoadViewModel(result);
                    var dt = new Date();
                    koHelper.UpdateStatus("StartService");
                    //$.windowsServiceManager.performedServiceStatusChange(data.ServiceName);
                }
            },
            error: function (jqXHR, textStatus, errorThrown) {
                var errorMessage = '';
                $('#message').html(jqXHR.responseText);
            }
        });
    },
    StopService: function (data, event) {
        $.ajax({
            url: "/Home/StopService/",
            type: 'post',
            data: JSON.stringify({ 'ServiceName': data.ServiceName }),
            contentType: 'application/json',
            dataType: 'json',
            success: function (result) {
                if (result != null) {
                    koHelper.LoadViewModel(result);
                    var dt = new Date();
                    koHelper.UpdateStatus("StopService");
                    //$.windowsServiceManager.performedServiceStatusChange(data.ServiceName);
                }
            },
            error: function (jqXHR, textStatus, errorThrown) {
                var errorMessage = '';
                $('#message').html(jqXHR.responseText);
            }
        });
    },
    RestartService: function (data, event) {
        $.ajax({
            url: "/Home/RestartService/",
            type: 'post',
            data: JSON.stringify({ 'ServiceName': data.ServiceName }),
            contentType: 'application/json',
            dataType: 'json',
            success: function (result) {
                if (result != null) {
                    koHelper.LoadViewModel(result);
                    var dt = new Date();
                    koHelper.UpdateStatus("RestartService");
                    //$.windowsServiceManager.performedServiceStatusChange(data.ServiceName);
                }
            },
            error: function (jqXHR, textStatus, errorThrown) {
                var errorMessage = '';
                $('#message').html(jqXHR.responseText);
            }
        });
    },
    UpdateStatus: function (statusData) {
        var content = "
" + new Date() + "
" + statusData + "
"; content += $('#statusInfo').html() ; $('#statusInfo').html(content); } }
We have used following controller actions:-
  • /Home/GetWindowsServices/
  • /Home/StartService/
  • /Home/StopService/
  • /Home/RestartService/
Now lets look at implementing these actions
 public JsonResult GetWindowsServices()
        {
            JsonResult retValue = null;
            WebLogger.Logger.Info("Started " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
            try
            {
                if (Session["Identification"] != null)
                {
                    WindowsServicesModel wsm = new WindowsServicesModel(Session["Identification"].ToString());
                    retValue = new JsonResult()
                    {
                        Data = wsm
                    };
                }
            }
            catch (Exception ex)
            {
                WebLogger.Logger.ErrorException("Error ", ex);
            }
            WebLogger.Logger.Info("Completed " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
            return retValue;
        }

[AcceptVerbs(HttpVerbs.Post)]
        public JsonResult StartService(string ServiceName)
        {
            JsonResult retValue = null;
            WebLogger.Logger.Info("Started " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
            try
            {
                if (Session["Identification"] != null)
                {
                    WinServiceManger.ServiceControllerStatus status = Global.WinServiceMangerClient.StartService(Session["Identification"].ToString(), ServiceName);
                    //if (status == WinServiceManger.ServiceControllerStatus.Running)
                    //{
                    WindowsServicesModel wsm = new WindowsServicesModel(Session["Identification"].ToString());
                    retValue = new JsonResult()
                    {
                        Data = wsm
                    };
                    //}
                }
            }
            catch (Exception ex)
            {
                WebLogger.Logger.ErrorException("Error ", ex);
            }
            WebLogger.Logger.Info("Completed " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
            return retValue;
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public JsonResult StopService(string ServiceName)
        {
            JsonResult retValue = null;
            WebLogger.Logger.Info("Started " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
            try
            {
                WinServiceManger.ServiceControllerStatus status = Global.WinServiceMangerClient.StopService(Session["Identification"].ToString(), ServiceName);
                WindowsServicesModel wsm = new WindowsServicesModel(Session["Identification"].ToString());
                retValue = new JsonResult()
                {
                    Data = wsm
                };
            }
            catch (Exception ex)
            {
                WebLogger.Logger.ErrorException("Error ", ex);
            }
            WebLogger.Logger.Info("Completed " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
            return retValue;
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public JsonResult RestartService(string ServiceName)
        {
            JsonResult retValue = null;
            WebLogger.Logger.Info("Started " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
            try
            {
                WinServiceManger.ServiceControllerStatus status = Global.WinServiceMangerClient.RestartService(Session["Identification"].ToString(), ServiceName);
                //if (status == WinServiceManger.ServiceControllerStatus.Running)
                //{
                WindowsServicesModel wsm = new WindowsServicesModel(Session["Identification"].ToString());
                retValue = new JsonResult()
                {
                    Data = wsm
                };
                //}
            }
            catch (Exception ex)
            {
                WebLogger.Logger.ErrorException("Error ", ex);
            }
            WebLogger.Logger.Info("Completed " + System.Reflection.MethodInfo.GetCurrentMethod().Name);
            return retValue;
        }
Thats it! we are done with main components required for implementing our requirement. Complete solution is available on GitHub at OnlineServiceManagement OnlineServiceManagement

“We have to stop optimizing for programmers and start optimizing for users.” – Jeff Atwood

4 comments:

Copyright © 2013 Template Doctor . Designed by Malith Madushanka - Cool Blogger Tutorials | Code by CBT | Images by by HQ Wallpapers