Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor launch settings to use typed models and JSON deserialization
- Created base LaunchSettingsModel with ProfileKind discriminator
- Derived ProjectLaunchSettingsModel and ExecutableLaunchSettingsModel
- Added JSON binding models for deserialization
- Updated providers to use JsonSerializer.Deserialize instead of manual traversal
- Exposed SupportedProfileTypes from LaunchSettingsManager
- Updated LaunchSettingsProfile.cs to use array of supported types with comment
- Updated RunCommand to use pattern matching on ProfileKind
- Split Execute method into ExecuteWithProjectProfile and ExecuteWithExecutableProfile
- Updated all method signatures to use base LaunchSettingsModel type

Co-authored-by: baronfel <[email protected]>
  • Loading branch information
2 people authored and tmat committed Dec 11, 2025
commit 5aba36384ea3859fd5a7adb65bc576959ff69468
9 changes: 6 additions & 3 deletions src/BuiltInTools/Watch/Process/LaunchSettingsProfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,16 @@ internal sealed class LaunchSettingsProfile
return null;
}

// Look for the first profile with a supported command name (Project or Executable)
// Look for the first profile with a supported command name
// Note: These must match the command names supported by LaunchSettingsManager in src/Cli/dotnet/Commands/Run/LaunchSettings/
var supportedCommandNames = new[] { "Project", "Executable" };
var defaultProfileKey = launchSettings.Profiles.FirstOrDefault(entry =>
entry.Value.CommandName == "Project" || entry.Value.CommandName == "Executable").Key;
entry.Value.CommandName != null && supportedCommandNames.Contains(entry.Value.CommandName, StringComparer.Ordinal)).Key;

if (defaultProfileKey is null)
{
logger.LogDebug("Unable to find 'Project' or 'Executable' command in the default launch profile.");
logger.LogDebug("Unable to find a supported command name in the default launch profile. Supported types: {SupportedTypes}",
string.Join(", ", supportedCommandNames));
return null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json.Serialization;

namespace Microsoft.DotNet.Cli.Commands.Run.LaunchSettings;

internal class ExecutableLaunchProfileJson
{
[JsonPropertyName("commandName")]
public string? CommandName { get; set; }

[JsonPropertyName("executablePath")]
public string? ExecutablePath { get; set; }

[JsonPropertyName("commandLineArgs")]
public string? CommandLineArgs { get; set; }

[JsonPropertyName("workingDirectory")]
public string? WorkingDirectory { get; set; }

[JsonPropertyName("environmentVariables")]
public Dictionary<string, string>? EnvironmentVariables { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.DotNet.Cli.Commands.Run.LaunchSettings;

public class ExecutableLaunchSettingsModel : LaunchSettingsModel
{
public string? ExecutablePath { get; set; }

public string? WorkingDirectory { get; set; }

public override LaunchProfileKind ProfileKind => LaunchProfileKind.Executable;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,82 +13,35 @@ internal class ExecutableLaunchSettingsProvider : ILaunchSettingsProvider

public LaunchSettingsApplyResult TryGetLaunchSettings(string? launchProfileName, JsonElement model)
{
var config = new ProjectLaunchSettingsModel
try
{
LaunchProfileName = launchProfileName
};

foreach (var property in model.EnumerateObject())
{
if (string.Equals(property.Name, nameof(ProjectLaunchSettingsModel.ExecutablePath), StringComparison.OrdinalIgnoreCase))
var profile = JsonSerializer.Deserialize<ExecutableLaunchProfileJson>(model.GetRawText());
if (profile == null)
{
if (!TryGetStringValue(property.Value, out var executablePathValue))
{
return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.CouldNotConvertToString, property.Name));
}

config.ExecutablePath = executablePathValue;
return new LaunchSettingsApplyResult(false, CliCommandStrings.LaunchProfileIsNotAJsonObject);
}
else if (string.Equals(property.Name, nameof(ProjectLaunchSettingsModel.CommandLineArgs), StringComparison.OrdinalIgnoreCase))
{
if (!TryGetStringValue(property.Value, out var commandLineArgsValue))
{
return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.CouldNotConvertToString, property.Name));
}

config.CommandLineArgs = commandLineArgsValue;
}
else if (string.Equals(property.Name, nameof(ProjectLaunchSettingsModel.WorkingDirectory), StringComparison.OrdinalIgnoreCase))
var config = new ExecutableLaunchSettingsModel
{
if (!TryGetStringValue(property.Value, out var workingDirectoryValue))
{
return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.CouldNotConvertToString, property.Name));
}
LaunchProfileName = launchProfileName,
ExecutablePath = profile.ExecutablePath,
CommandLineArgs = profile.CommandLineArgs,
WorkingDirectory = profile.WorkingDirectory
};

