Creating a Windows Service with C#, concluded

Welcome back to our series on developing a Windows Service with C#. So far we have created a rudimentary TCP server and installed it as a Windows Service. In this final installment we are going to add some simple event logging to our service and create a simple service controller application that can be minimized to the system tray.

Contributed by
Rating: 5 stars5 stars5 stars5 stars5 stars / 50
March 14, 2006
Rate this Article:
MEH MEH++


SEARCH ASP FREE
TOOLS YOU CAN USE

advertisement



The Zip file containing the source code for this and the other parts of this series can be found HERE.

The Last Leg

We will be using a class from vbAccelerator.com, found here: http://www.vbaccelerator.com/home/NET/Code/Libraries/Shell_Projects/SysTray/
SysTray_Code_zip_SysTrayTester_SysTray_cs.asp
, to interact with the system tray. The reason I use this class is that the .NET Framework does not expose any access to the system tray; we have to import Win32 API functions from various DLL files. The class from vbAccelerator.com handles all this and more-–it provides a balloon tooltip mechanism as an added bonus.

Event Logging

Using the Windows event log is a breeze with .NET. Using the System.Diagnostics namespace, which is already referenced in our TimeServerService class, we have access to the EventLog class. Before we actually code up any event logging, let’s at least make it configurable so that logging can be turned off without recompiling. We should also make the name of the custom log configurable.

Right click on the TimeService project, select “Add”,  “Add New Item”, “Application Configuration”. Leave the default name “App.config”. Open the App.config file and add a new node under configuration called “appSettings”. We will add two keys to the Application Settings section, “LoggingEnabled” and “LogName”. Your App.config file should look like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <appSettings>
      <add key="LoggingEnabled" value="true" />
      <add key="LogName" value="Time Service" />
   </appSettings>
</configuration>

You can change the value of LogName to whatever you want. We will write our code so that any value other than “true” means that the logging functionality of the service is turned off. To use these settings, add a reference to the System.Configuration namespace to TimeServerService.cs:

using System.Configuration;

Now we need to add a few properties and methods to our class. Since every time we need to add a message we have to check the LoggingEnabled setting, we will create a helper method in the TimeServerService class to handle writing the log entries. Add the following properties:

      private bool loggingEnabled;
      private string logName;
      private EventLog log;

Initialize these fields in the constructor, like so:

      public TimeServerService()
      {
         InitializeComponent();
         this.listener = new TcpListener(ip, port);
 
         this.logName = ConfigurationSettings.AppSettings
["LogName"];
         if (ConfigurationSettings.AppSettings["LoggingEnabled"]
== "true")
            this.loggingEnabled = true;
         else
            this.loggingEnabled = false;
 
         if (this.loggingEnabled)
         {
            if (!EventLog.SourceExists(this.logName))
               EventLog.CreateEventSource(this.ServiceName,
this.logName);
 
            this.log = new EventLog();
            this.log.Source = this.logName;         
         }
      }



The code above grabs our configuration settings and initializes the class's new fields accordingly. Next we need to create our helper method, or methods, to actually write the entries to the log. The EventLog.WriteEntry() method signature we will use accepts a string message and an EventLogEntryType type, and so will our helper method. We will create an overloaded version of our helper method that takes just an exception and one that takes just a string, defaulting to EventLogEntryType.Error and EventLogEntryType.Information, respectively. The methods are short and simple:

      private void Log(string message, EventLogEntryType type)
      {
         if (this.loggingEnabled)
            this.log.WriteEntry(message, type);
      }
 
      private void Log(string message)
      {
         this.Log(message, EventLogEntryType.Information);
      }
 
      private void Log(Exception e)
      {
         this.Log(e.Message + "\n" + e.StackTrace,
EventLogEntryType.Error);
      }



Now we can simply call Log(message) for general information messages and Log(exception) to log errors. The Log(message, type) version can be used for other types of messages such as warnings and audits if you decide to use those types later. Note that in the Log(message, type) method, we check to see if logging is enabled before writing anything to the event log.

Logging events

