Tutorial: Use .NET Aspire messaging integrations in ASP.NET Core

Cloud-native apps often require scalable messaging solutions that provide capabilities such as messaging queues and topics and subscriptions. .NET Aspire integrations simplify the process of connecting to various messaging providers, such as Azure Service Bus. In this tutorial, you'll create an ASP.NET Core app that uses .NET Aspire integrations to connect to Azure Service Bus to create a notification system. Submitted messages will be sent to a Service Bus topic for consumption by subscribers. You'll learn how to:

  • Create a basic .NET app that is set up to use .NET Aspire integrations
  • Add an .NET Aspire integration to connect to Azure Service Bus
  • Configure and use .NET Aspire integration features to send and receive data

Prerequisites

To work with .NET Aspire, you need the following installed locally:

For more information, see .NET Aspire setup and tooling, and .NET Aspire SDK.

In addition to the preceding prerequisites, you also need to install the Azure CLI. To install the Azure CLI, follow the instructions in the Azure CLI installation guide.

Set up the Azure Service Bus account

For this tutorial, you'll need access to an Azure Service Bus namespace with a topic and subscription configured. Use one of the following options to set up the require resources:

Alternatively:

  • Azure CLI: Run the following commands in the Azure CLI or CloudShell to set up the required Azure Service Bus resources:

    az group create -n <your-resource-group-name> --location eastus
    az servicebus namespace create -g <your-resource-group-name> --name <your-namespace-name> --location eastus
    az servicebus topic create -g <your-resource-group-name> --namespace-name <your-namespace-name> --name notifications
    az servicebus topic subscription create -g <your-resource-group-name> --namespace-name <your-namespace-name> --topic-name notifications --name mobile
    

    Note

    Replace the your-resource-group-name and your-namespace-name placeholders with your own values. Service Bus namespace names must be globally unique across Azure.

Azure authentication

This quickstart can be completed using either passwordless authentication or a connection string. Passwordless connections use Azure Active Directory and role-based access control (RBAC) to connect to a Service Bus namespace. You don't need to worry about having hard-coded connection string in your code, a configuration file, or in secure storage such as Azure Key Vault.

You can also use a connection string to connect to a Service Bus namespace, but the passwordless approach is recommended for real-world applications and production environments. For more information, read about Authentication and authorization or visit the passwordless overview page.

On your Service Bus namespace, assign the following role to the user account you logged into Visual Studio or the Azure CLI with:

Create the sample solution

To create a new .NET Aspire Starter Application, you can use either Visual Studio, Visual Studio Code, or the .NET CLI.

Visual Studio provides .NET Aspire templates that handle some initial setup configurations for you. Complete the following steps to create a project for this quickstart:

  1. At the top of Visual Studio, navigate to File > New > Project.

  2. In the dialog window, search for Aspire and select .NET Aspire Starter App. Select Next.

    A screenshot of the .NET Aspire Starter App template.

  3. On the Configure your new project screen:

    • Enter a Project Name of AspireSample.
    • Leave the rest of the values at their defaults and select Next.
  4. On the Additional information screen:

    • Make sure .NET 9.0 (Standard Term Support) is selected.
    • Ensure that Use Redis for caching (requires a supported container runtime) is checked and select Create.
    • Optionally, you can select Create a tests project. For more information, see Write your first .NET Aspire test.

Visual Studio creates a new solution that is structured to use .NET Aspire.

Visual Studio Code provides .NET Aspire project templates that handle some initial setup configurations for you. Complete the following steps to create a project for this quickstart:

  1. From a new instance of Visual Studio Code (without a folder open), select Create .NET project button.

  2. Select the .NET Aspire Starter App template.

    A screenshot of the .NET Aspire Starter App template.

If you haven't already installed the .NET Aspire templates, run the following dotnet new install command:

dotnet new install Aspire.ProjectTemplates

The preceding .NET CLI command ensures that you have the .NET Aspire templates available. To create the .NET Aspire Starter App from the template, run the following dotnet new command:

dotnet new aspire-starter --use-redis-cache --output AspireSample

For more information, see dotnet new. The .NET CLI creates a new solution that is structured to use .NET Aspire.

Add the Worker Service project

Next, add a Worker Service project to the solution to retrieve and process messages to and from Azure Service Bus.

  1. In the solution explorer, right click on the top level AspireSample solution node and select Add > New project.
  2. Search for and select the Worker Service template and choose Next.
  3. For the Project name, enter AspireSample.WorkerService and select Next.
  4. On the Additional information screen:
    • Make sure .NET 9.0 is selected.
    • Make sure Enlist in .NET Aspire orchestration is checked and select Create.

