| File: Program.cs | Web Access |
| Project: src\src\Aspire.Managed\Aspire.Managed.csproj (aspire-managed) |
// 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.Globalization; using Aspire.Hosting; using Aspire.Shared; using Aspire.TerminalHost; BundleVersionLease? acquiredBundleLease; try { acquiredBundleLease = BundleVersionLease.TryAcquireFromEnvironment("aspire-managed", args.FirstOrDefault()); } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or DirectoryNotFoundException or ArgumentException or NotSupportedException) { Console.Error.WriteLine($"Failed to acquire Aspire bundle lease: {ex.Message}"); return 1; } using var bundleLease = acquiredBundleLease; return args switch { ["dashboard", .. var rest] => await RunDashboard(rest).ConfigureAwait(false), ["server", .. var rest] => await RunServer(rest).ConfigureAwait(false), ["terminalhost", .. var rest] => await RunTerminalHost(rest).ConfigureAwait(false), _ => ShowUsage() }; static async Task<int> RunDashboard(string[] args) { var startInfo = CreateStartInfo(AppContext.BaseDirectory, args); if (!File.Exists(startInfo.FileName)) { Console.Error.WriteLine($"Dashboard executable was not found at '{startInfo.FileName}'. Reinstall or rebuild the Aspire bundle."); return 1; } using var process = new Process { StartInfo = startInfo }; // Legacy callers can launch this compatibility forwarder with a CLI parent identity. // Watch that parent as well as having the native Dashboard watch this forwarding process. // Without a parent identity, this is a no-op (including Windows callers using kill-on-close jobs). using var shutdownCts = new CancellationTokenSource(); var parentWatchdog = ParentProcessWatchdog.Start(shutdownCts); try { process.Start(); await process.WaitForExitAsync(shutdownCts.Token).ConfigureAwait(false); return process.ExitCode; } catch (OperationCanceledException) when (shutdownCts.IsCancellationRequested) { // Cancelling WaitForExitAsync does not stop the child. Keep the watchdog's force-exit // backstop armed until cleanup completes in case terminating the process gets stuck. if (!process.HasExited) { process.Kill(entireProcessTree: true); } await process.WaitForExitAsync().ConfigureAwait(false); return 0; } finally { if (parentWatchdog is not null) { await parentWatchdog.DisposeAsync().ConfigureAwait(false); } } static ProcessStartInfo CreateStartInfo(string managedDirectory, string[] args) { // Older AppHosts launch "aspire-managed dashboard". Keep that contract without loading // the Dashboard into the managed helper or requiring the AppHost to understand native executables. var dashboardDirectory = Path.GetFullPath(Path.Combine(managedDirectory, "..", BundleDiscovery.DashboardDirectoryName)); var startInfo = new ProcessStartInfo { FileName = Path.Combine(dashboardDirectory, BundleDiscovery.GetExecutableFileName(BundleDiscovery.DashboardExecutableName)), WorkingDirectory = dashboardDirectory, UseShellExecute = false }; foreach (var argument in args) { startInfo.ArgumentList.Add(argument); } // DCP owns this forwarding process. The native Dashboard must stop if DCP terminates it. startInfo.Environment[KnownConfigNames.CliProcessId] = Environment.ProcessId.ToString(CultureInfo.InvariantCulture); startInfo.Environment[KnownConfigNames.CliProcessStartedStable] = ProcessStartTimeHelper.GetCurrentProcessStartTimeUnixMilliseconds().ToString(CultureInfo.InvariantCulture); startInfo.Environment[KnownConfigNames.CliProcessStarted] = ProcessStartTimeHelper.GetCurrentProcessRuntimeStartTimeUnixSeconds().ToString(CultureInfo.InvariantCulture); return startInfo; } } static async Task<int> RunServer(string[] args) { await Aspire.Hosting.RemoteHost.RemoteHostServer.RunAsync(args).ConfigureAwait(false); return 0; } static async Task<int> RunTerminalHost(string[] args) { return await TerminalHostProcessRunner.RunAsync(args).ConfigureAwait(false); } static int ShowUsage() { Console.Error.WriteLine($"Usage: {AppDomain.CurrentDomain.FriendlyName} <dashboard|server|terminalhost> [args...]"); return 1; }