Using a Windows service in your .NET project part 7: Windows Service body part 1

Introduction

In the previous post we saw how to install and uninstall the Windows service of our demo project. Currently the service doesn’t do anything as there’s no method implementation anywhere but it’s good that we can safely install and uninstall it.

Let’s review what this service should be able to do and what dependencies it will need:

  • It should periodically check the MongoDb database for new Http jobs. We have a service method for this in IHttpJobService
  • It should then execute those jobs using an IHttpJobExecutionService
  • Log the actions using an interface and implementation we haven’t seen yet

I need to stress the importance of logging in a Windows service. First of all it’s vital to log at least some activity in any real business application so that you can trace what your users are doing and find the source of exceptions. It’s even more important to have logging in place for an application with no user interface such as a Windows service. A Windows service cannot even send messages to a command prompt for you to see during testing. If you have no logging in place then you’ll be wondering what is going on if the service doesn’t perform its job.

We won’t build a full-blown logging system here, just a simplified file-based one. You can read more about logging in two blog posts: an introduction and a concrete example with log4net.

Logging

Let’s get this done first. We’ll reuse the logging interface from the logging intro referred to above. Open the Demo.Infrastructure project and add a new folder called Logging. Insert the following interface:

public interface ILoggingService
{
	void LogInfo(object logSource, string message, Exception exception = null);
	void LogWarning(object logSource, string message, Exception exception = null);
	void LogError(object logSource, string message, Exception exception = null);
	void LogFatal(object logSource, string message, Exception exception = null);
}

We’ll implement a home-made file-based logging service. Insert the following class to the Logging folder:

public class FileBasedLoggingService : ILoggingService
{
	private readonly string _logFileFullPath;

	public FileBasedLoggingService(string logFileFullPath)
	{
		if (string.IsNullOrEmpty(logFileFullPath)) throw new ArgumentException("Log file full path");
		FileInfo logFileInfo = new FileInfo(logFileFullPath);
		if (!logFileInfo.Exists) throw new ArgumentNullException("Log file does not exist");
		_logFileFullPath = logFileFullPath;
	}

	public void LogInfo(object logSource, string message, Exception exception = null)
	{
		AppendMessage(logSource, message, "INFO", exception);
	}

	public void LogWarning(object logSource, string message, Exception exception = null)
	{
		AppendMessage(logSource, message, "WARNING", exception);
	}

	public void LogError(object logSource, string message, Exception exception = null)
	{
		AppendMessage(logSource, message, "ERROR", exception);
	}

	public void LogFatal(object logSource, string message, Exception exception = null)
	{
		AppendMessage(logSource, message, "FATAL", exception);
	}

	private void AppendMessage(object source, string message, string level, Exception exception)
	{
		try
		{
			File.AppendAllText(_logFileFullPath, FormatMessage(source, message, level, exception));
		}
		catch { }
	}

	private string FormatMessage(object source, string message, string level, Exception exception)
	{
		return string.Concat(Environment.NewLine, DateTime.UtcNow.ToString(), ": source: ", source.ToString(), ", level: ", level, ", message: ", message, ", any exception: ", (exception == null ? "None" : exception.Message) );
	}
}

In a real application you’d use a professional tool to log to a file but that would unnecessarily sidetrack us now. Also, we could take the level of abstraction even further. Note that FileBasedLoggingService directly uses objects like File and FileInfo which make it difficult to test the methods in this class. A more complete solution would require us to separate out the concrete file-based operations to an abstraction. We’ll leave the implementation as it is for now. You can clean up this code if you’d like to as a homework. You can take a look at this post where I go through how to factor out file-related operations. It ties in well with what I’ve written in this paragraph.

Create a text file like “log.txt” somewhere on your hard drive and take note of its full path. Make sure that the file is editable by the service. For demo purposes you can give full access to Everyone so that we don’t need to trace any logging-related exceptions:

Set log file access to Everyone

