[英]How to fail gracefully if custom configuration settings are not present/valid during ASP.NET Core 2 application startup (hosted in a Windows Service)?
我正在使用Windows服務中托管的ASP.NET Core 2(針對.NET Framework 4.6.1)實現Web API。
我從在Windows Service中托管ASP.NET Core應用程序中的示例開始,但是在實現以下附加要求時遇到了麻煩:
Windows服務(或它承載的Web API)啟動或重新啟動時,我想讀取一些配置(碰巧在注冊表中,但可以在任何地方)。
我不清楚Windows服務/ Web應用程序中讀取和驗證自定義配置的邏輯應該放在哪里。
根據配置邏輯的位置,如果配置無效,則需要采用一種優雅的方法來停止進一步的啟動進度並關閉所有已啟動的內容。
我在ASP.NET或Windows Services框架中看不到任何明顯的鈎子。
我曾考慮過將讀取和驗證邏輯放在以下位置,但是每個位置都有一些問題:
IWebHost.RunAsService()
之前 CustomWebHostService
的通知方法中(可能是OnStarting()
嗎?) Startup
類( Configure()
或ConfigureServices()
)期間 任何人都可以闡明如何做到這一點嗎?
這似乎是快速失敗的最干凈的方法,因為如果配置丟失/不正確,則Windows服務甚至都不會啟動。
但是,這意味着當服務控制管理器啟動托管可執行文件時,我們不會(通過IWebHost.RunAsService()
) IWebHost.RunAsService()
注冊,因此SCM返回錯誤:
[SC] StartService FAILED 1053:
The service did not respond to the start or control request in a timely fashion.
理想情況下,服務控制管理器應該知道啟動失敗的原因,然后可以將其記錄到事件日志中。
我認為這是不可能的,除非我們向服務控制管理器注冊,這使我們進入了選項2。
在Web API的前一個版本中(在WCF中),我將ServiceBase
子類化,在ServiceBaseSubclass.OnStart()
獲取並驗證了配置,如果該配置無效,請按照What is the中的建議設置this.ExitCode
並調用Stop()
Windows服務失敗的正確方法? 。 像這樣:
partial class WebServicesHost : ServiceBase
{
private ServiceHost _webServicesServiceHost;
// From https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx
private const int ErrorBadConfiguration = 1610;
protected override void OnStart(string[] args)
{
base.OnStart(args);
var customConfig = ReadAndValidateCustomConfig();
if (customConfig != null)
{
var webService = new WebService(customConfig);
_webServicesServiceHost = new ServiceHost(webService);
_webServicesServiceHost.Open();
}
else
{
// Configuration is bad, stop the service
ExitCode = ErrorBadConfiguration;
Stop();
}
}
}
然后,當您使用服務控制管理器啟動和查詢Windows服務時,它會正確報告失敗:
C:\> sc start MyService
SERVICE_NAME: MyService
TYPE : 10 WIN32_OWN_PROCESS
STATE : 2 START_PENDING
(NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x7d0
PID : 10764
FLAGS :
C:\> sc query MyService
SERVICE_NAME: MyService
TYPE : 10 WIN32_OWN_PROCESS
STATE : 1 STOPPED <-- Service is stopped
WIN32_EXIT_CODE : 1610 (0x64a) <-- and reports the reason for stopping
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
在ASP.NET Core 2中,如果我將WebHostService
子類WebHostService
(如在Windows Service中托管ASP.NET Core應用程序中所述 ),那么似乎僅存在用於通知Windows Service的啟動工作流進度的鈎子(例如OnStarting()
或OnStarted()
),但沒有提及如何安全地停止服務。
查看WebHostService
源代碼並反編譯ServiceBase
使我認為,從CustomWebHostService.OnStarting()
調用ServiceBase.Stop()
是一個非常糟糕的主意,因為我認為這會導致使用已處置的對象。
class CustomWebHostService : WebHostService
{
// From https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx
private const int ErrorBadConfiguration = 1610;
public CustomWebHostService(IWebHost host) : base(host)
{
}
protected override void OnStarting(string[] args)
{
base.OnStarting(args);
var customConfig = ReadAndValidateCustomConfig();
if (customConfig == null)
{
ExitCode = ErrorBadConfiguration;
Stop();
}
}
}
// Code from https://github.com/aspnet/Hosting/blob/2.0.1/src/Microsoft.AspNetCore.Hosting.WindowsServices/WebHostService.cs
public class WebHostService : ServiceBase
{
protected sealed override void OnStart(string[] args)
{
OnStarting(args);
_host
.Services
.GetRequiredService<IApplicationLifetime>()
.ApplicationStopped
.Register(() =>
{
if (!_stopRequestedByWindows)
{
Stop();
}
});
_host.Start();
OnStarted();
}
protected sealed override void OnStop()
{
_stopRequestedByWindows = true;
OnStopping();
_host?.Dispose();
OnStopped();
}
具體來說, CustomWebHostService.OnStarting()
將調用ServiceBase.Stop()
,后者又將調用WebHostService.OnStop()
來處理this._host
。 然后,使用this._host
WebServiceHost.OnStart()
的第二條語句。
令人困惑的是,由於CustomWebHostService.OnStarted()
最終沒有在我的實驗中被調用,因此這種方法實際上似乎“可行”。 但是,我懷疑這是因為事先拋出了異常。 這似乎不是應該依靠的東西,因此感覺也不是一個特別強大的解決方案。
Windows服務在啟動過程中失敗的正確方法是通過延遲ServiceBase.Stop()
調用(即讓Windows Service啟動,然后停止它的配置結果是錯誤的)提出了另一種方法,但這似乎通過在將來的任意時間停止Windows服務,允許Web API開始啟動,然后再將腿掃出Web API。
我認為,如果Windows服務可以告訴它不應該啟動Web API,或者啟動Web API讀取配置並關閉自身,則我不希望啟動Web API (請參閱選項3)。
同樣,仍然不清楚如何使customConfig
實例可用於ASP.NET Core 2 Web Service類。
這似乎是讀取配置的最佳位置,因為配置在邏輯上屬於Web服務,並且與托管無關。
我發現IApplicationLifetime
接口具有StopApplication()
方法(請參閱ASP.NET Core中的Hosting的 IApplicationLifetime部分)。 這似乎很理想。
public class Program
{
public static void Main(string[] args)
{
if (Debugger.IsAttached)
{
BuildWebHost(args).Run();
}
else
{
BuildWebHost(args).RunAsCustomService();
}
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseUrls("http://*:5000")
.Build();
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IApplicationLifetime appLifetime, IHostingEnvironment env)
{
appLifetime.ApplicationStarted.Register(OnStarted);
appLifetime.ApplicationStopping.Register(OnStopping);
appLifetime.ApplicationStopped.Register(OnStopped);
var customConfig = ReadAndValidateCustomConfig();
if (customConfig != null)
{
// TODO: Somehow make config available to MVC controllers
app.UseMvc();
}
else
{
appLifetime.StopApplication();
}
}
private void OnStarted()
{
// Perform post-startup activities here
Console.WriteLine("OnStarted");
}
private void OnStopping()
{
// Perform on-stopping activities here
Console.WriteLine("OnStopping");
}
private void OnStopped()
{
// Perform post-stopped activities here
Console.WriteLine("OnStopped");
}
}
在Visual Studio中進行調試時,此方法“有效”(即,如果配置良好,則進程獨立啟動並提供Web API;如果不良,則干凈地關閉該進程),盡管日志消息的顯示順序有些奇怪使我感到懷疑:
OnStopping
OnStarted
Hosting environment: Development
Content root path: [redacted]
Now listening on: http://[::]:5000
Application started. Press Ctrl+C to shut down.
OnStopped
作為配置無效的Windows服務啟動時,該服務保持運行狀態,但是Web API始終以404(未找到)響應。
據推測,這意味着Web API已關閉,但是Windows Service尚未注意到這一點,因此也未關閉自身。
再次查看IApplication文檔,我注意到它說:
IApplicationLifetime接口允許您執行啟動后和關閉后的活動。
這表明我真的不應該在啟動過程中調用StopApplication()
。 但是,在應用程序啟動之后,我看不到將呼叫延遲的方法。
如果這不可能,是否有另一種方式向ASP.NET Core 2發出信號,通知Startup.Configure()
函數已失敗並且應關閉應用程序?
我們歡迎任何有關達到上述要求的好方法的建議,並指出我嚴重誤解了Windows Services和/或ASP.NET Core 2! :-)
在HTTP.sys Windows服務中托管的Web API服務中,我讀取了Window服務的OnStart方法中的配置。 您可以在if (File.Exists(configurationFile))
添加else語句,然后記錄日志,然后拋出異常以停止啟動。
using System;
using System.IO;
using System.ServiceProcess;
using Cotg.Core2.Contract;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Newtonsoft.Json.Linq;
namespace Company.Test.Service
{
public class WindowsService : ServiceBase
{
public static AppSettings AppSettings;
public static ICoreLogger Logger;
private IWebHost _webHost;
protected override void OnStart(string[] Args)
{
// Parse configuration file.
string contentRoot = AppDomain.CurrentDomain.BaseDirectory;
const string environmentalVariableName = "ASPNETCORE_ENVIRONMENT";
string environment = Environment.GetEnvironmentVariable(environmentalVariableName) ?? EnvironmentNames.Development;
string configurationFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AppSettings.json");
if (File.Exists(configurationFile))
{
JObject configuration = JObject.Parse(File.ReadAllText(configurationFile));
AppSettings = configuration.GetValue(environment).ToObject<AppSettings>();
}
// Create logger.
Logger = new ConcurrentDatabaseLogger(Environment.MachineName, AppSettings.AppName, AppSettings.ProcessName, AppSettings.AppLogsDatabase);
// Create Http.sys web host.
Logger.Log("Creating web host using HTTP.sys.");
_webHost = WebHost.CreateDefaultBuilder(Args)
.UseStartup<WebApiStartup>()
.UseContentRoot(contentRoot)
.UseHttpSys(Options =>
{
Options.Authentication.Schemes = AppSettings.AuthenticationSchema;
Options.Authentication.AllowAnonymous = AppSettings.AllowAnonymous;
Options.MaxConnections = AppSettings.MaxConnections;
Options.MaxRequestBodySize = AppSettings.MaxRequestBodySize;
// Allow external access to service by opening TCP port via an Inbound Rule in Windows Firewall.
// To run service using a non-administrator account, grant the user access to URL via netsh.
// Use an exact URL: netsh http add urlacl url=http://servername:9092/ user=domain\user.
// Do not use a wildcard URL: netsh http add urlacl url=http://+:9092/ user=domain\user.
Options.UrlPrefixes.Add(AppSettings.Url);
})
.Build();
_webHost.Start();
Logger.Log($"Web host started. Listening on {AppSettings.Url}.");
}
protected override void OnStop()
{
_webHost?.StopAsync(TimeSpan.FromSeconds(60));
}
}
}
我不必擔心ASP.NET Core的ConfigBuilder,因為在調用ASP.NET Configure&ConfigureServices方法之前,Windows服務啟動中需要一個強類型的配置類。 我只是向ASP.NET Core的依賴項注入注冊了AppSettings類,因此任何需要它的控制器都可以使用它。
public void ConfigureServices(IServiceCollection Services)
{
Services.AddMvc();
// Configure dependency injection.
Services.AddSingleton(typeof(AppSettings), WindowsService.AppSettings);
Services.AddSingleton(typeof(ICoreLogger), WindowsService.Logger);
}
AppSettings.json具有適用於所有環境的條目:
{
"Dev": {
"Key1": "Value1",
"Key2": "Value2"
},
"Test": {
"Key1": "Value1",
"Key2": "Value2"
},
"Prod": {
"Key1": "Value1",
"Key2": "Value2"
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.