Hosting ASP.NET Core as Windows service

2019-01-16 04:20发布

问题:

As I get it in RC2 there's a support for hosting applications within Windows Services. I tried to test it on a simple web api project (using .NET Framework 4.6.1).

Here's my Program.cs code:

using System;
using System.IO;
using System.Linq;
using System.ServiceProcess;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.WindowsServices;

namespace WebApplication4
{
  public class Program : ServiceBase
  {
    public static void Main(string[] args)
    {
      if (args.Contains("--windows-service"))
      {
        Run(new Program());
        return;
      }

      var program = new Program();
      program.OnStart(null);
      Console.ReadLine();
      program.OnStop();
    }

    protected override void OnStart(string[] args)
    {
      var host = new WebHostBuilder()
      .UseKestrel()
      .UseContentRoot(Directory.GetCurrentDirectory())      
      .UseStartup<Startup>()
      .Build();

      host.RunAsService();
    }

    protected override void OnStop() {}
  }
}

All the other stuff are basically from .NET Core template (though I changed framework to net461 and added some dependencies in project.json).

After publishing it with dotnet publish and creating Windows Service with sc create I can succesfully start my service, but I can't reach any of my controllers (ports are not listeting). I assume I'm doing something wrong.

So I guess the main question is how to make self hosted web api and run it as Windows Service. All found solutions don't work after RC2 update.

回答1:

You've got a couple of options here - use Microsoft's WebHostService class, inherit WebHostService or write your own. The reason for the latter being that using Microsoft's implementation, we cannot write a generic extension method with a type parameter inheriting WebHostService since this class does not contain a parameterless constructor nor can we access the service locator.

Using WebHostService

In this example, I'm going to create a Console Application that uses Microsoft.DotNet.Web.targets, outputs an .exe and operates as a MVC app (Views, Controllers, etc). I assume that creating the Console Application, changing the targets in the .xproj and modifying the project.json to have proper publish options and copying the Views, Controllers and webroot from the standard .NET Core web app template - is trivial.

Now the essential part:

  1. Get this package and make sure your framework in project.json file is net451 (or newer)

  2. Make sure the content root in the entry point is properly set to the publish directory of the application .exe file and that the RunAsService() extension method is called. E.g:

    public static void Main(string[] args)
    {
        var exePath= System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
        var directoryPath = Path.GetDirectoryName(exePath);
    
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(directoryPath)
            .UseStartup<Startup>()
            .Build();
    
        if (Debugger.IsAttached || args.Contains("--debug"))
        {
            host.Run();
        }
        else
        {
            host.RunAsService();
        }
    }
    

The .exe can now easily be installed using the following command

    sc create MyService binPath = "Full\Path\To\The\Console\file.exe"

Once the service is started, the web application is hosted and successfully finds and renders its Views.

Inherit WebHostService

One major benefit of this approach is that this lets us override the OnStopping, OnStarting and OnStarted methods.

Let's assume that the following is our custom class that inherits WebHostService

internal class CustomWebHostService : WebHostService
{
    public CustomWebHostService(IWebHost host) : base(host)
    {
    }

    protected override void OnStarting(string[] args)
    {
        // Log
        base.OnStarting(args);
    }

    protected override void OnStarted()
    {
        // More log
        base.OnStarted();
    }

    protected override void OnStopping()
    {
        // Even more log
        base.OnStopping();
    }
}

Following the steps above, we have to write down our own extension method that runs the host using our custom class:

public static class CustomWebHostWindowsServiceExtensions
{
    public static void RunAsCustomService(this IWebHost host)
    {
        var webHostService = new CustomWebHostService(host);
        ServiceBase.Run(webHostService);
    }
}

The only line that remains to be changed from the previous example of the entry point is the else statement in the end, it has to call the proper extension method

host.RunAsCustomService();

Finally, installing the service using the same steps as above.

sc create MyService binPath = "Full\Path\To\The\Console\file.exe"


回答2:

If it is okay to run on full .NET Framework only, the previous answers are sufficient (Inheriting from ServiceBase or using Microsoft's WebHostService).

If, however, you have / want to run on .NET Core only (e.g. on windows nano server, single binary that works on linux as normal app and as service on windows, or need to run side-by-side with apps on an old .NET Framework version so you can't update the system framework), you'd have to make the appropriate windows API calls yourself via P/Invokes. Since i had to do that, i created a library that does exactly that.

There is a sample available that is also able to install itself when run with administrative privileges (e.g. dotnet MyService.dll --register-as-service).



回答3:

I found this topic while looking for a solution to a similar problem. Ended up implementing something that works pretty much like very (very) simplified Topshelf. You can easily run as console and/or install, uninstall and so one as a service. I think it can be pretty useful for somebody. You can find the code here : https://github.com/PeterKottas/DotNetCore.WindowsService and there's nuget here as well https://www.nuget.org/packages/PeterKottas.DotNetCore.WindowsService/ . Enjoy :)



回答4:

Windows Services can be easily created with Visual Studio 2017. You will need to move your .NET Core code to a class library that targets NetStandard that can then be referenced. Your Windows Service project will have to target the full .NET Framework but can reference your shared library.

By putting all of your code in the class library you would then have portability between the Windows Service and other non Windows apps.

With VS 2015 you couldn't make a Windows Service project and reference a .NET Core xproj. Visual Studio 2017 fixes that once you convert all your projects to csproj.

This blog walks through it in more detail: How to Create .NET Core Windows Services With Visual Studio 2017



回答5:

Using the latest VS2015 Update 2 ASP.NET Core Web Application (.NET Framework) template and with the mods to Program.cs as below, it is working just fine when run as a service, stand-alone and in VS. Notice that the path to the directory containing wwwroot needs to be set when run as a service.

So far, I really like this template and pattern. Can develop MVC6 code as usual in Visual Studio and then it deploys cleanly as a service. In fact, I noticed that when deploying a local instance of the service for testing that there is no need to delete/create service using sc.exe. Just stop the service, publish the code and restart the service and it'll pick up the new changes.

public class Program
{
    public static void Main(string[] args)
    {
        IWebHost host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();

        if (args.Contains("--windows-service"))
        {
            host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot("<directory-containing-wwwroot>")
            .UseIISIntegration()
            .UseStartup<Startup>()
            .UseUrls("http://+:5000")
            .Build();

            Startup.is_service = true;
            host.RunAsService();
        }
        else
        {
            host.Run();
        }
    }
}


回答6:

You need to reference Microsoft.AspNetCore.Hosting and Microsoft.AspNetCore.Hosting.WindowsServices and use this code in your Startup class to make it runnable as a windows service:

public static void Main(string[] args)
{
    var host = new WebHostBuilder()
                .UseIISIntegration()
                .UseKestrel()
                .UseContentRoot(@"Path\To\Content\Root")
                .UseStartup<Startup>()
                .Build();

    if (Debugger.IsAttached || args.Contains("--debug"))
    {
        host.Run();
    }
    else
    {
        host.RunAsService();
    }
}

Then you need to add an extension method for IWebHost.RunAsService to wrap it with your custom WebHostService class with OnStopping and OnStarting event handlers:

public static class MyWebHostServiceServiceExtensions
{
    public static void RunAsMyService(this IWebHost host)
    {
        var webHostService = new MyWebHostService(host);
        ServiceBase.Run(webHostService);
    }
}

internal class MyWebHostService : WebHostService
{
    public MyWebHostService(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();
    }
}

This post How to host your ASP.NET Core in a Windows Service and comments cover the details.

UPDATE (see more details in Quick summary of what’s new in ASP.NET Core 2.0):

In ASP.NET Core 1.1 we had something like this:

public class Program
{
    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();

        host.Run();
    }
}

In ASP.NET Core 2.0, it looks like this:

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

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();
}


回答7:

Updated for what I Use for .netcore 2.1, using as much of the stock template as possible, which includes reading configuration from the appsettings.json, command args, host filtering etc, as implemented inside the helper methods from the .netcore team. A lot of these settings are thus controlled via config files or command args. check microsoft's documentation on what he stock template provides and how to override settings.

Please note that I have seen the service fail to start if you define an endpoint with https and the default development certificate. It may simply be a matter of specifying a pfx via configuration. If anybody wants to improve upon this, feel free to do so.

anyway here is the stock template code I use in program.cs

public class Program
{
    public static void Main(string[] args)
    {

        if (args.Contains("--service"))
        {
            var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            Directory.SetCurrentDirectory(path); // need this because WebHost.CreateDefaultBuilder queries current directory to look for config files and content root folder. service start up sets this to win32's folder.
            var host = CreateWebHostBuilder(args).Build();
            host.RunAsService();
        }
        else
        {
            CreateWebHostBuilder(args).Build().Run();
        }

    }

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