Visual Studio adds the project to your solution and updates the Program.cs file of the AspireSample.AppHost project with a new line of code:

builder.AddProject<Projects.AspireSample_WorkerService>(
    "aspiresample-workerservice");

Visual Studio tooling added this line of code to register your new project with the IDistributedApplicationBuilder object, which enables orchestration features you'll explore later.

  1. From the Solution Explorer in Visual Studio Code, select the + button next to the solution name to add a new project to the solution:

    Visual Studio Code: Add new project from C# DevKit Solution Explorer.

  2. To filter the project templates, enter Worker in the search box and select the Worker Service template that's found:

    Visual Studio Code: Filter to Worker Service project template from Add project.

  3. Choose the Worker Service template and enter the project name as AspireSample.WorkerService.

  4. Select Default directory to create the project in the same directory as the solution.

  5. Select Create project to add the project to the solution.

  6. Right-click on the AspireSample.AppHost project in the Solution Explorer and select Add Project Reference:

    Visual Studio Code: Add project reference from AspireSample.AppHost to AspireSample.WorkerService.

  7. Add the following line of code to the Program.cs file in the AspireSample.AppHost project before the call to builder.Build().Run();:

    builder.AddProject<Projects.AspireSample_WorkerService>(
        "aspiresample-workerservice");
    
  1. In the root directory of the app, use the dotnet new command to create a new Worker Service app:

    dotnet new worker --name AspireSample.WorkerService
    
  2. Use the dotnet sln command to add the project to the solution:

    dotnet sln AspireSample.sln add AspireSample.WorkerService/AspireSample.WorkerService.csproj
    
  3. Use the dotnet add command to add a project reference between the .AppHost and .WorkerService project:

    dotnet add AspireSample.AppHost/AspireSample.AppHost.csproj reference AspireSample.WorkerService/AspireSample.WorkerService.csproj
    
  4. Add the following line of code to the Program.cs file in the AspireSample.AppHost project before the call to builder.Build().Run();:

    builder.AddProject<Projects.AspireSample_WorkerService>(
        "aspiresample-workerservice");
    

The completed solution structure should resemble the following, assuming the top-level directory is named aspire-messaging:

β””β”€β”€β”€πŸ“‚ aspire-messaging
     β”œβ”€β”€β”€πŸ“‚ AspireSample.WorkerService
     β”‚    β”œβ”€β”€β”€πŸ“‚ Properties
     β”‚    β”‚    └─── launchSettings.json
     β”‚    β”œβ”€β”€β”€ appsettings.Development.json
     β”‚    β”œβ”€β”€β”€ appsettings.json
     β”‚    β”œβ”€β”€β”€ AspireSample.WorkerService.csproj
     β”‚    β”œβ”€β”€β”€ Program.cs
     β”‚    └─── Worker.cs
     β”œβ”€β”€β”€πŸ“‚ AspireSample.ApiService
     β”‚    β”œβ”€β”€β”€πŸ“‚ Properties
     β”‚    β”‚    └─── launchSettings.json
     β”‚    β”œβ”€β”€β”€ appsettings.Development.json
     β”‚    β”œβ”€β”€β”€ appsettings.json
     β”‚    β”œβ”€β”€β”€ AspireSample.ApiService.csproj
     β”‚    └─── Program.cs
     β”œβ”€β”€β”€πŸ“‚ AspireSample.AppHost
     β”‚    β”œβ”€β”€β”€πŸ“‚ Properties
     β”‚    β”‚    └─── launchSettings.json
     β”‚    β”œβ”€β”€β”€ appsettings.Development.json
     β”‚    β”œβ”€β”€β”€ appsettings.json
     β”‚    β”œβ”€β”€β”€ AspireSample.AppHost.csproj
     β”‚    └─── Program.cs
     β”œβ”€β”€β”€πŸ“‚ AspireSample.ServiceDefaults
     β”‚    β”œβ”€β”€β”€ AspireSample.ServiceDefaults.csproj
     β”‚    └─── Extensions.cs
     β”œβ”€β”€β”€πŸ“‚ AspireSample.Web
     β”‚    β”œβ”€β”€β”€πŸ“‚ Components
     β”‚    β”‚    β”œβ”€β”€β”€πŸ“‚ Layout
     β”‚    β”‚    β”‚    β”œβ”€β”€β”€ MainLayout.razor
     β”‚    β”‚    β”‚    β”œβ”€β”€β”€ MainLayout.razor.css
     β”‚    β”‚    β”‚    β”œβ”€β”€β”€ NavMenu.razor
     β”‚    β”‚    β”‚    └─── NavMenu.razor.css
     β”‚    β”‚    β”œβ”€β”€β”€πŸ“‚ Pages
     β”‚    β”‚    β”‚    β”œβ”€β”€β”€ Counter.razor
     β”‚    β”‚    β”‚    β”œβ”€β”€β”€ Error.razor
     β”‚    β”‚    β”‚    β”œβ”€β”€β”€ Home.razor
     β”‚    β”‚    β”‚    └─── Weather.razor
     β”‚    β”‚    β”œβ”€β”€β”€ _Imports.razor
     β”‚    β”‚    β”œβ”€β”€β”€ App.razor
     β”‚    β”‚    └─── Routes.razor
     β”‚    β”œβ”€β”€β”€πŸ“‚ Properties
     β”‚    β”‚    └─── launchSettings.json
     β”‚    β”œβ”€β”€β”€πŸ“‚ wwwroot
     β”‚    β”‚    β”œβ”€β”€β”€πŸ“‚ bootstrap
     β”‚    β”‚    β”‚    β”œβ”€β”€β”€ bootstrap.min.css
     β”‚    β”‚    β”‚    └─── bootstrap.min.css.map
     β”‚    β”‚    β”œβ”€β”€β”€ app.css
     β”‚    β”‚    └─── favicon.png
     β”‚    β”œβ”€β”€β”€ appsettings.Development.json
     β”‚    β”œβ”€β”€β”€ appsettings.json
     β”‚    β”œβ”€β”€β”€ AspireSample.Web.csproj
     β”‚    β”œβ”€β”€β”€ Program.cs
     β”‚    └─── WeatherApiClient.cs
     └─── AspireSample.sln

