{
public static partial class SystemdHostBuilderExtensions
{
+ public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddSystemd(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; }
public static Microsoft.Extensions.Hosting.IHostBuilder UseSystemd(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder) { throw null; }
}
}
{
public static bool IsSystemdService() { throw null; }
}
- [System.Runtime.Versioning.UnsupportedOSPlatform("android")]
- [System.Runtime.Versioning.UnsupportedOSPlatform("browser")]
- [System.Runtime.Versioning.UnsupportedOSPlatform("ios")]
- [System.Runtime.Versioning.UnsupportedOSPlatform("maccatalyst")]
- [System.Runtime.Versioning.UnsupportedOSPlatform("tvos")]
+ [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
+ [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
+ [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
+ [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("maccatalyst")]
+ [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
public partial class SystemdLifetime : Microsoft.Extensions.Hosting.IHostLifetime, System.IDisposable
{
public SystemdLifetime(Microsoft.Extensions.Hosting.IHostEnvironment environment, Microsoft.Extensions.Hosting.IHostApplicationLifetime applicationLifetime, Microsoft.Extensions.Hosting.Systemd.ISystemdNotifier systemdNotifier, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
public System.Threading.Tasks.Task StopAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
public System.Threading.Tasks.Task WaitForStartAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
}
- [System.Runtime.Versioning.UnsupportedOSPlatform("browser")]
+ [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public partial class SystemdNotifier : Microsoft.Extensions.Hosting.Systemd.ISystemdNotifier
{
public SystemdNotifier() { }
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting.Systemd;
using Microsoft.Extensions.Logging.Console;
public static class SystemdHostBuilderExtensions
{
/// <summary>
- /// Sets the host lifetime to <see cref="SystemdLifetime" />,
+ /// Configures the <see cref="IHost"/> lifetime to <see cref="SystemdLifetime"/>,
/// provides notification messages for application started and stopping,
/// and configures console logging to the systemd format.
/// </summary>
/// notifications. See https://www.freedesktop.org/software/systemd/man/systemd.service.html.
/// </para>
/// </remarks>
- /// <param name="hostBuilder">The <see cref="IHostBuilder"/> to use.</param>
- /// <returns></returns>
+ /// <param name="hostBuilder">The <see cref="IHostBuilder"/> to configure.</param>
+ /// <returns>The <paramref name="hostBuilder"/> instance for chaining.</returns>
public static IHostBuilder UseSystemd(this IHostBuilder hostBuilder)
{
+ ThrowHelper.ThrowIfNull(hostBuilder);
+
if (SystemdHelpers.IsSystemdService())
{
hostBuilder.ConfigureServices((hostContext, services) =>
{
- services.Configure<ConsoleLoggerOptions>(options =>
- {
- options.FormatterName = ConsoleFormatterNames.Systemd;
- });
-
- // IsSystemdService() will never return true for android/browser/iOS/tvOS
-#pragma warning disable CA1416 // Validate platform compatibility
- services.AddSingleton<ISystemdNotifier, SystemdNotifier>();
- services.AddSingleton<IHostLifetime, SystemdLifetime>();
-#pragma warning restore CA1416 // Validate platform compatibility
+ AddSystemdLifetime(services);
});
}
return hostBuilder;
}
+
+ /// <summary>
+ /// Configures the lifetime of the <see cref="IHost"/> built from <paramref name="services"/> to
+ /// <see cref="SystemdLifetime"/>, provides notification messages for application started
+ /// and stopping, and configures console logging to the systemd format.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// This is context aware and will only activate if it detects the process is running
+ /// as a systemd Service.
+ /// </para>
+ /// <para>
+ /// The systemd service file must be configured with <c>Type=notify</c> to enable
+ /// notifications. See <see href="https://www.freedesktop.org/software/systemd/man/systemd.service.html"/>.
+ /// </para>
+ /// </remarks>
+ /// <param name="services">
+ /// The <see cref="IServiceCollection"/> used to build the <see cref="IHost"/>.
+ /// For example, <see cref="HostApplicationBuilder.Services"/> or the <see cref="IServiceCollection"/> passed to the
+ /// <see cref="IHostBuilder.ConfigureServices(System.Action{HostBuilderContext, IServiceCollection})"/> callback.
+ /// </param>
+ /// <returns>The <paramref name="services"/> instance for chaining.</returns>
+ public static IServiceCollection AddSystemd(this IServiceCollection services)
+ {
+ ThrowHelper.ThrowIfNull(services);
+
+ if (SystemdHelpers.IsSystemdService())
+ {
+ AddSystemdLifetime(services);
+ }
+ return services;
+ }
+
+ private static void AddSystemdLifetime(IServiceCollection services)
+ {
+ services.Configure<ConsoleLoggerOptions>(options =>
+ {
+ options.FormatterName = ConsoleFormatterNames.Systemd;
+ });
+
+ // IsSystemdService() will never return true for android/browser/iOS/tvOS
+#pragma warning disable CA1416 // Validate platform compatibility
+ services.AddSingleton<ISystemdNotifier, SystemdNotifier>();
+ services.AddSingleton<IHostLifetime, SystemdLifetime>();
+#pragma warning restore CA1416 // Validate platform compatibility
+
+ }
}
}
[Fact]
public void DefaultsToOffOutsideOfService()
{
- var host = new HostBuilder()
+ using IHost host = new HostBuilder()
.UseSystemd()
.Build();
- using (host)
+ var lifetime = host.Services.GetRequiredService<IHostLifetime>();
+ Assert.NotNull(lifetime);
+ Assert.IsNotType<SystemdLifetime>(lifetime);
+ }
+
+ [Fact]
+ public void ServiceCollectionExtensionMethodDefaultsToOffOutsideOfService()
+ {
+ var builder = new HostApplicationBuilder(new HostApplicationBuilderSettings
{
- var lifetime = host.Services.GetRequiredService<IHostLifetime>();
- Assert.NotNull(lifetime);
- Assert.IsNotType<SystemdLifetime>(lifetime);
- }
+ // Disable defaults that may not be supported on the testing platform like EventLogLoggerProvider.
+ DisableDefaults = true,
+ });
+
+ builder.Services.AddSystemd();
+ using IHost host = builder.Build();
+
+ var lifetime = host.Services.GetRequiredService<IHostLifetime>();
+ Assert.NotNull(lifetime);
+ Assert.IsNotType<SystemdLifetime>(lifetime);
}
}
}
{
public static partial class WindowsServiceLifetimeHostBuilderExtensions
{
+ public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddWindowsService(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; }
+ public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddWindowsService(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<Microsoft.Extensions.Hosting.WindowsServiceLifetimeOptions> configure) { throw null; }
public static Microsoft.Extensions.Hosting.IHostBuilder UseWindowsService(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder) { throw null; }
public static Microsoft.Extensions.Hosting.IHostBuilder UseWindowsService(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action<Microsoft.Extensions.Hosting.WindowsServiceLifetimeOptions> configure) { throw null; }
}
{
public static partial class WindowsServiceHelpers
{
- [System.Runtime.Versioning.SupportedOSPlatformGuard("windows")]
+ [System.Runtime.Versioning.SupportedOSPlatformGuardAttribute("windows")]
public static bool IsWindowsService() { throw null; }
}
- [System.Runtime.Versioning.SupportedOSPlatform("windows")]
+ [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")]
public partial class WindowsServiceLifetime : System.ServiceProcess.ServiceBase, Microsoft.Extensions.Hosting.IHostLifetime
{
public WindowsServiceLifetime(Microsoft.Extensions.Hosting.IHostEnvironment environment, Microsoft.Extensions.Hosting.IHostApplicationLifetime applicationLifetime, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.Extensions.Options.IOptions<Microsoft.Extensions.Hosting.HostOptions> optionsAccessor) { }
using Microsoft.Extensions.Hosting.WindowsServices;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.EventLog;
+using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.Hosting
{
public static class WindowsServiceLifetimeHostBuilderExtensions
{
/// <summary>
- /// Sets the host lifetime to WindowsServiceLifetime, sets the Content Root,
- /// and enables logging to the event log with the application name as the default source name.
+ /// Sets the host lifetime to <see cref="WindowsServiceLifetime"/> and enables logging to the event log with
+ /// the application name as the default source name.
/// </summary>
/// <remarks>
- /// This is context aware and will only activate if it detects the process is running
- /// as a Windows Service.
+ /// This is context aware and will only activate if it detects the process is running as a Windows Service.
/// </remarks>
/// <param name="hostBuilder">The <see cref="IHostBuilder"/> to operate on.</param>
- /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
+ /// <returns>The <paramref name="hostBuilder"/> instance for chaining.</returns>
public static IHostBuilder UseWindowsService(this IHostBuilder hostBuilder)
{
return UseWindowsService(hostBuilder, _ => { });
}
/// <summary>
- /// Sets the host lifetime to WindowsServiceLifetime, sets the Content Root,
- /// and enables logging to the event log with the application name as the default source name.
+ /// Sets the host lifetime to <see cref="WindowsServiceLifetime"/> and enables logging to the event log with the application
+ /// name as the default source name.
/// </summary>
/// <remarks>
/// This is context aware and will only activate if it detects the process is running
/// as a Windows Service.
/// </remarks>
/// <param name="hostBuilder">The <see cref="IHostBuilder"/> to operate on.</param>
- /// <param name="configure"></param>
- /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
+ /// <param name="configure">An <see cref="Action{WindowsServiceLifetimeOptions}"/> to configure the provided <see cref="WindowsServiceLifetimeOptions"/>.</param>
+ /// <returns>The <paramref name="hostBuilder"/> instance for chaining.</returns>
public static IHostBuilder UseWindowsService(this IHostBuilder hostBuilder, Action<WindowsServiceLifetimeOptions> configure)
{
+ ThrowHelper.ThrowIfNull(hostBuilder);
+
if (WindowsServiceHelpers.IsWindowsService())
{
- // Host.CreateDefaultBuilder uses CurrentDirectory for VS scenarios, but CurrentDirectory for services is c:\Windows\System32.
- hostBuilder.UseContentRoot(AppContext.BaseDirectory);
- hostBuilder.ConfigureLogging((hostingContext, logging) =>
+ hostBuilder.ConfigureServices(services =>
{
- Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
+ AddWindowsServiceLifetime(services, configure);
+ });
+ }
- logging.AddEventLog();
- })
- .ConfigureServices((hostContext, services) =>
- {
- Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
+ return hostBuilder;
+ }
- services.AddSingleton<IHostLifetime, WindowsServiceLifetime>();
- services.Configure<EventLogSettings>(settings =>
- {
- Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
+ /// <summary>
+ /// Configures the lifetime of the <see cref="IHost"/> built from <paramref name="services"/> to
+ /// <see cref="WindowsServiceLifetime"/> and enables logging to the event log with the application
+ /// name as the default source name.
+ /// </summary>
+ /// <remarks>
+ /// This is context aware and will only activate if it detects the process is running
+ /// as a Windows Service.
+ /// </remarks>
+ /// <param name="services">
+ /// The <see cref="IServiceCollection"/> used to build the <see cref="IHost"/>.
+ /// For example, <see cref="HostApplicationBuilder.Services"/> or the <see cref="IServiceCollection"/> passed to the
+ /// <see cref="IHostBuilder.ConfigureServices(Action{HostBuilderContext, IServiceCollection})"/> callback.
+ /// </param>
+ /// <returns>The <paramref name="services"/> instance for chaining.</returns>
+ public static IServiceCollection AddWindowsService(this IServiceCollection services)
+ {
+ return AddWindowsService(services, _ => { });
+ }
- if (string.IsNullOrEmpty(settings.SourceName))
- {
- settings.SourceName = hostContext.HostingEnvironment.ApplicationName;
- }
- });
- services.Configure(configure);
- });
+ /// <summary>
+ /// Configures the lifetime of the <see cref="IHost"/> built from <paramref name="services"/> to
+ /// <see cref="WindowsServiceLifetime"/> and enables logging to the event log with the application name as the default source name.
+ /// </summary>
+ /// <remarks>
+ /// This is context aware and will only activate if it detects the process is running
+ /// as a Windows Service.
+ /// </remarks>
+ /// <param name="services">
+ /// The <see cref="IServiceCollection"/> used to build the <see cref="IHost"/>.
+ /// For example, <see cref="HostApplicationBuilder.Services"/> or the <see cref="IServiceCollection"/> passed to the
+ /// <see cref="IHostBuilder.ConfigureServices(Action{HostBuilderContext, IServiceCollection})"/> callback.
+ /// </param>
+ /// <param name="configure">An <see cref="Action{WindowsServiceLifetimeOptions}"/> to configure the provided <see cref="WindowsServiceLifetimeOptions"/>.</param>
+ /// <returns>The <paramref name="services"/> instance for chaining.</returns>
+ public static IServiceCollection AddWindowsService(this IServiceCollection services, Action<WindowsServiceLifetimeOptions> configure)
+ {
+ ThrowHelper.ThrowIfNull(services);
+
+ if (WindowsServiceHelpers.IsWindowsService())
+ {
+ AddWindowsServiceLifetime(services, configure);
}
- return hostBuilder;
+ return services;
+ }
+
+ private static void AddWindowsServiceLifetime(IServiceCollection services, Action<WindowsServiceLifetimeOptions> configure)
+ {
+ Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
+
+ services.AddLogging(logging =>
+ {
+ Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
+ logging.AddEventLog();
+ });
+ services.AddSingleton<IHostLifetime, WindowsServiceLifetime>();
+ services.AddSingleton<IConfigureOptions<EventLogSettings>, EventLogSettingsSetup>();
+ services.Configure(configure);
+ }
+
+ private sealed class EventLogSettingsSetup : IConfigureOptions<EventLogSettings>
+ {
+ private readonly string? _applicationName;
+
+ public EventLogSettingsSetup(IHostEnvironment environment)
+ {
+ _applicationName = environment.ApplicationName;
+ }
+
+ public void Configure(EventLogSettings settings)
+ {
+ Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
+
+ if (string.IsNullOrEmpty(settings.SourceName))
+ {
+ settings.SourceName = _applicationName;
+ }
+ }
}
}
}
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFrameworks>$(NetCoreAppCurrent);$(NetFrameworkMinimum)</TargetFrameworks>
+ <!-- Use "$(NetCoreAppCurrent)-windows" to avoid PlatformNotSupportedExceptions from ServiceController. -->
+ <TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetFrameworkMinimum)</TargetFrameworks>
<EnableDefaultItems>true</EnableDefaultItems>
</PropertyGroup>
<ProjectReference Include="..\src\Microsoft.Extensions.Hosting.WindowsServices.csproj" />
</ItemGroup>
+ <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
+ <Reference Include="System.ServiceProcess" />
+ </ItemGroup>
+
</Project>
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.IO;
+using System.Reflection;
+using System.ServiceProcess;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting.Internal;
+using Microsoft.Extensions.Hosting.WindowsServices;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.EventLog;
+using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.Extensions.Hosting
{
public class UseWindowsServiceTests
{
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows))]
+ private static MethodInfo? _addWindowsServiceLifetimeMethod = null;
+
+ [Fact]
public void DefaultsToOffOutsideOfService()
{
- var host = new HostBuilder()
+ using IHost host = new HostBuilder()
.UseWindowsService()
.Build();
- using (host)
+ var lifetime = host.Services.GetRequiredService<IHostLifetime>();
+ Assert.IsType<ConsoleLifetime>(lifetime);
+ }
+
+ [Fact]
+ public void ServiceCollectionExtensionMethodDefaultsToOffOutsideOfService()
+ {
+ var builder = new HostApplicationBuilder();
+
+ builder.Services.AddWindowsService();
+ // No reason to write event logs in this test. Event log may be unsupported anyway.
+ builder.Logging.ClearProviders();
+
+ using IHost host = builder.Build();
+
+ var lifetime = host.Services.GetRequiredService<IHostLifetime>();
+ Assert.IsType<ConsoleLifetime>(lifetime);
+ }
+
+ [Fact]
+ public void ServiceCollectionExtensionMethodAddsWindowsServiceLifetimeInsideOfService()
+ {
+ var builder = new HostApplicationBuilder();
+
+ // Emulate calling builder.Services.AddWindowsService() from inside a Windows service.
+ AddWindowsServiceLifetime(builder.Services);
+
+ Assert.Single(builder.Services, serviceDescriptor =>
+ serviceDescriptor.ServiceType == typeof(IHostLifetime) &&
+ serviceDescriptor.ImplementationType == typeof(WindowsServiceLifetime));
+ }
+
+ [Fact]
+ public void ServiceCollectionExtensionMethodSetsEventLogSourceNameToApplicationNameInsideOfService()
+ {
+ string appName = Guid.NewGuid().ToString();
+
+ var builder = new HostApplicationBuilder(new HostApplicationBuilderSettings
{
- var lifetime = host.Services.GetRequiredService<IHostLifetime>();
- Assert.IsType<ConsoleLifetime>(lifetime);
- }
+ ApplicationName = appName,
+ });
+
+ // Emulate calling builder.Services.AddWindowsService() from inside a Windows service.
+ AddWindowsServiceLifetime(builder.Services);
+ // No reason to write event logs in this test.
+ builder.Logging.ClearProviders();
+
+ using IHost host = builder.Build();
+
+ var eventLogSettings = host.Services.GetRequiredService<IOptions<EventLogSettings>>().Value;
+ Assert.Same(appName, eventLogSettings.SourceName);
+ }
+
+ [Fact]
+ public void ServiceCollectionExtensionMethodCanBeCalledOnDefaultConfiguration()
+ {
+ var builder = new HostApplicationBuilder();
+
+ // Emulate calling builder.Services.AddWindowsService() from inside a Windows service.
+ AddWindowsServiceLifetime(builder.Services);
+ // No reason to write event logs in this test.
+ builder.Logging.ClearProviders();
+
+ using IHost host = builder.Build();
+
+ var lifetime = host.Services.GetRequiredService<IHostLifetime>();
+ Assert.IsType<WindowsServiceLifetime>(lifetime);
+ }
+
+ private void AddWindowsServiceLifetime(IServiceCollection services, Action<WindowsServiceLifetimeOptions> configure = null)
+ {
+ _addWindowsServiceLifetimeMethod ??= typeof(WindowsServiceLifetimeHostBuilderExtensions).GetMethod("AddWindowsServiceLifetime",
+ BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(IServiceCollection), typeof(Action<WindowsServiceLifetimeOptions>) }, null)
+ ?? throw new MissingMethodException();
+
+ configure ??= _ => { };
+ _addWindowsServiceLifetimeMethod.Invoke(null, new object[] { services, configure });
}
}
}
internal static void ApplyDefaultHostConfiguration(IConfigurationBuilder hostConfigBuilder, string[]? args)
{
- hostConfigBuilder.AddInMemoryCollection(new[]
+ // If we're running anywhere other than C:\Windows\system32, we default to using the CWD for the ContentRoot.
+ // However, since many things like Windows services and MSIX installers have C:\Windows\system32 as there CWD which is not likely
+ // to really be the home for things like appsettings.json, we skip changing the ContentRoot in that case. The non-"default" initial
+ // value for ContentRoot is AppContext.BaseDirectory (e.g. the executable path) which probably makes more sense than the system32.
+
+ // In my testing, both Environment.CurrentDirectory and Environment.GetFolderPath(Environment.SpecialFolder.System) return the path without
+ // any trailing directory separator characters. I'm not even sure the casing can ever be different from these APIs, but I think it makes sense to
+ // ignore case for Windows path comparisons given the file system is usually (always?) going to be case insensitive for the system path.
+ string cwd = Environment.CurrentDirectory;
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !string.Equals(cwd, Environment.GetFolderPath(Environment.SpecialFolder.System), StringComparison.OrdinalIgnoreCase))
{
- new KeyValuePair<string, string?>(HostDefaults.ContentRootKey, Directory.GetCurrentDirectory())
- });
+ hostConfigBuilder.AddInMemoryCollection(new[]
+ {
+ new KeyValuePair<string, string?>(HostDefaults.ContentRootKey, cwd),
+ });
+ }
hostConfigBuilder.AddEnvironmentVariables(prefix: "DOTNET_");
if (args is { Length: > 0 })
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.DotNet.RemoteExecutor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.UserSecrets;
using Microsoft.Extensions.DependencyInjection;
Assert.Equal(expected, env.ContentRootPath);
}
+ public static bool IsWindowsAndRemotExecutorIsSupported => PlatformDetection.IsWindows && RemoteExecutor.IsSupported;
+
+ [ConditionalFact(typeof(HostTests), nameof(IsWindowsAndRemotExecutorIsSupported))]
+ public void CreateDefaultBuilder_DoesNotChangeContentRootIfCurrentDirectoryIsWindowsSystemDirectory()
+ {
+ using var _ = RemoteExecutor.Invoke(() =>
+ {
+ string systemDirectory = Environment.GetFolderPath(Environment.SpecialFolder.System);
+ if (string.IsNullOrEmpty(systemDirectory))
+ {
+ // Skip the environments (like Nano Server) where Environment.SpecialFolder.System returns empty - https://github.com/dotnet/runtime/issues/21430
+ return;
+ }
+
+ // Test that the path gets normalized before comparison. Use C:\WINDOWS\SYSTEM32\ instead of C:\Windows\system32.
+ systemDirectory = systemDirectory.ToUpper() + "\\";
+
+ Environment.CurrentDirectory = systemDirectory;
+
+ IHostBuilder builder = Host.CreateDefaultBuilder();
+ using IHost host = builder.Build();
+
+ var config = host.Services.GetRequiredService<IConfiguration>();
+ var env = host.Services.GetRequiredService<IHostEnvironment>();
+
+ Assert.Null(config[HostDefaults.ContentRootKey]);
+ Assert.Equal(AppContext.BaseDirectory, env.ContentRootPath);
+ });
+ }
+
[Fact]
public void CreateDefaultBuilder_IncludesCommandLineArguments()
{
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);$(NetFrameworkMinimum)</TargetFrameworks>