Note how we simply ignore any exceptions thrown within AppendMessage. This is called fire-and-forget. This strategy can be acceptable in logging as we don’t want the application to stop just because e.g. the log file is inaccessible. In a testing phase you can catch the exception and log it somewhere else, such as the event log, in case you don’t see the logging messages in the target file, here’s an example:

private void AppendMessage(object source, string message, string level, Exception exception)
{
	try
	{
		File.AppendAllText(_logFileFullPath, FormatMessage(source, message, level, exception));
	}
	catch (Exception ex)
	{
		string s = "HttpJobRunner";
		string log = "Application";
		if (!EventLog.SourceExists(s))
		{
			EventLog.CreateEventSource(s, log);
		}
		EventLog.WriteEntry(s, "Exception in HttpJobRunner logging: " + ex.Message, EventLogEntryType.Error, 1234);
	}
}

Let’s add this as a dependency to HttpJobRunner. At present HttpJobRunner- the partial class that inherits from ServiceBase – is quite empty with a constructor that calls upon InitializeComponent and two empty overridden methods.

Change the implementation as follows:

public partial class HttpJobRunner : ServiceBase
{
	private readonly ILoggingService _loggingService;

	public HttpJobRunner(ILoggingService loggingService)
	{
		InitializeComponent();
		if (loggingService == null) throw new ArgumentNullException("LoggingService");
		_loggingService = loggingService;
	}

	protected override void OnStart(string[] args)
	{
		_loggingService.LogInfo(this, "HttpJobRunner starting up");
	}

	protected override void OnStop()
	{
		_loggingService.LogInfo(this, "HttpJobRunner stopping");
	}
}

You’ll need to add a project reference to the Infrastructure layer for this to compile. We let an ILoggingService be injected to HttpJobRunner through its constructor. Then we simply take note of the Windows service starting up and shutting down in the log file. Build the project and you should get an exception from Program.cs in the Windows service layer: HttpJobRunner doesn’t have an empty constructor. Change the implementation as follows:

static void Main()
{
	ServiceBase[] ServicesToRun;
	ServicesToRun = new ServiceBase[] 
        { 
             new HttpJobRunner(new FileBasedLoggingService(@"c:\logging\log.txt"))
       };
	ServiceBase.Run(ServicesToRun);
}

Make sure you insert the full path of the log file that you created before in the FileBasedLoggingService constructor.

Let’s see if this works. Rebuild the project and re-install the Windows service following the steps outlined in the previous part. Check the contents of the log file, there should be one entry similar to the following:

20/09/2014 20:07:25: source: Demo.WindowsService.HttpJobRunner, level: INFO, message: HttpJobRunner starting up, any exception: None

You can stop and/or restart the service in the Services window:

Stop and restart a service

The log messages should be added to the log file accordingly, e.g.:

20/09/2014 21:00:42: source: Demo.WindowsService.HttpJobRunner, level: INFO, message: HttpJobRunner starting up, any exception: None
20/09/2014 21:01:02: source: Demo.WindowsService.HttpJobRunner, level: INFO, message: HttpJobRunner shutting down, any exception: None
20/09/2014 21:01:03: source: Demo.WindowsService.HttpJobRunner, level: INFO, message: HttpJobRunner starting up, any exception: None

Great, we have some logging in place.

Restarting a Windows service can have negative consequences in your application. If the service is carrying out a job without saving its current state somewhere then restarting the service will in effect kill the job. So at a minimum we want to know any time the service status has changed. Add the following overrides to HttpJobRunner:

protected override void OnShutdown()
{
	_loggingService.LogInfo(this, "HttpJobRunner shutting down");
}

protected override void OnContinue()
{
	_loggingService.LogInfo(this, "HttpJobRunner continuing");
}

protected override void OnPause()
{
	_loggingService.LogInfo(this, "HttpJobRunner pausing");
}	

