【问题标题】:.Net Core 2.0 Windows Service.Net Core 2.0 Windows 服务
【发布时间】:2018-03-15 15:47:54
【问题描述】:

我正在尝试在 .Net Core 2.0 中构建一个 Windows 服务,但我一整天都在苦苦挣扎,但一点进展都没有。 一切似乎都在使用 Core 1.0/1.1,甚至 Microsoft 文档:

Host an ASP.NET Core app in a Windows Service

TopShelf 也不支持 2.0,就我所见。

我见过一些奇怪的解决方案,将所有代码放在 .Net 标准类库中,然后使用 .Net Framework 应用程序来托管 Windows 服务,但这在我看来并不优雅,我试图完全摆脱 .Net Framework。

我现在想做的事情是否可能?我错过了一些非常基本的东西吗?

【问题讨论】:

  • '试图完全摆脱 .Net Framework' 同时尝试构建 Windows 服务可能是一场艰苦的战斗..
  • @thisextendsthat - 不过,这样做是有充分理由的。 .NET Core 2.1 具有显着的 IO 相关性能改进,这是 .NET Framework 中没有的。
  • 我也希望使用 .NET core 2.1,但更多是为了面向未来的一些我正在更新的 Windows 服务。但是希望在我们现有的基础设施上快速运行,因此 windows 服务似乎比在 docker 等中运行更有利
  • @andrewpate 正如我在下面所说的,我目前在 .Net Core 2.0 上使用 DasMuli 的 nugget 运行我的服务,但它也可能适用于 2.1

标签: c# asp.net .net asp.net-core


【解决方案1】:

现在可以在没有第三方库的情况下在 .NET Core 2.0 中编写 Windows 服务,这要归功于 Windows Compatibility Pack 的发布(在撰写本文时,仍处于预发布阶段)。正如页面本身警告的那样:

但是在开始移植之前,您应该了解自己想要做什么 完成迁移。只是移植到 .NET Core,因为它是 一个新的 .NET 实现并不是一个足够好的理由(除非你是 真正的粉丝)。

特别是,现在可能可以在 .NET Core 中编写 Windows 服务,但您将无法获得开箱即用的跨平台兼容性,因为 Windows 以外平台的程序集只会抛出 PlatformNotSupportedException,如果您尝试使用服务代码。解决这个问题是可能的(例如使用RuntimeInformation.IsOSPlatform),但这完全是另一个问题。

此外,第三方库在安装服务方面可能仍然提供更好的界面:截至撰写本文时,兼容包的当前版本 (2.0.0-preview1-26216-02) 不支持 System.Configuration.Install 命名空间,因此默认使用ServiceProcessInstaller 类和installutil 的方法将不起作用。稍后会详细介绍。

综上所述,假设您从项目模板创建了一个全新的 Windows 服务 (Service1)(不是严格要求的,因为它不包含任何有趣的东西,除了继承自 ServiceBase 的类)。在 .NET Core 2.0 上构建它所需要做的就是编辑 .csproj 并将其替换为新格式:

<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp20</TargetFramework>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Windows.Compatibility" Version="2.0.0-*" />
  </ItemGroup>
</Project>

然后删除properties\AssemblyInfo.cs,因为它不再需要并且会与项目本身的版本信息冲突。

如果您已经有一个服务并且它有依赖关系,那么转换可能会更复杂。见here

现在您应该能够运行dotnet publish 并获得可执行文件。如前所述,您不能使用 ServiceProcessInstaller 类来安装服务,因此您必须手动

  • 注册服务使用的事件源;
  • 创建实际服务。

这可以通过一些 PowerShell 来完成。从包含您发布的可执行文件的位置的提升提示符中:

$messageResourceFile = "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\EventLogMessages.dll"
New-EventLog -LogName Application -Source Service1 -MessageResourceFile $messageResourceFile
sc.exe create Service1 binPath= (Resolve-Path .\WindowsService1.exe)

这在几个方面并不理想:这对消息资源文件的路径进行了硬编码(我们实际上应该确定它在注册表中的可执行文件和运行时路径的位置),它对服务进行了硬编码名称和可执行文件名。您可能希望通过在Program.cs 中进行一些命令行解析来为您的项目提供自己的安装功能,或者使用Cocowalla's answer 中提到的库之一。

