Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Build a Windows Worker Service Using .NET Core 3.1 for Removing Old Files in Folders

4.89/5 (18 votes)
4 Sep 2020CPOL5 min read 38.9K   1.3K  
A Windows service that removes files older than a specific date from a configurable set of folders
Creating a Windows service used to be a cumbersome process but has become much easier with the new worker service framework introduced in .NET Core 3.0. The code in this article is mostly to introduce the reader to worker services and explain how to deploy a worker service as a Windows services, but the application hopefully also has some real usefulness to it in itself.

Disclaimer

The service we are creating here is used for removing files older than a specific date from a list of folders specified in a stand-alone configuration file. This application has been tested to the extent that I know that it works in my own environment. I don’t give any guarantees that it will work flawless in yours. Before using this in a production environment, I strongly suggest that you perform some additional testing.

Introduction

In this article, I will go through how you can create a simple Windows Service using the Worker Service template in Visual Studio.

Before Worker Services in .NET Core 3.0 was introduced, you would probably create your Windows services using the .NET Windows Service template. I always thought that the experience of using that template never was very intuitive. The first issue you might encounter is that you can’t run the service within your Visual Studio environment without implementing some code hacks. Also, the template didn’t give you much guidance for what is actually needed to make the service run as expected. Fortunately, with .NET 3.0 and Worker Services, all of this has become much easier.

The Solution

To follow this article, you need to have Visual Studio 2019 and .NET Core 3.0 installed on your environment.

First, you need to create a new project and choose the Worker Process template as outlined in the image below:

Image 1

Adding Configuration Settings

In the appsettings.json file, we will need to add a few configurations. One configuration for the threshold stating the max allowed age for the file before we delete it. Let’s call this setting NumberOfDaysBeforeDelete. I set the value for this to 90 days. Besides this, we need the file path to the stand-alone configuration file containing the list of folders to monitor. I call this setting ConfigurationFilePath and point that to a location in my c:\temp folder. The last setting is needed for configuring the interval for which this service will be executing our custom code. I call this setting RunIntervallInHours and I set the value to 12 hours. This means that this service will check the content of each folder in the configuration file twice a day and remove all files with a last written date of more than 90 days. These three configuration settings are grouped into a node in the appsettings.json file which I call “App.Configurations”, see here below:

JavaScript
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "App.Configurations": {
    "ConfigurationFilePath": "C:\\Temp\\CleanUpFolders\\CleanUpConfigurations.config",
    "NumberOfDaysBeforeDelete": "10",
    "RunIntervallInHours": "12"
  }
}

Please note that you have both an “appsetttings.json” file as well as an “appsettings.development.json” file in your project. Which appsettings.json file is used during runtime depends on your environmental settings. When running this service from Visual Studio, it will default to use the development settings as configured in the file Properties/launchSettings.json.

Add Configuration Files With List of Folders

I create a file named CleanUpConfigurations.config in c:\Temp\CleauUpFoders\ and put the following two lines in it:

C:\Temp\CleanUpFolders\Folder1
C:\Temp\CleanUpFolders\Folder2

I also create the folders “Folder1” and “Folder2” in the same location.

Our Code

In the newly created solution, two files are initially of interest to us, program.cs used to start the application and worker.cs which will contain our custom code for this solution. In the program.cs file, you will find code for setting up the host environment. To facilitate running this application as a Windows service, you need to add the following NuGet package.

PowerShell
Install-Package Microsoft.Extensions.Hosting.WindowsServices

With that package installed, you can add the row .UseWindowsService() after the Host.CreateDefaultBuilder(args), so that you end up with the following in your program.cs file.

C#
public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
            .UseWindowsService()
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<worker>();
                });
    }

In the worker.cs file, we start by injecting an instance of IServiceScopeFactory to the constructor. This will be used to access the appsettings configuration file.

C#
public Worker(ILogger<worker> logger, IServiceScopeFactory serviceScopeFactory)
        {
            _logger = logger;
            _serviceScopeFactory = serviceScopeFactory;
        }