With the helper methods in place, actually logging events is a matter of calling Log(message). Typically you would put messages in OnStart() and OnStop() indicating service start and stop, and in this case you may want to log every time someone connects to the server. Add this line to OnStart():

this.Log(this.ServiceName + " Started");

And add this line to OnStop():

this.Log(this.ServiceName + " Stopped");

In the Start() method, inside the control loop, we need to check the number of bytes read to see if a request has been received and, if one has, to make a note of it. Add this line to the Start() method after the line reading “s.Send(time);”:

this.Log("Received request: " + bytesRead.ToString());

You have probably noticed that there are no try/catch blocks in this service. It would probably be wise to add one in our control loop! Wrap everything inside the while loop in a try statement, then in the catch, write to the event log, like this:

         while (!this.isStopped)
         {
            try
            {
               s = this.listener.AcceptSocket();
               incomingBuffer = new Byte[100];
               bytesRead = s.Receive(incomingBuffer);
 
               time = Encoding.ASCII.GetBytes(
                  System.DateTime.Now.ToString().ToCharArray());
               s.Send(time);
               this.Log("Received request: " + bytesRead.ToString
());
            }
            catch (Exception ex)
            {
               this.Log(ex);
            }
         }



Now that logging is complete, let’s move on to creating our controller application. Be sure you reinstall the service first to activate the changes we have made.

Creating the Controller

Add a new Windows Application project to the TimeApp solution called “TimeServiceController”. You will see a form named “Form1” now. Change its Text property to “Time Service Controller”, and add the following label and buttons:

Rename them “StatusLabel”, “StartButton”, and “StopButton”. Double-click on the Start button, click back into Design View, and double-click the Stop button. This adds in the click event handlers for our buttons. Now select the “Components” section of the toolbox and double-click “ServiceController”. Change the ServiceName property of the ServiceController to “TimeServerService”. We are done with Design View now; time to switch to Code View.

Now that we have our buttons and service controller wired up, we just need to put a bit of code in the click handlers. Look how simple it is:

      private void StartButton_Click(object sender,
System.EventArgs e)
      {
         this.serviceController1.Start();
         this.StatusLabel.Text = this.serviceController1.Status.ToString();
      }
 
      private void StopButton_Click(object sender,
System.EventArgs e)
      {
         this.serviceController1.Stop();
         this.StatusLabel.Text = this.serviceController1.Status.ToString();
      }



All we have to do now is set up a timer to poll the service status and update the status message in our controller. We will create the timer object in the constructor and call another method to check the status and update our label. The constructor should look like this:

      public Form1()
      {
         InitializeComponent();
 
         System.Timers.Timer t = new System.Timers.Timer(10000);
         t.Elapsed += new System.Timers.ElapsedEventHandler
(UpdateStatus);
         t.Start();
 
         this.StatusLabel.Text =
this.serviceController1.Status.ToString();
      }

The UpdateStatus() method should look like this:

     private void UpdateStatus(object sender,
System.Timers.ElapsedEventArgs e)
     {
        this.StatusLabel.Text = this.serviceController1.Status;
     }

Set this class as your startup project and run it for a test drive. See a problem? Even with the timer in place, it seems that our label does not stay quite in sync. To fix this problem, we need to use the Service Controller’s WaitForStatus() method, like this:

      private void StartButton_Click(object sender,
System.EventArgs e)
      {
         this.serviceController1.Start();
                   
         this.serviceController1.WaitForStatus(
            System.ServiceProcess.ServiceControllerStatus.Running);
 
         this.StatusLabel.Text =
this.serviceController1.Status.ToString();
      }
 
      private void StopButton_Click(object sender,
System.EventArgs e)
      {
         this.serviceController1.Stop();
 
         this.serviceController1.WaitForStatus(
            System.ServiceProcess.ServiceControllerStatus.Stopped);
 
         this.StatusLabel.Text =
this.serviceController1.Status.ToString();
      }



Using the SysTray Class

The last thing on our list is to send our TimeServiceController program to the system tray when it is minimized. We will also want to show a balloon with a message if the service status changes unexpectedly. Create a new class file called SysTray.cs in the TimeServiceController project and paste in the code from vbAccelerator.com: http://www.vbaccelerator.com/home/NET/Code/Libraries/Shell_Projects/
SysTray/SysTray_Code_zip_SysTrayTester_SysTray_cs.asp
.