【讨论】:

  • 我目前正在使用github.com/dasMulli/dotnet-win32-service 运行我的服务,但如果可以,我会尝试使用这个解决方案,因为它看起来是未来打样的最佳选择,而且它看起来会做它的本地方式。如果一切顺利,我会接受你的回答。
  • 这种方式支持什么类型的配置文件? App.config 还是 Appsettings.json?
  • @Justin:服务代码中也没有任何具体要求。您可以使用常规 .NET Core 控制台应用程序支持的任何内容(这取决于您添加的包和使用的代码)。
【解决方案2】:

将 .NET Core 2.0 Web API 作为 Windows 服务托管。我遵循了本指南Host ASP.NET Core in a Windows Service先决条件部分我不清楚。在一些错误之后,这是我所做的: Source Code

  1. 创建 ASP.NET Core Web 应用程序
  2. 选择 API
  3. 编辑 .csproj 文件,需要将目标框架从 netcoreapp2.0 更改为 net461,明确列出所有包引用而不是使用 Microsoft.AspNetCore。全部,如下

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

  <PropertyGroup>
    <TargetFramework>net461</TargetFramework>
    <RuntimeIdentifier>win7-x64</RuntimeIdentifier>
    <!--<TargetFramework>netcoreapp2.0</TargetFramework>-->
  </PropertyGroup>

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>

  <ItemGroup>
    <!--<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />-->
    <PackageReference Include="Microsoft.AspNetCore" Version="2.0.2" />
    <PackageReference Include="Microsoft.AspNetCore.Hosting.WindowsServices" Version="2.0.2" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.3" />
    <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.2" />
    <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" />
    <PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="2.0.2" />
  </ItemGroup>

  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.3" />
  </ItemGroup>

</Project>
  1. power shell [solution-folder] dotnet publish -o "[publish-folder]"
  2. power shell [solution-folder] sc.exe create CoreApi binpath="[publish-folder]\CoreApiHostedAsWindowsService.exe"
  3. power shell [solution-folder] sc.exe start CoreApi
  4. 访问默认api power shell [解决方案文件夹] Invoke-WebRequest http://localhost:5000/api/values

【讨论】:

  • 在我的情况下,我不希望它托管一个网站,我只是希望它运行一些后台任务,例如压缩一些文件并通过 FTP 和其他一些东西上传它们,你会发现这会也可以吗?
  • @DGaspar 根据您的要求,我会尝试创建一个 .Net Core 控制台应用程序,并使用nssm 来托管控制台应用程序。
【解决方案3】:

我将总结一些选项:

  1. 将您的代码移动到 .NET Standard 库中,并将其托管在 .NET Framework 应用程序中,这样您就可以使用 ServiceBase。这当然需要在目标机器上安装 .NET Framework
  2. 使用NSSM(非吸盘服务管理器)管理 .NET Core 控制台应用(它具有公共域许可证)
  3. 使用 Windows API 调用挂钩到 Windows 服务方法。这是DotNetCore.WindowsServicedotnet-win32-service 采用的方法(两者都获得了 MIT 许可)

我认为@JeroenMostert 的评论有点苛刻 - 我可以看到不依赖于目标计算机上可用的特定 .NET Framework 版本的吸引力。很多其他人显然也有同感,因为我链接到的 2 个存储库相当受欢迎。

【讨论】:

  • 我删除了我的评论。我还写了一个新的答案,指出现在.NET Core 2.0 中有一个基于 MS 的服务解决方案(尽管使用一些第三方解决方案仍然更方便)。我相信这可以缓解任何严厉。 :-)
【解决方案4】:

在 .NET Core 2.1 中,您可以使用 Host 和 HostBuilder 来获取作为服务运行的控制台应用程序。如果您将控制台应用程序容器化,您可以将容器部署到任何地方,它与作为服务运行一样。您可以在控制台应用程序中使用 Host 和 HostBuilder 来管理 DI、日志记录、正常关闭等。看看:

Hosting services in .NET Core console application

