Aspire is a stack for developing distributed applications provided by Microsoft.
This sample shows an Aspire AppHost project that orchestrates the Particular Platform, multiple NServiceBus endpoints, wiring up the required infrastructure pieces when using the Azure ServiceBus transport.
If you're missing certain capabilities to use Aspire with NServiceBus, share them and help shape the future of the platform.
Running the sample
- Run the AspireDemo.AppHost project
- Open the Aspire dashboard
- Review the metrics, traces, and structured log entries of each of the resources
This sample requires Docker to run. Ensure the predefined container ports are free and available. It also assumes an Azure ServiceBus instance has been setup.
Code walkthrough
AspireDemo.AppHost
The Aspire orchestration project defines multiple resources and the relationships between them:
- An Azure ServiceBus connection named
transport - Two projects, each of which is an NServiceBus endpoint. All of these projects reference the
transportresource.clientuisales
- ServiceControl error, audit and monitoring instances
- ServicePulse
Platform configuration
AddParticularPlatform registers the Particular Platform as a resource named particular. The WithTransportAzureServiceBus extension points the platform at the transport connection string resource defined earlier, so that the ServiceControl instances connect to the same Azure Service Bus broker as the endpoints.
var platform = builder
.AddParticularPlatform("particular")
.WithTransportAzureServiceBus(transport);
Transport
This sample assumes that an Azure Service Bus instance has already been provisioned. It is referenced through a connection string resource named transport, which is then passed to WithTransportAzureServiceBus:
var transport = builder.AddConnectionString("transport");
Alternatively, the broker can be defined as an Azure Service Bus resource managed by Aspire (from the Aspire. package), which Aspire provisions as a real namespace in Azure. Because WithTransportAzureServiceBus accepts any resource that exposes a connection string, the resulting resource can be passed in directly:
var transport = builder.AddAzureServiceBus("transport");
builder
.AddParticularPlatform("particular")
.WithTransportAzureServiceBus(transport);
Aspire can also run the Azure Service Bus emulator locally (RunAsEmulator), but it is not suitable for this sample. The emulator only serves a limited number of queues, topics, and subscriptions that must be declared up front in its configuration; it does not create entities on demand. NServiceBus instead relies on installers to create its topology at runtime, and this sample's topology exceeds the emulator's entity limits. For these reasons the sample uses a real Azure Service Bus namespace.
ServiceControl Database
The platform requires a database to store the data managed by its ServiceControl instances. AddPersistenceRavenDb adds a RavenDB resource named particular-persistence for this purpose.
var persistence = platform.AddPersistenceRavenDb("particular-persistence");
Usage reporting
The ServiceControl error instance can collect endpoint usage data, which powers the usage report in ServicePulse. When using Azure Service Bus, collecting this data requires Azure credentials with the Monitoring Reader role. These values are supplied as Aspire parameters:
var asbTenantId = builder.AddParameter("asb-tenant-id", "[YOUR_VALUE]");
var asbSubscriptionId = builder.AddParameter("asb-subscription-id", "[YOUR_VALUE]");
var asbClientId = builder.AddParameter("asb-client-id", "[YOUR_VALUE]");
var asbClientSecret = builder.AddParameter("asb-client-secret", "[YOUR_VALUE]", secret: true);
The parameters are passed to WithThroughputReporting when the error instance is registered. See usage reporting setup for details on how to obtain them. If usage reporting is not required, omit these parameters and the WithThroughputReporting call.
Default components
AddDefaultComponents registers the remaining platform components using their default configuration — the ServiceControl audit and monitoring instances and ServicePulse. The error instance is added explicitly above so that usage reporting can be configured on it.
platform.AddDefaultComponents();
Endpoints
Each NServiceBus endpoint is added as an Aspire project and linked to the platform with WithParticularPlatform. This wires the endpoint to the platform's transport connection string. The ClientUI endpoint additionally uses WaitFor(sales) so that the Sales endpoint exists before it starts sending messages to it.
var sales = builder.AddProject<Projects.Sales>("Sales")
.WithParticularPlatform(platform);
builder.AddProject<Projects.ClientUI>("ClientUI")
.WaitFor(sales)
.WithParticularPlatform(platform);
AspireDemo.ServiceDefaults
The Aspire service defaults project provides extension methods to configure application hosts and NServiceBus endpoints in a standardized way. This project is referenced by all of the NServiceBus endpoint projects.
The OpenTelemetry configuration has been updated to include NServiceBus metrics and traces.
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddMeter("NServiceBus.*")
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation();
})
.WithTracing(tracing =>
{
tracing.AddSource(builder.Environment.ApplicationName)
.AddSource("NServiceBus.*")
.AddAspNetCoreInstrumentation(tracing =>
// Exclude health check requests from tracing
tracing.Filter = context =>
!context.Request.Path.StartsWithSegments(HealthEndpointPath)
&& !context.Request.Path.StartsWithSegments(AlivenessEndpointPath)
)
// Uncomment the following line to enable gRPC instrumentation
// (requires the OpenTelemetry.Instrumentation.GrpcNetClient package)
//.AddGrpcClientInstrumentation()
.AddHttpClientInstrumentation();
});
Each endpoint project retrieves the connection string for the Azure ServiceBus broker and configures NServiceBus to use it as a transport:
var connectionString = builder.Configuration.GetConnectionString("transport");
if (connectionString is null)
{
throw new InvalidOperationException
($"No transport configured. Provide a 'ConnectionStrings:transport'.");
}
var routing = endpointConfiguration.UseTransport
(new AzureServiceBusTransport(connectionString, TopicTopology.Default));
Finally, each endpoint enables NServiceBus installers. Every time the application host is run, the transport and ServiceControl database are recreated and will not contain the queues and tables needed for the endpoints to run. Enabling installers allows NServiceBus to set up the assets that it needs at runtime.
endpointConfiguration.EnableInstallers();
Endpoint projects
Each of the endpoint projects contain the same code to create an application host, apply the configuration from the ServiceDefaults project on the NServiceBus endpoint.
var builder = Host.CreateApplicationBuilder();
builder
.AddServiceDefaults()
.AddNServiceBusEndpoint("Sales");
To demonstrate the platform's error handling, the Sales endpoint's handler throws an exception for a random subset of the messages it receives:
if (Random.Shared.Next(0, 5) == 0)
{
throw new Exception("Oops");
}
Failed messages are moved to the error queue, where they can be inspected and retried from ServicePulse.