| File: LaunchSettings\LaunchSettings.cs | Web Access |
| Project: src\sdk\src\Microsoft.DotNet.ProjectTools\Microsoft.DotNet.ProjectTools.csproj (Microsoft.DotNet.ProjectTools) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text.Json; namespace Microsoft.DotNet.ProjectTools; public static class LaunchSettings { private const string ProfilesKey = "profiles"; private const string CommandNameKey = "commandName"; private static readonly IReadOnlyDictionary<string, LaunchProfileParser> s_providers = new Dictionary<string, LaunchProfileParser> { { ProjectLaunchProfileParser.CommandName, ProjectLaunchProfileParser.Instance }, { ExecutableLaunchProfileParser.CommandName, ExecutableLaunchProfileParser.Instance } }; internal static IEnumerable<string> SupportedProfileTypes => s_providers.Keys; internal static string GetPropertiesLaunchSettingsPath(string directoryPath, string propertiesDirectoryName) => Path.Combine(directoryPath, propertiesDirectoryName, "launchSettings.json"); internal static string GetFlatLaunchSettingsPath(string directoryPath, string projectNameWithoutExtension) => Path.Join(directoryPath, $"{projectNameWithoutExtension}.run.json"); public static string? TryFindLaunchSettingsFile(string projectOrEntryPointFilePath, string? launchProfile, Action<string, bool> report) { var buildPathContainer = Path.GetDirectoryName(projectOrEntryPointFilePath); Debug.Assert(buildPathContainer != null); // VB.NET projects store the launch settings file in the // "My Project" directory instead of a "Properties" directory. // TODO: use the `AppDesignerFolder` MSBuild property instead, which captures this logic already var propsDirectory = string.Equals(Path.GetExtension(projectOrEntryPointFilePath), ".vbproj", StringComparison.OrdinalIgnoreCase) ? "My Project" : "Properties"; string launchSettingsPath = GetPropertiesLaunchSettingsPath(buildPathContainer, propsDirectory); bool hasLaunchSetttings = File.Exists(launchSettingsPath); string appName = Path.GetFileNameWithoutExtension(projectOrEntryPointFilePath); string runJsonPath = GetFlatLaunchSettingsPath(buildPathContainer, appName); bool hasRunJson = File.Exists(runJsonPath); if (hasLaunchSetttings) { if (hasRunJson) { report(string.Format(Resources.RunCommandWarningRunJsonNotUsed, runJsonPath, launchSettingsPath), false); } return launchSettingsPath; } if (hasRunJson) { return runJsonPath; } if (!string.IsNullOrEmpty(launchProfile)) { report(string.Format(Resources.RunCommandExceptionCouldNotLocateALaunchSettingsFile, launchProfile, $""" {launchSettingsPath} {runJsonPath} """), true); } return null; } internal static LaunchProfileParseResult ReadProfileSettingsFromFile(string launchSettingsPath, string? profileName = null) { try { var launchSettingsJsonContents = File.ReadAllText(launchSettingsPath); var jsonDocumentOptions = new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true, }; using (var document = JsonDocument.Parse(launchSettingsJsonContents, jsonDocumentOptions)) { var model = document.RootElement; if (model.ValueKind != JsonValueKind.Object || !model.TryGetProperty(ProfilesKey, out var profilesObject) || profilesObject.ValueKind != JsonValueKind.Object) { return LaunchProfileParseResult.Failure(Resources.LaunchProfilesCollectionIsNotAJsonObject); } var selectedProfileName = profileName; JsonElement profileObject; if (string.IsNullOrEmpty(profileName)) { var firstProfileProperty = profilesObject.EnumerateObject().FirstOrDefault(IsDefaultProfileType); selectedProfileName = firstProfileProperty.Value.ValueKind == JsonValueKind.Object ? firstProfileProperty.Name : null; profileObject = firstProfileProperty.Value; } else // Find a profile match for the given profileName { IEnumerable<JsonProperty> caseInsensitiveProfileMatches = [.. profilesObject .EnumerateObject() // p.Name shouldn't fail, as profileObject enumerables here are only created from an existing JsonObject .Where(p => string.Equals(p.Name, profileName, StringComparison.OrdinalIgnoreCase))]; if (caseInsensitiveProfileMatches.Count() > 1) { return LaunchProfileParseResult.Failure(string.Format(Resources.DuplicateCaseInsensitiveLaunchProfileNames, string.Join(",\n", caseInsensitiveProfileMatches.Select(p => $"\t{p.Name}")))); } if (!caseInsensitiveProfileMatches.Any()) { return LaunchProfileParseResult.Failure(string.Format(Resources.LaunchProfileDoesNotExist, profileName)); } profileObject = profilesObject.GetProperty(caseInsensitiveProfileMatches.First().Name); if (profileObject.ValueKind != JsonValueKind.Object) { return LaunchProfileParseResult.Failure(Resources.LaunchProfileIsNotAJsonObject); } } if (profileObject.ValueKind == default) { foreach (var prop in profilesObject.EnumerateObject()) { if (prop.Value.ValueKind == JsonValueKind.Object) { if (prop.Value.TryGetProperty(CommandNameKey, out var commandNameElement) && commandNameElement.ValueKind == JsonValueKind.String) { if (commandNameElement.GetString() is { } commandNameElementKey && s_providers.ContainsKey(commandNameElementKey)) { profileObject = prop.Value; break; } } } } } if (profileObject.ValueKind == default) { return LaunchProfileParseResult.Failure(Resources.UsableLaunchProfileCannotBeLocated); } if (!profileObject.TryGetProperty(CommandNameKey, out var finalCommandNameElement) || finalCommandNameElement.ValueKind != JsonValueKind.String) { return LaunchProfileParseResult.Failure(Resources.UsableLaunchProfileCannotBeLocated); } string? commandName = finalCommandNameElement.GetString(); if (!TryLocateHandler(commandName, out LaunchProfileParser? provider)) { return LaunchProfileParseResult.Failure(string.Format(Resources.LaunchProfileHandlerCannotBeLocated, commandName)); } return provider.ParseProfile(launchSettingsPath, selectedProfileName, profileObject.GetRawText()); } } catch (Exception ex) when (ex is JsonException or IOException) { return LaunchProfileParseResult.Failure(string.Format(Resources.DeserializationExceptionMessage, launchSettingsPath, ex.Message)); } } private static bool TryLocateHandler(string? commandName, [NotNullWhen(true)] out LaunchProfileParser? provider) { if (commandName == null) { provider = null; return false; } return s_providers.TryGetValue(commandName, out provider); } private static bool IsDefaultProfileType(JsonProperty profileProperty) { if (profileProperty.Value.ValueKind != JsonValueKind.Object || !profileProperty.Value.TryGetProperty(CommandNameKey, out var commandNameElement) || commandNameElement.ValueKind != JsonValueKind.String) { return false; } var commandName = commandNameElement.GetString(); return commandName != null && s_providers.ContainsKey(commandName); } }