/
WebAssemblyHostConfiguration.cs
195 lines (169 loc) · 7.1 KB
/
WebAssemblyHostConfiguration.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// 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.ObjectModel;
using System.Linq;
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.ToArray());
/// <summary>
/// Gets the providers used to obtain configuration values.
/// </summary>
IEnumerable<IConfigurationProvider> IConfigurationRoot.Providers => new ReadOnlyCollection<IConfigurationProvider>(_providers.ToArray());
/// <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()
{
var hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var result = new List<IConfigurationSection>();
foreach (var provider in _providers)
{
foreach (var child in provider.GetChildKeys(Enumerable.Empty<string>(), parentPath: null))
{
if (!hashSet.Add(child))
{
continue;
}
result.Add(GetSection(child));
}
}
return result;
}
/// <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));
}
// Adds 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="IConfigurationRoot.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();
}
}
}