Skip to content

Commit f7d34bf

Browse files
authored
Support file-based apps in user-secrets (#63496)
* Support file-based apps in user-secrets * Fix resource texts * Avoid restore * Improve naming and tests * Test `clear`
1 parent 2fbe331 commit f7d34bf

File tree

9 files changed

+127
-61
lines changed

9 files changed

+127
-61
lines changed

src/Tools/Shared/SecretsHelpers/MsBuildProjectFinder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public string FindMsBuildProject(string project)
4646

4747
if (!File.Exists(projectPath))
4848
{
49-
throw new FileNotFoundException(SecretsHelpersResources.FormatError_ProjectPath_NotFound(projectPath));
49+
throw new FileNotFoundException(SecretsHelpersResources.FormatError_File_NotFound(projectPath));
5050
}
5151

5252
return projectPath;

src/Tools/Shared/SecretsHelpers/ProjectIdResolver.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ public string Resolve(string project, string configuration)
5959
UseShellExecute = false,
6060
ArgumentList =
6161
{
62-
"msbuild",
62+
"build",
6363
projectFile,
64-
"/nologo",
64+
"--no-restore",
6565
"/t:_ExtractUserSecretsMetadata", // defined in SecretManager.targets
6666
"/p:_UserSecretsMetadataFile=" + outputFile,
6767
"/p:Configuration=" + configuration,
@@ -72,7 +72,7 @@ public string Resolve(string project, string configuration)
7272
};
7373

7474
#if DEBUG
75-
_reporter.Verbose($"Invoking '{psi.FileName} {psi.Arguments}'");
75+
_reporter.Verbose($"Invoking '{psi.FileName} {string.Join(' ', psi.ArgumentList)}'");
7676
#endif
7777

7878
using var process = new Process()

src/Tools/Shared/SecretsHelpers/SecretsHelpersResources.resx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,16 +124,16 @@
124124
<value>Multiple MSBuild project files found in '{projectPath}'. Specify which to use with the --project option.</value>
125125
</data>
126126
<data name="Error_NoProjectsFound" xml:space="preserve">
127-
<value>Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option.</value>
127+
<value>Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option. Use --file option for file-based apps.</value>
128128
</data>
129129
<data name="Error_ProjectFailedToLoad" xml:space="preserve">
130130
<value>Could not load the MSBuild project '{project}'.</value>
131131
</data>
132132
<data name="Error_ProjectMissingId" xml:space="preserve">
133133
<value>Could not find the global property 'UserSecretsId' in MSBuild project '{project}'. Ensure this property is set in the project or use the '--id' command line option.</value>
134134
</data>
135-
<data name="Error_ProjectPath_NotFound" xml:space="preserve">
136-
<value>The project file '{0}' does not exist.</value>
135+
<data name="Error_File_NotFound" xml:space="preserve">
136+
<value>The file '{0}' does not exist.</value>
137137
</data>
138138
<data name="Message_ProjectAlreadyInitialized" xml:space="preserve">
139139
<value>The MSBuild project '{project}' has already been initialized with a UserSecretsId.</value>
@@ -144,4 +144,4 @@
144144
<data name="Message_SetUserSecretsIdForProject" xml:space="preserve">
145145
<value>Set UserSecretsId to '{userSecretsId}' for MSBuild project '{project}'.</value>
146146
</data>
147-
</root>
147+
</root>

src/Tools/dotnet-user-secrets/src/CommandLineOptions.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class CommandLineOptions
1616
public bool IsHelp { get; private set; }
1717
public bool IsVerbose { get; private set; }
1818
public string Project { get; private set; }
19+
public string File { get; private set; }
1920

2021
public static CommandLineOptions Parse(string[] args, IConsole console)
2122
{
@@ -36,6 +37,9 @@ public static CommandLineOptions Parse(string[] args, IConsole console)
3637
var optionProject = app.Option("-p|--project <PROJECT>", "Path to project. Defaults to searching the current directory.",
3738
CommandOptionType.SingleValue, inherited: true);
3839

40+
var optionFile = app.Option("-f|--file <FILE>", "Path to file-based app.",
41+
CommandOptionType.SingleValue, inherited: true);
42+
3943
var optionConfig = app.Option("-c|--configuration <CONFIGURATION>", "The project configuration to use. Defaults to 'Debug'.",
4044
CommandOptionType.SingleValue, inherited: true);
4145

@@ -50,7 +54,7 @@ public static CommandLineOptions Parse(string[] args, IConsole console)
5054
app.Command("remove", c => RemoveCommand.Configure(c, options));
5155
app.Command("list", c => ListCommand.Configure(c, options));
5256
app.Command("clear", c => ClearCommand.Configure(c, options));
53-
app.Command("init", c => InitCommandFactory.Configure(c, options));
57+
app.Command("init", c => InitCommandFactory.Configure(c, options, optionFile));
5458

5559
// Show help information if no subcommand/option was specified.
5660
app.OnExecute(() => app.ShowHelp());
@@ -66,6 +70,12 @@ public static CommandLineOptions Parse(string[] args, IConsole console)
6670
options.IsHelp = app.IsShowingInformation;
6771
options.IsVerbose = optionVerbose.HasValue();
6872
options.Project = optionProject.Value();
73+
options.File = optionFile.Value();
74+
75+
if (options.File != null && options.Project != null)
76+
{
77+
throw new CommandParsingException(app, Resources.Error_ProjectAndFileOptions);
78+
}
6979

7080
return options;
7181
}

src/Tools/dotnet-user-secrets/src/Internal/InitCommand.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@ public class InitCommandFactory : ICommand
1616
{
1717
public CommandLineOptions Options { get; }
1818

19-
internal static void Configure(CommandLineApplication command, CommandLineOptions options)
19+
internal static void Configure(CommandLineApplication command, CommandLineOptions options, CommandOption optionFile)
2020
{
2121
command.Description = "Set a user secrets ID to enable secret storage";
2222
command.HelpOption();
2323

2424
command.OnExecute(() =>
2525
{
26+
if (optionFile.HasValue())
27+
{
28+
throw new CommandParsingException(command, Resources.Error_InitNotSupportedForFileBasedApps);
29+
}
30+
2631
options.Command = new InitCommandFactory(options);
2732
});
2833
}

src/Tools/dotnet-user-secrets/src/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,6 @@ internal string ResolveId(CommandLineOptions options, IReporter reporter)
106106
}
107107

108108
var resolver = new ProjectIdResolver(reporter, _workingDirectory);
109-
return resolver.Resolve(options.Project, options.Configuration);
109+
return resolver.Resolve(options.Project ?? options.File, options.Configuration);
110110
}
111111
}

src/Tools/dotnet-user-secrets/src/Resources.resx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?xml version="1.0" encoding="utf-8"?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<root>
33
<!--
44
Microsoft ResX Schema
@@ -133,15 +133,9 @@ Use the '--help' flag to see info.</value>
133133
<data name="Error_No_Secrets_Found" xml:space="preserve">
134134
<value>No secrets configured for this application.</value>
135135
</data>
136-
<data name="Error_NoProjectsFound" xml:space="preserve">
137-
<value>Could not find a MSBuild project file in '{projectPath}'. Specify which project to use with the --project option.</value>
138-
</data>
139136
<data name="Error_ProjectMissingId" xml:space="preserve">
140137
<value>Could not find the global property 'UserSecretsId' in MSBuild project '{project}'. Ensure this property is set in the project or use the '--id' command line option.</value>
141138
</data>
142-
<data name="Error_ProjectPath_NotFound" xml:space="preserve">
143-
<value>The project file '{path}' does not exist.</value>
144-
</data>
145139
<data name="Error_ProjectFailedToLoad" xml:space="preserve">
146140
<value>Could not load the MSBuild project '{project}'.</value>
147141
</data>
@@ -169,4 +163,10 @@ Use the '--help' flag to see info.</value>
169163
<data name="Message_SetUserSecretsIdForProject" xml:space="preserve">
170164
<value>Set UserSecretsId to '{userSecretsId}' for MSBuild project '{project}'.</value>
171165
</data>
172-
</root>
166+
<data name="Error_ProjectAndFileOptions" xml:space="preserve">
167+
<value>Cannot use both --file and --project options together.</value>
168+
</data>
169+
<data name="Error_InitNotSupportedForFileBasedApps" xml:space="preserve">
170+
<value>Init command is currently not supported for file-based apps. Please add '#:property UserSecretsId=...' manually.</value>
171+
</data>
172+
</root>

