Migrating a full-framework Windows Service to .Net Core

Migrating a full-framework Windows Service to .Net Core

With all the buzz around the release of .Net Core 2.0 many people are probably wondering how much effort it’s going to be to get their existing code over to .Net Core in Windows before they can even begin to think of making the app run in a cross-platform environment. Well, fear not! In this post, I’ll take a fairly simple Windows Service using the full .Net framework (4.6.1 to be exact) and convert it to a .Net Core 2.0 compatible app.

Full fat Windows service

The code for this service can be found on my GitHub. This service is a simple Windows Service that is intended to be deployed to AWS.

The service will register an SNS topic subscription using the amazing JustSaying
library. Once the service is started, it will publish a message and consume the message itself. The contents of the message will then be written to an S3 bucket.

Windows Service hosting is provided by TopShelf and the HTTP endpoints for AWS health checks are served via Nancy self-hosting.

If you’re interested; go and clone the code, hit F5 and have a poke around. You’ll see some console logs for the following:

  • A JustSaying bus get created
  • Some listeners for a topic set up
  • A test message published to that topic
  • The contents of the message get written to an S3 bucket

Note: if you are going to run this solution, be sure to do a find & replace for accesskey and secretKey and provide your own AWS credentials! You’ll also have to change the bucket name that gets set in the GenericMessageHandler as bucket names have to be unique.

Now the fun begins

The original solution was created in Visual Studio 2017 but Visual Studio Code is the de-facto editor of choice for .Net Core as it’s cross platform and has good support for .Net Core development. Setting up VS Code is not something that I am going to cover here but really all you need to do is download, and install both the app and the C# extension for VS Code and you’ll be good to go.

There is an element of cheating here. When I say “Windows service” in .Net Core, I actually mean a website hosted in IIS that listens to our messages. The website part will just be for our AWS health checks that we saw before in the full framework version and also, by being hosted in a web server, our app will be kept alive. Just like a Windows service!

Setting up your skeleton

Similar to the full framework solution, this solution will just have a single project that’s the same name as the solution. Below is the output from my creation of the project structure:

Looking at the highlighted lines above these are the actions that I’m taking:

  1. Creating a new solution
  2. Making a new directory for our project
  3. Changing to that new directory
  4. Creating a new .Net Core project using the webapi template (more about these templates here)
  5. Go back up to the solution directory
  6. Add our new project to the solution file (Note: solution files are kinda optional but I like them for organisational purposes and they play nicer with Visual Studio)

Now that we have a skeleton for our project, we’ll have a bunch of files in the folder:

Tidy up and build

Optional tidying up

Eagle-eyed readers will have spotted the folder named wwwroot in the section above. This folder is primarily used for storing static assets such as images and CSS files when building websites. We don’t need that so feel free to just delete this folder. If you do delete this folder, ensure that you also remove the reference to include it in the .csproj file. It should look something like this:

Set up AWS health checks

Being as we are going to run this on AWS, we need to add some of the keep-alive endpoints that it uses to decide whether the EC2 node that your code is on is healthy. The template generated a ValuesController for us. Rename this file to HealthController (VS Code is sometimes a bit weird and you may have to rename both the file and the class). You can then remove all of the code inside of the class (keep the imports, namespace and class declarations) and replace it with these two lines:

Now we have the same 3 health check routes that we used the Nancy self hosting for in our full framework version. Note the lack of App_Start folder with a RouteConfig class. That’s because we are doing all of the route configuration via attributes in the controller (back to the original MVC way!).

Note: you can have convention based routing if you wish, just set this up at Startup. More on this later.

Build

One thing to know before we get started: if you’re ever asked to restore stuff, hit yes, it’s just .Net SDK and NuGet things. If you’re ever asked to add configurations or similar by VS Code, say yes, it will add some scripts into a .vscode folder.

In VS Code, hit Ctrl + Shift + B (all the keybindings that I give will be default Windows ones) to run the default build task. As you haven’t set one up yet, you’ll be prompted by VS Code to add the default build configuration. Once the build finishes, you’ll see some output like below and new bin and obj folders will have been created for you. Congratulations, you’ve just built a .Net Core Web API!

Adding the packages that we need

If you have a look at the .csproj file that was generated, you’ll notice that it’s a lot smaller than the equivalent file in the full framework project (discounting the NuGet package references). This is because all of the required files no longer have to be explicitly referenced in the .csproj as the build process is now smart enough to vacuum up all of the files in the same folder and include them by default. You can still explicitly add files and folders but there’s no need here.

In the full framework project, there were a number of NuGet packages to handle stuff such as listening for messages. We need to install some similar (but not exactly the same!) packages in our project. Back to the console (I’m using the integrated Git bash terminal in VS Code if you were wondering), the command to add new packages is dotnet add package so let’s do that for the packages that we need:

  • JustSaying (add in -v 5.0.0-beta-313 as the .Net Core compatible version is a pre-release package at the time of writing)
  • AWSSDK.S3
  • Microsoft.Extensions.DependencyInjection
  • Microsoft.Extensions.Logging.Console

Whilst the first two packages should be obvious, the last two may not be: Microsoft.Extensions.DependencyInjection is only used here to provide an extension method for Microsoft’s built-in DI framework. We could do without this generic method and do some type casting instead, but this is a lot neater. Microsoft.Extensions.Logging.Console simply provides us with hooks to set up console logging for our app in an abstracted way (no Console.WriteLine() thanks).

After installing all of these, the .csproj file should look like this:

