Host ASP.NET Core in a Windows Service

By Luke Latham and Tom Dykstra

An ASP.NET Core app can be hosted on Windows as a Windows Service without using IIS. When hosted as a Windows Service, the app automatically starts after reboots.

View or download sample code (how to download)

Deployment type

You can create either a framework-dependent or self-contained Windows Service deployment. For information and advice on deployment scenarios, see .NET Core application deployment.

Framework-dependent deployment

Framework-dependent deployment (FDD) relies on the presence of a shared system-wide version of .NET Core on the target system. When the FDD scenario is used with an ASP.NET Core Windows Service app, the SDK produces an executable (*.exe), called a framework-dependent executable.

Self-contained deployment

Self-contained deployment (SCD) doesn't rely on the presence of shared components on the target system. The runtime and the app's dependencies are deployed with the app to the hosting system.

Convert a project into a Windows Service

Make the following changes to an existing ASP.NET Core project to run the app as a service:

Project file updates

Based on your choice of deployment type, update the project file:

Framework-dependent Deployment (FDD)

Add a Windows Runtime Identifier (RID) to the <PropertyGroup> that contains the target framework. In the following example, the RID is set to win7-x64. Add the <SelfContained> property set to false. These properties instruct the SDK to generate an executable (.exe) file for Windows.

A web.config file, which is normally produced when publishing an ASP.NET Core app, is unnecessary for a Windows Services app. To disable the creation of the web.config file, add the <IsTransformWebConfigDisabled> property set to true.

<PropertyGroup>
  <TargetFramework>netcoreapp2.2</TargetFramework>
  <RuntimeIdentifier>win7-x64</RuntimeIdentifier>
  <SelfContained>false</SelfContained>
  <IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
</PropertyGroup>

Add the <UseAppHost> property set to true. This property provides the service with an activation path (an executable, .exe) for an FDD.

<PropertyGroup>
  <TargetFramework>netcoreapp2.1</TargetFramework>
  <RuntimeIdentifier>win7-x64</RuntimeIdentifier>
  <UseAppHost>true</UseAppHost>
  <SelfContained>false</SelfContained>
  <IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
</PropertyGroup>

Self-contained Deployment (SCD)

Confirm the presence of a Windows Runtime Identifier (RID) or add a RID to the <PropertyGroup> that contains the target framework. Disable the creation of a web.config file by adding the <IsTransformWebConfigDisabled> property set to true.

<PropertyGroup>
  <TargetFramework>netcoreapp2.2</TargetFramework>
  <RuntimeIdentifier>win7-x64</RuntimeIdentifier>
  <IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
</PropertyGroup>

To publish for multiple RIDs:

  • Provide the RIDs in a semicolon-delimited list.

  • Use the property name <RuntimeIdentifiers> (plural).

    For more information, see .NET Core RID Catalog.

Add a package reference for Microsoft.AspNetCore.Hosting.WindowsServices.

To enable Windows Event Log logging, add a package reference for Microsoft.Extensions.Logging.EventLog.

For more information, see the Handle starting and stopping events section.

Program.Main updates

