dotnet core 3.1 windows service fails to load configuration settings

风流意气都作罢 提交于 2020-12-16 03:41:09

问题


I've run into a problem with a dotnet core 3.1 application that's designed to run as a windows service. The project originates from the Worker Service template in VS2019. Below is the call stack logged into the system event log. I can't find anything on SO or via web search. If I execute the application directly (double-click) or from a command line it runs fine. The exception is only thrown when executed as a windows service.

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

Here's the method 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();

The TfvcServer class summarized is

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

* EDIT * The source of the problem is the values of the DevOpsSettings are empty. Inside the ctor I create a VssConnection object that is failing since the sever (defined in config) is an empty string. So I need to figure out why the configuration settings are empty. appsettings.Production.json. The "AzureDevOps" section is not in 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
   }
}

回答1:


The problem is the current working directory (CWD) when running under the Service Control Manager (SCM) is C:\Windows\System32 (assuming default OS install). Therefore, when the configuration file is attempted to be loaded it cannot find the configuration file since it doesn't exist. This results in default settings from the POCO settings class, which in my case is string.Empty. It works fine under the debugger or a console since the CWD is the container folder of the executable file.

There are a few approaches to solve this. One being configure the service in SCM to pass a parameter being the path to the executable. To me this isn't very clean since now installation requires configuration. This results in added complexity with no value.

Another option is to use

System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)

instead of the call to Directory.GetCurrentDirectory(). From this SO question

A third option is to use

System.AppDomain.CurrentDomain.BaseDirectory

instead of the call to Directory.GetCurrentDirectory(). From this blog

I'm sure there are other possible solutions. The goal is to provide a couple options that work rather than an exhaustive list of solutions. Thank you to everyone who contributed. I really appreciate your time. Thanks to @JSteward for pointing out my brain fart to get me pointed in the right direction to a resolution. I rely on SO to solve issues and I appreciate those who contribute.



来源:https://stackoverflow.com/questions/61734720/dotnet-core-3-1-windows-service-fails-to-load-configuration-settings

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!