Porting the existing code

The simplest way to move things over is to copy the classes that we need! Copy over GenericMessage.cs, GenericMessageHandler.cs and GenericMessageService.cs to the same folder as the new .csproj file. Open these files up in VS Code now that they are in your project folder and delete all of the imports in each of these files because the imports will be a bit different and it’s easier to start from scratch.

No changes are needed for GenericMessage.cs as it’s just a POCO. Moving on!

A lot of the red squigglies can be fixed with some simple importing of namespaces and you should go ahead and do that to leave us with the real problems. For the other changes, let’s work through the files one-by-one:

GenericMessageHandler.cs

We no longer want to create a new logger in the constructor using the NLog LogManager and instead want to be passed the ILoggerFactory to create an abstracted logger that we can add providers for in our setup code.

After this change our constructor will look like this:

The _logger field is of the same type, ILogger, but this one comes from the Microsoft.Extensions.Logging namespace rather than the NLog one.

Although the name of the interface is the same, the methods for our new ILogger have slightly different names. It’s a quick job to change both the Info() and the Error() calls to LogInformation() and LogError() respectively.

The last change for this file is changing ContentType = ContentType.ApplicationJson to ContentType = "application/json" as the .Net Core AWSSDK doesn’t have the helper constant built in. No big deal.

GenericMessageService.cs

As above, change the constructor to take an ILoggerFactory and switch up the method names to remove some more red squigglies.

We’ll also have to pass our ILoggerFactory to the constructor of the GenericMessageHandler that gents created when setting up our JustSaying stack.

JustSaying v4 (i.e. full-framework) had a dependency on NLog but the pre-release v5 hooks in to all of the logging goodness that we’ve been seeing above so v5 no longer has the same dependency. NLog can still be used and we could set it up as a logging provider later but JustSaying is no longer tied to using NLog and the logging abstractions gives us more options. We can once again pass the ILoggerFactory when creating the stack for those hooks.

With those changes, creation of the JustSaying stack should look like this:

Startup and wiring everything up

If you open Program.cs you’ll see that it’s very different to our TopShelf host-builder code from the previous version. Currently, it does the following:

  • Creates a default WebhostBuilder with any command line parameters passed in
  • Tells it to use Startup as it’s start-up class
  • Builds the IWebHost
  • Starts running the web host

It’s the invocation of IWebHost.Run() that will start and keep our “website” alive. This is how we achieve the “always on” service-like behaviour but we also need to start our service and add some more logging hooks. After those changes, Program.cs will look like this:

We’re first splitting the build and running of the webhost in two. This is because any code in this file that’s called after Run() won’t get run. The additions to build the webhost have been highlighted in the snippet. All we’ve done here is tell our “website” to use IIS and added two different logging providers, one for the console and one to the Debug window. A good explanation of logging and how to add providers can be found in the Microsoft Documentation. This is where we’d hook in other logging providers such as NLog or Serilog as I mentioned above.

Whilst the webhost is held in captivity by the intermediary variable, we can pull our service out of the built-in DI container and start it. We can then let the webhost loose by calling Run().

It doesn’t look like we’ve done much here and we haven’t. All of the magic really lives in the Startup class that we told the webhost builder to use…

Startup.cs

If you have used OWIN before, the Startup class may feel familiar to you as it’s very similar to OWIN Startup classes. The purpose of this class is essentially to allow you to set up everything your application needs. Here’s what a “standard” Startup.cs looks like before we add any thing to it:

Extensive documentation on how this system works and what hooks it offers can once again be found over at the Microsoft documentation site but here’s a walkthrough of what is going on above:

  • The IConfiguration object passed into the constructor is used to get configuration values from whatever configuration providers (e.g. configuration files, environment variables or command line parameters) are set up. For information in how config is handled in .Net Core, take a look here
  • The ConfigureServices() method is where we can set up what we want to make available to our DI container. Above, the AddMvc() call is just an extension method to add a bunch of MVC services to the container in one call
  • The Configure() is where we can configure the HTTP pipeline and add any additional middleware components in. In the plain startup shown above, we’re telling our app to use the MVC framework and also to show us the developer exception page if we are running in a development environment. Once again, full documentation is available

The only thing that we need to add to ConfigureServices() is a single line to add our GenericMessageService to the container. The code for this is services.AddSingleton<GenericMessageService>();. I’ve put it above the line to add MVC service but it doesn’t matter where it goes.

For ConfigureServices(), a few more changes are needed. The full code for this method is below and I’ll walk through the changes:

All of the code that has been added has been to add additional behaviour when our application stops. Both IServiceProvider and IApplicationLifetime are method parameters that weren’t there before. The nice thing about the Configure() method is that any services that are available to it, can just be passed in to the constructor and the framework will do the rest.

Using IApplicationLifetime we’re registering a delegate, OnShutdown() to be called when the application stops. This local method will pull our GenericMessageService out of the IServiceProvider and call Stop() so that our messaging service can be shut down gracefully.

These additions to Configure() are not necessarily needed but I think they paint a good picture of what the purpose of this method is and how it can be used.

Go!

From here you can put some breakpoints in the code and hit F5 to start debugging! The debugging controls should feel familiar to anyone who has used Visual Studio before.

Wrapping up

I know that this is a simple example but in a lot of cases, I do think that transitioning to .Net Core is more around getting the startup and hosting right more than anything else.

The code for both the full framework and .Net Core projects can be found on my GitHub.

Leave a Reply

%d bloggers like this: