Skip to content

Commit 0c1df60

Browse files
committed
Update UserSecrets to better handle UserSecretsId from VS.
1 parent 6f248ed commit 0c1df60

9 files changed

Lines changed: 85 additions & 31 deletions

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ test/**/[Bb]in/
66
test/**/[Oo]bj/
77
.vs/
88
msbuild.*
9-
packages/
9+
/packages/
1010
samples/ConfigBuildersSample/ConfigBuildersSample/Bin/

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ This is the simplest of the config builders. It draws its values from Environmen
6060
<add name="UserSecrets"
6161
[mode|prefix|stripPrefix]
6262
(userSecretsId="12345678-90AB-CDEF-1234-567890" | userSecretsFile="~\secrets.file")
63-
[ignoreMissingFile="true"]
63+
[optional="true"]
6464
type="Microsoft.Configuration.ConfigurationBuilders.UserSecretsConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.UserSecrets" />
6565
```
6666
To enable a feature similar to .Net Core's user secrets you can use this config builder. Microsoft is considering future plans to better integrate secret management
@@ -70,11 +70,12 @@ be xml formatted. (If you need to share a secrets.json file with Core projects,
7070
There are three additional configuration attributes for this config builder:
7171
* `userSecretsId` - This is the preferred method for identifying an xml secrets file. It works similar to .Net Core, which uses a 'UserSecretsId' project
7272
property to store this identifier. (The string does not have to be a Guid. Just unique. The VS "Manage User Secrets" experience produces a Guid.) With this
73-
attribute, the `UserSecretsConfigBuilder` will look in a well-known local location for a secrets file belonging to this identifier. One of this attribute or
74-
the 'userSecretsFile' attribute is required.
73+
attribute, the `UserSecretsConfigBuilder` will look in a well-known local location for a secrets file belonging to this identifier. In MSBuild environments,
74+
the value of this attribute will be replaced with the project property $(UserSecretsId) in the output directory iff the initial value is '${UserSecretsId}'.
75+
One of this attribute or the 'userSecretsFile' attribute is required.
7576
* `userSecretsFile` - An optional attribute specifying the file containing the secrets. The '~' character can be used at the start to reference the app root.
7677
One of this attribute or the 'userSecretsId' attribute is required. If both are specified, 'userSecretsFile' takes precedence.
77-
* `ignoreMissingFile` - A simple boolean to avoid throwing exceptions if the secrets file cannot be found. The default is `true`.
78+
* `optional` - A simple boolean to avoid throwing exceptions if the secrets file cannot be found. The default is `true`.
7879

7980
### AzureKeyVaultConfigBuilder
8081
```xml

src/UserSecrets/UserSecretsConfigBuilder.cs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,26 @@ public class UserSecretsConfigBuilder : KeyValueConfigBuilder
1212
{
1313
public const string userSecretsFileTag = "userSecretsFile";
1414
public const string userSecretsIdTag = "userSecretsId";
15-
public const string ignoreMissingFileTag = "ignoreMissingFile";
15+
public const string optionalTag = "optional";
1616

1717
private ConcurrentDictionary<string, string> _secrets;
1818

1919
public string UserSecretsId { get; protected set; }
2020
public string UserSecretsFile { get; protected set; }
21-
public bool IgnoreMissingFile { get; protected set; }
21+
public bool Optional { get; protected set; }
2222

2323
public override void Initialize(string name, NameValueCollection config)
2424
{
2525
base.Initialize(name, config);
2626

2727
bool ignoreMissing;
28-
IgnoreMissingFile = (Boolean.TryParse(config?[ignoreMissingFileTag], out ignoreMissing)) ? ignoreMissing : true;
28+
Optional = (Boolean.TryParse(config?[optionalTag], out ignoreMissing)) ? ignoreMissing : true;
2929

3030
// Explicit file reference takes precedence over an identifier.
3131
string secretsFile = config?[userSecretsFileTag];
3232
if (String.IsNullOrWhiteSpace(secretsFile))
3333
{
3434
string secretsId = config?[userSecretsIdTag];
35-
if (String.IsNullOrWhiteSpace(secretsId))
36-
{
37-
throw new ArgumentException($"UserSecretsConfigBuilder '{name}': Secrets file must be specified with either the '{userSecretsFileTag}' or the '{userSecretsIdTag}' attribute.");
38-
}
3935
secretsFile = GetSecretsFileFromId(secretsId);
4036
}
4137

@@ -44,10 +40,15 @@ public override void Initialize(string name, NameValueCollection config)
4440
{
4541
ReadUserSecrets(UserSecretsFile);
4642
}
47-
else if (!IgnoreMissingFile)
43+
else if (!Optional)
4844
{
4945
throw new ArgumentException($"UserSecretsConfigBuilder '{name}': Secrets file does not exist.");
5046
}
47+
else
48+
{
49+
// If the file was optional and not found, create an empty collection to effectively no-op GetValue.
50+
_secrets = new ConcurrentDictionary<string, string>();
51+
}
5152
}
5253

5354
public override ICollection<KeyValuePair<string, string>> GetAllValues(string prefix)
@@ -66,6 +67,21 @@ public override string GetValue(string key)
6667
// This method is based heavily on .Net Core's Config.UserSecrets project in an attempt to keep similar conventions.
6768
private string GetSecretsFileFromId(string secretsId)
6869
{
70+
// The common VS scenario will leave this Id attribute empty, or as a place-holding token. In that case,
71+
// go look up the user secrets id from the magic file.
72+
if (String.IsNullOrWhiteSpace(secretsId) || secretsId.Equals("${UserSecretsId}", StringComparison.InvariantCultureIgnoreCase))
73+
{
74+
// The magic file should be deployed in our codebase
75+
string codebase = System.Reflection.Assembly.GetExecutingAssembly().CodeBase;
76+
string localpath = new Uri(codebase).LocalPath;
77+
string magicId = File.ReadAllText(localpath + ".UserSecretsId.txt");
78+
79+
if (!String.IsNullOrWhiteSpace(magicId))
80+
{
81+
secretsId = magicId.Trim();
82+
}
83+
}
84+
6985
// Make sure the identifier is legal for file paths.
7086
int badCharIndex = secretsId.IndexOfAny(Path.GetInvalidFileNameChars());
7187
if (badCharIndex != -1)
@@ -74,7 +90,7 @@ private string GetSecretsFileFromId(string secretsId)
7490
}
7591

7692
string root = Environment.GetEnvironmentVariable("APPDATA") ?? Environment.GetEnvironmentVariable("HOME");
77-
93+
7894
if (!String.IsNullOrWhiteSpace(root))
7995
return Path.Combine(root, "Microsoft", "UserSecrets", secretsId, "secrets.xml");
8096

src/packages/ConfigurationBuilders.Environment.nupkg/Microsoft.Configuration.ConfigurationBuilders.Environment.nuspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<projectUrl>http://www.asp.net/</projectUrl>
1414
<licenseUrl>http://www.microsoft.com/web/webpi/eula/net_library_eula_ENU.htm</licenseUrl>
1515
<requireLicenseAcceptance>true</requireLicenseAcceptance>
16-
<tags>Microsoft Configuration Builders Environment</tags>
16+
<tags>Microsoft Configuration Builders - Environment</tags>
1717

1818
<dependencies>
1919
<dependency id="Microsoft.Configuration.ConfigurationBuilders.Base" version="[$NuGetPackageVersion$]" />

src/packages/ConfigurationBuilders.UserSecrets.nupkg/Microsoft.Configuration.ConfigurationBuilders.UserSecrets.nuproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@
3434
<NuGetContent Include="Content\Net471\config.uninstall.xdt">
3535
<Destination>content\Net471\web.config.uninstall.xdt</Destination>
3636
</NuGetContent>
37+
<NuGetContent Include="build\Net471\$(NuGetPackageId).props">
38+
<Destination>build\Net471\$(NuGetPackageId).props</Destination>
39+
</NuGetContent>
40+
<NuGetContent Include="build\Net471\$(NuGetPackageId).targets">
41+
<Destination>build\Net471\$(NuGetPackageId).targets</Destination>
42+
</NuGetContent>
3743
</ItemGroup>
3844
<Import Project="$(RepositoryRoot)Tools\NuGetProj.targets"/>
3945
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2+
<ItemGroup>
3+
<!-- This capability represents the UserSecretsID + secrets file approach to storing local user secrets. -->
4+
<ProjectCapability Include="LocalUserSecrets" />
5+
</ItemGroup>
6+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2+
3+
<PropertyGroup>
4+
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
5+
6+
<!-- This is not an attribute in the desktop framework. But we use the same name anyway for consistency with Core project properties. -->
7+
<GenerateUserSecretsAttribute Condition="'$(GenerateUserSecretsAttribute)'==''">true</GenerateUserSecretsAttribute>
8+
<GeneratedUserSecretsFile Condition="'$(GeneratedUserSecretsFile)'==''">$(OutDir)\Microsoft.Configuration.ConfigurationBuilders.UserSecrets.dll.UserSecretsId.txt</GeneratedUserSecretsFile>
9+
10+
<BuildDependsOn>
11+
$(BuildDependsOn);
12+
GenerateUserSecretsId
13+
</BuildDependsOn>
14+
15+
<CleanDependsOn>
16+
$(CleanDependsOn);
17+
CleanupUserSecretsId
18+
</CleanDependsOn>
19+
</PropertyGroup>
20+
21+
22+
<Target Name="GenerateUserSecretsId"
23+
Condition="'$(UserSecretsId)'!='' and '$(TargetFileName)'!='' and '$(GenerateUserSecretsAttribute)'=='true' and '$(DesignTimeBuild)'!='true'" >
24+
<Message Text="Generating UserSecretsId file. [$(GeneratedUserSecretsFile)]" />
25+
26+
<WriteLinesToFile
27+
File="$(GeneratedUserSecretsFile)"
28+
Lines="$(UserSecretsId)"
29+
Overwrite="true" />
30+
</Target>
31+
32+
<Target Name="CleanupUserSecretsId" Condition="'$(DesignTimeBuild)'!='true'">
33+
<Message Text="Removing UserSecretsId file from build. [$(GeneratedUserSecretsFile)]" />
34+
<Delete Files="$(GeneratedUserSecretsFile)" />
35+
</Target>
36+
</Project>

src/packages/ConfigurationBuilders.UserSecrets.nupkg/content/Net471/config.install.xdt

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,10 @@
33

44
<configBuilders xdt:Transform="InsertAfter(/configuration/configSections)" />
55

6-
<!-- First, try using $(UserSecretsId) property from the project file. -->
7-
<!-- And yes, this is atrocious xml styling, but it get comments inserted AND tied to our 'add' entry so it can be removed by us as well. -->
6+
<!-- Always configure for $(UserSecretsId), since the VS dev scenario is the target use case for this feature -->
87
<configBuilders xdt:Locator="XPath(/configuration/configBuilders[last()])">
98
<builders xdt:Transform="InsertIfMissing">
10-
<add name="Secrets" xdt:Locator="Match(name)" xdt:Transform="InsertIfMissing" userSecretsId="$UserSecretsId$" type="Microsoft.Configuration.ConfigurationBuilders.UserSecretsConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.UserSecrets, Version=$version$, Culture=neutral">
11-
<!-- The attribute 'userSecretsId' should match the MSBuild property '$(UserSecretsId)' for this project if using "Manage User Secrets" in Visual Studio. -->
12-
<!-- Alternatively, a direct pointer to a secrets file can be supplied with the attribute 'userSecretsFile' attribute. -->
13-
</add>
14-
</builders>
15-
</configBuilders>
16-
17-
<!-- If $(UserSecretsId) property doesn't exist, link directly to a file. -->
18-
<configBuilders xdt:Locator="XPath(/configuration/configBuilders[last()])">
19-
<builders>
20-
<add name="Secrets" xdt:Locator="Condition(@name = 'Secrets' and @userSecretsId = '')" xdt:Transform="Replace" userSecretsFile="~/App_Data/secrets.xml" type="Microsoft.Configuration.ConfigurationBuilders.UserSecretsConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.UserSecrets, Version=$version$, Culture=neutral">
21-
<!-- If using the "Manage User Secrets" feature in Visual Studio, use the 'userSecretsId' instead of the 'userSecretsFile' attribute. -->
22-
<!-- The 'userSecretsId' attribute should match the MSBuild property '$(UserSecretsId)'. -->
23-
</add>
9+
<add name="Secrets" xdt:Locator="Match(name)" xdt:Transform="InsertIfMissing" type="Microsoft.Configuration.ConfigurationBuilders.UserSecretsConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.UserSecrets, Version=$version$, Culture=neutral" />
2410
</builders>
2511
</configBuilders>
2612

src/packages/packages.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
<None Include="ConfigurationBuilders.Azure.nupkg\content\Net471\config.install.xdt" />
3535
<None Include="ConfigurationBuilders.Azure.nupkg\content\Net471\config.uninstall.xdt" />
3636
<None Include="ConfigurationBuilders.Azure.nupkg\Microsoft.Configuration.ConfigurationBuilders.Azure.nuspec" />
37+
<None Include="ConfigurationBuilders.UserSecrets.nupkg\build\Net471\Microsoft.Configuration.ConfigurationBuilders.UserSecrets.props" />
38+
<None Include="ConfigurationBuilders.UserSecrets.nupkg\build\Net471\Microsoft.Configuration.ConfigurationBuilders.UserSecrets.targets" />
3739
<None Include="ConfigurationBuilders.UserSecrets.nupkg\content\Net471\config.install.xdt">
3840
<SubType>Designer</SubType>
3941
</None>
@@ -51,6 +53,7 @@
5153
<None Include="ConfigurationBuilders.Json.nupkg\content\Net471\config.uninstall.xdt" />
5254
<None Include="ConfigurationBuilders.Json.nupkg\Microsoft.Configuration.ConfigurationBuilders.Json.nuspec" />
5355
</ItemGroup>
56+
<ItemGroup />
5457
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
5558
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),MicrosoftConfigurationBuilders.sln))\tools\cleanup.targets" />
5659
<Target Name="Build">

0 commit comments

Comments
 (0)