src/Tools/dotnet-user-secrets/test/SecretManagerTests.cs

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.IO;
88
using System.Text;
99
using Microsoft.AspNetCore.InternalTesting;
10+
using Microsoft.AspNetCore.Tools;
1011
using Microsoft.Extensions.Configuration.UserSecrets;
1112
using Microsoft.Extensions.Configuration.UserSecrets.Tests;
1213
using Microsoft.Extensions.Tools.Internal;
@@ -64,7 +65,27 @@ public void Error_Project_DoesNotExist()
6465
var secretManager = CreateProgram();
6566

6667
secretManager.RunInternal("list", "--project", projectPath);
67-
Assert.Contains(Resources.FormatError_ProjectPath_NotFound(projectPath), _console.GetOutput());
68+
Assert.Contains(SecretsHelpersResources.FormatError_File_NotFound(projectPath), _console.GetOutput());
69+
}
70+
71+
[Fact]
72+
public void Error_ProjectAndFileOptions()
73+
{
74+
var projectPath = Path.Combine(_fixture.GetTempSecretProject(), "does_not_exist", "TestProject.csproj");
75+
var secretManager = CreateProgram();
76+
77+
secretManager.RunInternal("list", "--project", projectPath, "--file", projectPath);
78+
Assert.Contains(Resources.Error_ProjectAndFileOptions, _console.GetOutput());
79+
}
80+
81+
[Fact]
82+
public void Error_InitFile()
83+
{
84+
var dir = _fixture.CreateFileBasedApp(null);
85+
var secretManager = CreateProgram();
86+
87+
secretManager.RunInternal("init", "--file", Path.Combine(dir, "app.cs"));
88+
Assert.Contains(Resources.Error_InitNotSupportedForFileBasedApps, _console.GetOutput());
6889
}
6990

7091
[Fact]
@@ -81,9 +102,10 @@ public void SupportsRelativePaths()
81102
}
82103

83104
[Theory]
84-
[InlineData(true)]
85-
[InlineData(false)]
86-
public void SetSecrets(bool fromCurrentDirectory)
105+
[InlineData(false, true)]
106+
[InlineData(false, false)]
107+
[InlineData(true, false)]
108+
public void SetSecrets(bool fromCurrentDirectory, bool fileBasedApp)
87109
{
88110
var secrets = new KeyValuePair<string, string>[]
89111
{
@@ -95,18 +117,22 @@ public void SetSecrets(bool fromCurrentDirectory)
95117
new KeyValuePair<string, string>("--twoDashedKey", "--twoDashedValue")
96118
};
97119

98-
var projectPath = _fixture.GetTempSecretProject();
120+
var projectPath = fileBasedApp
121+
? _fixture.GetTempFileBasedApp(out _)
122+
: _fixture.GetTempSecretProject();
99123
var dir = fromCurrentDirectory
100124
? projectPath
101125
: Path.GetTempPath();
126+
ReadOnlySpan<string> pathArgs = fromCurrentDirectory
127+
? []
128+
: (fileBasedApp
129+
? ["-f", Path.Join(projectPath, "app.cs")]
130+
: ["-p", projectPath]);
102131
var secretManager = new Program(_console, dir);
103132

104133
foreach (var secret in secrets)
105134
{
106-
var parameters = fromCurrentDirectory ?
107-
new string[] { "set", secret.Key, secret.Value, "--verbose" } :
108-
new string[] { "set", secret.Key, secret.Value, "-p", projectPath, "--verbose" };
109-
secretManager.RunInternal(parameters);
135+
secretManager.RunInternal(["set", secret.Key, secret.Value, .. pathArgs, "--verbose"]);
110136
}
111137

112138
foreach (var keyValue in secrets)
@@ -117,10 +143,7 @@ public void SetSecrets(bool fromCurrentDirectory)
117143
}
118144