This doesn’t of course solve the problem of interrupted jobs but at least we know that something noteworthy has happened which can be the cause of a failed job. It is your responsibility to make your service “clever” enough to pick up an interrupted job when it’s restarted. In our example the service could check all jobs that have been started but not yet finished and start from there – or simply restart the job from the beginning. However, that’s beyond the scope of this series, but it’s still good to know about this issue.

Before we finish this post let’s add one more method to HttpJobRunner. The goal is that the service is never restarted due to an unhandled exception. If there’s an uncaught exception somewhere during the code execution the the service will fail and act according to the failure policy, such as “Restart the service” in our case. However, as noted above service restarts can be detrimental. It’s not always possible to catch all exceptions in a large code base even if you put try-catch clauses everywhere.

There’s a solution to catch all uncaught exceptions in a Windows service though. I mentioned it already in this short post on threading. We’ll reuse much of it here.

Within the HttpJobRunner constructore start typing…

TaskScheduler.UnobservedTaskException +=

…and press Tab twice. This will add a default handler that’s called whenever an uncaught exception bubbles up:

void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
	throw new NotImplementedException();
}

Insert the following implementation:

void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
	e.SetObserved();
	AggregateException aggregateException = (e.Exception as AggregateException).Flatten();
	aggregateException.Handle(ex =>
	{				
		return true;
	});
	List<Exception> inners = aggregateException.InnerExceptions.ToList();
	StringBuilder sb = new StringBuilder();
	sb.Append("Unhandled exception caught in HttpJobRunner by the generic catch-all handler.")
				.Append(NL);
	if (inners != null && inners.Count > 0)
	{
		foreach (Exception inner in inners)
		{
			sb.Append("Exception: ").Append(inner.Message).Append(NL).Append("Stacktrace: ").Append(NL)
				.Append(inner.StackTrace).Append(NL);
			if (inner.InnerException != null)
			{
				Exception innerInner = inner.InnerException;
				sb.Append("Inner exception has also an inner exception. Message: ")
					.Append(innerInner.Message).Append(". ").Append(NL)
					.Append("Stacktrace: ").Append(innerInner.StackTrace);
							
			}
			sb.Append(NL).Append(NL);
		}
	}
	else
	{
		sb.Append("No inner exceptions.").Append(NL);
		sb.Append("Plain message: ").Append(aggregateException.Message).Append(NL);
		sb.Append("Stacktrace: ").Append(aggregateException.StackTrace).Append(NL);
	}

	_loggingService.LogFatal(this, sb.ToString());
}

…where “NL” refers to a private variable to shorten Environment.NewLine:

private readonly string NL = Environment.NewLine;

This is enough for now. We’ll continue with the service implementation in the next post.

View the list of posts on Architecture and Patterns here.

Advertisements

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

3 Responses to Using a Windows service in your .NET project part 7: Windows Service body part 1

  1. Pingback: Architecture and patterns | Michael's Excerpts

  2. Ben says:

    Hi Andras,
    I want to say thank you. I really enjoy reading your blog.
    This blog makes me a better developer!

    Btw: This post is missing a link to the last part of the series.

    Best regards from Germany
    Ben

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

ultimatemindsettoday

A great WordPress.com site

iReadable { }

.NET Tips & Tricks

Robin Sedlaczek's Blog

Developer on Microsoft Technologies

HarsH ReaLiTy

A Good Blog is Hard to Find

Softwarearchitektur in der Praxis

Wissenswertes zu Webentwicklung, Domain-Driven Design und Microservices

the software architecture

thoughts, ideas, diagrams,enterprise code, design pattern , solution designs

Technology Talks

on Microsoft technologies, Web, Android and others

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

Anything around ASP.NET MVC,WEB API, WCF, Entity Framework & AngularJS

Cyber Matters

Bite-size insight on Cyber Security for the not too technical.

Guru N Guns's

OneSolution To dOTnET.

Johnny Zraiby

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

%d bloggers like this: