Skip to content

Commit a2e7350

Browse files
Refresh docs for V3 update. (#192)
1 parent e1523ff commit a2e7350

7 files changed

Lines changed: 822 additions & 604 deletions

File tree

MicrosoftConfigurationBuilders.sln

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleConsoleApp", "samples
9393
EndProject
9494
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleSectionHandlers", "samples\SampleSectionHandlers\SampleSectionHandlers.csproj", "{B3F1C0AD-957B-4453-A830-F104FE368BC4}"
9595
EndProject
96+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{905D083F-6414-4DEA-9779-76C8A2518F8D}"
97+
ProjectSection(SolutionItems) = preProject
98+
docs\CustomConfigBuilders.md = docs\CustomConfigBuilders.md
99+
docs\FAQ.md = docs\FAQ.md
100+
docs\Intro.md = docs\Intro.md
101+
docs\KeyValueConfigBuilders.md = docs\KeyValueConfigBuilders.md
102+
docs\SectionHandlers.md = docs\SectionHandlers.md
103+
EndProjectSection
104+
EndProject
96105
Global
97106
GlobalSection(SolutionConfigurationPlatforms) = preSolution
98107
Debug|Any CPU = Debug|Any CPU

README.md

Lines changed: 42 additions & 604 deletions
Large diffs are not rendered by default.

docs/CustomConfigBuilders.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Implementing More Key/Value Config Builders
2+
3+
If you don't see a config builder here that suits your needs, you can write your own. Referencing the `Basic` nuget package for this project will get you the base upon which
4+
all of these builders inherit. Most of the heavy-ish lifting and consistent behavior across key/value config builders comes from this base. Take a look at the code for more
5+
detail, but in many cases implementing a custom key/value config builder in this same vein is as easy as inheriting the base, and implementing two basic methods.
6+
```CSharp
7+
using Microsoft.Configuration.ConfigurationBuilders;
8+
9+
public class CustomConfigBuilder : KeyValueConfigBuilder
10+
{
11+
public override string GetValue(string key)
12+
{
13+
// Key lookup should be case-insensitive, because most key/value collections in .Net config sections are as well.
14+
return "Value for given key, or null.";
15+
}
16+
17+
public override ICollection<KeyValuePair<string, string>> GetAllValues(string prefix)
18+
{
19+
// Populate the return collection a little more smartly. ;)
20+
return new Dictionary<string, string>() { { "one", "1" }, { "two", "2" } };
21+
}
22+
}
23+
```
24+
25+
Additionally, there are a few virtual methods that you can take advantage of for more advanced techniques.
26+
```CSharp
27+
public class CustomConfigBuilder : KeyValueConfigBuilder
28+
{
29+
public override void Initialize(string name, NameValueCollection config)
30+
{
31+
// Use this initializer only for things that absolutely must be read from
32+
// the 'config' collection immediately upon creation.
33+
// AppSettings parameter substitution is not available at this point.
34+
// Use LazyInitialize(string, NameValueCollection) instead whenever possible.
35+
}
36+
37+
protected override void LazyInitialize(string name, NameValueCollection config)
38+
{
39+
// Use this for things that don't need to be initialized until just before
40+
// config values are retrieved.
41+
//
42+
// *First, set the default values for 'Enabled' and 'CharacterMap' if
43+
// different from the base.
44+
//
45+
// *Second, be sure to call 'base.LazyInitialize(name, config)'. AppSettings
46+
// parameter substitution via 'UpdateConfigSettingWithAppSettings(parameterName)'
47+
// will be available after that call.
48+
//
49+
// *Third, check the value of 'Enabled' to see if there is any need to continue.
50+
//
51+
// *Lastly, read any additional parameters and do any other tasks needed
52+
// in preparation for retrieving config values.
53+
}
54+
55+
public override bool MapKey(string key)
56+
{
57+
// If you know the format of the key pulled from *.config files is going to be invalid, but you
58+
// are able to translate the bad format to a known good format to get a value from your
59+
// config source, use this method.
60+
// Ex) AppSettings are commonly named things like "area:feature", but the ':' is not a legal
61+
// character for key names in Azure Key Vault. MapKey() can help translate the ':' to a
62+
// '-' in this case, which will allow the ability to look up a config value for this appSetting
63+
// in Key Vault, even though it's original key name is not valid in Key Vault.
64+
}
65+
66+
public override bool ValidateKey(string key)
67+
{
68+
// A no-op by default. If your backing storage cannot handle certain characters, this is a one-stop
69+
// place for screening key names. It is particularly helpful in `Strict` and `Token` modes where
70+
// key names for lookup are taken from *.config files and could potentially contain invalid
71+
// characters that cause exceptions in the backing config store.
72+
}
73+
74+
public override string UpdateKey(string rawKey)
75+
{
76+
// Just before replacing retrieved values in a config section, this method gets called.
77+
// 'AzureKeyVaultConfigBuilder' uses this override to strip version tags from keys.
78+
}
79+
}
80+
```

docs/FAQ.md

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# MicrosoftConfigurationBuilders FAQ
2+
3+
TODO - search for 'expand'
4+
We get a lot of questions about `KeyValueConfigBuilders` - and Configuration Builders in general. Here are
5+
some of the most frequent along with answers that are hopefully helpful.
6+
7+
8+
<details>
9+
<summary><a name="expand"><b>Why did you get rid of 'Expand' mode?</b></a></summary>
10+
11+
Because 'Expand' mode operated in the 'ProcessRawXml' phase of configuration building, while the other
12+
modes all operate in 'ProcessConfigurationSection.' It was a bit of a balancing act trying to develop
13+
features that work across both phases - a challenge which is sometimes quite difficult given the lack of
14+
information we have about the section we are processing in 'ProcessRawXml.'
15+
16+
For example, V3 of these builders tries to accomodate 'ConfigurationManager.OpenConfiguration()'
17+
scenarios where apps want to read a config file that is not their own. In these cases, we need to
18+
know information about the file and section we are processing that we just can't know in the
19+
'ProcessRawXml' phase. Another example is the [parameters from appSettings](KeyValueConfigBuilders.md#appsettings-parameters)
20+
feature which was disabled in 'Expand' mode while processing the appSettings section, but can
21+
still be used somewhat functionally when executing any of the modes that operate in 'ProcessConfigurationSection.'
22+
23+
To make things simpler across the board, 'Expand' mode was replaced with 'Token' mode which should
24+
operate in a fairly similar manner with the added benefit of being less prone to producing invalid
25+
XML to muck things up. :smiley:
26+
</details>
27+
28+
<details>
29+
<summary><a name="newhandler"><b>Can you add a `SectionHandler` for section `XYZ?`</b></a></summary>
30+
31+
We have included default `SectionHandlers` for `<appSettings>` and `<connectionStrings>` because they
32+
are by far the most commonly used "key/value" config sections. But we introduced the `SectionHandler<T>`
33+
API to allow for more sections to be processed.
34+
35+
We don't currently feel that there are any other sections out there that have enough demand to
36+
warrant including a default section handler in the base package that everybody is required to use.
37+
That does not mean that section handlers for other sections is not ever a valid scenario, and you
38+
are of course welcome and encouraged to leverage the section handler feature if it suits your needs.
39+
That is why we introduced the feature afterall.
40+
</details>
41+
42+
<details>
43+
<summary><a name="applicationsettings"><b>Can you add a `SectionHandler` for the client `ApplicationSettings` section?</b></a></summary>
44+
45+
See [above](#newhandler). `ApplicationSettings` is less commonly used. But more problematically, it
46+
isn't really a standard .Net configuration section like it appears to be on first glance. The classes
47+
that support ApplicationSettings provide a strict and strongly typed window into what looks like a
48+
standard configuration section in your app.config file. While we can easily write a section handler
49+
for the `ClientSettingsSection` ([example](../samples/SampleSectionHandlers/ClientSettingsSectionHandler.cs))
50+
it won't integrate into the ApplicationSettings framework seamlessly like one might expect. The
51+
ApplicationSetting framework has already determined the number and names (including casing, which
52+
is problematic in 'Greedy' mode) of all the settings it will present before the base configuration
53+
system even gets a crack at reading from the config file. So you can't *add* new values with 'Greedy'
54+
mode, and you can't override existing values in 'Greedy' mode if you don't properly match
55+
casing - despite the fact that ApplicationSettings is supposed to be case-insensitive.
56+
57+
If you wish, you can use the [sample section handler](../samples/SampleSectionHandlers/ClientSettingsSectionHandler.cs)
58+
to process ApplicationSettings in your application, but know that the use case is rather limited.
59+
It will work in 'Strict' mode... and maybe require some prodding to force the ApplicationSettings
60+
framework to forget the settings it's seen before and decide to look back into the config file to
61+
get new values.
62+
63+
You can read more about the architecture of the AppliationSettings framework [here](https://docs.microsoft.com/en-us/dotnet/desktop/winforms/advanced/application-settings-architecture?view=netframeworkdesktop-4.8)
64+
to see how it builds layers on top of the standard config system that often obscure any changes or
65+
additional settings that appear in the `ClientSettingsSection` but won't be seen in
66+
`MyApp.Properties.Settings`. That set of articles is also a good starting point for learning
67+
about `SettingsProvider` and how that might be leveraged to accomplish configuration injection
68+
through a different mechanism in the case when applications must use ApplicationSettings.
69+
</details>
70+
71+
<details>
72+
<summary><a name="azureappservices"><b>Do ConfigBuilders break the 'Application Settings' feature of Azure AppServices?</b></a></summary>
73+
74+
Maybe a little? It does appear that adding a 'configBuilders' tag to your 'appSettings' or 'connectionStrings'
75+
sections confuses the injection logic for the Azure AppServices "Application Settings" feature. I do not
76+
have any insight as to why that is other than to say that the two features "grew up" contemporaneously, so
77+
they were probably not aware that configBuilders could exist.
78+
79+
But all is not lost. The "Application Settings" feature injects all it's values into the environment of
80+
the service. So while using ConfigBuilders might interfere with the automatic injection of those values,
81+
you can also use ConfigBuilders to pull those values back in. See [this issue comment](#133#issuecomment-1049520479)
82+
for more details.
83+
</details>
84+
85+
<details>
86+
<summary><a name="iisschema"><b>Why does IIS/inetmgr complain about configBuilders?</b></a></summary>
87+
88+
Because IIS config tools are old and cranky, just like the old .Net config system wanted them to be. :smiling_imp:
89+
90+
The old .Net config system is supposed to be quite rigid and super-strongly typed. So when IIS developed
91+
tools to work with config, they took steps to ensure they didn't break folks by creating invalid configuration.
92+
In particular, they decided to use XML schema's to ensure the XML they save is on the up-and-up. (Just
93+
like Visual Studio does. But Visual Studio gets updated quite a bit more frequently than IIS tools and
94+
has a lower bar for fixing nagging bugs that have a workaround - and was therefore better equipped to
95+
change with the times when .Net config added new features and sections. Also, failing schema validation
96+
in Visual Studio simply resulted in red squiggles instead of error dialogs. :frowning:)
97+
98+
The workaround is really quite simple, but it isn't something we can do in these packages. As suggested
99+
in #126, simply add a schema file for IIS to help it understand that configBuilders are ok on some
100+
sections.
101+
102+
`%systemroot%\system32\inetsrv\config\schema\configBuilders_schema.xml`
103+
```xml
104+
<configSchema>
105+
<sectionSchema name="appSettings">
106+
<attribute name="configBuilders" type="string"/>
107+
</sectionSchema>
108+
<sectionSchema name="connectionStrings">
109+
<attribute name="configBuilders" type="string"/>
110+
</sectionSchema>
111+
</configSchema>
112+
```
113+
114+
</details>
115+
116+
<details>
117+
<summary><a name="windowscontainers"><b>My config builder isn't working in my Windows container.</b></a></summary>
118+
119+
That's a statement, not a question. But here's a likely explanation.
120+
121+
Windows containers only modify the environment block of the EntryPoint process. So if your application
122+
is running as a service (like IIS/ASP.Net apps) or some other process not directly created by the
123+
EntryPoint, any environment variables set when starting the container will not be visible to your
124+
app.
125+
126+
To work around this issue, [ASP.Net](https://github.com/microsoft/dotnet-framework-docker/tree/main/src/aspnet)
127+
and [IIS](https://github.com/microsoft/iis-docker) container images rely on a `ServiceMonitor.exe`
128+
utility to be the entry point for the container, and this utility proactively modifies the environment
129+
of the worker process with any additional environment variables passed to docker run.
130+
131+
For IIS/ASP.Net workloads, do try to use an IIS/ASP.Net derived container that uses `ServiceMonitor.exe.`
132+
For other workloads, try making your app the EntryPoint, or try a similar approach to how IIS/ASP.Net
133+
handle this... possibly even leveraging [ServiceMonitor.exe](https://github.com/Microsoft/IIS.ServiceMonitor)
134+
itself.
135+
</details>
136+
137+
<details>
138+
<summary><a name="vstyperes"><b>Why do I get an error in Visual Studio when I switch my web app to use IIS instead of IISExpress?</b></a></summary>
139+
140+
Many reasons. The gist of the situation is this... When you switch your web application to run in IIS
141+
instead of IISExpress, Visual Studio tries to read your config file to parse connection strings. I
142+
believe it's looking for 'LocalDB', but that's not really important. Your web app's config file is
143+
obviously not part of the process configuration for devenv.exe, so VS opens it via
144+
`ConfigurationManager.OpenConfiguration()` or something similar. Prior to V3, this was likely to
145+
result in failures in many of these key/value config builders if they were applied to the
146+
`<connectionStrings>` section.
147+
148+
In V3, we handle the `ConfigurationManager.OpenConfiguration()` scenario better, but we can still
149+
get tripped up by the insanely complicated way Visual Studio manages reference binding. As a result,
150+
there may be version mis-matches when trying to load some builders. The Azure builders seem particularly
151+
vulnerable to this. I haven't found a good way to deal with this.
152+
153+
**However,** even though the error appears in a scary dialog box, it does not affect the behavior of
154+
your application. When running/debugging your app on local IIS, the config builders are still able to
155+
execute as expected.
156+
</details>

0 commit comments

Comments
 (0)