To make sure that configurations are read every time the service is started, I override the method StartAsync and retrieve my application configurations here:

C#
public override Task StartAsync(CancellationToken cancellationToken)
        {
            _configuration = _serviceScopeFactory.CreateScope().
                             ServiceProvider.GetRequiredService<iconfiguration>();
            _numberOfDaysBeforeDelete = int.Parse(_configuration
                                        ["App.Configurations:NumberOfDaysBeforeDelete"]);
            _runIntervallInHours = int.Parse(_configuration
                                            ["App.Configurations:RunIntervallInHours"]);
            _folderPaths = File.ReadAllLines(_configuration
            ["App.Configurations:ConfigurationFilePath"]).Select(x => x.Trim()).ToList();

            return base.StartAsync(cancellationToken);
        }

Note how I am reading all lines from the file stated in the ConfigurationFilePath setting and put them in the list _folderPaths.

The following variables are declared in the Worker class:

C#
private IList<string> _folderPaths;
private int _numberOfDaysBeforeDelete;
private int _runIntervallInHours;

Our code for retrieving folders from our configuration file and remove all files older than 90 days will be placed in the method ExecuteAsync. ExecuteAsync takes a CancellationToken as a parameter. The CancellationToken is very important because it will be used to identify when the service is stopped. When this happens, the property IsCancellationRequested will be set to true. Hence, the first line of code in our ExecuteAsync is:

C#
while (!stoppingToken.IsCancellationRequested)

I will do a second check for IsCancellationRequested in this method further down to make sure that I stop the execution as quickly as possible when receiving a cancellation request. The code for getting files older than x number of days and deleting them are written in two lines:

C#
var files = Directory.GetFiles(path).Select(file => new FileInfo(file)).Where
(file => file.LastWriteTime < DateTime.Now.AddDays(-1* _numberOfDaysBeforeDelete)).ToList();

// Delete found files
files.ForEach(file => file.Delete());

Before closing the while loop, I put in a delay. Since our setting _runIntervallInHours is set to 12 hours, this code will put the service to rest for 12 hours before continuing the execution.

C#
await Task.Delay(TimeSpan.FromHours(_runIntervallInHours), stoppingToken);

Notice the use of the cancellation token here which causes the waiting process to be cancelled when the windows service stops.

The complete code for the ExecuteAsync method will look like this:

C#
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

                foreach (var path in _folderPaths)
                {
                    if (!stoppingToken.IsCancellationRequested)
                    {
                        // Get old files
                        var files = Directory.GetFiles(path).Select
                                    (file => new FileInfo(file)).Where
                                    (file => file.LastWriteTime < DateTime.Now.AddDays
                                             (-1* _numberOfDaysBeforeDelete)).ToList();

                        // Delete found files
                        files.ForEach(file => file.Delete());
                    }
                }
                
                await Task.Delay(TimeSpan.FromHours(_runIntervallInHours), stoppingToken);
            }
        }

That is all the code we need. You can simply press F5 and run the application directly from Visual Studio and see that it works.

Turning the Application Into a Windows Service

Now go forward and publish the application.

Image 2

To a folder:

Image 3

Open up a PowerShell terminal as an administrator and run the following command:

PowerShell
sc.exe create FolderCleanUpService binpath= "C:\Apps\FolderCleanUpService.exe" start= auto

This will install the service on your Windows machine.

You can put your service anywhere you want, I choose here to put it in the Apps folder on my c drive. I use the start=auto flag to make sure that the service is automatically started when Windows starts. To set a description on the service, you need to execute a second command:

PowerShell
sc.exe description "FolderCleanUpService" 
"This service monitors and deletes files older than 90 days"

Press (Window + R) and run the command services.msc, then you should see a list of all your services and in that list, you should be able to see the FolderCleanUpService service. You need to manually start it the first time, but since the startup type is set to automatic, it will automatically start up itself after a reboot.

Image 4

You can remove the service using this command:

PowerShell
sc.exe delete "FolderCleanUpService"

History

  • 3rd April, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)