Enable reading and editing from same Configuration object (#20647)
This commit is contained in:
parent
43bd9dbf3e
commit
c4703acfa7
|
|
@ -52,13 +52,11 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||||
// Private right now because we don't have much reason to expose it. This can be exposed
|
// Private right now because we don't have much reason to expose it. This can be exposed
|
||||||
// in the future if we want to give people a choice between CreateDefault and something
|
// in the future if we want to give people a choice between CreateDefault and something
|
||||||
// less opinionated.
|
// less opinionated.
|
||||||
Configuration = new ConfigurationBuilder();
|
Configuration = new WebAssemblyHostConfiguration();
|
||||||
RootComponents = new RootComponentMappingCollection();
|
RootComponents = new RootComponentMappingCollection();
|
||||||
Services = new ServiceCollection();
|
Services = new ServiceCollection();
|
||||||
Logging = new LoggingBuilder(Services);
|
Logging = new LoggingBuilder(Services);
|
||||||
|
|
||||||
Logging.SetMinimumLevel(LogLevel.Warning);
|
|
||||||
|
|
||||||
// Retrieve required attributes from JSRuntimeInvoker
|
// Retrieve required attributes from JSRuntimeInvoker
|
||||||
InitializeNavigationManager(jsRuntimeInvoker);
|
InitializeNavigationManager(jsRuntimeInvoker);
|
||||||
InitializeDefaultServices();
|
InitializeDefaultServices();
|
||||||
|
|
@ -111,10 +109,10 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an <see cref="IConfigurationBuilder"/> that can be used to customize the application's
|
/// Gets an <see cref="WebAssemblyHostConfiguration"/> that can be used to customize the application's
|
||||||
/// configuration sources.
|
/// configuration sources and read configuration attributes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IConfigurationBuilder Configuration { get; }
|
public WebAssemblyHostConfiguration Configuration { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the collection of root component mappings configured for the application.
|
/// Gets the collection of root component mappings configured for the application.
|
||||||
|
|
@ -177,8 +175,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||||
public WebAssemblyHost Build()
|
public WebAssemblyHost Build()
|
||||||
{
|
{
|
||||||
// Intentionally overwrite configuration with the one we're creating.
|
// Intentionally overwrite configuration with the one we're creating.
|
||||||
var configuration = Configuration.Build();
|
Services.AddSingleton<IConfiguration>(Configuration);
|
||||||
Services.AddSingleton<IConfiguration>(configuration);
|
|
||||||
|
|
||||||
// A Blazor application always runs in a scope. Since we want to make it possible for the user
|
// A Blazor application always runs in a scope. Since we want to make it possible for the user
|
||||||
// to configure services inside *that scope* inside their startup code, we create *both* the
|
// to configure services inside *that scope* inside their startup code, we create *both* the
|
||||||
|
|
@ -186,7 +183,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||||
var services = _createServiceProvider();
|
var services = _createServiceProvider();
|
||||||
var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope();
|
var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope();
|
||||||
|
|
||||||
return new WebAssemblyHost(services, scope, configuration, RootComponents.ToArray());
|
return new WebAssemblyHost(services, scope, Configuration, RootComponents.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void InitializeDefaultServices()
|
internal void InitializeDefaultServices()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// WebAssemblyHostConfiguration is a class that implements the interface of an IConfiguration,
|
||||||
|
/// IConfigurationRoot, and IConfigurationBuilder. It can be used to simulatneously build
|
||||||
|
/// and read from a configuration object.
|
||||||
|
/// </summary>
|
||||||
|
public class WebAssemblyHostConfiguration : IConfiguration, IConfigurationRoot, IConfigurationBuilder
|
||||||
|
{
|
||||||
|
private readonly List<IConfigurationProvider> _providers = new List<IConfigurationProvider>();
|
||||||
|
private readonly List<IConfigurationSource> _sources = new List<IConfigurationSource>();
|
||||||
|
|
||||||
|
private readonly List<IDisposable> _changeTokenRegistrations = new List<IDisposable>();
|
||||||
|
private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the sources used to obtain configuration values.
|
||||||
|
/// </summary>
|
||||||
|
IList<IConfigurationSource> IConfigurationBuilder.Sources => new ReadOnlyCollection<IConfigurationSource>(_sources.ToList());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the providers used to obtain configuration values.
|
||||||
|
/// </summary>
|
||||||
|
IEnumerable<IConfigurationProvider> IConfigurationRoot.Providers => new ReadOnlyCollection<IConfigurationProvider>(_providers.ToList());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a key/value collection that can be used to share data between the <see cref="IConfigurationBuilder"/>
|
||||||
|
/// and the registered <see cref="IConfigurationProvider"/> instances.
|
||||||
|
/// </summary>
|
||||||
|
// In this implementation, this largely exists as a way to satisfy the
|
||||||
|
// requirements of the IConfigurationBuilder and is not populated by
|
||||||
|
// the WebAssemblyHostConfiguration with any meaningful info.
|
||||||
|
IDictionary<string, object> IConfigurationBuilder.Properties { get; } = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string this[string key]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// Iterate through the providers in reverse to extract
|
||||||
|
// the value from the most recently inserted provider.
|
||||||
|
for (var i = _providers.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var provider = _providers[i];
|
||||||
|
|
||||||
|
if (provider.TryGet(key, out var value))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_providers.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Can only set property if at least one provider has been inserted.");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var provider in _providers)
|
||||||
|
{
|
||||||
|
provider.Set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a configuration sub-section with the specified key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key of the configuration section.</param>
|
||||||
|
/// <returns>The <see cref="IConfigurationSection"/>.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method will never return <c>null</c>. If no matching sub-section is found with the specified key,
|
||||||
|
/// an empty <see cref="IConfigurationSection"/> will be returned.
|
||||||
|
/// </remarks>
|
||||||
|
public IConfigurationSection GetSection(string key) => new ConfigurationSection(this, key);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the immediate descendant configuration sub-sections.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The configuration sub-sections.</returns>
|
||||||
|
IEnumerable<IConfigurationSection> IConfiguration.GetChildren()
|
||||||
|
{
|
||||||
|
return _providers
|
||||||
|
.SelectMany(s => s.GetChildKeys(Enumerable.Empty<string>(), null))
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(key => this.GetSection(key))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a <see cref="IChangeToken"/> that can be used to observe when this configuration is reloaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The <see cref="IChangeToken"/>.</returns>
|
||||||
|
public IChangeToken GetReloadToken() => _changeToken;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Force the configuration values to be reloaded from the underlying sources.
|
||||||
|
/// </summary>
|
||||||
|
public void Reload()
|
||||||
|
{
|
||||||
|
foreach (var provider in _providers)
|
||||||
|
{
|
||||||
|
provider.Load();
|
||||||
|
}
|
||||||
|
RaiseChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RaiseChanged()
|
||||||
|
{
|
||||||
|
var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
|
||||||
|
previousToken.OnReload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new configuration source, retrieves the provider for the source, and
|
||||||
|
/// adds a change listener that triggers a reload of the provider whenever a change
|
||||||
|
/// is detected.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The configuration source to add.</param>
|
||||||
|
/// <returns>The same <see cref="IConfigurationBuilder"/>.</returns>
|
||||||
|
public IConfigurationBuilder Add(IConfigurationSource source)
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ads this source and its associated provider to the source
|
||||||
|
// and provider references in this class. We make sure to load
|
||||||
|
// the data from the provider so that values are properly initialized.
|
||||||
|
_sources.Add(source);
|
||||||
|
var provider = source.Build(this);
|
||||||
|
provider.Load();
|
||||||
|
|
||||||
|
// Add a handler that will detect when the the configuration
|
||||||
|
// provider has reloaded data. This will invoke the RaiseChanged
|
||||||
|
// method which maps changes in individual providers to the change
|
||||||
|
// token on the WebAssemblyHostConfiguration object.
|
||||||
|
_changeTokenRegistrations.Add(ChangeToken.OnChange(() => provider.GetReloadToken(), () => RaiseChanged()));
|
||||||
|
|
||||||
|
// We keep a list of providers in this class so that we can map
|
||||||
|
// set and get methods on this class to the set and get methods
|
||||||
|
// on the individual configuration providers.
|
||||||
|
_providers.Add(provider);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds an <see cref="IConfiguration"/> with keys and values from the set of providers registered in
|
||||||
|
/// <see cref="Providers"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An <see cref="IConfigurationRoot"/> with keys and values from the registered providers.</returns>
|
||||||
|
public IConfigurationRoot Build()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// dispose change token registrations
|
||||||
|
foreach (var registration in _changeTokenRegistrations)
|
||||||
|
{
|
||||||
|
registration.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// dispose providers
|
||||||
|
foreach (var provider in _providers)
|
||||||
|
{
|
||||||
|
(provider as IDisposable)?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,230 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Xunit;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Configuration.Memory;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||||
|
{
|
||||||
|
public class WebAssemblyHostConfigurationTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CanSetAndGetConfigurationValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var initialData = new Dictionary<string, string>() {
|
||||||
|
{ "color", "blue" },
|
||||||
|
{ "type", "car" },
|
||||||
|
{ "wheels:year", "2008" },
|
||||||
|
{ "wheels:count", "4" },
|
||||||
|
{ "wheels:brand", "michelin" },
|
||||||
|
{ "wheels:brand:type", "rally" },
|
||||||
|
};
|
||||||
|
var memoryConfig = new MemoryConfigurationSource { InitialData = initialData };
|
||||||
|
var configuration = new WebAssemblyHostConfiguration();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
configuration.Add(memoryConfig);
|
||||||
|
configuration["type"] = "car";
|
||||||
|
configuration["wheels:count"] = "6";
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("car", configuration["type"]);
|
||||||
|
Assert.Equal("blue", configuration["color"]);
|
||||||
|
Assert.Equal("6", configuration["wheels:count"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SettingValueUpdatesAllProviders()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var initialData = new Dictionary<string, string>() { { "color", "blue" } };
|
||||||
|
var source1 = new MemoryConfigurationSource { InitialData = initialData };
|
||||||
|
var source2 = new CustomizedTestConfigurationSource();
|
||||||
|
var configuration = new WebAssemblyHostConfiguration();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
configuration.Add(source1);
|
||||||
|
configuration.Add(source2);
|
||||||
|
configuration["type"] = "car";
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("car", configuration["type"]);
|
||||||
|
IConfigurationRoot root = configuration;
|
||||||
|
Assert.All(root.Providers, provider =>
|
||||||
|
{
|
||||||
|
provider.TryGet("type", out var value);
|
||||||
|
Assert.Equal("car", value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanGetChildren()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var initialData = new Dictionary<string, string>() { { "color", "blue" } };
|
||||||
|
var memoryConfig = new MemoryConfigurationSource { InitialData = initialData };
|
||||||
|
var configuration = new WebAssemblyHostConfiguration();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
configuration.Add(memoryConfig);
|
||||||
|
IConfiguration readableConfig = configuration;
|
||||||
|
var children = readableConfig.GetChildren();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(children);
|
||||||
|
Assert.NotEmpty(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanGetSection()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var initialData = new Dictionary<string, string>() {
|
||||||
|
{ "color", "blue" },
|
||||||
|
{ "type", "car" },
|
||||||
|
{ "wheels:year", "2008" },
|
||||||
|
{ "wheels:count", "4" },
|
||||||
|
{ "wheels:brand", "michelin" },
|
||||||
|
{ "wheels:brand:type", "rally" },
|
||||||
|
};
|
||||||
|
var memoryConfig = new MemoryConfigurationSource { InitialData = initialData };
|
||||||
|
var configuration = new WebAssemblyHostConfiguration();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
configuration.Add(memoryConfig);
|
||||||
|
var section = configuration.GetSection("wheels").AsEnumerable(makePathsRelative: true).ToDictionary(k => k.Key, v => v.Value);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(4, section.Count);
|
||||||
|
Assert.Equal("2008", section["year"]);
|
||||||
|
Assert.Equal("4", section["count"]);
|
||||||
|
Assert.Equal("michelin", section["brand"]);
|
||||||
|
Assert.Equal("rally", section["brand:type"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanDisposeProviders()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var initialData = new Dictionary<string, string>() { { "color", "blue" } };
|
||||||
|
var memoryConfig = new MemoryConfigurationSource { InitialData = initialData };
|
||||||
|
var configuration = new WebAssemblyHostConfiguration();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
configuration.Add(memoryConfig);
|
||||||
|
Assert.Equal("blue", configuration["color"]);
|
||||||
|
var exception = Record.Exception(() => configuration.Dispose());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanSupportDeeplyNestedConfigs()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dic1 = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{"Mem1", "Value1"},
|
||||||
|
{"Mem1:", "NoKeyValue1"},
|
||||||
|
{"Mem1:KeyInMem1", "ValueInMem1"},
|
||||||
|
{"Mem1:KeyInMem1:Deep1", "ValueDeep1"}
|
||||||
|
};
|
||||||
|
var dic2 = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{"Mem2", "Value2"},
|
||||||
|
{"Mem2:", "NoKeyValue2"},
|
||||||
|
{"Mem2:KeyInMem2", "ValueInMem2"},
|
||||||
|
{"Mem2:KeyInMem2:Deep2", "ValueDeep2"}
|
||||||
|
};
|
||||||
|
var dic3 = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{"Mem3", "Value3"},
|
||||||
|
{"Mem3:", "NoKeyValue3"},
|
||||||
|
{"Mem3:KeyInMem3", "ValueInMem3"},
|
||||||
|
{"Mem3:KeyInMem4", "ValueInMem4"},
|
||||||
|
{"Mem3:KeyInMem3:Deep3", "ValueDeep3"},
|
||||||
|
{"Mem3:KeyInMem3:Deep4", "ValueDeep4"}
|
||||||
|
};
|
||||||
|
var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 };
|
||||||
|
var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 };
|
||||||
|
var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dic3 };
|
||||||
|
var configuration = new WebAssemblyHostConfiguration();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
configuration.Add(memConfigSrc1);
|
||||||
|
configuration.Add(memConfigSrc2);
|
||||||
|
configuration.Add(memConfigSrc3);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var dict = configuration.GetSection("Mem1").AsEnumerable(makePathsRelative: true).ToDictionary(k => k.Key, v => v.Value);
|
||||||
|
Assert.Equal(3, dict.Count);
|
||||||
|
Assert.Equal("NoKeyValue1", dict[""]);
|
||||||
|
Assert.Equal("ValueInMem1", dict["KeyInMem1"]);
|
||||||
|
Assert.Equal("ValueDeep1", dict["KeyInMem1:Deep1"]);
|
||||||
|
|
||||||
|
var dict2 = configuration.GetSection("Mem2").AsEnumerable(makePathsRelative: true).ToDictionary(k => k.Key, v => v.Value);
|
||||||
|
Assert.Equal(3, dict2.Count);
|
||||||
|
Assert.Equal("NoKeyValue2", dict2[""]);
|
||||||
|
Assert.Equal("ValueInMem2", dict2["KeyInMem2"]);
|
||||||
|
Assert.Equal("ValueDeep2", dict2["KeyInMem2:Deep2"]);
|
||||||
|
|
||||||
|
var dict3 = configuration.GetSection("Mem3").AsEnumerable(makePathsRelative: true).ToDictionary(k => k.Key, v => v.Value);
|
||||||
|
Assert.Equal(5, dict3.Count);
|
||||||
|
Assert.Equal("NoKeyValue3", dict3[""]);
|
||||||
|
Assert.Equal("ValueInMem3", dict3["KeyInMem3"]);
|
||||||
|
Assert.Equal("ValueInMem4", dict3["KeyInMem4"]);
|
||||||
|
Assert.Equal("ValueDeep3", dict3["KeyInMem3:Deep3"]);
|
||||||
|
Assert.Equal("ValueDeep4", dict3["KeyInMem3:Deep4"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NewConfigurationProviderOverridesOldOneWhenKeyIsDuplicated()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dic1 = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{"Key1:Key2", "ValueInMem1"}
|
||||||
|
};
|
||||||
|
var dic2 = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{"Key1:Key2", "ValueInMem2"}
|
||||||
|
};
|
||||||
|
var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 };
|
||||||
|
var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 };
|
||||||
|
|
||||||
|
var configuration = new WebAssemblyHostConfiguration();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
configuration.Add(memConfigSrc1);
|
||||||
|
configuration.Add(memConfigSrc2);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("ValueInMem2", configuration["Key1:Key2"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CustomizedTestConfigurationProvider : ConfigurationProvider
|
||||||
|
{
|
||||||
|
public CustomizedTestConfigurationProvider(string key, string value)
|
||||||
|
=> Data.Add(key, value.ToUpper());
|
||||||
|
|
||||||
|
public override void Set(string key, string value)
|
||||||
|
{
|
||||||
|
Data[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CustomizedTestConfigurationSource : IConfigurationSource
|
||||||
|
{
|
||||||
|
public IConfigurationProvider Build(IConfigurationBuilder builder)
|
||||||
|
{
|
||||||
|
return new CustomizedTestConfigurationProvider("initialKey", "initialValue");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -47,6 +47,22 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
Assert.Equal("Development key3-value", _appElement.FindElement(By.Id("key3")).Text);
|
Assert.Equal("Development key3-value", _appElement.FindElement(By.Id("key3")).Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WebAssemblyConfiguration_ReloadingWorks()
|
||||||
|
{
|
||||||
|
// Verify values from the default 'appsettings.json' are read.
|
||||||
|
Browser.Equal("Default key1-value", () => _appElement.FindElement(By.Id("key1")).Text);
|
||||||
|
|
||||||
|
// Change the value of key1 using the form in the UI
|
||||||
|
var input = _appElement.FindElement(By.Id("key1-input"));
|
||||||
|
input.SendKeys("newValue");
|
||||||
|
var submit = _appElement.FindElement(By.Id("trigger-change"));
|
||||||
|
submit.Click();
|
||||||
|
|
||||||
|
// Asser that the value of the key has been updated
|
||||||
|
Browser.Equal("newValue", () => _appElement.FindElement(By.Id("key1")).Text);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void WebAssemblyHostingEnvironment_Works()
|
public void WebAssemblyHostingEnvironment_Works()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
<Reference Include="Microsoft.AspNetCore.Components.WebAssembly" />
|
<Reference Include="Microsoft.AspNetCore.Components.WebAssembly" />
|
||||||
<Reference Include="Microsoft.AspNetCore.Components.Authorization" />
|
<Reference Include="Microsoft.AspNetCore.Components.Authorization" />
|
||||||
<Reference Include="Microsoft.AspNetCore.Components.DataAnnotations.Validation" />
|
<Reference Include="Microsoft.AspNetCore.Components.DataAnnotations.Validation" />
|
||||||
|
<Reference Include="Microsoft.Extensions.Logging.Configuration" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,18 @@
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div id="environment">@HostEnvironment.Environment</div>
|
<div id="environment">@HostEnvironment.Environment</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<input id="key1-input" @bind-value=newKey1 @bind-value:event="oninput" />
|
||||||
|
<button id="trigger-change" @onclick="@(() => TriggerChange())">Change key1</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
string newKey1 { get; set; }
|
||||||
|
|
||||||
|
void TriggerChange()
|
||||||
|
{
|
||||||
|
Config["key1"] = newKey1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Components.WebAssembly.Http;
|
||||||
using Microsoft.AspNetCore.Components.WebAssembly.Services;
|
using Microsoft.AspNetCore.Components.WebAssembly.Services;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Configuration;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
namespace BasicTestApp
|
namespace BasicTestApp
|
||||||
|
|
@ -45,8 +46,9 @@ namespace BasicTestApp
|
||||||
policy.RequireAssertion(ctx => ctx.User.Identity.Name?.StartsWith("B") ?? false));
|
policy.RequireAssertion(ctx => ctx.User.Identity.Name?.StartsWith("B") ?? false));
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Logging.Services.AddSingleton<ILoggerProvider, PrependMessageLoggerProvider>(s => new PrependMessageLoggerProvider("Custom logger", s.GetService<IJSRuntime>()));
|
builder.Logging.Services.AddSingleton<ILoggerProvider, PrependMessageLoggerProvider>(s =>
|
||||||
builder.Logging.SetMinimumLevel(LogLevel.Information);
|
new PrependMessageLoggerProvider(builder.Configuration["Logging:PrependMessage:Message"], s.GetService<IJSRuntime>()));
|
||||||
|
builder.Logging.AddConfiguration(builder.Configuration);
|
||||||
|
|
||||||
var host = builder.Build();
|
var host = builder.Build();
|
||||||
ConfigureCulture(host);
|
ConfigureCulture(host);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,12 @@
|
||||||
{
|
{
|
||||||
"key1": "Default key1-value",
|
"key1": "Default key1-value",
|
||||||
"key2": "Default key2-value"
|
"key2": "Default key2-value",
|
||||||
|
"Logging": {
|
||||||
|
"PrependMessage": {
|
||||||
|
"Message": "Custom logger",
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue