X

Running ASP.NET Core application as Windows service

ASP.NET Core 2.1 introduces new application host for Windows services. We can now run ASP.NET Core applications as Windows services with minimal effort. This blog post introduces how it is done and how to build and run Windows services on ASP.NET Core without any need for dirty hacks.

Creating default web application

We start with new default ASP.NET Core 2.1 web application.

I don’t configure HTTPS at this moment as this is sample application and it does nothing important.

By deafult Program class looks like this.

public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

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

Now we have working default application and it’s time to focus to Windows service.

Running ASP.NET Core application as Windows service

Running application as Windows service takes some effort before we can open browser and see it running under service. First we have to specify runtime for our application as ASP.NET Core supports also operating systems and architectures where Windows services doesn’t run. For this we have to modify project file.

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

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

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

</Project>

Next add reference to NuGet package Microsoft.AspNetCore.Hosting.WindowsServices. This package has everything needed to run ASP.NET Core application as Windows service.

NB! For me newest version 2.1.1 of Microsoft.AspNetCore.Hosting.WindowsServices conflicted with ASP.NET Core 2.1.0 and I webt with version 2.1.0 instead.

We have to modify also Main() method of Program class. In its simplest form Main() method looks like this.

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>()
        .Build();

    host.RunAsService();
}

All we have to do now is to publish our application, register it as a Windows service and start the service.

Running application as service or on console

Those who have built Windows services before know very well that debugging of services can be pain in one specific anatomic area as after building the service one has to deplpy new version of it, attach debugger etc. There is simple way around – we make our Windows service run also as a console application that is easy to run on debugger from Visual Studio.

We can apply the same trick also with ASP.NET Core application that is running as Windows service.

public static void Main(string[] args)
{
    var isService = !(Debugger.IsAttached || args.Contains("--console"));
    var pathToContentRoot = Directory.GetCurrentDirectory();
    var webHostArgs = args.Where(arg => arg != "--console").ToArray();

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

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

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

The code shown may seem a little tricky. Here are my explanations:

  • To run application on console from Visual Studio we control if debugger is attached. If we want to run application as console application outside from Visual Studio we can use –console argument.
  • When application runs as web application under web server we must use current directory as content root. But when application runs as a service we need executable path as content root.
  • We remove –console argument as ASP.NET Core expects all arguments to be name-value pairs.

Now try to run application from Visual Studio. It starts as a usual web application.

Making application run as Windows service

To make application run as Windows service we need some additional steps.

  1. Publish application to some folder
  2. Open command line in administrative permissions
  3. Register application as Windows service using command (space after “binPath=“ is mandatory)

    sc create AspNetWindowsService binPath= “path to my application exe”
     

  4. Start service
     
    sc start AspNetWindowsService
     
  5. When service starts open browser and navigate to http://localhost:5000 to see web application running.

Before releasing new version of service the current running instance must be stopped. For this we can use command sc stop AspNetWindowsService. To remove service run the following command: sc delete AspNetWindowsService.

Wrapping up

With new Microsoft.AspNetCore.Hosting.WindowsServices NuGet package it is easy to run ASP.NET Core applications as Windows services. We had to modify project file a little bit and make application Main() method to understand if application runs as a service or console application. Using this trick we are able to build web application on Visual Studio and run it as a usual web application. Our burden to get web application run as Windows service was minimal.

Liked this post? Empower your friends by sharing it!
Categories: ASP.NET

View Comments (17)

  • Did you also write the official documentation? Looks more or less identical.

    Yes, the UserInteractive property is exactly what you you might think you need. But on Core it always returns true 😑

  • No, I don't write ASP.NET Core documentation. This time documentation has coding style close to mine :)

  • Good to see Windows Serivce support in .Net Core. I updated old service using TopShelf. TopShelf(https://github.com/Topshelf) is excellent library to write windows services. You develop your application as "Console App" and use "TopShelf" fluent API to configure it.

  • Thanks, Andreas Gehrke. Knowing that UserInteractive is always true in .NET Core saved my day.

  • I'm writing an application with ASP.NET and a MySQL database.
    Following these steps, I tried to run the application in a console but it doesn't seem to be reading my configuration. The connection string is empty.

  • Did you check that the json file got copied to the output dirctory? If it doesn't get copied to the debug/release directory, then your app doesn't have a config to look at.

  • Yes. I just figured it out 15 minutes ago. For some reason, it wasn't copied. It does now and everything is fine. Thanks.

  • Thank you very much for sharing.

    This works well for me.

    I just have a question though, how can I properly modify the UseUrls value so that it won't use the default localhost:5000. Hardcoding the UserUrls value also works well for me. But I am having a problem using the appsettings.json file. It doesn't seem to work.

    here's the code from Program.cs

    var config = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .AddCommandLine(args)
    .Build();

    var host = WebHost.CreateDefaultBuilder(webHostArgs)
    .UseContentRoot(pathToContentRoot)
    .UseStartup()
    .UseUrls(config.GetSection("HostUrl").Value)
    .Build();

    and here's my appsettings.json file.

    {
    "Logging": {
    "LogLevel": {
    "Default": "Warning"
    }
    },
    "HostUrl": "http://localhost:5020",
    "AllowedHosts": "*"
    }

    The appsettings.json was copied when I published it. And I also confirmed that HostUrl worked when I start the application via cmd with args --console.

    But when I start as a service, it's still using the default localhost:5000.

    May I know what I am missing?

    Thank you very much!

  • Hi,

    In case you are using an application with the integrated framework (self container), it is necessary to change the current directory in this way:

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

    Directory.SetCurrentDirectory (pathToContentRoot);
    }

    Thanks Gunnar Peipman for this article, he helped me by hosting an application I have on Windows, IIS presents a lot of problems with ASP .NET CORE.

Related Post