Skip to content

Commit bf19d08

Browse files
Add ExpandWrapper sample. (#206)
1 parent 89d0e64 commit bf19d08

12 files changed

Lines changed: 125 additions & 22 deletions

File tree

MicrosoftConfigurationBuilders.msbuild

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
</ItemGroup>
1616

1717
<ItemGroup>
18-
<SampleProject Include="samples\SampleSectionHandlers\SampleSectionHandlers.csproj" />
1918
<SampleProject Include="samples\SampleConsoleApp\SampleConsoleApp.csproj" />
19+
<SampleProject Include="samples\SamplesLib\SamplesLib.csproj" />
2020
<SampleProject Include="samples\SampleWebApp\SampleWebApp.csproj" />
2121
<SampleProject Include="samples\SampleWebJob\SampleWebJob.csproj" />
2222
</ItemGroup>

MicrosoftConfigurationBuilders.sln

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleWebJob", "samples\Sam
9191
EndProject
9292
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleConsoleApp", "samples\SampleConsoleApp\SampleConsoleApp.csproj", "{0DBFA194-1632-40C0-A072-C59A9280412B}"
9393
EndProject
94-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleSectionHandlers", "samples\SampleSectionHandlers\SampleSectionHandlers.csproj", "{B3F1C0AD-957B-4453-A830-F104FE368BC4}"
95-
EndProject
9694
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{905D083F-6414-4DEA-9779-76C8A2518F8D}"
9795
ProjectSection(SolutionItems) = preProject
9896
docs\CustomConfigBuilders.md = docs\CustomConfigBuilders.md
@@ -102,6 +100,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{905D083F-6
102100
docs\SectionHandlers.md = docs\SectionHandlers.md
103101
EndProjectSection
104102
EndProject
103+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SamplesLib", "samples\SamplesLib\SamplesLib.csproj", "{BAA5EB76-5A85-48D6-9E8F-A01A309A2879}"
104+
EndProject
105105
Global
106106
GlobalSection(SolutionConfigurationPlatforms) = preSolution
107107
Debug|Any CPU = Debug|Any CPU
@@ -156,10 +156,10 @@ Global
156156
{0DBFA194-1632-40C0-A072-C59A9280412B}.Debug|Any CPU.Build.0 = Debug|Any CPU
157157
{0DBFA194-1632-40C0-A072-C59A9280412B}.Release|Any CPU.ActiveCfg = Release|Any CPU
158158
{0DBFA194-1632-40C0-A072-C59A9280412B}.Release|Any CPU.Build.0 = Release|Any CPU
159-
{B3F1C0AD-957B-4453-A830-F104FE368BC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
160-
{B3F1C0AD-957B-4453-A830-F104FE368BC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
161-
{B3F1C0AD-957B-4453-A830-F104FE368BC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
162-
{B3F1C0AD-957B-4453-A830-F104FE368BC4}.Release|Any CPU.Build.0 = Release|Any CPU
159+
{BAA5EB76-5A85-48D6-9E8F-A01A309A2879}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
160+
{BAA5EB76-5A85-48D6-9E8F-A01A309A2879}.Debug|Any CPU.Build.0 = Debug|Any CPU
161+
{BAA5EB76-5A85-48D6-9E8F-A01A309A2879}.Release|Any CPU.ActiveCfg = Release|Any CPU
162+
{BAA5EB76-5A85-48D6-9E8F-A01A309A2879}.Release|Any CPU.Build.0 = Release|Any CPU
163163
EndGlobalSection
164164
GlobalSection(SolutionProperties) = preSolution
165165
HideSolutionNode = FALSE
@@ -171,7 +171,7 @@ Global
171171
{B2D778B6-6E0A-4ED1-B1E3-7C85DD976F69} = {DFB1983C-B754-403E-AECC-ED3D4C9DC42A}
172172
{18D8B490-1CBC-4783-B6D7-D1A88E832224} = {2F759F48-7F89-4811-8F94-380BCCC83C69}
173173
{0DBFA194-1632-40C0-A072-C59A9280412B} = {2F759F48-7F89-4811-8F94-380BCCC83C69}
174-
{B3F1C0AD-957B-4453-A830-F104FE368BC4} = {2F759F48-7F89-4811-8F94-380BCCC83C69}
174+
{BAA5EB76-5A85-48D6-9E8F-A01A309A2879} = {2F759F48-7F89-4811-8F94-380BCCC83C69}
175175
EndGlobalSection
176176
GlobalSection(ExtensibilityGlobals) = postSolution
177177
SolutionGuid = {6380A53F-A088-4D0B-B415-C8D16222F022}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ linked here:
2222

2323
<a name="updates"></a>
2424
### V3 Updates:
25-
* :warning: ***Breaking Change*** - `Expand` mode is gone. It has been [replaced by `Token` mode](docs/KeyValueConfigBuilders.md#token).#TODO verify link (and #enabled and #charmap link)
25+
* :warning: ***Breaking Change*** - `Expand` mode is gone. It has been [replaced by `Token` mode](docs/KeyValueConfigBuilders.md#mode).
2626
* `Utils.MapPath` - This was somewhat broken in ASP.Net scenarios previously. It should now reliably go against `Server.MapPath()` in ASP.Net scenarios. It has
2727
also been updated to fall back against the directory of the config file being processed when resolving the app root in the case of a `Configuration`
2828
object being created by `ConfigurationManager.OpenConfiguration*` API's rather than being part of a fully-initialized runtime configuration stack.

docs/FAQ.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ some of the most frequent along with answers that are hopefully helpful.
2222
> To make things simpler across the board, 'Expand' mode was replaced with 'Token' mode which should
2323
> operate in a fairly similar manner with the added benefit of being less prone to producing invalid
2424
> XML to muck things up. :smiley:
25+
>
26+
> <sup><sub>
27+
> If you really, really need that raw plain-text processing because it's not possible to write an
28+
> `ISectionHandler` for your particular section, or because you have taken full advantage of building
29+
> xml through the use of token expansion that doesn't conform to the convenient mental paradigm of
30+
> only placing tokens within obvious key/value places of existing well-formed xml... you can try
31+
> [this wrapper approach](../samples/SamplesLib/ExpandWrapper.cs) as is demonstrated in the
32+
> [SampleConsoleApp](../samples/SampleConsoleApp/App.config#L36-L39).
33+
> </sub></sub>
2534
</details>
2635
2736
<a name="newhandler"></a>
@@ -47,15 +56,15 @@ some of the most frequent along with answers that are hopefully helpful.
4756
> isn't really a standard .Net configuration section like it appears to be on first glance. The classes
4857
> that support ApplicationSettings provide a strict and strongly typed window into what looks like a
4958
> standard configuration section in your app.config file. While we can easily write a section handler
50-
> for the `ClientSettingsSection` ([example](../samples/SampleSectionHandlers/ClientSettingsSectionHandler.cs))
59+
> for the `ClientSettingsSection` ([example](../samples/SamplesLib/ClientSettingsSectionHandler.cs))
5160
> it won't integrate into the ApplicationSettings framework seamlessly like one might expect. The
5261
> ApplicationSetting framework has already determined the number and names (including casing, which
5362
> is problematic in 'Greedy' mode) of all the settings it will present before the base configuration
5463
> system even gets a crack at reading from the config file. So you can't *add* new values with 'Greedy'
5564
> mode, and you can't override existing values in 'Greedy' mode if you don't properly match
5665
> casing - despite the fact that ApplicationSettings is supposed to be case-insensitive.
5766
>
58-
> If you wish, you can use the [sample section handler](../samples/SampleSectionHandlers/ClientSettingsSectionHandler.cs)
67+
> If you wish, you can use the [sample section handler](../samples/SamplesLib/ClientSettingsSectionHandler.cs)
5968
> to process ApplicationSettings in your application, but know that the use case is rather limited.
6069
> It will work in 'Strict' mode... and maybe require some prodding to force the ApplicationSettings
6170
> framework to forget the settings it's seen before and decide to look back into the config file to

samples/SampleConsoleApp/App.config

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<section name="configBuilders" type="System.Configuration.ConfigurationBuildersSection, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" restartOnExternalChanges="false" requirePermission="false" />
66
<section name="Microsoft.Configuration.ConfigurationBuilders.SectionHandlers" type="Microsoft.Configuration.ConfigurationBuilders.SectionHandlersSection, Microsoft.Configuration.ConfigurationBuilders.Base" restartOnExternalChanges="false" requirePermission="false" />
77
<section name="customSettings" type="System.Configuration.AppSettingsSection, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" restartOnExternalChanges="false" requirePermission="false" />
8+
<section name="expandedSettings" type="System.Configuration.AppSettingsSection, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" restartOnExternalChanges="false" requirePermission="false" />
89
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
910
<section name="SampleConsoleApp.ClientSettings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
1011
</sectionGroup>
@@ -15,12 +16,13 @@
1516
<add name="Env" type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Environment" />
1617
<add name="KeyPerFile" mode="Greedy" directoryPath="${SampleItems}/KeyPerFileRoot" type="Microsoft.Configuration.ConfigurationBuilders.KeyPerFileConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.KeyPerFile" />
1718
<add name="Json" mode="Greedy" jsonMode="Flat" jsonFile="${jsonFile}" type="Microsoft.Configuration.ConfigurationBuilders.SimpleJsonConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Json" />
19+
<add name="JsonExpand" mode="Token" jsonMode="Sectional" jsonFile="${jsonFile}" type="SamplesLib.ExpandWrapper`1[[Microsoft.Configuration.ConfigurationBuilders.SimpleJsonConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Json]], SamplesLib" />
1820
</builders>
1921
</configBuilders>
2022

2123
<Microsoft.Configuration.ConfigurationBuilders.SectionHandlers>
2224
<handlers>
23-
<add name="ClientSettingsHandler" type="SampleSectionHandlers.ClientSettingsSectionHandler, SampleSectionHandlers" />
25+
<add name="ClientSettingsHandler" type="SamplesLib.ClientSettingsSectionHandler, SamplesLib" />
2426
</handlers>
2527
</Microsoft.Configuration.ConfigurationBuilders.SectionHandlers>
2628

@@ -31,6 +33,11 @@
3133

3234
<customSettings configBuilders="Json" />
3335

36+
<expandedSettings configBuilders="JsonExpand">
37+
${expandedSetting1}
38+
${expandedSetting2}
39+
</expandedSettings>
40+
3441
<applicationSettings>
3542
<SampleConsoleApp.ClientSettings configBuilders="Env">
3643
<setting name="FirstTestSetting" serializeAs="String">

samples/SampleConsoleApp/Program.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ static void Main(string[] args)
2727
Console.WriteLine("");
2828
Console.WriteLine("");
2929

30+
Console.WriteLine("---------- Expanded Settings ----------");
31+
var expandedSettings = ConfigurationManager.GetSection("expandedSettings") as NameValueCollection;
32+
foreach (string setting in expandedSettings.Keys)
33+
{
34+
Console.WriteLine($"{setting}\t{expandedSettings[setting]}");
35+
}
36+
37+
Console.WriteLine("");
38+
Console.WriteLine("");
39+
3040
Console.WriteLine("---------- Client Application Settings ----------");
3141
Console.WriteLine("Note: These _might_ be inaccurate due to the additional layers of building and caching that the");
3242
Console.WriteLine("\tClient Application Settings framework uses. Read more about the Settings architecture");

samples/SampleConsoleApp/SampleConsoleApp.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,6 @@
6262
</None>
6363
</ItemGroup>
6464
<ItemGroup>
65-
<ProjectReference Include="..\SampleSectionHandlers\SampleSectionHandlers.csproj">
66-
<Project>{b3f1c0ad-957b-4453-a830-f104fe368bc4}</Project>
67-
<Name>SampleSectionHandlers</Name>
68-
</ProjectReference>
6965
<ProjectReference Include="..\..\src\Base\Base.csproj">
7066
<Project>{f382fbf8-146d-4968-a199-90d37f9ef9a7}</Project>
7167
<Name>Base</Name>
@@ -86,6 +82,10 @@
8682
<Project>{c60d6cbb-d513-4692-81a6-0be5d45e4702}</Project>
8783
<Name>UserSecrets</Name>
8884
</ProjectReference>
85+
<ProjectReference Include="..\SamplesLib\SamplesLib.csproj">
86+
<Project>{baa5eb76-5a85-48d6-9e8f-a01a309a2879}</Project>
87+
<Name>SamplesLib</Name>
88+
</ProjectReference>
8989
</ItemGroup>
9090
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
9191
</Project>

samples/SampleWebApp/App_Data/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,10 @@
2525
"setting2": "Complex Setting 2",
2626
"jsonArrayOfSettings": [ "one", "two", "three" ]
2727
}
28+
},
29+
30+
"expandedSettings": {
31+
"expandedSetting1": "<add key=\"oldExpandMode\" value=\"Is dead.\" />",
32+
"expandedSetting2": "<add key=\"longLiving\" value=\"Old Expand Mode!\" />"
2833
}
2934
}

samples/SampleSectionHandlers/ClientSettingsSectionHandler.cs renamed to samples/SamplesLib/ClientSettingsSectionHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
using System.Xml;
66
using Microsoft.Configuration.ConfigurationBuilders;
77

8-
namespace SampleSectionHandlers
8+
namespace SamplesLib
99
{
1010
/// <summary>
1111
/// A class that can be used by <see cref="KeyValueConfigBuilder"/>s to apply key/value config pairs to <see cref="ClientSettingsSection"/>.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Specialized;
4+
using System.Configuration;
5+
using System.Linq;
6+
using System.Reflection;
7+
using System.Xml;
8+
using Microsoft.Configuration.ConfigurationBuilders;
9+
10+
namespace SamplesLib
11+
{
12+
public class ExpandWrapper<T> : ConfigurationBuilder where T : KeyValueConfigBuilder, new()
13+
{
14+
private static MethodInfo _expandTokensMethod = typeof(KeyValueConfigBuilder).GetMethod("ExpandTokens", BindingFlags.NonPublic | BindingFlags.Instance);
15+
private T _underlyingBuilder;
16+
17+
public ExpandWrapper() { _underlyingBuilder = new T(); }
18+
19+
public override void Initialize(string name, NameValueCollection config) => _underlyingBuilder.Initialize(name, config);
20+
21+
public override XmlNode ProcessRawXml(XmlNode rawXml)
22+
{
23+
rawXml = _underlyingBuilder.ProcessRawXml(rawXml);
24+
25+
// !!!DO NOT APPLY TO APPSETTINGS!!!
26+
// AppSettings is special because it can be implicitly referenced when looking for config builder
27+
// settings, while it can simultaneously be processed by config builders. There used to be special
28+
// logic to help manage potential recursion in the base KeyValueConfigBuilder class, but that
29+
// protection is no more since 'Expand' mode and the use of _both_ ProcessRawXml() and ProcessConfigSection()
30+
// have been removed.
31+
if (rawXml.Name != "appSettings") // System.Configuration hard codes this, so we might as well too.
32+
{
33+
// Checking Enabled will kick off LazyInit, so only do that if we are actually going to do work here.
34+
if (_underlyingBuilder.Mode == KeyValueMode.Token && _underlyingBuilder.Enabled != KeyValueEnabled.Disabled)
35+
{
36+
// Old Expand-mode would do a recursion check here. We don't have internal access to RecursionGuard.
37+
return ExpandTokens(rawXml);
38+
}
39+
}
40+
41+
return rawXml;
42+
}
43+
44+
public override ConfigurationSection ProcessConfigurationSection(ConfigurationSection configSection)
45+
{
46+
// We have overridden the meaning of "Token" mode for this class. Don't do any processing in that mode.
47+
if (_underlyingBuilder.Mode == KeyValueMode.Token)
48+
return configSection;
49+
50+
return _underlyingBuilder.ProcessConfigurationSection(configSection);
51+
}
52+
53+
private XmlNode ExpandTokens(XmlNode rawXml)
54+
{
55+
string rawXmlString = rawXml.OuterXml;
56+
if (String.IsNullOrEmpty(rawXmlString))
57+
return rawXml;
58+
59+
string updatedXmlString = (string)_expandTokensMethod.Invoke(_underlyingBuilder, new object[] { rawXmlString });
60+
61+
XmlDocument doc = new XmlDocument();
62+
doc.PreserveWhitespace = true;
63+
doc.LoadXml(updatedXmlString);
64+
return doc.DocumentElement;
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)