c# - dotnet core 3.1 windows 服务无法加载配置设置
问题描述
我遇到了一个旨在作为 Windows 服务运行的 dotnet core 3.1 应用程序的问题。该项目源于VS2019中的Worker Service模板。下面是记录到系统事件日志中的调用堆栈。我在 SO 上或通过网络搜索找不到任何东西。如果我直接(双击)或从命令行执行应用程序,它运行良好。仅当作为 Windows 服务执行时才会引发异常。
Description: The process was terminated due to an unhandled exception.
Exception Info: System.UriFormatException: Invalid URI: The format of the URI could not be determined.
at System.Uri.CreateThis(String uri, Boolean dontEscape, UriKind uriKind)
at System.Uri..ctor(String uriString)
at BranchMonitor.TfvcServer..ctor(IOptions`1 settings) in C:\Users\some_user\source\repos\BranchMonitor\BranchManager\TfvcServer.cs:line 53
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitIEnumerable(IEnumerableCallSite enumerableCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
at BranchMonitor.Program.Main(String[] args) in C:\Users\some_user\source\repos\BranchMonitor\BranchManager\Program.cs:line 28
这是 CreateHostBuilder 的方法
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureHostConfiguration(hostBuilder =>
{
hostBuilder.SetBasePath(Directory.GetCurrentDirectory());
})
.ConfigureAppConfiguration((hostContext, builder) =>
{
builder.SetBasePath(Directory.GetCurrentDirectory());
builder.AddJsonFile("appsettings.json", true, true);
builder.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", true, true);
})
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
services.AddSingleton<ITfvcServer, TfvcServer>();
services.AddOptions();
services.Configure<DevOpsSettings>(hostContext.Configuration.GetSection("AzureDevOps"));
})
.UseWindowsService();
总结的 TfvcServer 类是
public class TfvcServer : ITfvcServer
{
public TfvcServer(IOptions<DevOpsSettings> settings)
{
m_server = settings.Value.Server;
m_collection = settings.Value.Collection;
m_project = settings.Value.Project;
m_definitionFolder = settings.Value.BuildDefinitionFolder;
m_connection = new VssConnection(new Uri($"{m_server}/{m_collection}"),
new VssCredentials()
{
PromptType = CredentialPromptType.DoNotPrompt
});
m_buildClient = m_connection.GetClient<BuildHttpClient>();
m_tfvcClient = m_connection.GetClient<TfvcHttpClient>();
}
}
* 编辑 * 问题的根源是 DevOpsSettings 的值为空。在 ctor 内部,我创建了一个失败的 VssConnection 对象,因为服务器(在配置中定义)是一个空字符串。所以我需要弄清楚为什么配置设置是空的。appsettings.Production.json。“AzureDevOps”部分不在 appsettings.json 中。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AzureDevOps": {
"Server": "http://tfsprod:8080/tfs",
"Collection": "MyCollection",
"DriveLetters": "KLMNOPSUVWXY",
"Project": "MyProject",
"PollingDelay": 60
}
}
解决方案
问题是在服务控制管理器 (SCM) 下运行时的当前工作目录 (CWD) 是 C:\Windows\System32(假设默认操作系统安装)。因此,当尝试加载配置文件时,由于该配置文件不存在,因此无法找到该配置文件。这会导致 POCO 设置类的默认设置,在我的例子中是 string.Empty。它在调试器或控制台下工作正常,因为 CWD 是可执行文件的容器文件夹。
有几种方法可以解决这个问题。一种是在 SCM 中配置服务以将参数作为可执行文件的路径。对我来说这不是很干净,因为现在安装需要配置。这会增加没有价值的复杂性。
另一种选择是使用
System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)
而不是调用 Directory.GetCurrentDirectory()。从这个 SO question
第三种选择是使用
System.AppDomain.CurrentDomain.BaseDirectory
而不是调用 Directory.GetCurrentDirectory()。从这个博客
我敢肯定还有其他可能的解决方案。目标是提供几个可行的选项,而不是提供详尽的解决方案列表。感谢所有做出贡献的人。我真的很感谢你的时间。感谢@JSteward 指出我的大脑放屁,让我指出正确的方向来解决问题。我依靠 SO 来解决问题,我感谢那些做出贡献的人。
推荐阅读
- java - 使用 AsyncHttpClient 自定义 SSL 证书
- laravel - Laravel 模型必须返回一个关系实例
- c# - In an AspNetCore API controller, is there a well-accepted way to DRY out repetitious validation code?
- python - 禁用 gunicorn 证书验证
- android - 将字符串转换为 LocalDateTime
- kubernetes - ReplicaSets 会取代 Pods 吗?
- networking - 网络中两个顶点之间怎么可能有多个最短路径?
- java - 哪个更昂贵:使用变量或使用 for 循环而不是声明变量来保存临时结果?
- java - Selenium:登录后页面刷新
- html - Bootstrap 4底部固定但顶部相对