config.WorkingDirectory = workingDirectoryValue;
}
else if (string.Equals(property.Name, nameof(ProjectLaunchSettingsModel.EnvironmentVariables), StringComparison.OrdinalIgnoreCase))
if (profile.EnvironmentVariables != null)
{
if (property.Value.ValueKind != JsonValueKind.Object)
{
return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.ValueMustBeAnObject, property.Name));
}

foreach (var environmentVariable in property.Value.EnumerateObject())
foreach (var (key, value) in profile.EnvironmentVariables)
{
if (TryGetStringValue(environmentVariable.Value, out var environmentVariableValue))
{
config.EnvironmentVariables[environmentVariable.Name] = environmentVariableValue!;
}
config.EnvironmentVariables[key] = value;
}
}
}

return new LaunchSettingsApplyResult(true, null, config);
}

private static bool TryGetStringValue(JsonElement element, out string? value)
{
switch (element.ValueKind)
return new LaunchSettingsApplyResult(true, null, config);
}
catch (JsonException ex)
{
case JsonValueKind.True:
value = bool.TrueString;
return true;
case JsonValueKind.False:
value = bool.FalseString;
return true;
case JsonValueKind.Null:
value = string.Empty;
return true;
case JsonValueKind.Number:
value = element.GetRawText();
return false;
case JsonValueKind.String:
value = element.GetString();
return true;
default:
value = null;
return false;
return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.DeserializationExceptionMessage, launchProfileName ?? "profile", ex.Message));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

namespace Microsoft.DotNet.Cli.Commands.Run.LaunchSettings;

public class LaunchSettingsApplyResult(bool success, string? failureReason, ProjectLaunchSettingsModel? launchSettings = null)
public class LaunchSettingsApplyResult(bool success, string? failureReason, LaunchSettingsModel? launchSettings = null)
{
public bool Success { get; } = success;

public string? FailureReason { get; } = failureReason;

public ProjectLaunchSettingsModel? LaunchSettings { get; } = launchSettings;
public LaunchSettingsModel? LaunchSettings { get; } = launchSettings;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ internal class LaunchSettingsManager
private const string DefaultProfileCommandName = "Project";
private static readonly IReadOnlyDictionary<string, ILaunchSettingsProvider> _providers;

public static IEnumerable<string> SupportedProfileTypes => _providers.Keys;

static LaunchSettingsManager()
{
_providers = new Dictionary<string, ILaunchSettingsProvider>
Expand Down
21 changes: 21 additions & 0 deletions src/Cli/dotnet/Commands/Run/LaunchSettings/LaunchSettingsModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.DotNet.Cli.Commands.Run.LaunchSettings;

public abstract class LaunchSettingsModel
{
public string? LaunchProfileName { get; set; }

public string? CommandLineArgs { get; set; }

public Dictionary<string, string> EnvironmentVariables { get; } = new Dictionary<string, string>(StringComparer.Ordinal);

public abstract LaunchProfileKind ProfileKind { get; }
}

public enum LaunchProfileKind
{
Project,
Executable
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json.Serialization;

namespace Microsoft.DotNet.Cli.Commands.Run.LaunchSettings;

internal class ProjectLaunchProfileJson
{
[JsonPropertyName("commandName")]
public string? CommandName { get; set; }

[JsonPropertyName("commandLineArgs")]
public string? CommandLineArgs { get; set; }

[JsonPropertyName("launchBrowser")]
public bool LaunchBrowser { get; set; }

[JsonPropertyName("launchUrl")]
public string? LaunchUrl { get; set; }

[JsonPropertyName("applicationUrl")]
public string? ApplicationUrl { get; set; }

[JsonPropertyName("dotnetRunMessages")]
public string? DotNetRunMessages { get; set; }

[JsonPropertyName("environmentVariables")]
public Dictionary<string, string>? EnvironmentVariables { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@

namespace Microsoft.DotNet.Cli.Commands.Run.LaunchSettings;

public class ProjectLaunchSettingsModel
public class ProjectLaunchSettingsModel : LaunchSettingsModel
{
public string? LaunchProfileName { get; set; }

public string? CommandLineArgs { get; set; }

public bool LaunchBrowser { get; set; }

public string? LaunchUrl { get; set; }
Expand All @@ -17,9 +13,5 @@ public class ProjectLaunchSettingsModel

public string? DotNetRunMessages { get; set; }

public string? ExecutablePath { get; set; }

public string? WorkingDirectory { get; set; }

public Dictionary<string, string> EnvironmentVariables { get; } = new Dictionary<string, string>(StringComparer.Ordinal);
public override LaunchProfileKind ProfileKind => LaunchProfileKind.Project;
}
Loading