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 when you don't use IIS is to run it in a Windows Service. That way it can automatically start after reboots and crashes, without waiting for someone to log in.

View or download sample code (how to download). See the Next Steps section for instructions on how to run it.

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>net452</TargetFramework>
      <RuntimeIdentifier>win7-x86</RuntimeIdentifier>
    </PropertyGroup>
    

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

  • If the app will get requests from the internet (not just from an internal network), it must use the WebListener web server rather than Kestrel. Kestrel must be used with IIS 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.

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

  • Make the following changes in Program.Main:

    • Call host.RunAsService instead of host.Run.

    • If your 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 = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(pathToContentRoot)
        .UseIISIntegration()
        .UseStartup<Startup>()
        .UseApplicationInsights()
        .Build();
    
        host.RunAsService();
    }
    
  • Publish the application to a folder.

    Use dotnet publish or a Visual Studio publish profile that publishes to a folder.

  • Test by creating and starting the service.

    Open an administrator command prompt window to use the sc.exe command-line tool to create and start a service.

    If you name your service MyService, you publish your app to c:\svc, and the app itself is named AspNetCoreService, the commands would look like this:

    sc create MyService binPath="C:\Svc\AspNetCoreService.exe"
    sc start MyService
    

    The binPath value is the path to your app's executable, including the executable filename itself.

    Console window create and start example

    When these commands finish, you can browse to the same path as when you run 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 you're running outside of a service, so it's customary to add code that calls host.RunAsService only under certain conditions. For example, you could run as a console app if you get 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 = new WebHostBuilder()
    .UseKestrel()
    .UseContentRoot(pathToContentRoot)
    .UseIISIntegration()
    .UseStartup<Startup>()
    .UseApplicationInsights()
    .Build();

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

Handle stopping and starting events

If you want to handle OnStarting, OnStarted, and OnStopping events, make the following additional changes:

  • 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();
        }
    }
    
  • Create an extension method for IWebHost that passes your custom WebHostService to ServiceBase.Run.

    public static class WebHostServiceExtensions
    {
        public static void RunAsCustomService(this IWebHost host)
        {
            var webHostService = new CustomWebHostService(host);
            ServiceBase.Run(webHostService);
        }
    }
    
  • In Program.Main change call the new extension method instead of host.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 = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(pathToContentRoot)
        .UseIISIntegration()
        .UseStartup<Startup>()
        .UseApplicationInsights()
        .Build();
    
        if (isService)
        {
            host.RunAsCustomService();
        }
        else
        {
            host.Run();
        }
    }
    

If your custom WebHostService code needs to get a service from dependency injection (such as a logger), you can get 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();
    }
}

Next steps

The sample application that accompanies this article is a simple MVC web app that has been modified as shown in preceding code examples. To run it in a service, do the following steps:

  • Publish to c:\svc.

  • Open an administrator window.

  • Enter the following commands:

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

If the app doesn't start up as expected when running in a service, a quick way to make error messages accessible is to add a logging provider such as the Windows EventLog provider.

Acknowledgments

This article was written with the help of sources that were already published. The earliest and most useful of them were these: