如何从.NET Core 2.1/2.2创建Windows服务

17

最近我需要将一个.NET Core 2.1或2.2控制台应用程序转换为Windows服务。

由于我不需要将此过程移植到Linux,因此我可以避免使用在Stack Overflow上看到的处理任何组合的.NET Framework、.NET Standard和.NET Core的多平台解决方案。


友情提醒:2.2不是长期支持(LTS)版本,2.1和3.1才是LTS版本。 - granadaCoder
3个回答

29

本文将介绍如何将.NET Core 2.1或2.2进程设置为Windows服务。

由于我没有Linux的需求,因此我可以寻找一种针对Windows的解决方案。

经过一些调查,我发现了Steve Gordon的一些文章(感谢他!),特别是他介绍Microsoft.Extensions.Hosting包和Windows托管的文章(点击这里查看文章,点击这里查看他的GitHub示例)。

以下是所需的步骤:

  • 首先创建一个.NET Core控制台应用程序。
  • 将语言版本设置为最低7.1以支持Main方法的异步任务。 (从项目设置-〉生成-〉高级-〉语言设置访问语言版本)。
  • 添加Microsoft.Extensions.Hosting和System.ServiceProcess.ServiceController包。
  • 编辑项目.csproj文件,在PropertyGroup中包含:<RuntimeIdentifier>win7-x64</RuntimeIdentifier>
  • 确保在PropertyGroup中有<OutputType>Exe</OutputType>

现在转到Program.cs并复制以下内容:

using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace AdvancedHost
{
    internal class Program
    {
        private static async Task Main(string[] args)
        {
            var isService = !(Debugger.IsAttached || args.Contains("--console"));

            var builder = new HostBuilder()
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<LoggingService>();
                });

            if (isService)
            {
                await builder.RunAsServiceAsync();
            }
            else
            {
                await builder.RunConsoleAsync();
            }
        }
    }
}

这段代码将支持交互式调试和生产执行,并运行示例类LoggingService。

下面是服务本身的框架示例:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;

namespace AdvancedHost
{
    public class LoggingService : IHostedService, IDisposable
    {

        public Task StartAsync(CancellationToken cancellationToken)
        {
            // Startup code

            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            // Stop timers, services
            return Task.CompletedTask;
        }

        public void Dispose()
        {
            // Dispose of non-managed resources
        }
    }
}

完成项目所需的最后两个文件:

文件ServiceBaseLifetime.cs

using Microsoft.Extensions.Hosting;
using System;
using System.ServiceProcess;
using System.Threading;
using System.Threading.Tasks;

namespace AdvancedHost
{

    public class ServiceBaseLifetime : ServiceBase, IHostLifetime
    {
        private readonly TaskCompletionSource<object> _delayStart = new TaskCompletionSource<object>();

        public ServiceBaseLifetime(IApplicationLifetime applicationLifetime)
        {
            ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
        }

        private IApplicationLifetime ApplicationLifetime { get; }

        public Task WaitForStartAsync(CancellationToken cancellationToken)
        {
            cancellationToken.Register(() => _delayStart.TrySetCanceled());
            ApplicationLifetime.ApplicationStopping.Register(Stop);

            new Thread(Run).Start(); // Otherwise this would block and prevent IHost.StartAsync from finishing.
            return _delayStart.Task;
        }

        private void Run()
        {
            try
            {
                Run(this); // This blocks until the service is stopped.
                _delayStart.TrySetException(new InvalidOperationException("Stopped without starting"));
            }
            catch (Exception ex)
            {
                _delayStart.TrySetException(ex);
            }
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            Stop();
            return Task.CompletedTask;
        }

        // Called by base.Run when the service is ready to start.
        protected override void OnStart(string[] args)
        {
            _delayStart.TrySetResult(null);
            base.OnStart(args);
        }

        // Called by base.Stop. This may be called multiple times by service Stop, ApplicationStopping, and StopAsync.
        // That's OK because StopApplication uses a CancellationTokenSource and prevents any recursion.
        protected override void OnStop()
        {
            ApplicationLifetime.StopApplication();
            base.OnStop();
        }
    }
}

文件 ServiceBaseLifetimeHostExtensions.cs:

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace AdvancedHost
{

    public static class ServiceBaseLifetimeHostExtensions
    {
        public static IHostBuilder UseServiceBaseLifetime(this IHostBuilder hostBuilder)
        {
            return hostBuilder.ConfigureServices((hostContext, services) => services.AddSingleton<IHostLifetime, ServiceBaseLifetime>());
        }

        public static Task RunAsServiceAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default)
        {
            return hostBuilder.UseServiceBaseLifetime().Build().RunAsync(cancellationToken);
        }
    }
}

