Host an ASP.NET Core app in a Windows Service

By Tom Dykstra

The recommended way to host an ASP.NET Core app on Windows without using IIS is to run it in a Windows Service. When hosted as a Windows Service, the app can automatically start after reboots and crashes without requiring human intervention.

View or download sample code (how to download). For instructions on how to run the sample app, see the sample's README.md file.

Prerequisites

  • The app must run on the .NET Framework runtime. In the .csproj file, specify appropriate values for TargetFramework and RuntimeIdentifier. Here's an example:

    <PropertyGroup>
      <TargetFramework>net461</TargetFramework>
      <RuntimeIdentifier>win7-x64</RuntimeIdentifier>
    </PropertyGroup>
    

    When creating a project in Visual Studio, use the ASP.NET Core Application (.NET Framework) template.

  • If the app receives requests from the Internet (not just from an internal network), it must use the HTTP.sys web server (formerly known as WebListener for ASP.NET Core 1.x apps) rather than Kestrel. IIS is recommended for use as a reverse proxy server with Kestrel for edge deployments. For more information, see When to use Kestrel with a reverse proxy.

Getting started

This section explains the minimum changes required to set up an existing ASP.NET Core project to run in a service.

  1. Install the NuGet package Microsoft.AspNetCore.Hosting.WindowsServices.

  2. Make the following changes in Program.Main:

    • Call host.RunAsService instead of host.Run.

    • If the code calls UseContentRoot, use a path to the publish location instead of Directory.GetCurrentDirectory().

    public static void Main(string[] args)
    {
        var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
        var pathToContentRoot = Path.GetDirectoryName(pathToExe);
    
        var host = WebHost.CreateDefaultBuilder(args)
            .UseContentRoot(pathToContentRoot)
            .UseStartup<Startup>()
            .UseApplicationInsights()
            .Build();
    
        host.RunAsService();
    }
    
  3. Publish the app to a folder. Use dotnet publish or a Visual Studio publish profile that publishes to a folder.

  4. Test by creating and starting the service.

    Open a command shell with administrative privileges to use the sc.exe command-line tool to create and start a service. If the service is named MyService, published to c:\svc, and named AspNetCoreService, the commands are:

    sc create MyService binPath="c:\svc\aspnetcoreservice.exe"
    sc start MyService
    

    The binPath value is the path to the app's executable, which includes the executable file name.

    Console window create and start example

    When these commands finish, browse to the same path as when running as a console app (by default, http://localhost:5000):

    Running in a service

Provide a way to run outside of a service

It's easier to test and debug when running outside of a service, so it's customary to add code that calls RunAsService only under certain conditions. For example, the app can run as a console app with a --console command-line argument or if the debugger is attached:

public static void Main(string[] args)
{
    bool isService = true;
    if (Debugger.IsAttached || args.Contains("--console"))
    {
        isService = false;
    }

    var pathToContentRoot = Directory.GetCurrentDirectory();
    if (isService)
    {
        var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
        pathToContentRoot = Path.GetDirectoryName(pathToExe);
    }

    var host = WebHost.CreateDefaultBuilder(args)
        .UseContentRoot(pathToContentRoot)
        .UseStartup<Startup>()
        .UseApplicationInsights()
        .Build();

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

Handle stopping and starting events

To handle OnStarting, OnStarted, and OnStopping events, make the following additional changes:

  1. Create a class that derives from WebHostService:

    internal class CustomWebHostService : WebHostService
    {
        public CustomWebHostService(IWebHost host) : base(host)
        {
        }
    
        protected override void OnStarting(string[] args)
        {
            base.OnStarting(args);
        }
    
        protected override void OnStarted()
        {
            base.OnStarted();
        }
    
        protected override void OnStopping()
        {
            base.OnStopping();
        }
    }
    
  2. Create an extension method for IWebHost that passes the custom WebHostService to ServiceBase.Run:

    public static class WebHostServiceExtensions
    {
        public static void RunAsCustomService(this IWebHost host)
        {
            var webHostService = new CustomWebHostService(host);
            ServiceBase.Run(webHostService);
        }
    }
    
  3. In Program.Main, call the new extension method, RunAsCustomService, instead of RunAsService:

    public static void Main(string[] args)
    {
        bool isService = true;
        if (Debugger.IsAttached || args.Contains("--console"))
        {
            isService = false;
        }
    
        var pathToContentRoot = Directory.GetCurrentDirectory();
        if (isService)
        {
            var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
            pathToContentRoot = Path.GetDirectoryName(pathToExe);
        }
    
        var host = WebHost.CreateDefaultBuilder(args)
            .UseContentRoot(pathToContentRoot)
            .UseStartup<Startup>()
            .UseApplicationInsights()
            .Build();
    
        if (isService)
        {
            host.RunAsCustomService();
        }
        else
        {
            host.Run();
        }
    }
    

If the custom WebHostService code requires a service from dependency injection (such as a logger), obtain it from the Services property of IWebHost:

internal class CustomWebHostService : WebHostService
{
    private ILogger _logger;

    public CustomWebHostService(IWebHost host) : base(host)
    {
        _logger = host.Services.GetRequiredService<ILogger<CustomWebHostService>>();
    }

    protected override void OnStarting(string[] args)
    {
        _logger.LogDebug("OnStarting method called.");
        base.OnStarting(args);
    }

    protected override void OnStarted()
    {
        _logger.LogDebug("OnStarted method called.");
        base.OnStarted();
    }

    protected override void OnStopping()
    {
        _logger.LogDebug("OnStopping method called.");
        base.OnStopping();
    }
}

Acknowledgments

This article was written with the help of published sources: