Mobile and web applications might be all the rage these days, but a lot of the real work still gets done via background services and scheduled tasks. One of the common problems with .NET Core when it first came out was how to create Windows Services. This article focuses on how to take code that was designed to be portable with .NET Core and use it in a Windows Service.
The Problem: .NET Core Does Not Like Windows Specific “Stuff”
The strength of .NET Core is the portability of your code across multiple types of apps and operating systems. This requires a unified surface area of APIs. Anything too OS specific is not supported. For example, Windows registry, WMI, Microsoft specific cryptography APIs, and… Windows Services are some quick examples. Across the entire .NET Core framework there is tiny trade-offs and differences here and there based on Windows vs Linux.
Windows Services only work with the full .NET Framework. The good news is you can mix .NET projects that target .NET Core and the full .NET Framework.
You effectively have 2 options now when creating a Windows Service:
- Standard .NET 4.5 Windows App referencing .NET 4.5 packages/assemblies
- Standard .NET 4.5 Windows App referencing .NETStandard packages
For the last few years, all .NET code has been written with .NET 4.5 and basic assembly references or NuGet packages. With .NET Core, we now reference packages based on the .NETStandard. This is what enables the cross-platform capabilities.
Most popular 3rd party libraries have all switched to the new .NETStandard. Common libraries like JSON.NET, log4net, NLog, Dapper, Entity Framework and many others now support it. Our own StackifyLib package for sending application errors, logs, and metrics to our platform has been converted. You are likely these days to be referencing .NETStandard packages and you don’t even know it!
The key to making a .NET Core Windows Service is ensuring your code is in a shared class library that targets .NETStandard. You can then use that code in non-Windows apps, ASP.NET, and Windows Services. If you also need to write some Windows specific code, you can do that directly in the Windows Service.
Our product, Prefix, is a good example of this. We converted it to .NET Core so that we could support .NET and Java on Windows, but then also make a version for Java on the Mac. All of the core code for the app is in shared class libraries. Our Windows Service uses those libraries and we have some Windows specific code in a separate library. For us, that includes some special code for WMI, IIS, etc.
Creating .NET Core Windows Services
Warning About Visual Studio 2015
Visual Studio 2015 does not allow project references between .NET Core (xproj) and .NET Framework (csproj) projects. If you are working with .NET Core, I would highly recommend upgrading to Visual Studio 2017 so you can use all the latest tooling. There are a lot of changes around the project files.
Mixing .NET Framework & .NET Core with Visual Studio 2017
With Visual Studio 2017, all projects now use csproj files again. You can easily create project references between your projects. Windows Services are much easier to create with 2017.
If you plan on deploying to Windows and Linux, I would recommend putting your code in a separate project that acts as a shared class library. You can then access this code from an ASP.NET application, console application, or Windows Service. This would give you the flexibility to deploy your code and applications to Windows or Linux with maximum flexibility.
I suggest treating your Windows Service as basically a shell and putting all of your key application logic in the share .NETStandard library. If you have code that is very Windows specific, you can put that code directly in the Windows Services project.
For this article, I created a really simple sample project to walk through how to make all this work nicely. I created a .NET Core ASP.NET app and Windows Service both. They both reference a shared class library. This shows off how to mix .NET Core and .NET Framework projects together.
Note: You are able to make a Windows Service exactly the same way you always have as long as your .NET Core code is in a .NETStandard library. I will skip going into any real details about what a Windows Service is or how to make one in detail. This article is focused on getting .NET Core code to work with your Windows Service.
After creating all three projects here are the default frameworks that Visual Studio 2017 is using for all three:
WindowsService1 – .NET Framework 4.5.2
WebApplication1 – .NETCoreApp1.1
ClassLibrary1 – .NETStandard 1.4
I modified both by my web app and Windows Service to reference ClassLibrary1. Adding the project reference worked just fine, but I cannot build. I get this error below:
<span class="str">'C:\BitBucket\CoreWindowsServicesExample\ClassLibrary1\ClassLibrary1.csproj'</span><span class="pln"> targets </span><span class="str">'.NETStandard,Version=v1.4'</span><span class="pun">.</span> <span class="typ">It</span><span class="pln"> cannot be referenced </span><span class="kwd">by</span><span class="pln"> a project that targets </span><span class="str">'.NETFramework,Version=v4.5.2'</span><span class="pun">.</span>
This means that my Windows Service can’t reference the class library based on the current target frameworks. A .NET Framework 4.5.2 app can’t reference a class library that is targeting .NETStandard 1.4. They are not compatible. There is a simple fix, though.
To fix my compilation error, I changed my Windows Service project to target .NET Framework 4.6.1 and now it works fine!
My ASP.NET web app that targets .NETCoreApp1.1 works perfectly with .NETStandard with no changes needed. Now we have a shared class library that can be used in an ASP.NET web app on Linux plus a Windows Service!
Currently, the class library is my example using .NETStandard 1.4. You likely will want to target 1.5 to have maximum compatibility with other libraries that you may want to use. If you change your Windows Services project to target .NET Framework 4.6.1, it will then work class libraries targeting .NETStandard 1.5.
DLL Hell is Gone. Welcome to .NET Framework/Core/Standard Hell! ?
To make all of this work, you have to understand the target frameworks that you should be using so that you can reuse the same code across Windows and Linux. After all, that is the true power of .NET Core. Unfortunately, all of this is pretty confusing. It is hard to grasp the different between .NET Framework, .NETCoreApp and .NETStandard. Hopefully, this article and example help make it less confusing and not more confusing.
The goal of .NETStandard is to solve the exact problem we are highlighting in this article. It provides a common surface area that can be targeted by different types of applications. You can create a class library and use it within an application deployed to Windows and Linux both. This gives you ultimate portability and reuse of your code.
If you really want to get into the mess of all this, you should try making a NuGet package that requires references to ASP.NET Core. You are forced to target .NETStandard 1.6 and .NET 4.5.1 both. You have to do this so people using ASP.NET Core, either way, can use your package. There is no way to target a single framework that works for both. ?
“Self Hosting” ASP.NET Core in a Windows Service
You can absolutely “self-host” the Kestrel web server and ASP.NET Core in a Windows Service. This is similar to self-hosting ASP.NET Web API or WCF in a Windows Service. Although, using IIS as a reverse proxy in front of Kestrel is the preferred deployment option, especially for public facing web servers.
Self-hosting in a Windows Service is most likely used in software that is distributed. Prefix is a good example of this. We decided to make the UI all HTML based and we leverage an embedded web server within the app to power it. Self-hosting enables some interesting deployment scenarios without requiring the full installation of IIS on Windows desktops.
The goal of this article was to showcase how to take a .NET Core project and deploy it as a Windows Service. To do so, you need to make sure your code is in a class library that targets .NETStandard. You can then use that class library in a Windows Service and in other .NET Core projects.
A Windows Service project must use the full .NET Framework. If you don’t want to deploy any of your code to a Mac or Linux, you really don’t need to use “.NET Core CLR” at all. You can target the full .NET Framework for all of your projects and still use .NET Core features like ASP.NET Core.
The real trick is mixing multiple projects that could be web apps, Windows apps, shared libraries, and non-Windows apps. To solve this problem you will want to use class libraries and .NETStandard so your code base can be shared.