为了安装、运行或删除服务,我使用“sc”实用程序:

sc create AdvancedHost binPath="C:\temp\AdvancedHost\AdvancedHost.exe"

其中AdvancedHost是服务名称,binPath的值是编译后的可执行文件。

创建完服务后,要启动:

sc start AdvancedHost

停止:

sc stop AdvancedHost

最终删除(一旦停止):

sc delete AdvancedHost

sc中包含许多其他特性;只需在命令行中单独输入'sc'即可查看。

使用服务Windows控制面板可以查看sc的结果。


4
现在,您不再需要复制粘贴大量代码来实现此操作。 您只需要安装包Microsoft.Extensions.Hosting.WindowsServices

然后:
  • UseWindowsService()添加到HostBuilder。 这也配置了您的应用程序使用EventLog记录器。
  • 更改项目中的SDK为Microsoft.NET.Sdk.Worker (<Project Sdk="Microsoft.NET.Sdk.Worker">)。
  • 确保输出项目类型是EXE文件 (<OutputType>Exe</OutputType>)
  • <RuntimeIdentifier>win7-x64</RuntimeIdentifier>附加到项目文件中。
像常规控制台应用程序一样调试服务,然后运行dotnet publishsc create ...等命令。
就是这样。 这也适用于.NET Core 3.0/3.1。 阅读更多信息这里
最简代码示例如下。

.csproj文件:

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

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

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="3.1.3" />
  </ItemGroup>

</Project>

文件 Program.cs:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace NetcoreWindowsService
{
    class Program
    {
        static void Main()
        {
            new HostBuilder()
                .ConfigureServices(services => services.AddHostedService<MyService>())
                .UseWindowsService()
                .Build()
                .Run();
        }
    }
}

文件 MyService.cs:

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;

namespace NetcoreWindowsService
{
    internal class MyService : IHostedService
    {
        private readonly ILogger<MyService> _logger;

        public MyService(ILogger<MyService> logger) => _logger = logger;

        public Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("The service has been started");
            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("The service has been stopped");
            return Task.CompletedTask;
        }
    }
}

0

Top shelf一直是Windows服务的一个很好的“框架”。

它为所有Windows服务共享的常规任务提供了良好的功能。

特别是“安装”功能。

基本原理如下:

https://www.nuget.org/packages/Topshelf/

请注意上面的NuGet可在NetStandard2.0下运行。

现在以下。 您可以创建MyWindowsServiceExe.csproj ..并将其编码为Exe/2.1或Exe/3.1(在打开csproj时显示)。 请注意,2.2不再是长期支持版本,我建议避免编写新代码到2.2。(离题了,但链接在这里 https://dotnet.microsoft.com/platform/support/policy/dotnet-core

public class LoggingService : TopShelf.ServiceControl
{
private const string logFileFullName = @"C:\temp\servicelog.txt";

private void MyLogMe(string logMessage)
{
    Directory.CreateDirectory(Path.GetDirectoryName(logFileFullName));
    File.AppendAllText(logFileFullName, DateTime.UtcNow.ToLongTimeString() + " : " + logMessage + Environment.NewLine);
}

public bool Start(HostControl hostControl)
{
    MyLogMe("Starting");
    return true;
}

public bool Stop(HostControl hostControl)
{
    MyLogMe("Stopping");
    return true;
}
}


static void Main(string[] args)
{
HostFactory.Run(x =>
    {
        x.Service<LoggingService>();
        x.EnableServiceRecovery(r => r.RestartService(TimeSpan.FromSeconds(333)));
        x.SetServiceName("MyTestService");
        x.StartAutomatically();
     }
);
}

并将其构建/发布到Windows:

dotnet publish -r win-x64 -c Release

然后是我提到的辅助方法

MyWindowsServiceExe.exe install

(现在在控制面板下检查windows-services是否已安装)(也要检查“如果崩溃,我应该怎么做”选项卡)。

最后(另一个助手),您可以停止windows-service,您可以从命令行中运行它(在windows-service之外)。这是我用于调试的最爱。

MyWindowsServiceExe.exe start


我正在点赞Mark McWhirter的回答,因为它很好。这个答案提供了一个替代方案和一个“FYI”给TopShelf。 - granadaCoder

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