Host ASP.NET Core Application as a Windows Service

.NET Core 2.1 had a ton of cool stuff in it. David Fowler did a bunch of tweets a while back on some of the hidden gems in this release and one that really jumped out at me was the ability to host an ASP.NET Core application in a Windows Service. This post is going to walk through creating a new ASP.NET Core application and then making the changes needed for it to run as a Windows Service. I pulled most of the information I needed from the official docs.

Project Creation

We will be using the .NET CLI to create the project, but you can also use Visual Studio if you like. Open a command prompt in the directory where you want the project created and run the following commands.

dotnet new razor --no-https
dotnet new sln
dotnet sln add WindowsServiceHosted.csproj

Open the new solution in Visual Studio.

Project File Changes

Right click on your project field and select Edit.

The first step is to add a runtime identifier. The docs are using win7-x64 so we are going to use the same. I did try using win and win7 but they don’t work since there isn’t a specific runtime associated with them. In this same step, we are going to add a reference to the Microsoft.AspNetCore.Hosting.WindowServices NuGet package.

Before:
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" />
  </ItemGroup>

</Project>

After:
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <RuntimeIdentifier>win7-x64</RuntimeIdentifier>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Microsoft.AspNetCore.Hosting.WindowsServices" Version="2.1.1" />
  </ItemGroup>

</Project>

Program Class Changes

Open the Program project’s  class. In the Main function, Run call on the Web Host needs to change to RunAsService.

Before:
CreateWebHostBuilder(args).Build().Run();

After:
CreateWebHostBuilder(args).Build().RunAsService();

Next, in the CreateWebHostBuilder function, we need to change the content root to be the directory of the application. We are using the Process class to pull the filename and using that to get the directory the process is running in.

Before:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>();

After:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseContentRoot(Path
                        .GetDirectoryName(Process
                                          .GetCurrentProcess()
                                          .MainModule
                                          .FileName))
        .UseStartup<Startup>();

Publish

In the pre-core versions this step could have been skipped, but for .NET Core the only way to get an actual exe out of your project that can be installed is to publish the application. You can either use the .NET CLI or Visual Studio to publish the application. I’m going to use the following .NET CLI command run from the same directory as the project file.

dotnet publish

Since I didn’t specify a configuration value the project was built in debug and ended up in the bin\Debug\netcoreapp2.1\win7-x64\publish directory.

Installation

Open a command prompt in admin mode and run the following command to create a windows service. The binPath needs to be the full path to your exe or your service will fail to start even it is created successfully.

sc create WindowsServiceHosted binPath= "C:\WindowsServiceHosted\bin\Debug\netcoreapp2.1\win7-x64\publish\WindowsServiceHosted.exe"

Also, note that the space after binPath= and before the exe name is needed.

Service Management

Now that the service is installed run the following command to start it.

sc start WindowsServiceHosted

After the service is started you can open a browser and go to http://localhost:5000/ and see your application running.

To check the state of your service use the following command.

sc query WindowsServiceHosted

To stop your service use the following command.

sc stop WindowsServiceHosted

Finally, to uninstall your service use the following command.

sc delete WindowsServiceHosted

Debugging

While debugging a Windows Service can be done, it is a pain. Thankfully the docs walk us through away to run the application normally if it is run in debug mode, but this does require more changes in the Program class.

First, change the CreateWebHostBuilder back to its original state.

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>();

Next, in the Main function, we have to decide if we are running as a service or not and if so switch around how we start the application.

public static void Main(string[] args)
{
    var runAsService = !Debugger.IsAttached;
    var builder = CreateWebHostBuilder(args);

    if (runAsService)
    {
        builder.UseContentRoot(Path
                               .GetDirectoryName(Process
                                                 .GetCurrentProcess()
                                                 .MainModule.FileName));
    }

    var host = builder.Build();

    if (runAsService)
    {
        host.RunAsService();
    }
    else
    {
        host.Run();
    }
}

For this example, we are going to run as a service only if the debugger isn’t attached. You can see how the rest of the codes is using this runAsService boolean to change between the setup needed for a service and that of a normal web application host.

Wrapping Up

I’m very happy to have the ability to host an ASP.NET Core application as a Window Service. This seems to be a case I run into a lot more that one would think.

I hope to see things that simplify Windows Services like Topshelf add support for .NET Core 2.1 (this issue has links to all the issues related to 2.1 if you want to check on the progress). It will be nice to have .NET Core Windows Services with the same level of support and the previous version of .NET.


Also published on Medium.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.