Add the .NET Aspire integration to the API

Add the .NET Aspire Azure Service Bus integration to your AspireSample.ApiService app:

  1. In the Solution Explorer, double-click the AspireSample.ApiService.csproj file to open its XML file.

  2. Add the following <PackageReference> item to the <ItemGroup> element:

    <ItemGroup>
        <PackageReference Include="Aspire.Azure.Messaging.ServiceBus"
                          Version="8.0.1" />
    </ItemGroup>
    
  1. In the Solution Explorer, right-click on the AspireSample.ApiService project and select Add NuGet Package:

    Visual Studio Code: Add NuGet package to AspireSample.ApiService project.

  2. Enter Aspire.Azure.Messaging.ServiceBus in the search box and select the package from the list.

  3. Select the (latest) version to install the package.

dotnet add package Aspire.Azure.Messaging.ServiceBus

In the Program.cs file of the AspireSample.ApiService project, add a call to the AddAzureServiceBusClient extension methodβ€”replacing the existing call to AddServiceDefaults:

// Add service defaults & Aspire integrations.
builder.AddServiceDefaults();
builder.AddAzureServiceBusClient("serviceBusConnection");

For more information, see AddAzureServiceBusClient.

This method accomplishes the following tasks:

  • Registers a ServiceBusClient with the DI container for connecting to Azure Service Bus.
  • Automatically enables corresponding health checks, logging, and telemetry for the respective services.

In the appsettings.json file of the same project, add the corresponding connection information:

{
  // Existing configuration is omitted for brevity.
  "ConnectionStrings": {
    "serviceBusConnection": "{your_namespace}.servicebus.windows.net"
  }
}

Note

Make sure to replace {your_namespace} in the service URIs with the name of your own Service Bus namespace.

Create the API endpoint

The API must provide an endpoint to receive data and publish it to the Service Bus topic and broadcast to subscribers. Add the following endpoint to the AspireSample.ApiService project to send a message to the Service Bus topic. Replace all of the contents of the Program.cs file with the following C# code:

using Azure.Messaging.ServiceBus;

var builder = WebApplication.CreateBuilder(args);

// Add service defaults & Aspire integrations.
builder.AddServiceDefaults();
builder.AddAzureServiceBusClient("serviceBusConnection");

// Add services to the container.
builder.Services.AddProblemDetails();

var app = builder.Build();

// Configure the HTTP request pipeline.
app.UseExceptionHandler();

app.MapPost("/notify", static async (ServiceBusClient client, string message) =>
{
    var sender = client.CreateSender("notifications");

    // Create a batch
    using ServiceBusMessageBatch messageBatch =
        await sender.CreateMessageBatchAsync();

    if (messageBatch.TryAddMessage(
            new ServiceBusMessage($"Message {message}")) is false)
    {
        // If it's too large for the batch.
        throw new Exception(
            $"The message {message} is too large to fit in the batch.");
    }

    // Use the producer client to send the batch of
    // messages to the Service Bus topic.
    await sender.SendMessagesAsync(messageBatch);

    Console.WriteLine($"A message has been published to the topic.");
});

