There are many situations while you are developing and debugging an application where it can be useful to quickly and repeatedly run commands against the running application to assist you. You might want to automate the creation of users, call certain endpoints, run functions, reset database data, or any number of other scenarios. Aspire provides you with the capability to create your own custom resource commands and HTTP functions that you can add to resources in the dashboard which enable you to do just that. If you can write the code to make something happen, you can add a button to the Aspire dashboard to execute that command.
When you launch an Aspire app and go into the dashboard, each resource will have a number of commands available by default. For instance, let’s look at an application created from the default Aspire template. It has a Blazor Web App, an ASP.NET Web API back end, and you might have opted to include Redis for caching. When you launch the app and the dashboard opens, you’ll see something like the following:
On the right end of the dashboard list for each resource, you’ll see an “Actions” column. In that column there may be one or more buttons, such as a Stop button, a Start button, or a button that will open the console logs for that resource. If you click the ellipses button (…) you may see a longer list of available commands that you can execute against that resource. These are available by default for each resource in Aspire.
You can also add your own custom buttons to that list to carry out whatever command you want in your local development environment. That’s an important note to keep in mind. These commands are only available when you run your application locally. If you do, for some reason, deploy the dashboard component alongside the rest of your application to Azure Container Apps (not generally recommended, but you can), these commands will not be available to you.
There are two basic types of commands available to you: custom resource commands and HTTP commands. A resource command will execute a piece of custom code that you write and add to your AppHost project. An HTTP command will call an HTTP endpoint on the associated resource.
Custom Resource Command
If you can write code for it, the custom resource command can execute it. It doesn’t matter what the code does. It might invoke a service, play a song, write to a database, open a file, execute a command, or whatever else you want to write into your code. It is written as an extension method tied to an IResourceBuilder object of the type you want to add the command to. For example, if I want to write a command to seed (or reseed) my database resource, I might create a command file such as the following:
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace AspireDemoApp.AppHost
{
internal static class SqlServerDatabaseExtensions
{
public static IResourceBuilder<SqlServerDatabaseResource> WithSeedDatabaseCommand(
this IResourceBuilder<SqlServerDatabaseResource> builder)
{
var commandOptions = new CommandOptions()
{
UpdateState = OnUpdateResourceState,
IconName = "CloudDatabase",
IconVariant = IconVariant.Filled
};
builder.WithCommand(
name: "seed-database",
displayName: "(Re)Seed Database",
executeCommand: context => OnSeedDatabaseCommandAsync(builder, context),
commandOptions: commandOptions);
return builder;
}
private static async Task<ExecuteCommandResult> OnSeedDatabaseCommandAsync(
IResourceBuilder<SqlServerDatabaseResource> builder,
ExecuteCommandContext context)
{
// Implement the logic to seed the database here.
// You can use context to get information about the execution environment.
return CommandResults.Success();
}
private static ResourceCommandState OnUpdateResourceState(UpdateCommandStateContext context)
{
return context.ResourceSnapshot.HealthStatus is HealthStatus.Healthy
? ResourceCommandState.Enabled
: ResourceCommandState.Hidden;
}
}
}There’s a bit to cover there, so let’s look at each of the three functions. At the top we have the WithSeedDatabaseCommand function. This provides all the details that define the command: the button that will appear on the dashboard, what it should look like, the text that will appear next to it, the command it will execute when the user clicks it, and so forth.
The CommandOptions object defines various options related to the command, such as the details of the icon and when it will appear. All of these options, and the CommandOptions object itself, are optional. The IconName is the name of the icon to be used. The full list of available icons comes from the Fluent UI Blazor Library. You have to provide the text name of the icon. It might have been better to provide an enum list, but it is what it is.
The IconVariant allows you to select the variant. The default is Regular, which means the hollow outline style of icon.
The alternate is Filled, which means a solid icon.
The Fluent UI library also has a ‘Light’ variant, but those aren’t available to you in Aspire.
The other part of the CommandOptions object we show above is the UpdateState. This can let you specify the logic for when the button should appear. This can either be a separate function or inline code. In our example we’re calling the OnUpdateResourceState function, so let’s take a moment to look at that function. It’s a single line:
return context.ResourceSnapshot.HealthStatus is HealthStatus.Healthy
? ResourceCommandState.Enabled
: ResourceCommandState.Hidden;All this does is do a health check. If the app is up and running, the button is visible and enabled (.Enabled). If it isn’t healthy, the button is hidden (.Hidden). There is a third option available, .Disabled, which makes the button visible, but disabled.
The next part of the WithSeedDatabaseCommand function is the .WithCommand block. This provides the context of what function to run when the button is clicked, as well as the accompanying text, and passing in the icon details we specified with the CommandOptions.
The name provides the button with a unique identifier. It is required, and all of the custom resource commands in your Aspire application need to have a unique command identifier.
The displayName field provides the text to go in the menu. It’s not required, but it will make your button look a little odd if you don’t provide context for what it does to the user.
The executeCommand field provides the function to be called when the button is clicked. It can be an inline function, or a defined function. In our example we’re calling the OnSeedDatabaseCommandAsync function. That’s where the guts of the functionality would go, telling our application what to do when the button gets clicked.
The commandOptions field lets us pass in the CommandOptions object, which we used to provide the details for the icon. This is optional as well. An icon is not required, but it looks better if you’ve got one of course.
To add the command button to our database resource, in our AppHost file, we’ll add the command to the database instance:
var dbserver = builder.AddSqlServer("sqlserver");
var database = dbserver.AddDatabase("database")
.WithSeedDatabaseCommand();And now, when we run our application, our database resource now has a new menu item in the commands section.
HTTP Command
The other type of command available is the HTTP command. This command specifically sends an HTTP request to an endpoint on your resource. For instance, if we wanted to call the demo application’s provided /weatherforecast endpoint, we would add the command using the following syntax:
var apiService = builder.AddProject<Projects.AspireDemoApp_ApiService>("apiservice")
.WithHttpHealthCheck("/health")
.WithHttpCommand(
path: "/weatherforecast",
displayName: "Get forecast",
commandOptions: new HttpCommandOptions()
{
Description = "Gets the weather forecast data",
IconName = "DocumentLightning",
IsHighlighted = true,
Method = HttpMethod.Get
});This command adds a button that will call the API endpoint that returns the weather forecast.
The path field is our endpoint URI for endpoints on our associated resource.
The displayName provides the text that will appear next to the icon in the menu, as with custom resource commands.
The commandOptions section provides some additional details on the command, similar to the custom resource command, and many of the options are the same. Description is the help text that will appear when you hover over the command. The IsHighlighted option will specify whether the button appears in the “Actions” column as a button (see screenshot below), or only in the expanded menu when you click the ellipsis (…).
Specific to HTTP commands, we have the Method field. If you don’t specify the HTTP method, Aspire assumes it is a POST call. We’ve specified GET in our example to match the correct method. There are a couple of other options available specific to HTTP commands. The PrepareRequest field allows you to write or call a function that will be used to prepare the request. For example, if you need to specify any headers, such as a bearer token or a key or MIME type.
PrepareRequest = (context) => {
context.Request.Headers.Add("Authorization", $"bearer {myBearerToken}");
return Task.CompletedTask;
}If we need to do something with the results of the call, we can make use of the GetCommandResult option.
GetCommandResult = async (req) =>
{
var result = CommandResults.Success();
if (req.Response.IsSuccessStatusCode)
{
var content = await req.Response.Content.ReadAsStringAsync();
//do something with content if needed
return CommandResults.Success();
}
return CommandResults.Failure($"Request failed with status code {req.Response.StatusCode} - {req.Response.ReasonPhrase}");
}By default, when you call a custom HTTP endpoint, you will just get a success or failure result popup in the top right of the dashboard. The default success message doesn’t provide you with any way to display a custom message related to those results.
A small note while we’re covering HTTP commands. If you need to call an external HTTP destination that isn’t an endpoint on your related resource, you would make use of the custom resource command to write code to use something like HttpClient to call that external resource. The WithHttpCommand can only call endpoints on the related resource you attach it to, using the resource base URI as the base URI for the call.
What if you need to do more? With v13 of Aspire, we now have the new Interaction Service, currently in Preview.
Aspire Interaction Service
Sometimes it would be helpful to allow Aspire to interact more with the user. Now it can. With the new Interaction Service, Aspire can create simple interaction dialog boxes to display information to the user, or to get input. We’re calling the weatherforecast endpoint. Now let’s display the response back to the user. We’ll need to add a bit of code to support this. Add an @using for DependencyInjection at the top of our code. Then we’ll add a little block to our GetCommandResult function. Because this is a preview feature, you may need to disable the warning using a #pragma block to get your IDE to build and run it.
using Microsoft.Extensions.DependencyInjection;
...
GetCommandResult = async (req) =>
{
var result = CommandResults.Success();
if (req.Response.IsSuccessStatusCode)
{
var content = await req.Response.Content.ReadAsStringAsync();
// Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable ASPIREINTERACTION001
var service = builder.ExecutionContext.ServiceProvider.GetRequiredService<IInteractionService>();
#pragma warning restore ASPIREINTERACTION001
await service.PromptNotificationAsync("The Weather", content);
return CommandResults.Success();
}
return CommandResults.Failure($"Request failed with status code {req.Response.StatusCode} - {req.Response.ReasonPhrase}");
}There are a couple of different styles of dialog box available to you. This one uses the PromptNotificationAsync version, and produces a popup at the top of the dashboard showing our results.
You could also use the PromptMessageBoxAsync version:
await service.PromptMessageBoxAsync("The Weather", content);This will provide you with a dialog that looks like this:
There are also dialog boxes for getting basic input from the user, the PromptInputAsync and PromptInputsAsync, for getting a single, or multiple, values from the user. The two input dialogs are also available for interactions on the Aspire CLI, but the other dialog boxes are not. You can prompt for input values like this:
var inputs = new List<InteractionInput>
{
new()
{
Name = "Zip Code For Weather",
InputType = InputType.Number,
Required = true,
Placeholder = "43232"
},
new()
{
Name = "Include 7 Day Forecast",
InputType = InputType.Boolean,
Required = false
}
};
var inputResult = await service.PromptInputsAsync(
title:"Get the Weather",
message: "Select Weather Options:",
inputs: inputs);This will give you a dialog box as follows:
Just remember, the Interaction Service is in Preview. It may completely change in future versions or get tossed out entirely, so use it with caution and don’t be too reliant on it just yet.
Conclusion
Aspire provides a multitude of ways to interact with the user and allow them to carry out various tasks, enabling them to execute commands and get information right in the dashboard. A wise use of these custom resource and HTTP commands can make your development work better than ever before.


