Reputation: 169
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
}
}
Upvotes: 2
Views: 2377
Reputation: 169
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.
Upvotes: 6