Make the following changes in Program.Main:

  • To test and debug when running outside of a service, add code to determine if the app is running as a service or a console app. Inspect if the debugger is attached or a --console command-line argument is present.

    If either condition is true (the app isn't run as a service), call Run on the Web Host.

    If the conditions are false (the app is run as a service):

    Because the Command-line Configuration Provider requires name-value pairs for command-line arguments, the --console switch is removed from the arguments before CreateDefaultBuilder receives them.

  • To write to the Windows Event Log, add the EventLog provider to ConfigureLogging. Set the logging level with the Logging:LogLevel:Default key in the appsettings.Production.json file. For demonstration and testing purposes, the sample app's Production settings file sets the logging level to Information. In production, the value is typically set to Error. For more information, see Logging in ASP.NET Core.

public class Program
{
    public static void Main(string[] args)
    {
        var isService = !(Debugger.IsAttached || args.Contains("--console"));
        
        if (isService)
        {
            var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
            var pathToContentRoot = Path.GetDirectoryName(pathToExe);
            Directory.SetCurrentDirectory(pathToContentRoot);
        }

        var builder = CreateWebHostBuilder(
            args.Where(arg => arg != "--console").ToArray());

        var host = builder.Build();

        if (isService)
        {
            // To run the app without the CustomWebHostService change the
            // next line to host.RunAsService();
            host.RunAsCustomService();
        }
        else
        {
            host.Run();
        }
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .ConfigureLogging((hostingContext, logging) =>
            {
                logging.AddEventLog();
            })
            .ConfigureAppConfiguration((context, config) =>
            {
                // Configure the app here.
            })
            .UseStartup<Startup>();
}

Publish the app

Publish the app using dotnet publish, a Visual Studio publish profile, or Visual Studio Code. When using Visual Studio, select the FolderProfile and configure the Target Location before selecting the Publish button.

To publish the sample app using command-line interface (CLI) tools, run the dotnet publish command at a command prompt from the project folder with a Release configuration passed to the -c|--configuration option. Use the -o|--output option with a path to publish to a folder outside of the app.

Publish a Framework-dependent Deployment (FDD)

In the following example, the app is published to the c:\svc folder:

dotnet publish --configuration Release --output c:\svc

Publish a Self-contained Deployment (SCD)

The RID must be specified in the <RuntimeIdenfifier> (or <RuntimeIdentifiers>) property of the project file. Supply the runtime to the -r|--runtime option of the dotnet publish command.

In the following example, the app is published for the win7-x64 runtime to the c:\svc folder:

dotnet publish --configuration Release --runtime win7-x64 --output c:\svc

Create a user account

Create a user account for the service using the net user command from an administrative command shell:

net user {USER ACCOUNT} {PASSWORD} /add

The default password expiration is six weeks.

For the sample app, create a user account with the name ServiceUser and a password. In the following command, replace {PASSWORD} with a strong password.

net user ServiceUser {PASSWORD} /add

If you need to add the user to a group, use the net localgroup command, where {GROUP} is the name of the group:

net localgroup {GROUP} {USER ACCOUNT} /add

For more information, see Service User Accounts.

An alternative approach to managing users when using Active Directory is to use Managed Service Accounts. For more information, see Group Managed Service Accounts Overview.

Set permissions

Access to the app folder

Grant write/read/execute access to the app's folder using the icacls command from an administrative command shell:

icacls "{PATH}" /grant {USER ACCOUNT}:(OI)(CI){PERMISSION FLAGS} /t
  • {PATH} – Path to the app's folder.
  • {USER ACCOUNT} – The user account (SID).
  • (OI) – The Object Inherit flag propagates permissions to subordinate files.
  • (CI) – The Container Inherit flag propagates permissions to subordinate folders.
  • {PERMISSION FLAGS} – Sets the app's access permissions.
    • Write (W)
    • Read (R)
    • Execute (X)
    • Full (F)
    • Modify (M)
  • /t – Apply recursively to existing subordinate folders and files.

For the sample app published to the c:\svc folder and the ServiceUser account with write/read/execute permissions, use the following command:

icacls "c:\svc" /grant ServiceUser:(OI)(CI)WRX /t

For more information, see icacls.

Log on as a service

To grant the Log on as a service privilege to the user account:

  1. Locate the User Rights Assignment policies in either the Local Security Policy console or Local Group Policy Editor console. For instructions, see: Configure security policy settings.
  2. Locate the Log on as a service policy. Double-click the policy to open it.
  3. Select Add User or Group.
  4. Select Advanced and select Find Now.
  5. Select the user account created in the Create a user account section earlier. Select OK to accept the selection.
  6. Select OK after confirming that the object name is correct.
  7. Select Apply. Select OK to close the policy window.

Manage the service

Create the service

Use the sc.exe command-line tool to create the service from an administrative command shell. The binPath value is the path to the app's executable, which includes the executable file name. The space between the equal sign and the quote character of each parameter and value is required.

sc create {SERVICE NAME} binPath= "{PATH}" obj= "{DOMAIN}\{USER ACCOUNT}" password= "{PASSWORD}"
  • {SERVICE NAME} – The name to assign to the service in Service Control Manager.
  • {PATH} – The path to the service executable.
  • {DOMAIN} – The domain of a domain-joined machine. If the machine isn't domain-joined, use the local machine name.
  • {USER ACCOUNT} – The user account under which the service runs.
  • {PASSWORD} – The user account password.

Warning

Do not omit the obj parameter. The default value for obj is the LocalSystem account account. Running a service under the LocalSystem account presents a significant security risk. Always run a service with a user account that has restricted privileges.

In the following example for the sample app:

  • The service is named MyService.
  • The published service resides in the c:\svc folder. The app executable is named SampleApp.exe. Enclose the binPath value in double quotation marks (").
  • The service runs under the ServiceUser account. Replace {DOMAIN} with the user account's domain or local machine name. Enclose the obj value in double quotation marks ("). Example: If the hosting system is a local machine named MairaPC, set obj to "MairaPC\ServiceUser".
  • Replace {PASSWORD} with the user account's password. Enclose the password value in double quotation marks (").
sc create MyService binPath= "c:\svc\sampleapp.exe" obj= "{DOMAIN}\ServiceUser" password= "{PASSWORD}"

Important

Make sure that the spaces between the parameters' equal signs and the parameters' values are present.

Start the service

Start the service with the sc start {SERVICE NAME} command.

To start the sample app service, use the following command:

sc start MyService

The command takes a few seconds to start the service.

Determine the service status

To check the status of the service, use the sc query {SERVICE NAME} command. The status is reported as one of the following values:

  • START_PENDING
  • RUNNING
  • STOP_PENDING
  • STOPPED

Use the following command to check the status of the sample app service:

sc query MyService

Browse a web app service

When the service is in the RUNNING state and if the service is a web app, browse the app at its path (by default, http://localhost:5000, which redirects to https://localhost:5001 when using HTTPS Redirection Middleware).

For the sample app service, browse the app at http://localhost:5000.

Stop the service

Stop the service with the sc stop {SERVICE NAME} command.

The following command stops the sample app service:

sc stop MyService

Delete the service

After a short delay to stop a service, uninstall the service with the sc delete {SERVICE NAME} command.

Check the status of the sample app service:

sc query MyService

When the sample app service is in the STOPPED state, use the following command to uninstall the sample app service:

sc delete MyService

Handle starting and stopping events

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

  1. Create a class that derives from WebHostService with the OnStarting, OnStarted, and OnStopping methods:

    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.LogInformation("OnStarting method called.");
            base.OnStarting(args);
        }
    
        protected override void OnStarted()
        {
            _logger.LogInformation("OnStarted method called.");
            base.OnStarted();
        }
    
        protected override void OnStopping()
        {
            _logger.LogInformation("OnStopping method called.");
            base.OnStopping();
        }
    }
    
  2. Create an extension method for IWebHost that passes the CustomWebHostService to 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 RunAsCustomService extension method instead of RunAsService:

    host.RunAsCustomService();
    

    To see the location of RunAsService in Program.Main, refer to the code sample shown in the Convert a project into a Windows Service section.

Proxy server and load balancer scenarios

Services that interact with requests from the Internet or a corporate network and are behind a proxy or load balancer might require additional configuration. For more information, see Configure ASP.NET Core to work with proxy servers and load balancers.

Configure HTTPS

To configure the service with a secure endpoint:

  1. Create an X.509 certificate for the hosting system using your platform's certificate acquisition and deployment mechanisms.

  2. Specify a Kestrel server HTTPS endpoint configuration to use the certificate.

Use of the ASP.NET Core HTTPS development certificate to secure a service endpoint isn't supported.

Current directory and content root

The current working directory returned by calling GetCurrentDirectory for a Windows Service is the C:\WINDOWS\system32 folder. The system32 folder isn't a suitable location to store a service's files (for example, settings files). Use one of the following approaches to maintain and access a service's assets and settings files.

Set the content root path to the app's folder

The ContentRootPath is the same path provided to the binPath argument when the service is created. Instead of calling GetCurrentDirectory to create paths to settings files, call SetCurrentDirectory with the path to the app's content root.

In Program.Main, determine the path to the folder of the service's executable and use the path to establish the app's content root:

var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
var pathToContentRoot = Path.GetDirectoryName(pathToExe);
Directory.SetCurrentDirectory(pathToContentRoot);

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

Store the service's files in a suitable location on disk

Specify an absolute path with SetBasePath when using an IConfigurationBuilder to the folder containing the files.

Additional resources