Add the vbAccelerator.Components.Shell namespace to your Form1 class file:

using vbAccelerator.Components.Shell;

Add a SysTray object as a class field:

private SysTray sysTray;

Initialize the SysTray object at the end of the constructor:

this.sysTray = new SysTray(this);
iconList.Images.Add(new Icon("../../App.ico"));
this.sysTray.IconImageList = iconList;
this.sysTray.IconIndex = 0;

Now we need to wire up our form to keep track of when it has been minimized so we can hide it and add it to the task bar. Add this line to the end of InitializeComponent() method:

this.SizeChanged += new System.EventHandler(DoResize);

The Minimize() method looks like this:

      private void DoResize(object sender, System.EventArgs e)
      {
         if (this.WindowState == FormWindowState.Minimized)
         {
            this.sysTray.ShowInSysTray = true;
            this.Hide();
         }
      }

If you run the project now you will see that when you minimize, it does indeed show up on your system tray and the main window vanishes from the taskbar altogether-–but clicking on the system tray icon does not restore it. That’s because we haven’t created an event handler for the sysTray.DoubleClick event. Add this line in the constructor:

this.sysTray.DoubleClick += new EventHandler(DoRestore);

And add this method:

      private void DoRestore(object sender, EventArgs e)
      {
         this.sysTray.ShowInSysTray = false;
 
         if (!this.Visible)
         {
            this.Visible = true;
         }
         if (this.WindowState == FormWindowState.Minimized)
         {
            this.WindowState = FormWindowState.Normal;
         }
         this.BringToFront();
      }

Now when you run your controller application you can minimize it to the system tray and double click the system tray icon to restore it. The last thing we need to do is add a bit of code to our timed method UpdateStatus() to check for an unexpected service status and show a balloon tooltip. Since we will be stopping and starting the service with the buttons in our application, any service status change, when minimized, should warrant a popup balloon. Thanks to vbAccelerator.com’s SysTray class, this is pretty simple:

     private void UpdateStatus(object sender,
System.Timers.ElapsedEventArgs e)
     {
        string status = this.serviceController1.Status.ToString
();
        
        if (this.Visible == false)
           this.sysTray.ShowBalloonTip(status);
 
        this.StatusLabel.Text = status;
     }

To test this tooltip out, run the application and minimize it. Then go to Computer Management -> Services and stop the TimeServerService. You will see a balloon popup notification within ten seconds!

The End

All done! We’ve done quite a bit in these past three articles and I hope you learned something you didn’t already know. We have created a basic TCP server and installed it as a Windows Service with configurable logging and a small application to stop and start the service that runs minimized on the system tray and notifies us of services status changes. You can use this service as a foundation for a lot of interesting stuff, and I encourage you to experiment with building a more advanced server and client and to add more complex features to the service controller.

blog comments powered by Disqus
C# ARTICLES

- Beginning C#
- ASP.NET RedirectPermanent Method using C# an...
- C Programming Language and UNIX Pioneer Pass...
- Using Facebook JavaScript SDK in ASP.NET wit...
- ASP.NET Export to Excel and Word using VB.NE...
- WAV and MP3 Streaming with ASP.Net and C#
- Game Programming using SDL: the File I/O API
- C# and Java Developer Jobs on the Rise
- The Future Evolution of C# and VB.NET
- C# If and Else-if Statements
- How To Use the C# String Replace Method
- 5 Ways to Parse XML in C#
- C# Meets Design Patterns
- Coding a CRC-Generating Algorithm in C
- Cyclic Redundancy Check

ASP Web Hosting ASP.Net Web Hosting Windows Web Hosting
 
 
 

ASP Free Forums 
 RSS  Tutorials RSS
 RSS  Forums RSS
 RSS  All Feeds
Site Map 
Request Media Kit
Write For Us Get Paid 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Privacy Policy 
Support 


© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 2 - Follow our Sitemap
Most Popular Topics
All ASP.Net Tutorials