Host An ASP.NET Core Application As A Windows Service

Introduction
 
An ASP.NET Core application can be hosted using various methods including IIS and HTTP.sys servers. This article is about hosting ASP.NET Core applications as a Windows Service. Windows Service feature is available only on Windows platform. This is one of the ways to host ASP.NET Core applications on Windows platform without using IIS.
 
Implementation of hosting an ASP.NET Core application as Windows services is not relevant to .NET Core. When an application is hosted as Windows Service, it can start/stop automatically if the service status is start/stop.
 
When ASP.NET Core applications host as a Windows service, the application must run on the .NET Framework, so we need to specify appropriate values for TargetFramework in csproj file. To demonstrate the example, I have run my application on .NET Framework 4.6.1.
  1. <PropertyGroup>  
  2.   <TargetFramework>net461</TargetFramework>  
  3. </PropertyGroup>    
The first step is to install Microsoft.AspNetCore.Hosting.WindowsServices package from nuGet. This package can be installed either by using NuGet Package Manager or by using .NET Core CLI.
 
Using nuget Package Manager
  1. PM > Install-Package Microsoft.AspNetCore.Hosting.WindowsServices  
Using .NET Core CLI 
  1. > dotnet add package Microsoft.AspNetCore.Hosting.WindowsServices  
This package contains the extension method named "RunAsService" for IWebHost. This extension method runs the specified web application as a Windows Service and port is blocked until the service is stopped. Here, we want to run our application as a service, so we need to call IWebHost.RunAsService() method instead of IWebHost.Run() in the main method of program.cs file. Also, we need to specify the path for content root directory to publish the location.
 
Program.cs
  1. using Microsoft.AspNetCore;  
  2. using Microsoft.AspNetCore.Hosting;  
  3. using Microsoft.AspNetCore.Hosting.WindowsServices;  
  4. using System.Diagnostics;  
  5. using System.IO;  
  6.   
  7. namespace TestApp  
  8. {  
  9.     public class Program  
  10.     {  
  11.         public static void Main(string[] args)  
  12.         {  
  13.             var pathToExe = Process.GetCurrentProcess().MainModule.FileName;  
  14.             var pathToContentRoot = Path.GetDirectoryName(pathToExe);  
  15.   
  16.             var host = WebHost.CreateDefaultBuilder(args)  
  17.                 .UseContentRoot(pathToContentRoot)  
  18.                 .UseStartup<Startup>()  
  19.                 .Build();  
  20.   
  21.             host.RunAsService();  
  22.         }  
  23.     }  
  24. }  
The next step is to publish this application and register as a Windows Service. To register a Windows service, open a command shell with administrative privileges and use sc.exe command-line tool to create and start a service. In the following command, binPath is an executable of the application.
  1. >sc create TestService binPath="D:\PubTest\TestApp.exe"  
  2. >sc start TestService  
 
When the above command executes successfully and Windows service has started, we can browse the same path as when running without the service (i.e. default url is http://localhost:5000).
 
 
Provide a way to host application outside of a service
 
When running and hosting the application as a Windows service, it is very difficult to debug. So we can add a condition so that the application does not run as service.
  1. public static void Main(string[] args)  
  2. {  
  3.     var pathToExe = Process.GetCurrentProcess().MainModule.FileName;  
  4.     var pathToContentRoot = Path.GetDirectoryName(pathToExe);  
  5.   
  6.     IWebHost host;  
  7.   
  8.     if (Debugger.IsAttached || args.Contains("console"))  
  9.     {  
  10.         Debugger.Launch();  
  11.         host = WebHost.CreateDefaultBuilder()  
  12.                 .UseContentRoot(Directory.GetCurrentDirectory())  
  13.                 .UseStartup<Startup>()  
  14.                 .Build();  
  15.         host.Run();  
  16.     }  
  17.     else  
  18.     {  
  19.         host = WebHost.CreateDefaultBuilder(args)  
  20.                 .UseContentRoot(pathToContentRoot)  
  21.                 .UseStartup<Startup>()  
  22.                 .Build();  
  23.         host.RunAsService();  
  24.     }  
  25. }  
Now, I am running my application using dotnet run commad and passing "console" string as an argument, so .NET framework hosts our application on a port configured in launchSettings.json.
  1. > dotnet run console  


Handle starting and stopping events of Windows service

To handle OnStarting, OnStarted, and OnStopping events, we need to register our own WebHostService class that has these methods. Following are the steps for such events:
 
Create web host service class which derives from WebHostService class.
  1. public class MyWebHostService : WebHostService  
  2. {  
  3.     public MyWebHostService(IWebHost host) : base(host)  
  4.     {  
  5.     }  
  6.   
  7.     protected override void OnStarting(string[] args)  
  8.     {  
  9.         System.Diagnostics.Debugger.Launch();  
  10.         base.OnStarting(args);  
  11.     }  
  12.   
  13.     protected override void OnStarted()  
  14.     {  
  15.         base.OnStarted();  
  16.     }  
  17.   
  18.     protected override void OnStopping()  
  19.     {  
  20.         base.OnStopping();  
  21.     }  
  22. }  
Next step is to create an extension method for IWebHost that registers our custom WebHostService and passes to ServiceBase.Run method.
  1. public static class WebHostServiceExtensions  
  2. {  
  3.     public static void RunAsCustomService(this IWebHost host)  
  4.     {  
  5.         var webHostService = new MyWebHostService(host);  
  6.         ServiceBase.Run(webHostService);  
  7.     }  
  8. }  
Now, we need to call a new extension method RunAsCustomService instead of RunAsService in Program.Main method.
  1. public static void Main(string[] args)  
  2. {  
  3.     var pathToExe = Process.GetCurrentProcess().MainModule.FileName;  
  4.     var pathToContentRoot = Path.GetDirectoryName(pathToExe);  
  5.   
  6.     var host = WebHost.CreateDefaultBuilder(args)  
  7.         .UseContentRoot(pathToContentRoot)  
  8.         .UseStartup<Startup>()  
  9.         .Build();  
  10.   
  11.     host.RunAsCustomService();  
  12. }  
If we want any service that is required in our custom service from dependency injection, such as a logger, that can be obtained from IWebHost.Services property using GetRequiredService method.
  1. public class MyWebHostService : WebHostService  
  2. {  
  3.     private ILogger _logger;  
  4.   
  5.     public MyWebHostService(IWebHost host) : base(host)  
  6.     {  
  7.         _logger = host.Services.GetRequiredService<ILogger<MyWebHostService>>();  
  8.     }  
  9.   
  10.     ...  
  11.     ...  
  12. }  
Summary
 
This article explained about hosting ASP.NET Core applications as a Windows service. This is one of the recommended ways to host an ASP.NET Core app on Windows without using IIS.
 
You can view or download the source code from the following GitHub link.