不将 IIS 用作 Windows 服务时,可在 Windows 上托管 ASP.NET Core 应用。 作为 Windows 服务托管时,无需人工干预应用即可在重新启动和崩溃后自动启动。
要将现有 ASP.NET Core 项目设置为作为服务运行,至少需要执行以下更改:
<PropertyGroup>
中:
1 2 3 4 5 |
<span class="hljs-tag"><<span class="hljs-name">PropertyGroup</span>></span> <span class="hljs-tag"><<span class="hljs-name">TargetFramework</span>></span>netcoreapp2.1<span class="hljs-tag"></<span class="hljs-name">TargetFramework</span>></span> <span class="hljs-tag"><<span class="hljs-name">RuntimeIdentifier</span>></span>win7-x64<span class="hljs-tag"></<span class="hljs-name">RuntimeIdentifier</span>></span> <span class="hljs-tag"></<span class="hljs-name">PropertyGroup</span>></span> |
要发布多个 RID:
<RuntimeIdentifiers>
(复数)。有关详细信息,请参阅 .NET Core RID 目录。
Program.Main
中,进行下列更改:
host.Run
。Directory.GetCurrentDirectory()
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>) </span>{ CreateWebHostBuilder(args).Build().RunAsService(); } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> IWebHostBuilder <span class="hljs-title">CreateWebHostBuilder</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>) </span>{ <span class="line-highlight"> <span class="hljs-keyword">var</span> pathToExe = Process.GetCurrentProcess().MainModule.FileName;</span> <span class="line-highlight"> <span class="hljs-keyword">var</span> pathToContentRoot = Path.GetDirectoryName(pathToExe);</span> <span class="hljs-keyword">return</span> WebHost.CreateDefaultBuilder(args) .ConfigureAppConfiguration((context, config) => { <span class="hljs-comment">// Configure the app here.</span> }) <span class="line-highlight"> .UseContentRoot(pathToContentRoot)</span> .UseStartup<Startup>(); } |
要使用命令行接口 (CLI) 工具发布示例应用,请在项目文件夹的命令提示符处运行 dotnet publish 命令。 必须在项目文件的 <RuntimeIdenfifier>
(或 <RuntimeIdentifiers>
)属性中指定 RID。 在以下示例中,应用在 win7-x64
运行时的发布配置中发布:
1 2 |
dotnet publish --configuration Release --runtime win7-x64 |
binPath
值是应用的可执行文件的路径,其中包括可执行文件的文件名。 等于号和路径开头的引号字符之间需要添加空格。
1 2 |
sc create <SERVICE_NAME> binPath= "<PATH_TO_SERVICE_EXECUTABLE>" |
对于项目文件夹中发布的服务,请使用 publish 文件夹的路径创建服务。 如下示例中:
Release
配置中发布。netcoreapp2.1
。win7-x64
。示例:
1 2 |
sc create MyService binPath= "c:\my_services\AspNetCoreService\bin\Release\netcoreapp2.1\win7-x64\publish\AspNetCoreService.exe" |
重要
确保 binPath=
参数与其值之间存在空格。
从其他文件夹发布和启动服务:
dotnet publish
命令上的 --output <OUTPUT_DIRECTORY> 选项。 如果使用 Visual Studio,请在“FolderProfile”发布属性页面中配置“目标位置”,然后再选择“发布”按钮。sc.exe
命令创建服务。 在向 binPath
提供的路径中包含服务可执行文件的名称。sc start <SERVICE_NAME>
命令启动服务。
要启动示例应用服务,请使用以下命令:
1 2 |
sc start MyService |
此命令需要几秒钟才能启动服务。
sc query <SERVICE_NAME>
命令。 状态报告为以下值之一:
START_PENDING
RUNNING
STOP_PENDING
STOPPED
使用以下命令检查示例应用服务的状态:
1 2 |
sc query MyService |
RUNNING
状态并且服务是 Web 应用时,在应用所在路径中浏览应用(默认路径为 http://localhost:5000
;在使用 HTTPS 重定向中间件时,它将重定向到 https://localhost:5001
)。
对于示例应用服务,请在 http://localhost:5000
浏览应用。
sc stop <SERVICE_NAME>
命令停止服务。
以下命令可停止示例应用服务:
1 2 |
sc stop MyService |
sc delete <SERVICE_NAME>
命令卸载服务。
检查示例应用服务的状态:
1 2 |
sc query MyService |
当示例应用服务处于 STOPPED
状态时,使用以下命令卸载示例应用服务:
1 2 |
sc delete MyService |
在服务之外运行时更便于进行测试和调试,因此通常仅在特定情况下添加调用 RunAsService
的代码。 例如,应用可以使用 --console
命令行参数或在已附加调试器时作为控制台应用运行:
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 |
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>) </span>{ <span class="hljs-keyword">var</span> isService = !(Debugger.IsAttached || args.Contains(<span class="hljs-string">"--console"</span>)); <span class="hljs-keyword">var</span> builder = CreateWebHostBuilder(args.Where(arg => arg != <span class="hljs-string">"--console"</span>).ToArray()); <span class="hljs-keyword">if</span> (isService) { <span class="hljs-keyword">var</span> pathToExe = Process.GetCurrentProcess().MainModule.FileName; <span class="hljs-keyword">var</span> pathToContentRoot = Path.GetDirectoryName(pathToExe); builder.UseContentRoot(pathToContentRoot); } <span class="hljs-keyword">var</span> host = builder.Build(); <span class="hljs-keyword">if</span> (isService) { host.RunAsService(); } <span class="hljs-keyword">else</span> { host.Run(); } } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> IWebHostBuilder <span class="hljs-title">CreateWebHostBuilder</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>) </span>=> WebHost.CreateDefaultBuilder(args) .ConfigureAppConfiguration((context, config) => { <span class="hljs-comment">// Configure the app here.</span> }) .UseStartup<Startup>(); |
由于 ASP.NET Core 配置需要命令行参数的名称/值对,因此将先删除 --console
开关,然后再将参数传递到 CreateDefaultBuilder。
备注
不将 Main
中的 isService
传递到 CreateWebHostBuilder
,因为 CreateWebHostBuilder
的签名必须是 CreateWebHostBuilder(string[])
才能使集成测试正常运行。
要处理 OnStarting、OnStarted 和 OnStopping 事件,请执行以下额外更改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CustomWebHostService</span> : <span class="hljs-title">WebHostService</span> { <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">CustomWebHostService</span>(<span class="hljs-params">IWebHost host</span>) : <span class="hljs-title">base</span>(<span class="hljs-params">host</span>) </span>{ } <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnStarting</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>) </span>{ <span class="hljs-keyword">base</span>.OnStarting(args); } <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnStarted</span>() </span>{ <span class="hljs-keyword">base</span>.OnStarted(); } <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnStopping</span>() </span>{ <span class="hljs-keyword">base</span>.OnStopping(); } } |
WebHostService
传递给 ServiceBase.Run 的 IWebHost 的扩展方法:
1 2 3 4 5 6 7 8 9 |
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">WebHostServiceExtensions</span> { <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">RunAsCustomService</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> IWebHost host</span>) </span>{ <span class="hljs-keyword">var</span> webHostService = <span class="hljs-keyword">new</span> CustomWebHostService(host); ServiceBase.Run(webHostService); } } |
Program.Main
中,调用新扩展方法 RunAsCustomService
,而不是 RunAsService:
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 |
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>) </span>{ <span class="hljs-keyword">var</span> isService = !(Debugger.IsAttached || args.Contains(<span class="hljs-string">"--console"</span>)); <span class="hljs-keyword">var</span> builder = CreateWebHostBuilder(args.Where(arg => arg != <span class="hljs-string">"--console"</span>).ToArray()); <span class="hljs-keyword">if</span> (isService) { <span class="hljs-keyword">var</span> pathToExe = Process.GetCurrentProcess().MainModule.FileName; <span class="hljs-keyword">var</span> pathToContentRoot = Path.GetDirectoryName(pathToExe); builder.UseContentRoot(pathToContentRoot); } <span class="hljs-keyword">var</span> host = builder.Build(); <span class="hljs-keyword">if</span> (isService) { <span class="line-highlight"> host.RunAsCustomService();</span> } <span class="hljs-keyword">else</span> { host.Run(); } } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> IWebHostBuilder <span class="hljs-title">CreateWebHostBuilder</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>) </span>=> WebHost.CreateDefaultBuilder(args) .ConfigureAppConfiguration((context, config) => { <span class="hljs-comment">// Configure the app here.</span> }) .UseStartup<Startup>(); |
备注
不将 Main
中的 isService
传递到 CreateWebHostBuilder
,因为 CreateWebHostBuilder
的签名必须是 CreateWebHostBuilder(string[])
才能使集成测试正常运行。
如果自定义 WebHostService
代码需要来自依赖项注入(如记录器)的服务,请从 IWebHost.Services 属性中获取:
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 |
<span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CustomWebHostService</span> : <span class="hljs-title">WebHostService</span> { <span class="hljs-keyword">private</span> ILogger _logger; <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">CustomWebHostService</span>(<span class="hljs-params">IWebHost host</span>) : <span class="hljs-title">base</span>(<span class="hljs-params">host</span>) </span>{ <span class="line-highlight"> _logger = host.Services</span> <span class="line-highlight"> .GetRequiredService<ILogger<CustomWebHostService>>();</span> } <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnStarting</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>) </span>{ _logger.LogDebug(<span class="hljs-string">"OnStarting method called."</span>); <span class="hljs-keyword">base</span>.OnStarting(args); } <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnStarted</span>() </span>{ _logger.LogDebug(<span class="hljs-string">"OnStarted method called."</span>); <span class="hljs-keyword">base</span>.OnStarted(); } <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnStopping</span>() </span>{ _logger.LogDebug(<span class="hljs-string">"OnStopping method called."</span>); <span class="hljs-keyword">base</span>.OnStopping(); } } |
与来自 Internet 或公司网络的请求进行交互且在代理或负载均衡器后方的服务可能需要其他配置。 有关更多信息,请参见配置 ASP.NET Core 以使用代理服务器和负载均衡器。
通过为 Windows 服务调用 Directory.GetCurrentDirectory()
返回的当前工作目录是 C:\WINDOWS\system32 文件夹。 system32 文件夹不是存储服务文件(如设置文件)的合适位置。 使用 IConfigurationBuilder 时,通过以下某种方法使用 FileConfigurationExtensions.SetBasePath 维护和访问服务的资产和设置文件:
IHostingEnvironment.ContentRootPath
是创建服务时提供给 binPath
参数的同一路径。 不要使用 Directory.GetCurrentDirectory()
创建设置文件的路径,而是使用内容根路径并在应用的内容根中维护文件。SetBasePath
指定到包含文件的文件夹的绝对路径。
from:https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/windows-service?view=aspnetcore-2.1