04 July 2020 | Stanisław Suchodolski

Windows Services with Topshelf

In this article we are going to talk about windows services. Our goal will be to create custom windows service with Topshelf project. Two question should come up at this moment. Why windows service and why Topshelf? Windows service because more often than not there is a requirement that forces us to deliver such a solution. Yes, there are cases when we can use something like Hangfire but sometime it is not an option. Sometime we need to create good oldfashion windows service. It is doable but not so easy to do. Plus we are force to create a lot of infrastructure, which is not logical part of application. Doing this waists our time. And time is money. That is why I started using Topshelf. This project simplifies the repetitive task and lets us focus on actual logic of windows service.

First step and goal

Now, that we know what the goal, let’s start coding. Just like with every new project the best place to start is at the project home page. In this case the URL will be http://topshelf-project.com/. Over there we can find all the basic project information and documentation.

We are all programmers. We know that the best way to learn is by doing. So we are going to create working windows service, using Topshelf. The purpose of this service will be to monitor a folder and remove files that can be dangerous. Let’s say that we are afraid of the files that have word “virus” in their name. It is not the best protection, but always something.

The Code

First order of business is to create a project and install needed NuGet packages. Console application is the project we need. The packages we are going to use are Topshelf and Topshelf.Nlog. First one contains main Topshelf features and the second allows us the add Nlog features to our Topshelf code. Now, that we have basic knowledge let’s work on the code.

public static class ExtendedMethod {     public static void Rename(this FileInfo fileInfo, string newName)     {         fileInfo.MoveTo(fileInfo.Directory.FullName + "\\" + newName);     } }

Our code is going to be using one extended method. We are going to extend FileInfo class. This method allows us to rename the file, just by providing new name of the file.

public static class UtilityMethods {     public static bool GetExclusiveAccess(string filePath)     {         try         {             using (FileStream file = new FileStream(filePath, FileMode.Append, FileAccess.Write))             {                 file.Close();                 return true;             }         }         catch (IOException)         {             return false;         }     } }

We also need one utility method. This method checks whether the file is not being use by any process. We need such a method because FileSystemWatcher is very aggressive when it comes to file access.

public class ServiceLogic {     private FileSystemWatcher _fileSystemWatcher;     private static readonly LogWriter _logger = HostLogger.Get<ServiceLogic>();       public bool Start()     {         _fileSystemWatcher = new FileSystemWatcher(@"d:\TopShelfDemo", "*.jpg");         _fileSystemWatcher.NotifyFilter = NotifyFilters.Attributes |                 NotifyFilters.CreationTime |                 NotifyFilters.FileName |                 NotifyFilters.LastAccess |                 NotifyFilters.LastWrite |                 NotifyFilters.Size |                 NotifyFilters.Security;           _fileSystemWatcher.Created += (sender, args) =>             {                 while (true)                 {                     Thread.Sleep(2500);                     if (UtilityMethods.GetExclusiveAccess(args.FullPath))                     {                         var fileInfo = new FileInfo(args.FullPath);                           if (args.Name.ToLower().Contains("virus"))                         {                             fileInfo.Delete();                             _logger.InfoFormat("{0} deleted", args.FullPath);                         }                         else                         {                             fileInfo.Rename(Path.GetFileNameWithoutExtension(args.FullPath) + "_ValidFile" + fileInfo.Extension);                             _logger.InfoFormat("{0} veryfied", args.FullPath);                         }                           break;                     }                 }                 };           _fileSystemWatcher.EnableRaisingEvents = true;           return true;     }       public bool Stop()     {         _fileSystemWatcher.Dispose();         return true;     } }

This class is the heart and soul of our service. The purpose of this class is to hold logic for windows service. It basically has two methods. Start and stop. This methods match the event that can occur on windows service. They are not all events. For example there is pause, and more.

Start method sets up file watcher and makes it react on creating jpg in specific folder. When the file is created is gets verified or deleted. All the information about service activities are being log to a file via Nlog. And the stop method cleans objects we have created in start method.

Now let’s see why Topshelf is so handy.

class Program { static void Main(string[] args) { HostFactory.Run(serviceConfig => { serviceConfig.UseNLog(); serviceConfig.Service<ServiceLogic>(serviceInstance => { serviceInstance.ConstructUsing(() => new ServiceLogic()); serviceInstance.WhenStarted(serviceLogic => serviceLogic.Start()); serviceInstance.WhenStopped(serviceLogic => serviceLogic.Stop()); }); serviceConfig.EnableServiceRecovery(option => { option.RestartService(5); option.RestartService(30); option.RestartComputer(60,"Error with top shelf demo"); }); serviceConfig.SetServiceName("TopShelfDemoServiceName"); serviceConfig.SetDisplayName("Top Shelf Demo Service Display Name"); serviceConfig.StartAutomatically(); }); } }

This is literally all the code we need to write. Configuration is childish easy. We set the logger. Wire up the events. Configure the actions on service error.

Installation

Installation of the service can be done via Windows Power Shell. To do this we need to go do bin/debug folder and run: .\TopShelfDemo install. After that, new service will be visible in system services list.

ll we need to do now is to press play button and service is working. Let’s test if it is working. I will paste some files into monitored folder.

After we done this, the log file is being populated. We can see that two files got verified and one got deleted. Because it was a virus.

Conclusion

As I shown. Topshelf makes our work easy, when it comes to creating windows services. Hope you enjoy the reading. If you are interested, the code is available at https://github.com/Nivo1985/TopShelfDemo.


Karol Rogowski