.NET Core 2.0 Windows服务

50
我试图在.Net Core 2.0中构建Windows服务,但是我已经烦恼了一整天,没有任何进展。甚至Microsoft的文档也似乎都在使用Core 1.0/1.1: 在Windows服务中托管ASP.NET Core应用程序 TopShelf也不支持2.0,就我所知。
我看到了一些奇怪的解决方案,将所有代码放入.Net Standard类库中,然后使用.Net Framework应用程序来托管Windows服务,但这在我看来并不优雅,而且我正在尝试完全摆脱.Net Framework。
我想做的事情现在是否可能?我是否忽略了一些非常基本的东西?

3
在尝试构建Windows服务时,试图完全摆脱.Net Framework可能是一场艰苦的战斗。 - thisextendsthat
2
@thisextendsthat - 尽管如此,想要这样做有很好的理由。.NET Core 2.1具有显著的与IO相关的性能改进,而.NET Framework则没有。 - Joel Mueller
我也想使用.NET Core 2.1,但更多是为了未来更新一些Windows服务。但是现在我们希望能够快速将东西部署到现有基础设施上,因此Windows服务似乎比在Docker中运行更可取。 - andrew pate
@andrewpate 正如我下面所说,我目前正在使用DasMuli的NuGet在.Net Core 2.0上运行我的服务,但它也可能适用于2.1。 - DGaspar
10个回答

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

但在开始移植之前,您应该了解您希望通过迁移实现什么。仅仅因为它是一个新的.NET实现而将其移植到.NET Core并不是一个足够好的理由(除非你是一个真正的粉丝)。

特别是,在.NET Core中编写Windows服务现在可能是可行的,但您不会在开箱即用时获得跨平台兼容性,因为除Windows外的其他平台的程序集只会在尝试使用服务代码时抛出PlatformNotSupportedException。虽然可以通过一些方法解决此问题(例如使用RuntimeInformation.IsOSPlatform),但这是另一个问题。
此外,第三方库可能仍然提供更好的界面来安装服务:截至撰写本文时,兼容性包的当前版本(2.0.0-preview1-26216-02)不支持System.Configuration.Install命名空间,因此默认方法使用ServiceProcessInstaller类和installutil无法工作。稍后会有更多详细信息。
假设您已经从项目模板创建了一个全新的Windows服务(Service1),那么只需要编辑并替换.csproj为新格式即可使其在.NET Core 2.0上构建。
<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,因为它不再需要,并且会与项目本身的版本信息冲突。

如果您已经有一个服务并且它有依赖关系,则转换可能更加复杂。请参见此处

现在,您应该能够运行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中提到的库之一来为项目提供自己的安装功能。

我目前正在使用https://github.com/dasMulli/dotnet-win32-service运行我的服务,但是如果可以的话,我会尝试调整这个解决方案,因为它看起来是最好的未来证明,并且当然它看起来将是本地化的方式。如果一切顺利,我会接受你的答案。 - DGaspar
采用这种方法,支持哪种配置文件类型?是App.config还是Appsettings.json? - Justin
@Justin:服务代码中没有任何具体规定。您可以使用普通的.NET Core控制台应用程序所支持的任何内容(这取决于您添加的包和使用的代码)。 - Jeroen Mostert

17

如何将.NET Core 2.0 Web API托管为Windows服务。我按照这篇指南在Windows服务中托管ASP.NET Core进行操作。其中的先决条件部分对我来说不太清楚。经过一些错误,以下是我的做法: 源代码

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

<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. 使用PowerShell命令行工具,输入命令:[解决方案文件夹] dotnet publish -o "[发布目录]"
  2. 使用PowerShell命令行工具,输入命令:[解决方案文件夹] sc.exe create CoreApi binpath="[发布目录]\CoreApiHostedAsWindowsService.exe"
  3. 使用PowerShell命令行工具,输入命令:[解决方案文件夹] sc.exe start CoreApi
  4. 访问默认API,使用PowerShell命令行工具,输入命令:Invoke-WebRequest http://localhost:5000/api/values

4
在我的情况下,我不想让它托管一个网站,我只想让它运行一些后台任务,例如压缩一些文件并通过FTP上传它们以及其他一些工作。您认为这也可以正常工作吗? - DGaspar
根据您的要求,我建议尝试创建一个 .Net Core 控制台应用,并使用 nssm 来托管控制台应用程序。 - Weicheng

14

我将总结一些选项:

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

我认为@JeroenMostert的评论有点苛刻-不依赖于特定的.NET Framework版本在目标机器上可用的吸引力显而易见。正如我链接的这两个仓库所示,许多其他人显然也有相同的感觉。


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

9
在.NET Core 2.1中,您可以使用Host和HostBuilder来获取作为服务运行的控制台应用程序。如果将控制台应用程序容器化,则可以在任何地方部署容器,并且与运行作为服务完全相同。您可以使用Host和HostBuilder来管理DI、Logging、Graceful shut down等控制台应用程序中的内容。请参阅:在.NET Core控制台应用程序中托管服务

4
一个简单的创建.NET Core Windows服务的方法是使用Peter Kottas'的DotNetCore.WindowsService library
NuGet包是PeterKottas.DotNetCore.WindowsService。要使用Visual Studio Package Manager控制台安装它,只需运行
Install-Package PeterKottas.DotNetCore.WindowsService

这里也有如何入门的好笔记


正如我之前提到的那样,PeterKottas的nugget在内部使用了DasMuli的nugget代码。我目前正在使用DasMuli的代码,但PeterKottas的代码也应该可以工作。 - DGaspar
乍一看,这似乎完美地运作。请注意,您需要创建一个.NET Core控制台应用程序并添加包。然后,您可以安装该控制台应用程序以像服务一样运行。在我成功运行之前,我曾经忽略了几次。因此,它不适用于.NET Windows服务。 - CodingYourLife

3

我们只需要安装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 create binPath=""

  3. sc start


可以用。虽然我不得不在binPath中提供.exe的完整路径,可能是因为我有代码找到pathtoContentRoot。 - andrew pate

3

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

要求:PowerShell 6.2或更高版本

基于框架的部署(FDD):

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

在包含目标框架的<PropertyGroup>中添加Windows运行时标识符(RID)。在下面的示例中,RID设置为win7-x64。添加<SelfContained>属性设置为false。这些属性指示SDK为Windows生成可执行文件(.exe)。

对于Windows服务应用程序,通常在发布ASP.NET Core应用程序时生成一个web.config文件是不必要的。为禁用web.config文件的创建,请添加<IsTransformWebConfigDisabled>属性设置为true

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

自包含部署(SCD):

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

确认存在Windows Runtime Identifier (RID),或将 RID 添加到包含目标框架的<PropertyGroup>中。通过添加<IsTransformWebConfigDisabled>属性设置为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)

在项目文件的 <RuntimeIdentifier> (或 <RuntimeIdentifiers> )属性中必须指定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)
    • 完全控制(F)
    • 修改(M)
  • /t – 递归应用于现有的下属文件夹和文件。

命令:

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

使用RegisterService.ps1 PowerShell脚本注册服务。 从管理权限的PowerShell 6命令行窗口中,使用以下命令执行脚本:
.\RegisterService.ps1 
    -Name MyService 
    -DisplayName "My Cool Service" 
    -Description "This is the Sample App service." 
    -Exe "c:\svc\SampleApp.exe" 
    -User Desktop-PC\ServiceUser

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

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

Program.Main:

host.RunAsCustomService();

将内容根目录路径设置为应用程序文件夹:

Program.Main:

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

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

Source:

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

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


注意:如果您使用像appsettings.json这样的配置文件,您可能需要执行以下操作才能获取它... _jsonConfigurationFile = Path.Combine(pathToContentRoot, jsonConfigurationFile) - andrew pate

2
也许这是一个完全的退缩,但请记住,有了更好的Docker支持,您可能能够在容器内构建服务。此时,它仍然是.NET Core(2.0),但在您的Windows主机上运行。更重要的是,您将来可以在任何地方部署它。
随着.NET Core的成熟,我认为这是一个越来越好的解决方案,前提是您的服务不需要本地主机资源。

1

0

5
顶级答案已经解释了这一点。仅提供链接而不实际解释问题如何解决并不是很有帮助。 - Panagiotis Kanavos
2
它没有指出任何东西。这只是对sc.exe的几个调用,没有任何解释。那个Github仓库随时可能消失,这将使得答案无法使用。目前为止,某人必须已经了解服务和sc.exe,才能理解该仓库包含的内容。 - Panagiotis Kanavos
此外,Jeroen的回答解释了这个仓库中缺失的重要步骤 - 配置事件源。 - Panagiotis Kanavos
欢迎提供解决方案的链接,但请确保您的回答在没有链接的情况下也是有用的:通过添加链接周围的上下文信息,让其他用户知道链接的内容和作用,然后引用与问题最相关的页面部分,以防目标页面不可用。如果答案仅仅是一个链接,可能会被删除。 - Dima Kozhevin

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