From 9233731397dfa453c6a60a286eddcdfa769951e6 Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Wed, 31 Jan 2024 23:02:44 +1100 Subject: [PATCH] Use IConfiguration (#2011) * Use IConfiguration This change removes the `IEnvironmentVariables` abstraction, replacing it and usages with `IConfiguration` from the framework. The built-in `IConfiguration` system is more flexible, allowing configuration values to come from various other sources. * Remove MockConfiguration class * Use IConfiguration in ApplicationExecutor --- src/Aspire.Dashboard/Aspire.Dashboard.csproj | 2 +- .../DashboardWebApplication.cs | 8 +- src/Aspire.Dashboard/Model/DashboardClient.cs | 10 +- src/Aspire.Hosting/Aspire.Hosting.csproj | 2 +- .../Dashboard/DashboardServiceData.cs | 5 +- .../Dashboard/DashboardServiceHost.cs | 9 +- src/Aspire.Hosting/Dashboard/DcpDataSource.cs | 5 +- src/Aspire.Hosting/Dcp/ApplicationExecutor.cs | 23 +-- .../DistributedApplicationBuilder.cs | 1 - src/Shared/IConfigurationExtensions.cs | 121 ++++++++++++++ src/Shared/IEnvironmentVariables.cs | 155 ------------------ .../MockEnvironmentVariables.cs | 30 ---- .../Model/DashboardClientTests.cs | 18 +- 13 files changed, 166 insertions(+), 223 deletions(-) create mode 100644 src/Shared/IConfigurationExtensions.cs delete mode 100644 src/Shared/IEnvironmentVariables.cs delete mode 100644 tests/Aspire.Dashboard.Tests/MockEnvironmentVariables.cs diff --git a/src/Aspire.Dashboard/Aspire.Dashboard.csproj b/src/Aspire.Dashboard/Aspire.Dashboard.csproj index f4e6f2fe6d..95e61ea584 100644 --- a/src/Aspire.Dashboard/Aspire.Dashboard.csproj +++ b/src/Aspire.Dashboard/Aspire.Dashboard.csproj @@ -170,7 +170,7 @@ Protos\resource_service.proto - + diff --git a/src/Aspire.Dashboard/DashboardWebApplication.cs b/src/Aspire.Dashboard/DashboardWebApplication.cs index 8c037beca8..01caf989f1 100644 --- a/src/Aspire.Dashboard/DashboardWebApplication.cs +++ b/src/Aspire.Dashboard/DashboardWebApplication.cs @@ -29,13 +29,9 @@ public DashboardWebApplication() builder.Logging.AddFilter("Microsoft.Hosting.Lifetime", LogLevel.None); builder.Logging.AddFilter("Microsoft.AspNetCore.Server.Kestrel", LogLevel.Error); - var environmentVariables = new EnvironmentVariables(); + var dashboardUris = builder.Configuration.GetUris(DashboardUrlVariableName, new(DashboardUrlDefaultValue)); - builder.Services.AddSingleton(environmentVariables); - - var dashboardUris = environmentVariables.GetUris(DashboardUrlVariableName, new(DashboardUrlDefaultValue)); - - var otlpUris = environmentVariables.GetUris(DashboardOtlpUrlVariableName, new(DashboardOtlpUrlDefaultValue)); + var otlpUris = builder.Configuration.GetUris(DashboardOtlpUrlVariableName, new(DashboardOtlpUrlDefaultValue)); if (otlpUris.Length > 1) { diff --git a/src/Aspire.Dashboard/Model/DashboardClient.cs b/src/Aspire.Dashboard/Model/DashboardClient.cs index c85679e834..726ba61d70 100644 --- a/src/Aspire.Dashboard/Model/DashboardClient.cs +++ b/src/Aspire.Dashboard/Model/DashboardClient.cs @@ -39,7 +39,7 @@ internal sealed class DashboardClient : IDashboardClient private readonly object _lock = new(); private readonly ILoggerFactory _loggerFactory; - private readonly IEnvironmentVariables _environmentVariables; + private readonly IConfiguration _configuration; private readonly ILogger _logger; private ImmutableHashSet> _outgoingChannels = []; @@ -56,14 +56,14 @@ internal sealed class DashboardClient : IDashboardClient private Task? _connection; - public DashboardClient(ILoggerFactory loggerFactory, IEnvironmentVariables environmentVariables) + public DashboardClient(ILoggerFactory loggerFactory, IConfiguration configuration) { _loggerFactory = loggerFactory; - _environmentVariables = environmentVariables; + _configuration = configuration; _logger = loggerFactory.CreateLogger(); - var address = environmentVariables.GetUri(ResourceServiceUrlVariableName); + var address = configuration.GetUri(ResourceServiceUrlVariableName); if (address is null) { @@ -307,7 +307,7 @@ Task IDashboardClient.WhenConnected string IDashboardClient.ApplicationName { get => _applicationName - ?? _environmentVariables.GetString("DOTNET_DASHBOARD_APPLICATION_NAME") + ?? _configuration["DOTNET_DASHBOARD_APPLICATION_NAME"] ?? "Aspire"; } diff --git a/src/Aspire.Hosting/Aspire.Hosting.csproj b/src/Aspire.Hosting/Aspire.Hosting.csproj index bfd45d100d..a71622b038 100644 --- a/src/Aspire.Hosting/Aspire.Hosting.csproj +++ b/src/Aspire.Hosting/Aspire.Hosting.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Aspire.Hosting/Dashboard/DashboardServiceData.cs b/src/Aspire.Hosting/Dashboard/DashboardServiceData.cs index fd45b85558..fcbde72f34 100644 --- a/src/Aspire.Hosting/Dashboard/DashboardServiceData.cs +++ b/src/Aspire.Hosting/Dashboard/DashboardServiceData.cs @@ -4,6 +4,7 @@ using System.Runtime.CompilerServices; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Dcp; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Aspire.Hosting.Dashboard; @@ -21,13 +22,13 @@ internal sealed class DashboardServiceData : IAsyncDisposable public DashboardServiceData( DistributedApplicationModel applicationModel, KubernetesService kubernetesService, - IEnvironmentVariables environmentVariables, + IConfiguration configuration, ILoggerFactory loggerFactory) { _resourcePublisher = new ResourcePublisher(_cts.Token); _consoleLogPublisher = new ConsoleLogPublisher(_resourcePublisher); - _ = new DcpDataSource(kubernetesService, applicationModel, environmentVariables, loggerFactory, _resourcePublisher.IntegrateAsync, _cts.Token); + _ = new DcpDataSource(kubernetesService, applicationModel, configuration, loggerFactory, _resourcePublisher.IntegrateAsync, _cts.Token); } public async ValueTask DisposeAsync() diff --git a/src/Aspire.Hosting/Dashboard/DashboardServiceHost.cs b/src/Aspire.Hosting/Dashboard/DashboardServiceHost.cs index 9626817c5c..48f609b247 100644 --- a/src/Aspire.Hosting/Dashboard/DashboardServiceHost.cs +++ b/src/Aspire.Hosting/Dashboard/DashboardServiceHost.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Internal; @@ -49,7 +50,7 @@ public DashboardServiceHost( DistributedApplicationOptions options, DistributedApplicationModel applicationModel, KubernetesService kubernetesService, - IEnvironmentVariables environmentVariables, + IConfiguration configuration, IOptions publishingOptions, ILoggerFactory loggerFactory, IConfigureOptions loggerOptions) @@ -68,8 +69,8 @@ public DashboardServiceHost( { var builder = WebApplication.CreateBuilder(); - // Environment - builder.Services.AddSingleton(); + // Configuration + builder.Services.AddSingleton(configuration); // Logging builder.Services.AddSingleton(loggerFactory); @@ -98,7 +99,7 @@ public DashboardServiceHost( void ConfigureKestrel(KestrelServerOptions kestrelOptions) { // Inspect environment for the address to listen on. - var uri = environmentVariables.GetUri(ResourceServiceUrlVariableName); + var uri = configuration.GetUri(ResourceServiceUrlVariableName); string? scheme; diff --git a/src/Aspire.Hosting/Dashboard/DcpDataSource.cs b/src/Aspire.Hosting/Dashboard/DcpDataSource.cs index b4aa80b385..f357967d0a 100644 --- a/src/Aspire.Hosting/Dashboard/DcpDataSource.cs +++ b/src/Aspire.Hosting/Dashboard/DcpDataSource.cs @@ -8,6 +8,7 @@ using Aspire.Hosting.Dcp; using Aspire.Hosting.Dcp.Model; using k8s; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Aspire.Hosting.Dashboard; @@ -35,7 +36,7 @@ internal sealed class DcpDataSource public DcpDataSource( KubernetesService kubernetesService, DistributedApplicationModel applicationModel, - IEnvironmentVariables environmentVariables, + IConfiguration configuration, ILoggerFactory loggerFactory, Func onResourceChanged, CancellationToken cancellationToken) @@ -96,7 +97,7 @@ bool IsFilteredResource(T resource) where T : CustomResource // We filter out any resources that start with aspire-dashboard (there are services as well as executables). if (resource.Metadata.Name.StartsWith(KnownResourceNames.AspireDashboard, StringComparisons.ResourceName)) { - return environmentVariables.GetBool("DOTNET_ASPIRE_SHOW_DASHBOARD_RESOURCES") is not true; + return configuration.GetBool("DOTNET_ASPIRE_SHOW_DASHBOARD_RESOURCES") is not true; } return false; diff --git a/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs b/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs index 7dbd588519..f70069fcf6 100644 --- a/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs +++ b/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs @@ -8,6 +8,7 @@ using Aspire.Hosting.Lifecycle; using Aspire.Hosting.Utils; using k8s; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -55,7 +56,7 @@ internal sealed class ApplicationExecutor(ILogger logger, DistributedApplicationOptions distributedApplicationOptions, KubernetesService kubernetesService, IEnumerable lifecycleHooks, - IEnvironmentVariables environmentVariables, + IConfiguration configuration, IOptions options, DashboardServiceHost dashboardHost) { @@ -137,12 +138,12 @@ private async Task ConfigureAspireDashboardResource(IResource dashboardResource, dashboardResource.Annotations.Add(new EnvironmentCallbackAnnotation(context => { - if (Environment.GetEnvironmentVariable("ASPNETCORE_URLS") is not { } appHostApplicationUrl) + if (configuration["ASPNETCORE_URLS"] is not { } appHostApplicationUrl) { throw new DistributedApplicationException("Failed to configure dashboard resource because ASPNETCORE_URLS environment variable was not set."); } - if (Environment.GetEnvironmentVariable("DOTNET_DASHBOARD_OTLP_ENDPOINT_URL") is not { } otlpEndpointUrl) + if (configuration["DOTNET_DASHBOARD_OTLP_ENDPOINT_URL"] is not { } otlpEndpointUrl) { throw new DistributedApplicationException("Failed to configure dashboard resource because DOTNET_DASHBOARD_OTLP_ENDPOINT_URL environment variable was not set."); } @@ -202,9 +203,9 @@ private async Task StartDashboardAsDcpExecutableAsync(CancellationToken cancella // Matches DashboardWebApplication.DashboardUrlDefaultValue const string defaultDashboardUrl = "http://localhost:18888"; - var otlpEndpointUrl = environmentVariables.GetString("DOTNET_DASHBOARD_OTLP_ENDPOINT_URL"); - var dashboardUrls = environmentVariables.GetString("ASPNETCORE_URLS") ?? defaultDashboardUrl; - var aspnetcoreEnvironment = environmentVariables.GetString("ASPNETCORE_ENVIRONMENT"); + var otlpEndpointUrl = configuration["DOTNET_DASHBOARD_OTLP_ENDPOINT_URL"]; + var dashboardUrls = configuration["ASPNETCORE_URLS"] ?? defaultDashboardUrl; + var aspnetcoreEnvironment = configuration["ASPNETCORE_ENVIRONMENT"]; dashboardExecutableSpec.Env = [ @@ -239,11 +240,11 @@ private async Task StartDashboardAsDcpExecutableAsync(CancellationToken cancella await CheckDashboardAvailabilityAsync(dashboardUrls, cancellationToken).ConfigureAwait(false); } - private static TimeSpan DashboardAvailabilityTimeoutDuration + private TimeSpan DashboardAvailabilityTimeoutDuration { get { - if (Environment.GetEnvironmentVariable("DOTNET_ASPIRE_DASHBOARD_TIMEOUT_SECONDS") is { } timeoutString && int.TryParse(timeoutString, out var timeoutInSeconds)) + if (configuration["DOTNET_ASPIRE_DASHBOARD_TIMEOUT_SECONDS"] is { } timeoutString && int.TryParse(timeoutString, out var timeoutInSeconds)) { return TimeSpan.FromSeconds(timeoutInSeconds); } @@ -492,7 +493,7 @@ private void PrepareProjectExecutables() annotationHolder.Annotate(Executable.CSharpProjectPathAnnotation, projectMetadata.ProjectPath); annotationHolder.Annotate(Executable.OtelServiceNameAnnotation, ers.Metadata.Name); - if (!string.IsNullOrEmpty(environmentVariables.GetString(DebugSessionPortVar))) + if (!string.IsNullOrEmpty(configuration[DebugSessionPortVar])) { exeSpec.ExecutionType = ExecutionType.IDE; if (project.TryGetLastAnnotation(out var lpa)) @@ -503,7 +504,7 @@ private void PrepareProjectExecutables() else { exeSpec.ExecutionType = ExecutionType.Process; - if (environmentVariables.GetBool("DOTNET_WATCH") is true) + if (configuration.GetBool("DOTNET_WATCH") is true) { exeSpec.Args = [ "run", @@ -657,7 +658,7 @@ private async Task CreateExecutablesAsync(IEnumerable executableRes { // We just check the HTTP endpoint because this will prove that the // dashboard is listening and is ready to process requests. - if (Environment.GetEnvironmentVariable("ASPNETCORE_URLS") is not { } dashboardUrls) + if (configuration["ASPNETCORE_URLS"] is not { } dashboardUrls) { throw new DistributedApplicationException("Cannot check dashboard availability since ASPNETCORE_URLS environment variable not set."); } diff --git a/src/Aspire.Hosting/DistributedApplicationBuilder.cs b/src/Aspire.Hosting/DistributedApplicationBuilder.cs index ade2d8a365..1409f4a97e 100644 --- a/src/Aspire.Hosting/DistributedApplicationBuilder.cs +++ b/src/Aspire.Hosting/DistributedApplicationBuilder.cs @@ -61,7 +61,6 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options) _innerBuilder.Services.AddHostedService(); _innerBuilder.Services.AddHostedService(); _innerBuilder.Services.AddSingleton(options); - _innerBuilder.Services.AddSingleton(); // Dashboard _innerBuilder.Services.AddSingleton(); diff --git a/src/Shared/IConfigurationExtensions.cs b/src/Shared/IConfigurationExtensions.cs new file mode 100644 index 0000000000..768d3dcac8 --- /dev/null +++ b/src/Shared/IConfigurationExtensions.cs @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Configuration; + +namespace Aspire; + +internal static class IConfigurationExtensions +{ + /// + /// Gets the named configuration value as a boolean. + /// + /// + /// Parses true and false, along with integer values (where non-zero is ). + /// + /// The this method extends. + /// The configuration key. + /// The parsed value, or if no value exists or it couldn't be parsed. + public static bool? GetBool(this IConfiguration configuration, string key) + { + var value = configuration[key]; + + if (value is null or []) + { + return null; + } + else if (bool.TryParse(value, out var b)) + { + return b; + } + else if (int.TryParse(value, out var i)) + { + return i != 0; + } + + return null; + } + + /// + /// Gets the named configuration value as a boolean. + /// + /// + /// Parses true and false, along with 1 and 0. + /// + /// The this method extends. + /// The configuration key. + /// A default value, for when the configuration value is unspecified or white space. + /// + public static bool GetBool(this IConfiguration configuration, string key, bool defaultValue) + { + return configuration.GetBool(key) ?? defaultValue; + } + + /// + /// Parses a configuration value into a object. + /// + /// The this method extends. + /// The configuration key. + /// A default value, for when the configuration value is unspecified or white space. May be . + /// The parsed value, or the default value if specified and parsing failed. Returns if is and parsing failed. + /// The configuration value could not be accessed, or contained incorrectly formatted data. + [return: NotNullIfNotNull(nameof(defaultValue))] + public static Uri? GetUri(this IConfiguration configuration, string key, Uri? defaultValue = null) + { + try + { + var uri = configuration[key]; + + if (string.IsNullOrWhiteSpace(uri)) + { + return defaultValue; + } + else + { + return new Uri(uri, UriKind.Absolute); + } + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error parsing URIs from configuration value '{key}'.", ex); + } + } + + /// + /// Parses a configuration value's semicolon-delimited value into an array of objects. + /// + /// The this method extends. + /// The configuration key. + /// A default value, for when the configuration value is unspecified or white space. May be . + /// The parsed values, or the default value if specified and parsing failed. Returns if is and parsing failed. + /// The configuration value could not be accessed, or contained incorrectly formatted data. + [return: NotNullIfNotNull(nameof(defaultValue))] + public static Uri[]? GetUris(this IConfiguration configuration, string key, Uri? defaultValue = null) + { + try + { + var uris = configuration[key]; + + if (string.IsNullOrWhiteSpace(uris)) + { + return defaultValue switch + { + not null => [defaultValue], + null => null + }; + } + else + { + return uris + .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(url => new Uri(url, UriKind.Absolute)) + .ToArray(); + } + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error parsing URIs from configuration value '{key}'.", ex); + } + } +} diff --git a/src/Shared/IEnvironmentVariables.cs b/src/Shared/IEnvironmentVariables.cs deleted file mode 100644 index 66bd65f345..0000000000 --- a/src/Shared/IEnvironmentVariables.cs +++ /dev/null @@ -1,155 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; - -namespace Aspire; - -/// -/// An abstraction over the current process's environment variables. -/// -internal interface IEnvironmentVariables -{ - /// - /// Gets the named environment variable's value, or . - /// - /// The name of the environment variable. - /// An optional default value to return if the environment variable is not present. - /// The named environment variable's value if present, or if the variable is not present and no default was specified. - [return: NotNullIfNotNull(nameof(defaultValue))] - string? GetString(string variableName, string? defaultValue = null); -} - -internal sealed class EnvironmentVariables : IEnvironmentVariables -{ - /// - /// A cache of all queried environment variables. - /// - private ImmutableDictionary _valueByName = ImmutableDictionary.Empty.WithComparers(StringComparers.EnvironmentVariableName); - - [return: NotNullIfNotNull(nameof(defaultValue))] - public string? GetString(string variableName, string? defaultValue = null) - { - // Environment.GetEnvironmentVariable queries the variable each time, - // but our variables don't change during the lifetime of the process. - // So we cache them for faster repeat lookup. - var value = ImmutableInterlocked.GetOrAdd(ref _valueByName, key: variableName, valueFactory: Environment.GetEnvironmentVariable); - - return value ?? defaultValue; - } -} - -internal static class IEnvironmentVariablesExtensions -{ - /// - /// Gets the named environment variable's value as a boolean. - /// - /// - /// Parses true and false, along with integer values (where non-zero is ). - /// - /// The this method extends. - /// The name of the environment variable. - /// The parsed value, or if no value exists or it couldn't be parsed. - public static bool? GetBool(this IEnvironmentVariables env, string variableName) - { - var str = env.GetString(variableName); - - if (str is null or []) - { - return null; - } - else if (bool.TryParse(str, out var b)) - { - return b; - } - else if (int.TryParse(str, out var i)) - { - return i != 0; - } - - return null; - } - - /// - /// Gets the named environment variable's value as a boolean. - /// - /// - /// Parses true and false, along with 1 and 0. - /// - /// The this method extends. - /// The name of the environment variable. - /// A default value, for when the environment variable is unspecified or white space. - /// - public static bool GetBool(this IEnvironmentVariables env, string variableName, bool defaultValue) - { - return env.GetBool(variableName) ?? defaultValue; - } - - /// - /// Parses a environment variable's value into a object. - /// - /// The this method extends. - /// The name of the environment variable. - /// A default value, for when the environment variable is unspecified or white space. May be . - /// The parsed value, or the default value if specified and parsing failed. Returns if is and parsing failed. - /// The environment variable could not be accessed, or contained incorrectly formatted data. - [return: NotNullIfNotNull(nameof(defaultValue))] - public static Uri? GetUri(this IEnvironmentVariables env, string variableName, Uri? defaultValue = null) - { - try - { - var uri = env.GetString(variableName); - - if (string.IsNullOrWhiteSpace(uri)) - { - return defaultValue; - } - else - { - return new Uri(uri, UriKind.Absolute); - } - } - catch (Exception ex) - { - throw new InvalidOperationException($"Error parsing URIs from environment variable '{variableName}'.", ex); - } - } - - /// - /// Parses a environment variable's semicolon-delimited value into an array of objects. - /// - /// The this method extends. - /// The name of the environment variable. - /// A default value, for when the environment variable is unspecified or white space. May be . - /// The parsed values, or the default value if specified and parsing failed. Returns if is and parsing failed. - /// The environment variable could not be accessed, or contained incorrectly formatted data. - [return: NotNullIfNotNull(nameof(defaultValue))] - public static Uri[]? GetUris(this IEnvironmentVariables env, string variableName, Uri? defaultValue = null) - { - try - { - var uris = env.GetString(variableName); - - if (string.IsNullOrWhiteSpace(uris)) - { - return defaultValue switch - { - not null => [defaultValue], - null => null - }; - } - else - { - return uris - .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) - .Select(url => new Uri(url, UriKind.Absolute)) - .ToArray(); - } - } - catch (Exception ex) - { - throw new InvalidOperationException($"Error parsing URIs from environment variable '{variableName}'.", ex); - } - } -} diff --git a/tests/Aspire.Dashboard.Tests/MockEnvironmentVariables.cs b/tests/Aspire.Dashboard.Tests/MockEnvironmentVariables.cs deleted file mode 100644 index 2584e13922..0000000000 --- a/tests/Aspire.Dashboard.Tests/MockEnvironmentVariables.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections; -using System.Diagnostics.CodeAnalysis; - -namespace Aspire; - -internal sealed class MockEnvironmentVariables : IEnvironmentVariables, IEnumerable -{ - private readonly Dictionary _valueByName = new(StringComparers.EnvironmentVariableName); - - public void Add(string name, string value) - { - _valueByName[name] = value; - } - - [return: NotNullIfNotNull(nameof(defaultValue))] - public string? GetString(string variableName, string? defaultValue = null) - { - if (_valueByName.TryGetValue(variableName, out var value)) - { - return value; - } - - return defaultValue; - } - - IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); -} diff --git a/tests/Aspire.Dashboard.Tests/Model/DashboardClientTests.cs b/tests/Aspire.Dashboard.Tests/Model/DashboardClientTests.cs index 6914f1ea9d..39fe560401 100644 --- a/tests/Aspire.Dashboard.Tests/Model/DashboardClientTests.cs +++ b/tests/Aspire.Dashboard.Tests/Model/DashboardClientTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Dashboard.Model; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; using Xunit; @@ -9,12 +10,19 @@ namespace Aspire.Dashboard.Tests.Model; public sealed class DashboardClientTests { - private readonly IEnvironmentVariables _environmentVariables = new MockEnvironmentVariables() { { "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL", "http://localhost:12345" } }; + private readonly IConfiguration _configuration; + + public DashboardClientTests() + { + var configuration = new ConfigurationManager(); + configuration.AddInMemoryCollection(new Dictionary() { { "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL", "http://localhost:12345" } }); + _configuration = configuration; + } [Fact] public async Task SubscribeResources_OnCancel_ChannelRemoved() { - var instance = new DashboardClient(NullLoggerFactory.Instance, _environmentVariables); + var instance = new DashboardClient(NullLoggerFactory.Instance, _configuration); IDashboardClient client = instance; var cts = new CancellationTokenSource(); @@ -42,7 +50,7 @@ public async Task SubscribeResources_OnCancel_ChannelRemoved() [Fact] public async Task SubscribeResources_OnDispose_ChannelRemoved() { - var instance = new DashboardClient(NullLoggerFactory.Instance, _environmentVariables); + var instance = new DashboardClient(NullLoggerFactory.Instance, _configuration); IDashboardClient client = instance; Assert.Equal(0, instance.OutgoingResourceSubscriberCount); @@ -68,7 +76,7 @@ public async Task SubscribeResources_OnDispose_ChannelRemoved() [Fact] public async Task SubscribeResources_ThrowsIfDisposed() { - IDashboardClient client = new DashboardClient(NullLoggerFactory.Instance, _environmentVariables); + IDashboardClient client = new DashboardClient(NullLoggerFactory.Instance, _configuration); await client.DisposeAsync(); @@ -78,7 +86,7 @@ public async Task SubscribeResources_ThrowsIfDisposed() [Fact] public async Task SubscribeResources_IncreasesSubscriberCount() { - var instance = new DashboardClient(NullLoggerFactory.Instance, _environmentVariables); + var instance = new DashboardClient(NullLoggerFactory.Instance, _configuration); IDashboardClient client = instance; Assert.Equal(0, instance.OutgoingResourceSubscriberCount);