Skip to content

Commit 9e1955b

Browse files
Merge branch 'master' into 54_AppSettings-config-with-colon
2 parents 2ac2524 + f7a396d commit 9e1955b

13 files changed

Lines changed: 267 additions & 40 deletions

File tree

README.md

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Version 2 is here with some new features:
1919
* Base Optional Tag - The [optional](#optional) tag that some of the builders in this project employed in V1 has been moved into the base class and is now available
2020
on all key/value config builders.
2121
* Section Handlers - This feature allows users to develop extensions that will apply key/value config to sections other than `appSettings` and `connectionStrings`
22-
if desired. Read more about this feature in the [Section Handlers](#sectionhandlers) segment below.
22+
if desired. Read more about this feature in the [Section Handlers](#section-handlers) segment below.
2323

2424
## Key/Value Config Builders
2525

@@ -69,6 +69,13 @@ key name before being inserted into AppSettings. `stripPrefix` is a boolean valu
6969
This setting is a boolean that specified whether to avoid throwing exceptions when the backing configuration source cannot be found or connected.
7070
The default default value is `true`, though some config builders (such as the Azure-based builders) will use a different default.
7171

72+
#### escapeExpandedValues
73+
.Net configuration is XML-based in it's raw form. While these config builders work on `ConfigurationSection` objects in `Strict` and `Greedy` modes,
74+
when operating in `Expand` mode, tokens in the raw XML input are directly replaced with values. Applications that use `Expand` mode may do so because
75+
they need to inject additional XML rather than just a string value. But for the cases when a simple string replacement is the goal, unescaped XML
76+
characters in replacement values may result in invalid XML. In these cases, simply set the `escapeExpandedValues` attribute to `true` and the
77+
config builder will escape special XML characters before replacing tokens in `Expand` mode. The default value is `false`.
78+
7279
#### tokenPattern
7380
This is a setting that is shared between all KeyValueConfigBuilder-derived builders is `tokenPattern`. When describing the `Expand` behavior of these builders
7481
above, it was mentioned that the raw xml is searched for tokens that look like __`${token}`__. This is done with a regular expression. `@"\$\{(\w+)\}"` to be exact.
@@ -119,7 +126,7 @@ preceded with an '@' symbol.
119126
### EnvironmentConfigBuilder
120127
```xml
121128
<add name="Environment"
122-
[mode|@prefix|@stripPrefix|tokenPattern|@optional=true]
129+
[mode|@prefix|@stripPrefix|tokenPattern|@escapeExpandedValues|@optional=true]
123130
type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Environment" />
124131
```
125132
This is the most basic of the config builders. It draws its values from Environment, and it does not have any additional configuration options.
@@ -132,7 +139,7 @@ This is the most basic of the config builders. It draws its values from Environm
132139
### UserSecretsConfigBuilder
133140
```xml
134141
<add name="UserSecrets"
135-
[mode|@prefix|@stripPrefix|tokenPattern|@optional=true]
142+
[mode|@prefix|@stripPrefix|tokenPattern|@escapeExpandedValues|@optional=true]
136143
(@userSecretsId="12345678-90AB-CDEF-1234-567890" | @userSecretsFile="~\secrets.file")
137144
type="Microsoft.Configuration.ConfigurationBuilders.UserSecretsConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.UserSecrets" />
138145
```
@@ -168,7 +175,7 @@ and currently exposes the format of the file which, as mentioned above, should b
168175
### AzureAppConfigurationBuilder
169176
```xml
170177
<add name="AzureAppConfig"
171-
[mode|@prefix|@stripPrefix|tokenPattern|@optional=false]
178+
[mode|@prefix|@stripPrefix|tokenPattern|@escapeExpandedValues|@optional=false]
172179
(@vaultName="MyVaultName" | @uri="https://MyVaultName.vault.azure.net")
173180
[@connectionString="connection string"]
174181
[@version="secrets version"]
@@ -211,7 +218,7 @@ entries: `item1` and `item2`.
211218
### AzureKeyVaultConfigBuilder
212219
```xml
213220
<add name="AzureKeyVault"
214-
[mode|@prefix|@stripPrefix|tokenPattern|@optional=false]
221+
[mode|@prefix|@stripPrefix|tokenPattern|@escapeExpandedValues|@optional=false]
215222
(@vaultName="MyVaultName" | @uri="https://MyVaultName.vault.azure.net")
216223
[@connectionString="connection string"]
217224
[@version="secrets version"]
@@ -249,7 +256,7 @@ entries: `item1` and `item2`.
249256
### KeyPerFileConfigBuilder
250257
```xml
251258
<add name="KeyPerFile"
252-
[mode|@prefix|@stripPrefix|tokenPattern|@optional=false]
259+
[mode|@prefix|@stripPrefix|tokenPattern|@escapeExpandedValues|@optional=false]
253260
(@directoryPath="PathToSourceDirectory")
254261
[@ignorePrefix="ignore."]
255262
[keyDelimiter=":"]
@@ -267,7 +274,7 @@ their orchestrated windows containers in this key-per-file manner.
267274
### SimpleJsonConfigBuilder
268275
```xml
269276
<add name="SimpleJson"
270-
[mode|@prefix|@stripPrefix|tokenPattern|@optional=true]
277+
[mode|@prefix|@stripPrefix|tokenPattern|@escapeExpandedValues|@optional=true]
271278
@jsonFile="~\config.json"
272279
[@jsonMode="(Flat|Sectional)"]
273280
type="Microsoft.Configuration.ConfigurationBuilders.SimpleJsonConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Json" />
@@ -339,7 +346,25 @@ collection. As an example, here is what their explicit declaration would look li
339346
</handlers>
340347
</Microsoft.Configuration.ConfigurationBuilders.SectionHandlers>
341348
```
342-
When adding additional handlers, the name of the section must be 'Microsoft.Configuration.ConfigurationBuilders.SectionHandlers' so key/value config builders can find it.
349+
When adding additional handlers, the name of this section must be 'Microsoft.Configuration.ConfigurationBuilders.SectionHandlers' so key/value config builders can find it.
350+
Also note that a more qualified type will be required so the runtime can determine which assembly contains the new handler type. When working
351+
with ASP.Net applications, it is hit and miss regarding whether its able to define new section handlers in `App_Code` or not. Some configuration
352+
sections (such as `appSettings`) get loaded by ASP.Net before `App_Code` is compiled, so handlers for those sections will need to be
353+
compiled in a separate assembly in the 'bin' directory. For example:
354+
```xml
355+
<configSections>
356+
<section name="Microsoft.Configuration.ConfigurationBuilders.SectionHandlers" type="Microsoft.Configuration.ConfigurationBuilders.SectionHandlersSection, Microsoft.Configuration.ConfigurationBuilders.Base" restartOnExternalChanges="false" requirePermission="false" />
357+
</configSections>
358+
359+
<Microsoft.Configuration.ConfigurationBuilders.SectionHandlers>
360+
<handlers>
361+
<remove name="DefaultAppSettingsHandler" />
362+
<add name="DefaultAppSettingsHandler" type="MyCustomAppSettingsSectionHandler, RefAssemblyInBin" />
363+
<remove name="DefaultConnectionStringsHandler" />
364+
<add name="DefaultConnectionStringsHandler" type="MyCustomConnectionStringsSectionHandler, App_Code" />
365+
</handlers>
366+
</Microsoft.Configuration.ConfigurationBuilders.SectionHandlers>
367+
```
343368

344369
## Implementing More Key/Value Config Builders
345370

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"expandTestCS": "A & really ' bad \" unescaped < connection > string."
3+
}

samples/SampleWebApp/App_Data/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"jsonComplex": {
2222
"setting1": "Complex Setting 1",
2323
"setting2": "Complex Setting 2",
24-
"jsonArrayOfSettings": [ "one", "two", "three"]
24+
"jsonArrayOfSettings": [ "one", "two", "three" ]
2525
}
2626
}
2727
}

samples/SampleWebApp/SampleWebApp.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
</ItemGroup>
9292
<ItemGroup>
9393
<Content Include="App_Data\settings.json" />
94+
<Content Include="App_Data\expandTest.json" />
9495
<None Include="packages.config" />
9596
<None Include="Web.Debug.config">
9697
<DependentUpon>Web.config</DependentUpon>

samples/SampleWebApp/Web.config

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,19 @@
2222

2323
<configBuilders>
2424
<builders>
25-
<add name="Environment" type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Environment, Version=2.0.0.0, Culture=neutral" />
26-
<add name="Secrets" userSecretsFile="~/App_Data/secrets.xml" mode="Greedy" type="Microsoft.Configuration.ConfigurationBuilders.UserSecretsConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.UserSecrets, Version=2.0.0.0, Culture=neutral" />
27-
<add name="Json" jsonFile="${JSONConfigFile}" optional="true" type="Microsoft.Configuration.ConfigurationBuilders.SimpleJsonConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Json, Version=2.0.0.0, Culture=neutral" />
28-
<add name="KeyPerFile" directoryPath="~/../KeyPerFileSampleRoot" mode="Strict" keyDelimiter="--" type="Microsoft.Configuration.ConfigurationBuilders.KeyPerFileConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.KeyPerFile, Version=2.0.0.0, Culture=neutral" />
25+
<add name="Environment" type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Environment, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
26+
<add name="Secrets" userSecretsFile="~/App_Data/secrets.xml" mode="Greedy" type="Microsoft.Configuration.ConfigurationBuilders.UserSecretsConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.UserSecrets, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
27+
<add name="Json" jsonFile="${JSONConfigFile}" optional="true" type="Microsoft.Configuration.ConfigurationBuilders.SimpleJsonConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Json, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
28+
<add name="KeyPerFile" directoryPath="~/../KeyPerFileSampleRoot" mode="Strict" keyDelimiter="--" type="Microsoft.Configuration.ConfigurationBuilders.KeyPerFileConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.KeyPerFile, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
2929

30-
<add name="KV1" vaultName="${ConfigBuilderTestKeyVaultName}" type="Microsoft.Configuration.ConfigurationBuilders.AzureKeyVaultConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Azure, Version=2.0.0.0, Culture=neutral" />
31-
<add name="KV2" vaultName="${ConfigBuilderTestKeyVaultName}" version="d14197de791c4ffe8e79bc7fc0f766b0" type="Microsoft.Configuration.ConfigurationBuilders.AzureKeyVaultConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Azure, Version=2.0.0.0, Culture=neutral" />
32-
<add name="KV3" vaultName="${ConfigBuilderTestKeyVaultName}" mode="Greedy" type="Microsoft.Configuration.ConfigurationBuilders.AzureKeyVaultConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Azure, Version=2.0.0.0, Culture=neutral" />
33-
<add name="KV4" vaultName="${ConfigBuilderTestKeyVaultName}" mode="Greedy" version="0de51928e49144ce86eb1de9056ac937" type="Microsoft.Configuration.ConfigurationBuilders.AzureKeyVaultConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Azure, Version=2.0.0.0, Culture=neutral" />
30+
<add name="KV1" vaultName="${ConfigBuilderTestKeyVaultName}" type="Microsoft.Configuration.ConfigurationBuilders.AzureKeyVaultConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Azure, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
31+
<add name="KV2" vaultName="${ConfigBuilderTestKeyVaultName}" version="d14197de791c4ffe8e79bc7fc0f766b0" type="Microsoft.Configuration.ConfigurationBuilders.AzureKeyVaultConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Azure, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
32+
<add name="KV3" vaultName="${ConfigBuilderTestKeyVaultName}" mode="Greedy" type="Microsoft.Configuration.ConfigurationBuilders.AzureKeyVaultConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Azure, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
33+
<add name="KV4" vaultName="${ConfigBuilderTestKeyVaultName}" mode="Greedy" version="0de51928e49144ce86eb1de9056ac937" type="Microsoft.Configuration.ConfigurationBuilders.AzureKeyVaultConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Azure, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
3434

35-
<add name="AS_Sub_Test" optional="${Boolean}" type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Environment, Version=2.0.0.0, Culture=neutral" />
36-
<add name="AS_Sub_Test2" optional="${app~Settings_Colon-and$friends@super+duper,awesome#cool:Test.}" type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Environment, Version=2.0.0.0, Culture=neutral" />
35+
<add name="AS_Sub_Test" optional="${Boolean}" type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Environment, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
36+
<add name="AS_Sub_Test2" optional="${app~Settings_Colon-and$friends@super+duper,awesome#cool:Test.}" type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Environment, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
37+
<add name="ExpTest" mode="Expand" escapeExpandedValues="true" jsonFile="~/App_Data/expandTest.json" jsonMode="Flat" type="Microsoft.Configuration.ConfigurationBuilders.SimpleJsonConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Json, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
3738
</builders>
3839
</configBuilders>
3940

@@ -64,7 +65,9 @@
6465
<!-- key="Secret3" value="Will be added by KV3:Latest3. IFF already added, will be "updated" by KV1 to Latest3. KV2 and KV4 don't have versions matching this secret." -->
6566
</appSettings>
6667

67-
<connectionStrings configBuilders="Json,AS_Sub_Test2">
68+
<connectionStrings configBuilders="Json,ExpTest,AS_Sub_Test2">
69+
<add name="expansionTest" connectionString="${expandTestCS}" />
70+
<add name="expandTestCS" connectionString="Only replaced in Strict/Greedy modes. Not Expand."/>
6871
<add name="jsonConnectionString1" connectionString="Will be replaced by 'Json' in 'Flat' AND 'Sectional' jsonModes, but with different values." />
6972
<add name="connectionStrings:jsonConnectionString1" connectionString="Will only be replaced by 'Json' in 'Flat' jsonMode." />
7073
<add name="jsonConnectionString2" connectionString="Will only be replaced by 'Json' in 'Sectional' jsonMode." />

src/Azure/AzureKeyVaultConfigBuilder.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,33 @@ public override ICollection<KeyValuePair<string, string>> GetAllValues(string pr
143143
return d;
144144
}
145145

146+
/// <summary>
147+
/// Transform the given key to an intermediate format that will be used to look up values in backing store.
148+
/// </summary>
149+
/// <param name="key">The string to be mapped.</param>
150+
/// <returns>The key string to be used while looking up config values..</returns>
151+
public override string MapKey(string key)
152+
{
153+
if (String.IsNullOrEmpty(key))
154+
return key;
155+
156+
// Colons and underscores are common in appSettings keys, but not allowed in key vault key names.
157+
// It's likely that apps will want to lookup config values with these characters in their name in
158+
// key vault. More extensive key mapping can be done with subclasses. But let's handle the most
159+
// most common case here.
160+
key = key.Replace(':', '-');
161+
key = key.Replace('_', '-');
162+
return key;
163+
}
164+
146165
/// <summary>
147166
/// Makes a determination about whether the input key is valid for this builder and backing store.
148167
/// </summary>
149168
/// <param name="key">The string to be validated. May be partial.</param>
150169
/// <returns>True if the string is valid. False if the string is not a valid key.</returns>
151170
public override bool ValidateKey(string key)
152171
{
153-
// Key Vault only allows alphanumerics and '-'. This builder also allows for one '/'.
172+
// Key Vault only allows alphanumerics and '-'. This builder also allows for one '/' for versioning.
154173
return Regex.IsMatch(key, "^[a-zA-Z0-9-]+(/?[a-zA-Z0-9-]+)?$");
155174
}
156175

src/AzureAppConfig/AzureAppConfigurationBuilder.cs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Collections.Specialized;
77
using System.Linq;
8+
using System.Text.RegularExpressions;
89
using System.Threading;
910
using System.Threading.Tasks;
1011
using Microsoft.Azure.AppConfiguration.Azconfig;
@@ -45,9 +46,9 @@ protected override void LazyInitialize(string name, NameValueCollection config)
4546

4647
base.LazyInitialize(name, config);
4748

48-
_keyFilter = config[keyFilterTag];
49-
_labelFilter = config[labelFilterTag];
50-
_dateTimeFilter = DateTimeOffset.TryParse(config[dateTimeFilterTag], out _dateTimeFilter) ? _dateTimeFilter : DateTimeOffset.MinValue;
49+
_keyFilter = UpdateConfigSettingWithAppSettings(keyFilterTag);
50+
_labelFilter = UpdateConfigSettingWithAppSettings(labelFilterTag);
51+
_dateTimeFilter = DateTimeOffset.TryParse(UpdateConfigSettingWithAppSettings(dateTimeFilterTag), out _dateTimeFilter) ? _dateTimeFilter : DateTimeOffset.MinValue;
5152

5253
// Place some restrictions on label filter, similar to the .net core provider.
5354
// The idea is to restrict queries to one label, and one label only. Even if that
@@ -102,8 +103,21 @@ protected override void LazyInitialize(string name, NameValueCollection config)
102103
/// <returns>True if the string is valid. False if the string is not a valid key.</returns>
103104
public override bool ValidateKey(string key)
104105
{
105-
// Azure App Config does not restrict key names, although a couple characters have special meaning if not escaped.
106-
// We may want to restrict using those characters unescaped in a key name in the future.
106+
// From - https://docs.microsoft.com/en-us/azure/azure-app-configuration/concept-key-value
107+
// You can use any unicode character in key names entered into App Configuration except for *, ,, and \. These characters are
108+
// reserved.If you need to include a reserved character, you must escape it by using \{ Reserved Character}.
109+
if (String.IsNullOrWhiteSpace(key))
110+
return false;
111+
112+
if (key.Contains('*') || key.Contains(','))
113+
return false;
114+
115+
// We don't want to completely disallow '\' since it is used for escaping. But writing a full parser for someone elses
116+
// naming format could be error prone. If we see a '\' followed by a '{', just call it good. Don't bother with the Regex
117+
// if there aren't any backslashes though.
118+
if (key.Contains('\\'))
119+
return !Regex.IsMatch(key, @"\\[^{]");
120+
107121
return true;
108122
}
109123

0 commit comments

Comments
 (0)