如何在.NET CORE 下开发 "windows 服务"

在.NET Core中并没有像 .NET Framework下的 “windows服务”可创建,但我们依然可以通过powershell这个工具,将.NET CORE 下创建的项目以”windows服务”的形式来寄宿运行。

0.新建项目

新建、使用一个 web 应用程序 项目即可

本次示例所用 .NET CORE 版本为 2.1

项目结构如下:

1.修改项目csproj,增加RuntimeIdentifiers和IsTransformWebConfigDisabled

<PropertyGroup>
  <TargetFramework>netcoreapp2.1</TargetFramework>
  <RuntimeIdentifiers>win10-x64;win81-x64</RuntimeIdentifiers>
  <IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
</PropertyGroup>
  • 将Windows运行时标识符(RID)添加到包含目标框架的
    • 如果要发布到多个 Rids ,则 需要用复数: ,并且不同 Rid 之间用分号分割,如:

<RuntimeIdentifiers>win10-x64;win81-x64</RuntimeIdentifiers>

2.添加包引用:

Microsoft.AspNetCore.Hosting.WindowsServices

Microsoft.Extensions.Logging.EventLog ——-为了启用 windows事件日志

启用 windows日志 start

创建一个自定义类,继承自 WebHostService ,用来重写 onstarting 等事件

public class MqWebHostService : WebHostService
{
    private ILogger _logger;

    public MqWebHostService(IWebHost host) : base(host)
    {
        _logger = host.Services
            .GetRequiredService<ILogger<MqWebHostService>>();
    }

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

创建一个 IHost 的扩展方法,用来运行自定义的服务:MqWebHostService

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

3.修改 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)
    {
        var hostingConfig = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("hostsettings.json", optional: true)
            .Build(); //指定 urls 键值即可,需要注意不能用 IIS EXPRESS,另外对于端口而言需要避免一些不安全端口,如 6000 端口
        return WebHost.CreateDefaultBuilder(args)
            .ConfigureLogging((hostingContext, logging) =>
            {
                logging.SetMinimumLevel(LogLevel.Trace);
                logging.AddFilter("System", LogLevel.Warning);
                logging.AddFilter("Microsoft", LogLevel.Warning);
                if (Environment.OSVersion.Platform == PlatformID.Unix)
                {
                    logging.AddLog4Net("log4net.linux.config");
                }
                else
                {
                    logging.AddLog4Net();
                }
            })
            .ConfigureAppConfiguration((context, config) =>
            {
                // Configure the app here.
            })
            .UseConfiguration(hostingConfig)
            .UseStartup<Startup>()
            ;
    }
}

4.发布

  • SCD 部署

使用 dotnet public -c release 进行
  • FDD部署

dotnet public -c release -r win81-x64

7.使用 powershell 注册 windows 服务:

需要 6.1.3 或以上版本,下载:https://github.com/PowerShell/PowerShell/releases

以下使用 localservice 这个系统服务作为用户名进行注册服务。

New-Service -Name "MqConsumerForMemberCenter" -BinaryPathName "D:\Services\MemberCenterMqConsumer\SanbenTech.MC.Mq.Subscriber.exe"  -Description "会员中心消息队列 Consumer,定位用户使用小程序时的区域" -DisplayName "MqConsumerForMemberCenter" -StartupType Automatic

删除服务:

remove-service -Name "MqConsumerForMemberCenter"

也可以做成一个批处理,如下:

registerService.ps1

#Requires -Version 6.1.3
#Requires -RunAsAdministrator

param(
    [Parameter(mandatory=$true)]
    $Name,
    [Parameter(mandatory=$true)]
    $DisplayName,
    [Parameter(mandatory=$true)]
    $Description,
    [Parameter(mandatory=$true)]
    $Path,
    [Parameter(mandatory=$true)]
    $Exe,
    [Parameter(mandatory=$true)]
    $User
)

$cred = Get-Credential -Credential $User

$acl = Get-Acl $Path
$aclRuleArgs = $cred.UserName, "Read,Write,ReadAndExecute", "ContainerInherit, ObjectInherit", "None", "Allow"
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $aclRuleArgs
$acl.SetAccessRule($accessRule)
$acl | Set-Acl $Path

New-Service -Name $Name -BinaryPathName "$Path\$Exe" -Credential $cred -Description $Description -DisplayName $DisplayName -StartupType Automatic

如果需要用其他用户来管理服务,则可以:

1.新建用户

#创建用户
net user {mcRabbitMqSubscriber}  {mypwd} /add /expires:never 
#添加到组
net localgroup {GROUP} {USER ACCOUNT} /add
#删除用户
net user {user account} /delete

2.给用户赋予权限:作为服务登录

icacls "{PATH}" /grant "{USER ACCOUNT}:(OI)(CI){PERMISSION FLAGS}" /t
    # {PATH} – Path to the app's folder.
    # {USER ACCOUNT} – The user account (SID).
    # (OI) – Object Inherit标志将权限传播给从属文件。
    # (CI) – Container Inherit标志将权限传播到下级文件夹。
    # {PERMISSION FLAGS} – Sets the app's access permissions.
    # Write (W)
    # Read (R)
    # Execute (X)
    # Full (F)
    # Modify (M)
    # /t – 递归应用于现有的从属文件夹和文件。
#如:(注意这里把useraccount OI,CI,PERMISSION FLAGS 用双引号括起来,否则报错)
icacls "X:\publish" /grant “mcRabbitMqSubscriber:(OI)(CI)WRX” /t

3.调整注册服务

registerService.ps1

#Requires -Version 6.1.3
#Requires -RunAsAdministrator

param(
    [Parameter(mandatory=$true)]
    $Name,
    [Parameter(mandatory=$true)]
    $DisplayName,
    [Parameter(mandatory=$true)]
    $Description,
    [Parameter(mandatory=$true)]
    $Path,
    [Parameter(mandatory=$true)]
    $Exe,
    [Parameter(mandatory=$true)]
    $User
)

$cred = Get-Credential -Credential $User

$acl = Get-Acl $Path
$aclRuleArgs = $cred.UserName, "Read,Write,ReadAndExecute", "ContainerInherit, ObjectInherit", "None", "Allow"
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $aclRuleArgs
$acl.SetAccessRule($accessRule)
$acl | Set-Acl $Path

New-Service -Name $Name -BinaryPathName "$Path\$Exe" -Credential $cred -Description $Description -DisplayName $DisplayName -StartupType Automatic

参考:

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

https://docs.microsoft.com/zh-cn/powershell/scripting/learn/using-familiar-command-names?view=powershell-6

常言道:学然后知不足,教然后知困。

我知道你的焦虑,一起共进加油:P

关不关注都无所谓,会根据生活节奏紧凑度定期分享些开发经验、搬砖生涯、痛点、感悟。

欢迎关注我的订阅号:P