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
|
||||
// in the future if we want to give people a choice between CreateDefault and something
|
||||
// less opinionated.
|
||||
Configuration = new ConfigurationBuilder();
|
||||
Configuration = new WebAssemblyHostConfiguration();
|
||||
RootComponents = new RootComponentMappingCollection();
|
||||
Services = new ServiceCollection();
|
||||
Logging = new LoggingBuilder(Services);
|
||||
|
||||
Logging.SetMinimumLevel(LogLevel.Warning);
|
||||
|
||||
// Retrieve required attributes from JSRuntimeInvoker
|
||||
InitializeNavigationManager(jsRuntimeInvoker);
|
||||
InitializeDefaultServices();
|
||||
|
|
@ -111,10 +109,10 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IConfigurationBuilder"/> that can be used to customize the application's
|
||||
/// configuration sources.
|
||||
/// Gets an <see cref="WebAssemblyHostConfiguration"/> that can be used to customize the application's
|
||||
/// configuration sources and read configuration attributes.
|
||||
/// </summary>
|
||||
public IConfigurationBuilder Configuration { get; }
|
||||
public WebAssemblyHostConfiguration Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of root component mappings configured for the application.
|
||||
|
|
@ -177,8 +175,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
|||
public WebAssemblyHost Build()
|
||||
{
|
||||
// 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
|
||||
// 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 scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope();
|
||||
|
||||
return new WebAssemblyHost(services, scope, configuration, RootComponents.ToArray());
|
||||
return new WebAssemblyHost(services, scope, Configuration, RootComponents.ToArray());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
[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]
|
||||
public void WebAssemblyHostingEnvironment_Works()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
<Reference Include="Microsoft.AspNetCore.Components.WebAssembly" />
|
||||
<Reference Include="Microsoft.AspNetCore.Components.Authorization" />
|
||||
<Reference Include="Microsoft.AspNetCore.Components.DataAnnotations.Validation" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Configuration" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -8,3 +8,18 @@
|
|||
</ul>
|
||||
|
||||
<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.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Configuration;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace BasicTestApp
|
||||
|
|
@ -45,8 +46,9 @@ namespace BasicTestApp
|
|||
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.SetMinimumLevel(LogLevel.Information);
|
||||
builder.Logging.Services.AddSingleton<ILoggerProvider, PrependMessageLoggerProvider>(s =>
|
||||
new PrependMessageLoggerProvider(builder.Configuration["Logging:PrependMessage:Message"], s.GetService<IJSRuntime>()));
|
||||
builder.Logging.AddConfiguration(builder.Configuration);
|
||||
|
||||
var host = builder.Build();
|
||||
ConfigureCulture(host);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
{
|
||||
"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