将ASP.NET Core作为Windows服务进行托管

36

据我了解,在RC2中支持在Windows服务中托管应用程序。 我尝试在一个简单的Web API项目上进行测试(使用.NET Framework 4.6.1)。

这是我的Program.cs代码:

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() {}
  }
}

除此之外的所有内容基本上都来自于.NET Core模板(尽管我将框架更改为net461并在project.json中添加了一些依赖项)。

使用dotnet publish进行发布,并使用sc create创建Windows服务,我可以成功启动我的服务,但我无法访问任何控制器(端口没有监听)。我想我做错了什么。

所以我猜主要问题是如何创建自托管Web API并将其作为Windows服务运行。所有找到的解决方案在RC2更新后都不起作用。


1
我不太明白发生了什么,但是只要我在运行Windows服务时传递--windows-service参数,你的代码就可以正常工作。 - svick
实际上,ASP.NET团队已经提供了这个类。甚至还有一个NuGet可用。只需尝试一下,但似乎也不容易:https://github.com/aspnet/Home/issues/1386 - Thomas
我遇到了同样的问题。通过命令行运行(即没有使用--windows-service)时,它可以正常工作。创建服务也很好(例如,“sc.exe ABC -binPath= "<path to exe> --window-service"),从 Services 启动服务也很好,并显示它正在运行,但尝试通过 http://localhost:5000 访问服务无法正常工作。如果有任何建议,请告知。 - howardlo
1
你好,你是如何成功添加对System.ServiceProcess的引用的?我无法将该引用添加到我的netcoreapp1.0目标项目中。 - luisgepeto
1
@LuisBecerril System.ServiceProcessnetcoreapp1.0 (CoreCLR) 中还不可用。如上所述,您需要针对完整框架,例如 net461,然后引用 frameworkAssemblies - Stajs
1
Maria-p,你能让它正常工作吗? - DoomerDGR8
7个回答

56
你有几个选择 - 使用 Microsoft 的 WebHostService 类,继承 WebHostService 或编写自己的类。后者的原因是,如果使用 Microsoft 的实现,我们无法编写一个包含继承 WebHostService 类型参数的通用扩展方法,因为该类没有无参数构造函数,也无法访问服务定位器。
使用 WebHostService 在这个例子中,我将创建一个控制台应用程序,使用 Microsoft.DotNet.Web.targets 作为输出 .exe 文件,并作为 MVC 应用程序(视图、控制器等)运行。我假设创建控制台应用程序、更改.xproj 文件中的目标、修改 project.json 以具有适当的发布选项,并从标准 .NET Core web app 模板复制视图、控制器和 webroot 是微不足道的。
现在是重点:
  1. 获取 包,并确保项目 json 文件中的框架为 net451(或更新版本)。

  2. 确保入口点中的内容根据正确设置为应用程序 .exe 文件的发布目录,并调用 RunAsService() 扩展方法。例如:

    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();
        }
    }
    
  3. 现在可以使用以下命令轻松安装 .exe 文件:
  4.     sc create MyService binPath = "Full\Path\To\The\Console\file.exe"
    

    一旦服务启动,Web应用程序将被托管并成功查找和渲染其视图。

    继承WebHostService

    这种方法的一个主要优点是,它允许我们重写OnStopping、OnStarting和OnStarted方法。

    假设以下是我们自定义的类,该类继承了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();
        }
    }
    

    按照上述步骤,我们需要编写自己的扩展方法来运行主机,使用我们的自定义类:

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

    唯一需要更改的是入口点的前一个示例中的else语句,它必须调用正确的扩展方法。

    host.RunAsCustomService();
    

    最后,按照与上面相同的步骤安装该服务。

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

@Ivan Prodanov,你知道在Linux上能否运行这个吗? - Joseph Woodward
@JosephWoodward 我非常确定在当前形式下,这在Linux上不会起作用。 - Ivan Prodanov
1
@nadav GetCurrentDirectory() 在从服务控制管理器启动时将返回 %WinDir%\System32(或 x64 时为 %winDir%\SysWoW64)。 - Ivan Prodanov
1
Dotnet Core 2.0 有什么变化吗? - aleha_84
@aleha 这个解决方案适用于dotnet core 2.1,但不适用于早期版本。 - bstoney
显示剩余5条评论

9
如果仅运行于完整 .NET Framework,前面的答案已经足够了(继承自 ServiceBase 或使用 Microsoft 的 WebHostService)。
但是,如果您需要/想要仅在.NET Core 上运行(例如在 Windows Nano Server 上、作为单个二进制文件在 Linux 上正常运行并在 Windows 上作为服务运行,或者需要与旧版 .NET Framework 版本上的应用程序共存以便无法更新系统框架),则必须通过 P/Invokes 自己进行适当的 Windows API 调用。由于我不得不这样做,所以我创建了一个库来实现这一点
有一个示例可用,也可以在具有管理员权限时安装自己(例如:dotnet MyService.dll --register-as-service)。

1
它支持 .net core 2.0 吗? - Isantipov
@Isantipov 看看他提供的Github链接: ... 先决条件:.NET Core SDK 2.0.3或更高版本(基于.csproj的工具)。 - Nicollas Braga

7

您需要引用Microsoft.AspNetCore.HostingMicrosoft.AspNetCore.Hosting.WindowsServices,并在Startup类中使用以下代码,使其可以作为Windows服务运行:

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();
    }
}

然后您需要为IWebHost.RunAsService添加一个扩展方法,用您的自定义WebHostService类包装它,并使用OnStoppingOnStarting事件处理程序:

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();
    }
}

这篇文章如何在Windows服务中托管ASP.NET Core及其评论详细介绍了相关内容。 更新(更多详情请参见ASP.NET Core 2.0新特性快速总结):
ASP.NET Core 1.1中,我们有类似以下的内容:
public class Program
{
    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();

        host.Run();
    }
}

在ASP.NET Core 2.0中,它看起来像这样:
public class Program
{
    public static void Main(string[] args)
    {
        BuildWebHost(args).Run();
    }

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

看了你的更新,从ASP.NET Core 1.1到ASP.NET Core 2.0的更改,这是否意味着如果我的环境是2.0,我应该“升级”我的代码,还是1.1版本仍然可以工作? - Wouter Vanherck
1
@WouterVanherck 你可以随时检查它到底做了什么 https://github.com/aspnet/MetaPackages/blob/rel/2.0.0-preview1/src/Microsoft.AspNetCore/WebHost.cs - Dmitry Pavlov
@DmitryPavlov - 我在 .Net Core 2.0 中遇到了“无法解析符号 WebHost”的错误。你能帮我解决一下吗? - prog1011
在 .NET Core 3 中:https://devblogs.microsoft.com/aspnet/net-core-workers-as-windows-services - Dmitry Pavlov
2
不错的例子,但我认为你可能需要将 host.RunAsService(); 这一行更改为 host.RunAsMyService(); 以使其与扩展程序匹配。 - s1cart3r

2
使用Visual Studio 2017可以轻松创建Windows服务。您需要将.NET Core代码移动到一个目标为NetStandard的类库中,然后可以引用它。您的Windows服务项目将必须针对完整的.NET Framework,但可以引用共享库。
通过将所有代码放入类库中,您就可以在Windows服务和其他非Windows应用程序之间实现可移植性。
在使用VS 2015时,无法创建Windows服务项目并引用.NET Core xproj。但是,一旦将所有项目转换为csproj,使用Visual Studio 2017即可解决此问题。
这篇博客会更详细地介绍:《如何使用Visual Studio 2017创建.NET Core Windows服务》(链接:https://stackify.com/creating-net-core-windows-services/)

2

1

更新了我用于 .netcore 2.1 的内容,尽可能使用原始模板,其中包括从 appsettings.json、命令参数、主机过滤等读取配置的实现,这些设置大部分都是通过配置文件或命令参数控制的。请查看微软的文档以了解原始模板提供的内容和如何覆盖设置。

请注意,如果您使用 https 和默认开发证书定义端点,则可能会导致服务启动失败。可能只需要通过配置指定 pfx。如果有人想改进此处,请随意这样做。

无论如何,这是我在 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>();
}

1
使用最新的VS2015 Update 2 ASP.NET Core Web Application (.NET Framework)模板,并按照以下方式修改Program.cs,当作为服务、独立运行和在VS中运行时都能正常工作。注意,当作为服务运行时需要设置包含wwwroot目录的路径。
到目前为止,我真的很喜欢这个模板和模式。可以像在Visual Studio中一样开发MVC6代码,然后将其干净地部署为服务。实际上,我注意到在部署用于测试的本地服务实例时,无需使用sc.exe删除/创建服务。只需停止服务、发布代码并重新启动服务,它就会获取新的更改。
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();
        }
    }
}

当操作系统启动服务时,是否会添加“--windows-service”命令行参数? - Simon Ordo
2
sc.exe <服务名称> binPath= "<服务可执行文件路径> --windows-service",因此将--windows-service作为binPath的一部分。 - howardlo
1
"Startup.is_service = true;" 在我的rc2模板中似乎不起作用。我有什么遗漏的吗?提前感谢任何帮助 :) - JDTLH9
1
@JDTLH9 我假设这是一个自定义属性。 - Demodave
2
我在IWebHost上没有RunAsService - 这是从哪里来的? - Cocowalla

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接