app.MapDefaultEndpoints();

app.Run();

Add the .NET Aspire integration to the Worker Service

Add the .NET Aspire Azure Service Bus integration to your AspireSample.WorkerService project. Follow the same steps as you did before when you added the Aspire.Azure.Messaging.ServiceBus NuGet package to the AspireSample.ApiService project. Once it's been added, you can configure the worker service to process messages from the Service Bus topic.

In the Program.cs file of the AspireSample.WorkerService project, repace the existing code with the following:

using AspireSample.WorkerService;

var builder = Host.CreateApplicationBuilder(args);

builder.AddAzureServiceBusClient("serviceBusConnection");

builder.Services.AddHostedService<Worker>();

var host = builder.Build();
host.Run();

The AddAzureServiceBusClient method accomplishes the following tasks:

  • Registers a ServiceBusClient with the DI container for connecting to Azure Service Bus.
  • Automatically enables corresponding health checks, logging, and telemetry for the respective services.

In the appsettings.json file of the AspireSample.WorkerService project, add the corresponding connection information:

{
  // Existing configuration is omitted for brevity.
  "ConnectionStrings": {
    "serviceBusConnection": "{your_namespace}.servicebus.windows.net"
  }
}

Note

Make sure to replace {your_namespace} in the Service URIs with the name of your own Service Bus namespace.

Process the message from the subscriber

When a new message is placed on the messages queue, the worker service should retrieve, process, and delete the message. Update the Worker.cs class to match the following code:

using Azure.Messaging.ServiceBus;

namespace AspireSample.WorkerService;

public sealed class Worker(
    ILogger<Worker> logger,
    ServiceBusClient client) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var processor = client.CreateProcessor(
                "notifications",
                "mobile",
                new ServiceBusProcessorOptions());

            // Add handler to process messages
            processor.ProcessMessageAsync += MessageHandler;

            // Add handler to process any errors
            processor.ProcessErrorAsync += ErrorHandler;

            // Start processing
            await processor.StartProcessingAsync();

            logger.LogInformation("""
                Wait for a minute and then press any key to end the processing
                """);

            Console.ReadKey();

            // Stop processing
            logger.LogInformation("""

                Stopping the receiver...
                """);

            await processor.StopProcessingAsync();

            logger.LogInformation("Stopped receiving messages");
        }
    }

    async Task MessageHandler(ProcessMessageEventArgs args)
    {
        string body = args.Message.Body.ToString();

        logger.LogInformation("Received: {Body} from subscription.", body);

        // Complete the message. messages is deleted from the subscription.
        await args.CompleteMessageAsync(args.Message);
    }

    // Handle any errors when receiving messages
    Task ErrorHandler(ProcessErrorEventArgs args)
    {
        logger.LogError(args.Exception, "{Error}", args.Exception.Message);
        
        return Task.CompletedTask;
    }
}

Run and test the app locally

The sample app is now ready for testing. Verify that the data submitted to the API is sent to the Azure Service Bus topic and consumed by the subscriber worker service:

  1. Launch the .NET Aspire project by selecting the Start debugging button, or by pressing F5. The .NET Aspire dashboard app should open in the browser.
  1. Launch the .NET Aspire project by selecting the Start debugging button, or by pressing F5. The .NET Aspire dashboard app should open in the browser.
  1. Launch the .NET Aspire project by running dotnet run --project AspireSample.AppHost. The .NET Aspire dashboard app should open in the browser.
  1. On the resources page, in the apiservice row, find the link in the Endpoints that opens the weatherforecast endpoint. Note the HTTPS port number.

  2. On the .NET Aspire dashboard, navigate to the logs for the aspiresample-workerservice project.

  3. In a terminal window, use the curl command to send a test message to the API:

    curl -X POST -H "Content-Type: application/json" https://localhost:{port}/notify?message=hello%20aspire  
    

    Be sure to replace {port} with the port number from earlier.

  4. Switch back to the aspiresample-workerservice logs. You should see the test message printed in the output logs.

Congratulations! You created and configured an ASP.NET Core API that connects to Azure Service Bus using Aspire integrations.

Clean up resources

Run the following Azure CLI command to delete the resource group when you no longer need the Azure resources you created. Deleting the resource group also deletes the resources contained inside of it.

az group delete --name <your-resource-group-name>

For more information, see Clean up resources in Azure.