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

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

0.新建项目

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

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

项目结构如下:

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

1
2
3
4
5
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeIdentifiers>win10-x64;win81-x64</RuntimeIdentifiers>
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
</PropertyGroup>
  • 将Windows运行时标识符(RID)添加到包含目标框架的

    • 如果要发布到多个 Rids ,则 需要用复数: ,并且不同 Rid 之间用分号分割,如:
1
<RuntimeIdentifiers>win10-x64;win81-x64</RuntimeIdentifiers>

2.添加包引用:

Microsoft.AspNetCore.Hosting.WindowsServices

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

启用 windows日志 start

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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

1
2
3
4
5
6
7
8
public static class WebHostServiceExtensions
{
public static void RunAsCustomService(this IWebHost host)
{
var webHostService = new MqWebHostService(host);
ServiceBase.Run(webHostService);
}
}

3.修改 Program.Main 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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 部署

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

1
dotnet public -c release -r win81-x64

7.使用 powershell 注册 windows 服务:

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

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

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

删除服务:

1
remove-service -Name "MqConsumerForMemberCenter"

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

registerService.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#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.新建用户

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#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