1. Home
  2. DevOps
  3. Processes and Procedures
  4. Create Generic Windows Services
  1. Home
  2. DevOps
  3. Infrastructure
  4. Create Generic Windows Services
  1. Home
  2. Workflow & Automation
  3. Windows Services
  4. Create Generic Windows Services

Create Generic Windows Services

This article walks you through the process of using a Worker Service to create a Windows Service using BackgroundService all while using .NET 6.0. This article is based off the Create a Windows Service using BackgroundService article by Microsoft.

To follow this guide, it’s expected that you have the .NET 6.0 SDK installed, are working on a Windows operating system, and are using the Visual Studio IDE.

Create the project

Start by creating a new project in Visual Studio, select the project type Worker Service and select Next.

Searching for "worker" and selecting "Worker Service" (C#)

On the next page specify a Project Name and select Next.

Naming your project

On the next page specify a Framework and select Create. Select .NET 6.0 as the Framework.

Creating a .NET 6.0 framework project

Configuring the project

Once you’ve created a Worker Service project it needs to be configured to be used as a Windows Service and the creation of a service class itself.

Install NuGet packages

Using the NuGet package manager, install the following packages:

  • Microsoft.Extensions.Hosting
  • Microsoft.Extensions.Hosting.WindowsServices
  • Microsoft.Extensions.Http

After the installation ensure that your project file contains the following:

<ItemGroup>
	<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
	<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
	<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
</ItemGroup>

Create your service class

The next step is to create your service. For this example, you’ll create a service called JokeService which will make an API call to get a joke and return it.

Enter the following code in the JokeService class within the Namespace brackets:

public class JokeService
{
    private readonly HttpClient _httpClient;
    private readonly JsonSerializerOptions _options = new()
    {
        PropertyNameCaseInsensitive = true
    };

    private const string JokeApiUrl =
        "https://karljoke.herokuapp.com/jokes/programming/random";

    public JokeService(HttpClient httpClient) => _httpClient = httpClient;

    public async Task<string> GetJokeAsync()
    {
        try
        {
            // The API returns an array with a single entry.
            Joke[]? jokes = await _httpClient.GetFromJsonAsync<Joke[]>(
                JokeApiUrl, _options);

            Joke? joke = jokes?[0];

            return joke is not null
                ? $"{joke.Setup}{Environment.NewLine}{joke.Punchline}"
                : "No joke here...";
        }
        catch (Exception ex)
        {
            return $"That's not funny! {ex}";
        }
    }
}

public record Joke(int Id, string Type, string Setup, string Punchline);

You will also need to include the following using statements at the top of the JokeService class:

using System.Net.Http.Json;
using System.Text.Json;

Update the Worker class

Replace the contents within the Namespace brackets in the Worker class with the following code:

public class Worker : BackgroundService
{
    private readonly JokeService _jokeService;
    private readonly ILogger<Worker> _logger;

    public Worker(
        JokeService jokeService,
        ILogger<Worker> logger) =>
        (_jokeService, _logger) = (jokeService, logger);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            string joke = await _jokeService.GetJokeAsync();
            _logger.LogWarning(joke);

            await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
        }
    }
}

In the code above the JokeService and ILogger are injected. In the ExecuteAsync method the JokeService is called, and the returned joke is then logged to the windows log event. This is repeated once every minute while the service is running.

Update the Program class

Inside the Program class replace the assignment of host with the following code:

IHost host = Host.CreateDefaultBuilder(args)
    .UseWindowsService(options =>
    {
        options.ServiceName = ".NET Demo Service";
    })
    .ConfigureServices(services =>
    {
        services.AddHostedService<Worker>();
        services.AddHttpClient<JokeService>();
    })
    .Build();

The UseWindowsService extension configures the app to work as a Windows Service. Here the name of the service is set to .NET Demo Service. The hosted service is registered and the HttpClient is registered to the JokeService for dependency injection.

Publish the service

The first step to publishing your service is to update your project file. Inside the PropertyGroup element, add the following elements:

<OutputType>exe</OutputType>
<PublishSingleFile>true</PublishSingleFile>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PlatformTarget>x64</PlatformTarget>

Next, right-click the project in the Solution Explorer and select Publish.

Publishing the project using the Solution Explorer

In the Publish window, for your Target, select Folder and then Next.

Publishing the target to the selected folder

On the Location page leave everything as default and select Finish.

You’ll now be brought to the publish page, here you need to adjust a few more settings before publishing, select Show all settings.

Showing all settings

A Profile Settings window will appear. Ensure that the settings match those in the screenshot below:

Configuration is set to "Release Any CPU", Target framework is "net6.0", Deployment mode is "Self-contained", Target runtime is "win-x64", Target location is your selected folder, and File publish option has "Produce single file" and "Enable ReadyToRun compilation" selected

Once you’ve updated your Profile settings and confirmed the settings match the screenshot select Save to return to the publish page. Once back to the publish page select Publish to begin the process using the publish profile you just configured.

Publish the page

Your service will then be compiled and published to Target Location.

Create, Start, Validate, Stop, and Delete the service

Once you have a published service it’s time to create the service in Windows and start it. Here you’ll be guided through the process of doing just that, validating it works, and stopping the service.

Create the service

To create a service in Windows you’ll use the native Windows Service Control Manager’s create command. First step is to open a new PowerShell window as Administrator. Then you’ll want to enter the following command, note the path to your service is the path it was published to:

sc.exe create ".NET Demo Service" binpath="C:\Path\To\App.WindowsService.exe"

You should see some output indicating the service was successfully created.

Start the service

To start the service, navigate to the Services application by pressing the Windows Key and searching Services.

Once in the Services application find your service in the list. Once found right-click it and select Start. Your service will now start running.

Right-clicking the service you created and selecting "Start" (CTRL-S)

Validate the service

After starting your service, you can validate it by pressing the Windows key and searching Event Viewer, select the Event Viewer application. In the Event Viewer, navigate to Windows Logs then Application. You should then see a Warning from your Demo Service.

Warning from your program source appearing at the top of the event viewer

You can double click the warning to view its contents and in this case view the joke that was returned.

Checking the content of the warning for the joke that was returned

Stop the service

To stop your service return to the Services application (Windows Key and search Services) and locate your service. Once your service has been found from the list, right-click it and select Stop.

Stopping the service (CTRL-O)

Delete the service

To delete the service, open a PowerShell window as Administrator and enter the following command.

sc.exe delete ".NET Demo Service"

You’ll get an output confirming the service has been successfully deleted.

Deleting the service in the PowerShell (DeleteService SUCCESS)

Debugging the service

To debug the service there’s a couple things that need to be done. If you try to debug the service normally (pressing F5) you’ll get the following error:

Error content: "Unable to attach to CoreCLR. Operation not supported. Unknown error: 0X80004005"

The reason for this error is because single file debugging currently isn’t supported and the service is set to compile to a single file. To get around this there are two steps, first is to update the project file.

Replace the following line:

<PublishSingleFile>true</PublishSingleFile>

With this:

<PublishSingleFile Condition="'$(Configuration)' == 'Release'">true</PublishSingleFile>

You can now use the Debug configuration and the service will not be compiled into a single file which will allow you to debug the service.

You can switch between configurations using the configuration dropdown.

Debugging configuration

Now you can drop a breakpoint and debug your service (press F5) if you’re on the Debug configuration.

Application being debugged

Updated on November 2, 2022

Was this article helpful?

Related Articles

Need Support?
Can’t find the answer you’re looking for? Don’t worry we’re here to help!
Contact Support

Leave a Comment