diff --git a/docs/extensibility/interaction-service.md b/docs/extensibility/interaction-service.md new file mode 100644 index 0000000000..b2157cbdc0 --- /dev/null +++ b/docs/extensibility/interaction-service.md @@ -0,0 +1,359 @@ +--- +title: Interaction Service (Preview) +description: Use the interaction service API to prompt users for input, request confirmation, and display messages in the Aspire dashboard or CLI during publish and deploy. +ms.date: 07/21/2025 +--- + +# Interaction Service (Preview) + +The interaction service (`Aspire.Hosting.IInteractionService`) allows you to prompt users for input, request confirmation, and display messages. The interaction service works in two different contexts: + +- **Aspire dashboard**: When running `aspire run` or launching the app host directly, interactions appear as dialogs and notifications in the dashboard UI. +- **Aspire CLI**: When running `aspire publish` or `aspire deploy`, interactions are prompted through the command-line interface. + +This is useful for scenarios where you need to gather information from the user or provide feedback on the status of operations, regardless of how the application is being launched or deployed. + +## The `IInteractionService` API + +The `IInteractionService` interface is retrieved from the dependency injection container. `IInteractionService` can be injected into types created from DI or resolved from , which is usually available on a context argument passed to events. + +When you request `IInteractionService`, be sure to check if it's available for usage. If you attempt to use the interaction service when it's not available (`IInteractionService.IsAvailable` returns `false`), an exception is thrown. + +```csharp +var interactionService = serviceProvider.GetRequiredService(); +if (interactionService.IsAvailable) +{ + var result = await interactionService.PromptConfirmationAsync( + title: "Delete confirmation", + message: "Are you sure you want to delete the data?"); + + if (result.Data) + { + // Run your resource/command logic. + } +} +``` + +The interaction service has several methods that you use to interact with users or display messages. The behavior of these methods depends on the execution context: + +- **Dashboard context** (`aspire run` or direct app host launch): Interactions appear as modal dialogs, notifications, and form inputs in the [Aspire dashboard web interface](../fundamentals/dashboard/overview.md). +- **CLI context** (`aspire publish` or `aspire deploy`): Interactions are prompted through the command-line interface with text-based prompts and responses. + +The following sections describe how to use these APIs effectively in both contexts: + +| Method | Description | Contexts supported | +|--|--|--| +| `PromptMessageBoxAsync` | Displays a modal dialog box with a message and buttons for user interaction. | Dashboard only | +| `PromptNotificationAsync` | Displays a nonmodal notification in the dashboard as a message bar. | Dashboard only | +| `PromptConfirmationAsync` | Displays a confirmation dialog with options for the user to confirm or cancel an action. | Dashboard only | +| `PromptInputAsync` | Prompts the user for a single input value, such as text or secret. | Dashboard, CLI | +| `PromptInputsAsync` | Prompts the user for multiple input values in a single dialog (dashboard) or sequentially (CLI). | Dashboard, CLI | + +> [!IMPORTANT] +> During `aspire publish` and `aspire deploy` operations, only `PromptInputAsync` and `PromptInputsAsync` are available. Other interaction methods (`PromptMessageBoxAsync`, `PromptNotificationAsync`, and `PromptConfirmationAsync`) will throw an exception if called in CLI contexts. + +## Where to use the interaction service + +Any of the available callback-based extension methods of `IResourceBuilder` can use the interaction service to prompt users for input or confirmation. Use the interaction service in these scenarios: + +- **Custom resource types**: Gather input from users or confirm actions when you create custom resource types. + + Resource types are free to define dashboard interactions, such as prompting for user input or displaying messages. The interaction service allows you to create a more interactive experience for users when they manage resources in the Aspire dashboard or CLI. For more information, see [Create custom .NET Aspire hosting integrations](custom-hosting-integration.md). + +- **Custom resource commands**: Add commands to resources in the Aspire dashboard or CLI. Use the interaction service to prompt users for input or confirmation when these commands run. + + When you chain a call to on a target `IResourceBuilder`, for example, your callback can use the interaction service to gather input or confirm actions. For more information, see [Custom resource commands in .NET Aspire](../fundamentals/custom-resource-commands.md). + +- **Publish and deploy workflows**: During `aspire publish` or `aspire deploy` operations, use the interaction service to gather deployment-specific configuration and confirm destructive operations through the CLI. + +These approaches help you create interactive, user-friendly experiences for local development, dashboard interactions, and deployment workflows. + +> [!IMPORTANT] +> This article demonstrates the interaction service in the context of a `WithCommand` callback with a `FakeResource` type, but the same principles apply to other extension methods that support user interactions. +> +> For example: +> +> ```csharp +> var builder = DistributedApplication.CreateBuilder(args); +> +> builder.AddFakeResource("fake-resource") +> .WithCommand("msg-dialog", "Example Message Dialog", async context => +> { +> var interactionService = context.GetRequiredService(); +> +> // Use interaction service... +> +> return CommandResults.Success(); +> }); +> ``` +> +> For CLI specific contexts, the interaction service is retrieved from either the `PublishingContext` or `DeploymentContext` depending on the operation being performed. + +## Display messages + +There are several ways to display messages to the user: + +- **Dialog messages**: Show important information in a dialog box. +- **Notification messages**: Display less critical information in a notification. + +> [!NOTE] +> Message display methods (`PromptMessageBoxAsync` and `PromptNotificationAsync`) are only available in dashboard contexts. These methods throw an exception if called during `aspire publish` or `aspire deploy` operations. + +### Display a dialog message box + +Dialog messages provide important information that requires user attention. + + + +The `IInteractionService.PromptMessageBoxAsync` method displays a message with customizable response options. + +:::code source="snippets/InteractionService/AppHost.MessageBoxExample.cs" id="example"::: + +**Dashboard view:** + +:::image type="content" source="media/interaction-service-message-dialog.png" lightbox="media/interaction-service-message-dialog.png" alt-text="Aspire dashboard interface showing a message dialog with a title, message, and buttons."::: + +**CLI view:** + +The `PromptMessageBoxAsync` method only works in the dashboard context. If you call it during `aspire publish` or `aspire deploy`, the method throws an exception and doesn't display a message in the CLI. + +### Display a notification message + +Notification messages provide nonmodal notifications. + +> [!TIP] +> In the dashboard, notification messages appear stacked at the top, so you can show several messages at once. You can display notifications one after another by awaiting each dismissal before showing the next. Or, you can display multiple notifications at the same time without waiting for each to be dismissed. + + + +The `IInteractionService.PromptNotificationAsync` method displays informational messages with optional action links in the dashboard context. You don't have to await the result of a notification message if you don't need to. This is especially useful for notifications, since you might want to display a notification and continue without waiting for user to dismiss it. + +:::code source="snippets/InteractionService/AppHost.NotificationExample.cs" id="example"::: + +The previous example demonstrates several ways to use the notification API. Each approach displays different types of notifications, all of which are invoked in parallel and displayed in the dashboard around the same time. + +**Dashboard view:** + +:::image type="content" source="media/interaction-service-message-bar.png" lightbox="media/interaction-service-message-bar.png" alt-text="Aspire dashboard interface showing multiple notification messages stacked near the top of the page. There are five notifications displayed, each with a different type of notification."::: + +**CLI view:** + +The `PromptNotificationAsync` method isn't available in CLI contexts. If you call it during `aspire publish` or `aspire deploy`, it throws an exception. + +## Prompt for user confirmation + + + +Use the interaction service when you need the user to confirm an action before proceeding. The `IInteractionService.PromptConfirmationAsync` method displays a confirmation prompt in the dashboard context. Confirmation prompts are essential for destructive operations or actions that have significant consequences. They help prevent accidental actions and give users a chance to reconsider their decisions. + +### Prompt for confirmation before destructive operations + +For operations that can't be undone, such as deleting resources, always prompt for confirmation: + +:::code source="snippets/InteractionService/AppHost.ConfirmationExample.cs" id="example"::: + +**Dashboard view:** + +:::image type="content" source="media/interaction-service-confirmation.png" lightbox="media/interaction-service-confirmation.png" alt-text="Aspire dashboard interface showing a confirmation dialog with a title, message, and buttons for confirming or canceling the operation."::: + +**CLI view:** + +The `PromptConfirmationAsync` method isn't available in CLI contexts. If you call it during `aspire publish` or `aspire deploy`, the method throws an exception. + +## Prompt for user input + +The interaction service API allows you to prompt users for input in various ways. You can collect single values or multiple values, with support for different input types including text, secrets, choices, booleans, and numbers. The presentation adapts automatically to the execution context. + +| Type | Dashboard | CLI prompt | +|--------------|---------------------------|-----------------------| +| `Text` | Textbox | Text prompt | +| `SecretText` | Textbox with masked input | Masked text prompt | +| `Choice` | Dropdown box of options | Choice prompt | +| `Boolean` | Checkbox | Boolean choice prompt | +| `Number` | Number textbox | Text prompt | + +### Prompt the user for input values + + + +You can prompt for a single value using the `IInteractionService.PromptInputAsync` method, or collect multiple pieces of information with `IInteractionService.PromptInputsAsync`. In the dashboard, multiple inputs appear together in a single dialog. In the CLI, each input is requested one after another. + +> [!IMPORTANT] +> It's possible to create wizard-like flows using the interaction service. By chaining multiple prompts together—handling the results from one prompt before moving to the next—you can guide users through a series of related questions, making it easier to collect all the necessary information. + +Consider the following example, which prompts the user for multiple input values: + +:::code source="snippets/InteractionService/AppHost.MultipleInputExample.cs" id="example"::: + +**Dashboard view:** + +This renders on the dashboard as shown in the following image: + +:::image type="content" source="media/interaction-service-multiple-input.png" lightbox="media/interaction-service-multiple-input.png" alt-text="Aspire dashboard interface showing a multiple input dialog with labels, input fields, and buttons for confirming or canceling the input."::: + +Imagine you fill out the dialog with the following values: + +:::image type="content" source="media/interaction-service-multiple-input-filled.png" lightbox="media/interaction-service-multiple-input-filled.png" alt-text="Aspire dashboard interface showing a multiple input dialog with filled input fields and buttons for confirming or canceling the input."::: + +After you select the **Ok** button, the resource logs display the following output: + +:::image type="content" source="media/interaction-service-multiple-input-logs.png" lightbox="media/interaction-service-multiple-input-logs.png" alt-text="Aspire dashboard interface showing logs with the input values entered in the multiple input dialog."::: + +**CLI view:** + +In the CLI context, the same inputs are requested sequentially: + +```Aspire +aspire deploy + +Step 1: Analyzing model. + + ✓ DONE: Analyzing the distributed application model for publishing and deployment capabilities. 00:00:00 + Found 1 resources that support deployment. (FakeResource) + +✅ COMPLETED: Analyzing model. completed successfully + +═══════════════════════════════════════════════════════════════════════════════════════════════════════════════ + +Configure your application deployment settings: +Application Name: example-app +Environment: Staging +Instance Count: 3 +Enable Monitoring: [y/n] (n): y +✓ DEPLOY COMPLETED: Deploying completed successfully +``` + +Depending on the input type, the CLI might display additional prompts. For example, the `Enable Monitoring` input is a boolean choice, so the CLI prompts for a yes/no response. If you enter `y`, it enables monitoring; if you enter `n`, it disables monitoring. For the environment input, the CLI displays a list of available environments for selection: + +```Aspire +Configure your application deployment settings: +Application Name: example-app +Environment: + +> Development + Staging + Testing + +(Type to search) +``` + +#### Input validation + + + +Basic input validation is available by configuring `InteractionInput`. It provides options for requiring a value, or the maximum text length of `Text` or `SecretText` fields. + +For complex scenarios, you can provide custom validation logic using the `InputsDialogInteractionOptions.ValidationCallback` property: + +```csharp +// Multiple inputs with custom validation +var databaseInputs = new List +{ + new() + { + Label = "Database Name", + InputType = InputType.Text, + Required = true, + Placeholder = "myapp-db" + }, + new() + { + Label = "Username", + InputType = InputType.Text, + Required = true, + Placeholder = "admin" + }, + new() + { + Label = "Password", + InputType = InputType.SecretText, + Required = true, + Placeholder = "Enter a strong password" + }, + new() + { + Label = "Confirm password", + InputType = InputType.SecretText, + Required = true, + Placeholder = "Confirm your password" + } +}; + +var validationOptions = new InputsDialogInteractionOptions +{ + ValidationCallback = async context => + { + var passwordInput = context.Inputs.FirstOrDefault(i => i.Label == "Password"); + var confirmPasswordInput = context.Inputs.FirstOrDefault(i => i.Label == "Confirm password"); + + // Validate password strength + if (passwordInput?.Value is { Length: < 8 }) + { + context.AddValidationError(passwordInput, "Password must be at least 8 characters long"); + } + + // Validate password confirmation + if (passwordInput?.Value != confirmPasswordInput?.Value) + { + context.AddValidationError(confirmPasswordInput!, "Passwords do not match"); + } + + await Task.CompletedTask; + } +}; + +var dbResult = await interactionService.PromptInputsAsync( + title: "Database configuration", + message: "Configure your PostgreSQL database connection:", + inputs: databaseInputs, + options: validationOptions); + +if (!dbResult.Canceled && dbResult.Data != null) +{ + // Use the validated configuration +} +``` + +Prompting the user for a password and confirming they match is referred to as "dual independent verification" input. This approach is common in scenarios where you want to ensure the user enters the same password twice to avoid typos or mismatches. + +### Best practices for user input + +When prompting for user input, consider these best practices: + +1. **Group related inputs**: Use multiple input prompts to collect related configuration values. In the dashboard, these appear in a single dialog; in the CLI, they're requested sequentially but grouped conceptually. +1. **Provide clear labels and placeholders**: Help users understand what information is expected, regardless of context. +1. **Use appropriate input types**: Choose the right input type for the data you're collecting (secret for passwords, choice for predefined options, etc.). Both contexts support these input types appropriately. +1. **Implement validation**: Validate user input and provide clear error messages when validation fails. Both contexts support validation feedback. +1. **Make required fields clear**: Mark required fields and provide appropriate defaults for optional ones. +1. **Handle cancellation**: Always check if the user canceled the input prompt and handle it gracefully. Users can cancel in both dashboard and CLI contexts. + +## Interaction contexts + +The interaction service behaves differently depending on how your Aspire solution is launched: + +### Dashboard context + +When you run your application using `aspire run` or by directly launching the app host project, interactions appear in the Aspire dashboard web interface: + +- **Modal dialogs**: Message boxes and input prompts appear as overlay dialogs that require user interaction. +- **Notification messages**: Informational messages appear as dismissible banners at the top of the dashboard. +- **Rich UI**: Full support for interactive form elements, validation, and visual feedback. +- **All methods available**: All interaction service methods are supported in dashboard contexts. + +### CLI context + +When you run `aspire publish` or `aspire deploy`, interactions are prompted through the command-line interface: + +- **Text prompts**: Only input prompts (`PromptInputAsync` and `PromptInputsAsync`) are available and appear as text-based prompts in the terminal. +- **Sequential input**: Multiple inputs are requested one at a time rather than in a single dialog. +- **Limited functionality**: Message boxes, notifications, and confirmation dialogs aren't available and throws exceptions if called. + +> [!IMPORTANT] +> The interaction service adapts automatically to dashboard and CLI contexts. In CLI mode, only input-related methods—`PromptInputAsync` and `PromptInputsAsync`—are supported. Calling `PromptMessageBoxAsync`, `PromptNotificationAsync`, or `PromptConfirmationAsync` in CLI operations like `aspire publish` or `aspire deploy` results in an exception. + +## See also + + + +- [Aspire.Hosting.IInteractionService](https://github.com/dotnet/aspire/blob/main/src/Aspire.Hosting/IInteractionService.cs) +- [.NET Aspire extensibility overview](../extensibility/custom-hosting-integration.md) diff --git a/docs/extensibility/media/interaction-service-confirmation.png b/docs/extensibility/media/interaction-service-confirmation.png new file mode 100644 index 0000000000..dc1cd87bbc Binary files /dev/null and b/docs/extensibility/media/interaction-service-confirmation.png differ diff --git a/docs/extensibility/media/interaction-service-message-bar.png b/docs/extensibility/media/interaction-service-message-bar.png new file mode 100644 index 0000000000..e67ddd8acd Binary files /dev/null and b/docs/extensibility/media/interaction-service-message-bar.png differ diff --git a/docs/extensibility/media/interaction-service-message-dialog.png b/docs/extensibility/media/interaction-service-message-dialog.png new file mode 100644 index 0000000000..8a12ff0035 Binary files /dev/null and b/docs/extensibility/media/interaction-service-message-dialog.png differ diff --git a/docs/extensibility/media/interaction-service-multiple-input-filled.png b/docs/extensibility/media/interaction-service-multiple-input-filled.png new file mode 100644 index 0000000000..59c1e83e2f Binary files /dev/null and b/docs/extensibility/media/interaction-service-multiple-input-filled.png differ diff --git a/docs/extensibility/media/interaction-service-multiple-input-logs.png b/docs/extensibility/media/interaction-service-multiple-input-logs.png new file mode 100644 index 0000000000..8594fcfbe0 Binary files /dev/null and b/docs/extensibility/media/interaction-service-multiple-input-logs.png differ diff --git a/docs/extensibility/media/interaction-service-multiple-input.png b/docs/extensibility/media/interaction-service-multiple-input.png new file mode 100644 index 0000000000..3cf1ed340a Binary files /dev/null and b/docs/extensibility/media/interaction-service-multiple-input.png differ diff --git a/docs/extensibility/snippets/InteractionService/AppHost.ConfirmationExample.cs b/docs/extensibility/snippets/InteractionService/AppHost.ConfirmationExample.cs new file mode 100644 index 0000000000..2dd97211b3 --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/AppHost.ConfirmationExample.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.DependencyInjection; + +partial class Program +{ + public static async Task ShowConfirmationExample(ExecuteCommandContext context) + { + // + var interactionService = context.ServiceProvider.GetRequiredService(); + + // Prompt for confirmation before resetting database + var resetConfirmation = await interactionService.PromptConfirmationAsync( + title: "Confirm Reset", + message: "Are you sure you want to reset the `development-database`? This action **cannot** be undone.", + options: new MessageBoxInteractionOptions + { + Intent = MessageIntent.Confirmation, + PrimaryButtonText = "Reset", + SecondaryButtonText = "Cancel", + ShowSecondaryButton = true, + EnableMessageMarkdown = true + }); + + if (resetConfirmation.Data is true) + { + // Perform the reset operation... + + return CommandResults.Success(); + } + else + { + return CommandResults.Failure("Database reset canceled by user."); + } + // + } +} diff --git a/docs/extensibility/snippets/InteractionService/AppHost.MessageBoxExample.cs b/docs/extensibility/snippets/InteractionService/AppHost.MessageBoxExample.cs new file mode 100644 index 0000000000..3d0eaff02e --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/AppHost.MessageBoxExample.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.DependencyInjection; + +partial class Program +{ + public static async Task ShowMessageBoxExample(ExecuteCommandContext context) + { + // + var interactionService = context.ServiceProvider.GetRequiredService(); + + var result = await interactionService.PromptMessageBoxAsync( + "Simple Message Box: Example", + """ + ##### 🤓 Nice! + + It's worth noting that **Markdown** is _supported_ + (and demonstrated here) in the message body. Simply + configure the options as: + + ```csharp + var options = new MessageBoxInteractionOptions + { + EnableMessageMarkdown = true, + // Other options... + }; + ``` + + Cool, [📖 learn more](https://learn.microsoft.com/dotnet/aspire/extensibility/interaction-service)... + """, + new MessageBoxInteractionOptions + { + EnableMessageMarkdown = true, + PrimaryButtonText = "Awesome" + } + ); + + if (result.Canceled) + { + return CommandResults.Failure("User cancalled."); + } + + return result.Data + ? CommandResults.Success() + : CommandResults.Failure("The user doesn't like the example"); + // + } +} diff --git a/docs/extensibility/snippets/InteractionService/AppHost.MultipleInputExample.cs b/docs/extensibility/snippets/InteractionService/AppHost.MultipleInputExample.cs new file mode 100644 index 0000000000..ca68b4fa7f --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/AppHost.MultipleInputExample.cs @@ -0,0 +1,80 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +partial class Program +{ + public static async Task ShowMultipleInputExample( + ExecuteCommandContext context, FakeResource fakeResource) + { + // + var interactionService = context.ServiceProvider.GetRequiredService(); + var loggerService = context.ServiceProvider.GetRequiredService(); + var logger = loggerService.GetLogger(fakeResource); + + var inputs = new List + { + new() + { + Label = "Application Name", + InputType = InputType.Text, + Required = true, + Placeholder = "my-app" + }, + new() + { + Label = "Environment", + InputType = InputType.Choice, + Required = true, + Options = + [ + new("dev", "Development"), + new("staging", "Staging"), + new("test", "Testing") + ] + }, + new() + { + Label = "Instance Count", + InputType = InputType.Number, + Required = true, + Placeholder = "1" + }, + new() + { + Label = "Enable Monitoring", + InputType = InputType.Boolean, + Required = false + } + }; + + var appConfigurationInput = await interactionService.PromptInputsAsync( + title: "Application Configuration", + message: "Configure your application deployment settings:", + inputs: inputs); + + if (!appConfigurationInput.Canceled) + { + // Process the collected input values + var appName = appConfigurationInput.Data[0].Value; + var environment = appConfigurationInput.Data[1].Value; + var instanceCount = int.Parse(appConfigurationInput.Data[2].Value ?? "1"); + var enableMonitoring = bool.Parse(appConfigurationInput.Data[3].Value ?? "false"); + + logger.LogInformation(""" + Application Name: {AppName} + Environment: {Environment} + Instance Count: {InstanceCount} + Monitoring Enabled: {EnableMonitoring} + """, + appName, environment, instanceCount, enableMonitoring); + + // Use the collected values as needed + return CommandResults.Success(); + } + else + { + return CommandResults.Failure("User canceled application configuration input."); + } + // + } +} diff --git a/docs/extensibility/snippets/InteractionService/AppHost.NotificationExample.cs b/docs/extensibility/snippets/InteractionService/AppHost.NotificationExample.cs new file mode 100644 index 0000000000..77003071b8 --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/AppHost.NotificationExample.cs @@ -0,0 +1,63 @@ +using Microsoft.Extensions.DependencyInjection; + +partial class Program +{ + public static async Task ShowNotificationExample(ExecuteCommandContext context) + { + // + var interactionService = context.ServiceProvider.GetRequiredService(); + + // Demonstrating various notification types with different intents + var tasks = new List + { + interactionService.PromptNotificationAsync( + title: "Confirmation", + message: "Are you sure you want to proceed?", + options: new NotificationInteractionOptions + { + Intent = MessageIntent.Confirmation + }), + interactionService.PromptNotificationAsync( + title: "Success", + message: "Your operation completed successfully.", + options: new NotificationInteractionOptions + { + Intent = MessageIntent.Success, + LinkText = "View Details", + LinkUrl = "https://learn.microsoft.com/dotnet/aspire/success" + }), + interactionService.PromptNotificationAsync( + title: "Warning", + message: "Your SSL certificate will expire soon.", + options: new NotificationInteractionOptions + { + Intent = MessageIntent.Warning, + LinkText = "Renew Certificate", + LinkUrl = "https://portal.azure.com/certificates" + }), + interactionService.PromptNotificationAsync( + title: "Information", + message: "There is an update available for your application.", + options: new NotificationInteractionOptions + { + Intent = MessageIntent.Information, + LinkText = "Update Now", + LinkUrl = "https://learn.microsoft.com/dotnet/aspire" + }), + interactionService.PromptNotificationAsync( + title: "Error", + message: "An error occurred while processing your request.", + options: new NotificationInteractionOptions + { + Intent = MessageIntent.Error, + LinkText = "Troubleshoot", + LinkUrl = "https://learn.microsoft.com/dotnet/aspire/troubleshoot" + }) + }; + + await Task.WhenAll(tasks); + + return CommandResults.Success(); + // + } +} diff --git a/docs/extensibility/snippets/InteractionService/AppHost.cs b/docs/extensibility/snippets/InteractionService/AppHost.cs new file mode 100644 index 0000000000..e29158f589 --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/AppHost.cs @@ -0,0 +1,64 @@ +using Microsoft.Extensions.DependencyInjection; + +var builder = DistributedApplication.CreateBuilder(args); + +var fakeResource = builder.AddFakeResource("fake-resource-01") + .WithCommand("msg-dialog", "Example Message Dialog", ShowMessageBoxExample) + .WithCommand("msg-bar", "Example Message Bar", ShowNotificationExample) + .WithCommand("confirm", "Confirmation Example", ShowConfirmationExample); + +fakeResource.WithCommand( + "multi-input", + "Multi Input Example", + context => ShowMultipleInputExample(context, fakeResource.Resource)); + + +builder.AddFakeResource("fake-resource-02") + .WithDeployment(async context => + { + var interactionService = context.Services.GetRequiredService(); + var logger = context.Logger; + + var inputs = new List + { + new() + { + Label = "Application Name", + InputType = InputType.Text, + Required = true, + Placeholder = "my-app" + }, + new() + { + Label = "Environment", + InputType = InputType.Choice, + Required = true, + Options = + [ + new("dev", "Development"), + new("staging", "Staging"), + new("test", "Testing") + ] + }, + new() + { + Label = "Instance Count", + InputType = InputType.Number, + Required = true, + Placeholder = "1" + }, + new() + { + Label = "Enable Monitoring", + InputType = InputType.Boolean, + Required = false + } + }; + + var appConfigurationInput = await interactionService.PromptInputsAsync( + title: "Application Configuration", + message: "Configure your application deployment settings:", + inputs: inputs); + }); + +builder.Build().Run(); diff --git a/docs/extensibility/snippets/InteractionService/FakeResource.cs b/docs/extensibility/snippets/InteractionService/FakeResource.cs new file mode 100644 index 0000000000..569bec5e05 --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/FakeResource.cs @@ -0,0 +1,6 @@ +public sealed class FakeResource(string name) : IResource +{ + string IResource.Name => name; + + ResourceAnnotationCollection IResource.Annotations { get; } = []; +} diff --git a/docs/extensibility/snippets/InteractionService/FakeResourceExtensions.cs b/docs/extensibility/snippets/InteractionService/FakeResourceExtensions.cs new file mode 100644 index 0000000000..c5e021f457 --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/FakeResourceExtensions.cs @@ -0,0 +1,32 @@ +namespace Aspire.Hosting; + +public static class FakeResourceExtensions +{ + public static IResourceBuilder AddFakeResource( + this IDistributedApplicationBuilder builder, + [ResourceName] string name) + { + var fakeResource = new FakeResource(name); + + return builder.AddResource(fakeResource) + .WithInitialState(new() + { + ResourceType = "Fake Resource", + State = KnownResourceStates.Running, + Properties = + [ + new(CustomResourceKnownProperties.Source, "Fake") + ] + }) + .ExcludeFromManifest(); + } + + public static IResourceBuilder WithDeployment( + this IResourceBuilder builder, + Func callback) + { + builder.WithAnnotation(new DeployingCallbackAnnotation(callback)); + + return builder; + } +} \ No newline at end of file diff --git a/docs/extensibility/snippets/InteractionService/InteractionService.AppHost.csproj b/docs/extensibility/snippets/InteractionService/InteractionService.AppHost.csproj new file mode 100644 index 0000000000..f1a5d6f97e --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/InteractionService.AppHost.csproj @@ -0,0 +1,17 @@ + + + + + + net9.0 + enable + enable + Exe + $(NoWarn);ASPIREINTERACTION001;ASPIREPUBLISHERS001 + + + + + + + diff --git a/docs/extensibility/snippets/InteractionService/InteractionService.slnx b/docs/extensibility/snippets/InteractionService/InteractionService.slnx new file mode 100644 index 0000000000..d364a11967 --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/InteractionService.slnx @@ -0,0 +1,3 @@ + + + diff --git a/docs/extensibility/snippets/InteractionService/Properties/launchSettings.json b/docs/extensibility/snippets/InteractionService/Properties/launchSettings.json new file mode 100644 index 0000000000..701d4b730e --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17251;http://localhost:15199", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21161", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22175" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15199", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19277", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20014" + } + } + } +} diff --git a/docs/fundamentals/dashboard/explore.md b/docs/fundamentals/dashboard/explore.md index 18b8736275..e8b6cea822 100644 --- a/docs/fundamentals/dashboard/explore.md +++ b/docs/fundamentals/dashboard/explore.md @@ -1,8 +1,9 @@ --- title: Explore .NET Aspire dashboard description: Explore the .NET Aspire dashboard features through the .NET Aspire Starter app. -ms.date: 04/07/2025 +ms.date: 07/21/2025 ms.topic: reference +ai-usage: ai-assisted --- # Explore the .NET Aspire dashboard @@ -426,6 +427,22 @@ The following shortcuts are available: - ?: Got to **Help**. - Shift + s: Go to **Settings**. +## Interaction prompts + +Some resources or commands might prompt you for values when using the dashboard. This interactive functionality is powered by the [interaction service](../../extensibility/interaction-service.md), which allows integrations to display notifications or to request input from users when needed. + +For example, Azure resources that are missing required configuration might prompt you for configuration values when the dashboard starts or when you interact with those resources. These prompts help ensure that resources are properly configured and can function correctly within your .NET Aspire application. + +In the dashboard, interaction prompts appear as: + +- Input dialogs for missing configuration values. +- Confirmation dialogs for important actions. +- Notification messages with details about resource status. + +These prompts appear directly in the dashboard interface, making it easy to provide the necessary information without switching to external tools or configuration files. + +For detailed information about using the interaction service API, including examples and CLI support, see [Interaction Service](../../extensibility/interaction-service.md). + ## Next steps > [!div class="nextstepaction"] diff --git a/docs/toc.yml b/docs/toc.yml index a6912b9fc2..cb1f37310c 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -76,6 +76,8 @@ items: href: fundamentals/networking-overview.md - name: Eventing in .NET Aspire href: app-host/eventing.md + - name: Prompt user from CLI or Dashboard + href: extensibility/interaction-service.md - name: Use external parameters displayName: external parameters,configuration href: fundamentals/external-parameters.md