119145
_console.ClearOutput();
120-
var args = fromCurrentDirectory
121-
? new string[] { "list", "--verbose" }
122-
: new string[] { "list", "-p", projectPath, "--verbose" };
123-
secretManager.RunInternal(args);
146+
secretManager.RunInternal(["list", .. pathArgs, "--verbose"]);
124147
foreach (var keyValue in secrets)
125148
{
126149
Assert.Contains(
@@ -132,21 +155,24 @@ public void SetSecrets(bool fromCurrentDirectory)
132155
_console.ClearOutput();
133156
foreach (var secret in secrets)
134157
{
135-
var parameters = fromCurrentDirectory ?
136-
new string[] { "remove", secret.Key, "--verbose" } :
137-
new string[] { "remove", secret.Key, "-p", projectPath, "--verbose" };
138-
secretManager.RunInternal(parameters);
158+
secretManager.RunInternal(["remove", secret.Key, .. pathArgs, "--verbose"]);
139159
}
140160

141161
// Verify secrets are removed.
142162
_console.ClearOutput();
143-
args = fromCurrentDirectory
144-
? new string[] { "list", "--verbose" }
145-
: new string[] { "list", "-p", projectPath, "--verbose" };
146-
secretManager.RunInternal(args);
163+
secretManager.RunInternal(["list", .. pathArgs, "--verbose"]);
147164
Assert.Contains(Resources.Error_No_Secrets_Found, _console.GetOutput());
148165
}
149166

167+
[Fact]
168+
public void SetSecrets_FileBasedAppInCurrentDirectory()
169+
{
170+
var directoryPath = _fixture.GetTempFileBasedApp(out _);
171+
var secretManager = new Program(_console, directoryPath);
172+
secretManager.RunInternal("set", "key1", "value1", "--verbose");
173+
Assert.Contains(SecretsHelpersResources.FormatError_NoProjectsFound(directoryPath), _console.GetOutput());
174+
}
175+
150176
[Fact]
151177
public void SetSecret_Update_Existing_Secret()
152178
{
@@ -268,16 +294,25 @@ public void List_Empty_Secrets_File()
268294
}
269295

270296
[Theory]
271-
[InlineData(true)]
272-
[InlineData(false)]
273-
public void Clear_Secrets(bool fromCurrentDirectory)
297+
[InlineData(false, true)]
298+
[InlineData(false, false)]
299+
[InlineData(true, false)]
300+
public void Clear_Secrets(bool fromCurrentDirectory, bool fileBasedApp)
274301
{
275-
var projectPath = _fixture.GetTempSecretProject();
302+
var projectPath = fileBasedApp
303+
? _fixture.GetTempFileBasedApp(out _)
304+
: _fixture.GetTempSecretProject();
276305

277306
var dir = fromCurrentDirectory
278307
? projectPath
279308
: Path.GetTempPath();
280309

310+
ReadOnlySpan<string> pathArgs = fromCurrentDirectory
311+
? []
312+
: (fileBasedApp
313+
? ["-f", Path.Join(projectPath, "app.cs")]
314+
: ["-p", projectPath]);
315+
281316
var secretManager = new Program(_console, dir);
282317

283318
var secrets = new KeyValuePair<string, string>[]
@@ -290,10 +325,7 @@ public void Clear_Secrets(bool fromCurrentDirectory)
290325

291326
foreach (var secret in secrets)
292327
{
293-
var parameters = fromCurrentDirectory ?
294-
new string[] { "set", secret.Key, secret.Value, "--verbose" } :
295-
new string[] { "set", secret.Key, secret.Value, "-p", projectPath, "--verbose" };
296-
secretManager.RunInternal(parameters);
328+
secretManager.RunInternal(["set", secret.Key, secret.Value, .. pathArgs, "--verbose"]);
297329
}
298330

299331
foreach (var keyValue in secrets)
@@ -305,10 +337,7 @@ public void Clear_Secrets(bool fromCurrentDirectory)
305337

306338
// Verify secrets are persisted.
307339
_console.ClearOutput();
308-
var args = fromCurrentDirectory ?
309-
new string[] { "list", "--verbose" } :
310-
new string[] { "list", "-p", projectPath, "--verbose" };
311-
secretManager.RunInternal(args);
340+
secretManager.RunInternal(["list", .. pathArgs, "--verbose"]);
312341
foreach (var keyValue in secrets)
313342
{
314343
Assert.Contains(
@@ -318,11 +347,9 @@ public void Clear_Secrets(bool fromCurrentDirectory)
318347

319348
// Clear secrets.
320349
_console.ClearOutput();
321-
args = fromCurrentDirectory ? new string[] { "clear", "--verbose" } : new string[] { "clear", "-p", projectPath, "--verbose" };
322-
secretManager.RunInternal(args);
350+
secretManager.RunInternal(["clear", .. pathArgs, "--verbose"]);
323351

324-
args = fromCurrentDirectory ? new string[] { "list", "--verbose" } : new string[] { "list", "-p", projectPath, "--verbose" };
325-
secretManager.RunInternal(args);
352+
secretManager.RunInternal(["list", .. pathArgs, "--verbose"]);
326353
Assert.Contains(Resources.Error_No_Secrets_Found, _console.GetOutput());
327354
}
328355

0 commit comments

Comments
 (0)