【讨论】:

    【解决方案5】:

    创建 .NET Core Windows 服务的一种简单方法是使用 Peter Kottas 的DotNetCore.WindowsService library

    NuGet 包是 PeterKottas.DotNetCore.WindowsService。要使用 Visual Studio 包管理器控制台安装它,只需运行

    Install-Package PeterKottas.DotNetCore.WindowsService
    

    还有good notes on how to get started

    【讨论】:

    • 如前所述,PeterKottas 的 nugget 内部使用了 DasMuli 的 nugget 代码。我目前正在使用 DasMuli 的,但 PeterKottas 的应该也可以。
    • 乍一看,这似乎工作得很好。请注意,您需要创建一个 .NET Core CONSOLE 应用程序并添加包。然后,您可以安装该控制台应用程序以像服务一样运行。在我运行它之前,我已经忽略了几次。所以它不适用于 .NET Windows 服务
    【解决方案6】:

    我们只需要 System.ServiceProcess.ServiceController NuGet 包即可将 .NET Core 应用程序作为 Windows 服务运行。

    下面是.csproj文件,

    <Project Sdk="Microsoft.NET.Sdk">
    
    <PropertyGroup>
      <OutputType>Exe</OutputType>
      <TargetFramework>netcoreapp2.1</TargetFramework>
      <RuntimeIdentifier>win7-x64</RuntimeIdentifier>
    </PropertyGroup>
    
    <ItemGroup>
    <PackageReference Include="System.ServiceProcess.ServiceController" 
     Version="4.5.0" />
    </ItemGroup>
    
    </Project>
    

    Program.cs 文件,

    using System.ServiceProcess;
    namespace WindowsService101
    {
    class Program
    {
        static void Main(string[] args)
        {
            using (var service = new HelloWorldService())
            {
                ServiceBase.Run(service);
            }
        }
    }
    }
    
    
    
    public class HelloWorldService : ServiceBase
    {
        protected override void OnStart(string[] args)
        {
           // Code Here
        }
    
        protected override void OnStop()
        {
            // Code Here
        }
    }
    

    构建并发布解决方案。

    1. 从 .exe 文件夹以管理员模式打开 Cmd 提示 示例:\WindowsService101\bin\Debug\netcoreapp2.1\publish

    2. sc 创建 binPath=""

    3. sc 开始

    【讨论】:

    • 为我工作。虽然我必须在 binPath 中提供 .exe 的完整路径,但可能是因为我有代码找到 pathtoContentRoot。
    【解决方案7】:

    .NET Core 2.2 的 Windows 服务中的 ASP.NET Core。对现有 ASP.NET Core 项目进行以下更改以将应用作为服务运行:

    要求:PowerShell 6.2 or later

    依赖框架的部署 (FDD):

    依赖于框架的部署 (FDD) 依赖于目标系统上是否存在共享系统范围的 .NET Core 版本。当 FDD 场景与 ASP.NET Core Windows 服务应用程序一起使用时,SDK 会生成一个可执行文件 (*.exe),称为依赖于框架的可执行文件。

    将 Windows Runtime Identifier (RID) 添加到包含目标框架的 &lt;PropertyGroup&gt;。在以下示例中,RID 设置为win7-x64。将 &lt;SelfContained&gt; 属性集添加到 false。这些属性指示 SDK 为 Windows 生成可执行 (.exe) 文件。

    通常在发布 ASP.NET Core 应用程序时生成的 web.config 文件对于 Windows 服务应用程序来说是不必要的。要禁用 web.config 文件的创建,请将 &lt;IsTransformWebConfigDisabled&gt; 属性集添加到 true

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

    自包含部署 (SCD):

    自包含部署 (SCD) 不依赖于目标系统上是否存在共享组件。运行时和应用程序的依赖项与应用程序一起部署到托管系统。

    确认存在Windows Runtime Identifier (RID) 或将RID 添加到包含目标框架的&lt;PropertyGroup&gt;。通过将 &lt;IsTransformWebConfigDisabled&gt; 属性集添加到 true 来禁用 web.config 文件的创建。

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

    Program.Main

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

    发布依赖于框架的部署 (FDD):

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

    发布自包含部署 (SCD)

    必须在项目文件的&lt;RuntimeIdenfifier&gt;(或&lt;RuntimeIdentifiers&gt;)属性中指定 RID。为dotnet publish 命令的-r|--runtime 选项提供运行时。

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

    通过管理 PowerShell 6 命令外壳使用 icacls 命令授予对应用文件夹的写入/读取/执行访问权限。

    icacls "{PATH}" /grant "{USER ACCOUNT}:(OI)(CI){PERMISSION FLAGS}" /t
    
    • {PATH} – 应用文件夹的路径。
    • {USER ACCOUNT} – 用户帐户 (SID)。
    • (OI) – 对象继承标志将权限传播到下级 文件。
    • (CI) – 容器继承标志将权限传播到 下级文件夹。
    • {PERMISSION FLAGS} – 设置应用程序的访问权限。
      • 写入 (W)
      • 读取 (R)
      • 执行 (X)
      • 全(女)
      • 修改(M)
    • /t – 递归应用到现有的从属文件夹和文件。

    命令:

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

    使用RegisterService.ps1 PowerShell 脚本注册服务。在管理 PowerShell 6 命令 shell 中,使用以下命令执行脚本:

    .\RegisterService.ps1 
        -Name MyService 
        -DisplayName "My Cool Service" 
        -Description "This is the Sample App service." 
        -Exe "c:\svc\SampleApp.exe" 
        -User Desktop-PC\ServiceUser
    

    使用Start-Service -Name {NAME} PowerShell 6 命令启动服务。

    Start-Service -Name MyService
    

    处理启动和停止事件

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

    扩展方法:

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

    程序.Main:

    host.RunAsCustomService();
    

    将内容根路径设置为应用的文件夹:

    程序.Main:

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

    来源:

    https://github.com/aspnet/AspNetCore.Docs/tree/master/aspnetcore/host-and-deploy/windows-service/

    https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/windows-service?view=aspnetcore-2.2

    【讨论】:

    • 注意:如果你使用appsettings.json之类的配置文件,你可能需要做这样的事情来获取它... _jsonConfigurationFile = Path.Combine(pathToContentRoot, jsonConfigurationFile)
    【解决方案8】:

    也许这是一个完全的逃避,但请记住,通过更强大的 docker 支持,您可以构建一个在容器内运行的服务。那时,它仍然是 .net core (2.0),但在您的 Windows 机器上运行。此外,您可以在未来的任何地方进行部署。

    随着 dotnet core 的成熟,我这是一个越来越好的解决方案,假设您的服务不需要主机本地资源。

    【讨论】:

      【解决方案9】:

      对于那些发现这个问题但想使用 .NET Core 3.x 实现 Windows 服务的人

      https://csharp.christiannagel.com/2019/10/15/windowsservice/

      通用主机加上后台服务加上命令行工具sc的组合,你就有了一个windows服务。

      【讨论】:

        【解决方案10】:

        随着 Microsoft 发布 Microsoft.Windows.Compatibility,我会使用它,因为它似乎最适合将来使用。

        自安装服务的简单示例在这里https://github.com/janantos/service_core

        【讨论】:

        • 这就是最佳答案已经解释的内容。仅链接的答案实际上并没有解释问题是如何解决的,这不是很有帮助
        • 它没有指出任何东西。这只是对sc.exe 的几次调用,没有任何解释。该 github 存储库可能随时消失,这会使 this 答案无法使用。事实上,有人必须已经了解服务和 sc.exe 才能了解该存储库包含的内容
        • 此外,Jeroen 的回答解释了此 repo 中缺少的重要步骤 - 配置事件源。
        • 欢迎提供解决方案链接,但请确保您的答案在没有它的情况下有用:add context around the link 这样您的其他用户就会知道它是什么以及为什么会出现,然后引用最相关的您链接到的页面的一部分,以防目标页面不可用。 Answers that are little more than a link may be deleted.
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-11-17
        • 2019-12-20
        • 1970-01-01
        • 2018-11-17
        • 1970-01-01
        • 1970-01-01
        • 2018-01-23
        相关资源
        最近更新 更多