Blog
Build and test your own .NET Aspire component
This blog post describes how to develop and test your own .NET Aspire component and how the WireMock.Net Aspire component can be used to mock a referenced API Service within the context of a distributed .NET Aspire application.
π Intro
This blog post gives a quick intro on .NET Aspire. The next chapters describe how you can build and test your own .NET Aspire component to mock an API Service within the context of a distributed .NET Aspire application using WireMock.Net.
π‘ .NET Aspire
During the latest MS Build (May 21st, 2024), .NET Aspire was announced to be General Available. It was first released as Preview 1 at November 14th, 2023 and has been in development since then.

.NET Aspire is a new, opinionated stack designed to streamline the development of observable, production-ready, cloud-native .NET services. This stack brings together tools, templates, and a collection of NuGet packages that address specific cloud-native concerns, making it easier to build distributed applications in .NET.
Whether you are developing a new application, integrating cloud-native capabilities into an existing one, or already deploying .NET apps to the cloud, .NET Aspire enhances your development experience by facilitating faster deployment. Cloud-native applications typically consist of small, interconnected microservices rather than a single, monolithic codebase and often utilize a variety of services including databases, messaging, and caching to optimize performance and scalability.
You can start using .NET Aspire today in Visual Studio 2022 17.10, the .NET CLI, or Visual Studio Code.
βοΈ .NET Aspire orchestration overview
.NET Aspire’s orchestration streamlines the management of your cloud-native application’s configuration and interactions, making your local development experience more efficient and straightforward.
See the next picture of an overview how a .NET Aspire orchestration can be visualized:
– AppHost project: The .NET project that orchestrates the complete app model.
– App model: A collection of resources that make up your distributed application.
– Resource: A resource implements a part of an application. This can be a .NET project, Docker Container, executable, or some other resource like a (cloud) service.
A Reference defines the dependency or link between the resources.
The next chapters zoom into the building-blocks (Resources and References).
π₯οΈ Resources
This chapter describes all the default .NET Aspire resources and which interface these implement.
ContainerResource
A container image, such as a Docker image.
The implemented interfaces are:
– IResourceWithEnvironment: Represents a resource that is associated with an environment.
– IResourceWithArgs: Represents a resource that is associated with commandline arguments.
– IResourceWithEndpoints: Represents a resource that has endpoints associated with it.
ExecutableResource
A resource that represents a specified executable process. This resources uses the same interfaces as the ContainerResource.
ProjectResource
A resource that represents a .NET project in the solution.
The implemented interfaces are:
– IResourceWithEnvironment: Represents a resource that is associated with an environment.
– IResourceWithArgs: Represents a resource that is associated with commandline arguments.
– IResourceWithServiceDiscovery: A resource that supports exporting service discovery information.
Custom resource
It’s also possible to define your own resource which only uses specific interfaces. When your resource implements more interfaces, it has a broader usage.
For example if you want to build a resource which uses a Docker image, but it should also support service discovery information, the resource should extend the ContainerResource and implement the IResourceWithServiceDiscovery.
π Note that all the interfaces described above are empty interfaces, which means they do not contain any methods or properties. These are also known as a marker interfaces, and can be used to signify or tag a class with specific metadata. This allows the .NET Aspire framework to identify or classify classes that implement this interface without requiring any actual methods or properties to be defined.
π References
Components and resources can automatically inherit configurations based on project references. Thus references are the glue between the components and resources used within the .NET Aspire orchestration.
In the example overview picture, the AppHost project can be a Sales Application which contains a “webfrontend” which uses the backend service “apiservice” to retrieve and update sales-orders.
The “webfrontend” does also have a dependency on a “cache” solution which is hosted as Docker Container. Note that each resource must have a unique name.
πΆ Networking
When a container resource is added to .NET Aspire, a random port is automatically assigned to that container. To specify a container port, configure the container resource with the desired port:
builder.AddContainer("frontend", "mcr.microsoft.com/dotnet/samples", "aspnetapp")
.WithHttpEndpoint(port: 8000, targetPort: 8080);
The previous code does the following:
– Create a container resource named frontend, using the mcr.microsoft.com/dotnet/samples:aspnetapp Docker image.
– Expose an http endpoint by binding the host to port 8000 and map it to the container’s port 8080. This makes sure that when this container is started, it’s accessible on the 8080 port.
See the next picture which explains this in more detail:

Resources that implement the IResourceWithEndpoints interface can utilize the WithEndpoint extension methods. This extension provides multiple overloads, enabling you to define the scheme, container port, host port, environment variable name, and whether the endpoint should be proxied.
πͺ Lifecycle hooks
For a distributed application which is created using the IDistributedApplicationBuilder, it’s possible use hooks to execute your own code when a certain Lifecycle event happens.
The following hooks are available:
– BeforeStartAsync: this hook executes before the distributed application starts.
– AfterEndpointsAllocatedAsync: this hook executes after the orchestrator allocates endpoints for resources in the application model.
– AfterResourcesCreatedAsync: this hook executes after the orchestrator has created the resources in the application model.
π Note this IDistributedApplicationBuilder implements all the above methods with a default interface implementation, this means that you only need to override the hook(s) you need.
π« Restrictions
At this moment only Linux containers are supported which means that in case you want to build a custom .NET Aspire component resource which extends the default ContainerResource, you can only use Linux based Docker images.
π§ͺ Testing
.NET Aspire App Host
In order to unit-test your .NET Aspire component, you need to create a simple .NET Aspire AppHost which only registers your new .NET Aspire component, like this:
using MyAspireComponent.TestAppHost;
var builder = DistributedApplication.CreateBuilder(args);
builder
.AddMyAspireComponent("my-api-service");
builder
.Build()
.Run();
DistributedApplicationTestingBuilder
Create a Test Project (e.g. xUnit) and add a PackageReference to the Aspire.Hosting.Testing NuGet, which includes the required types to write tests for .NET Aspire apps.
Next, in each unit-test, create an instance of the IDistributedApplicationTestingBuilder based on the entry point assembly of that simple .NET Aspire AppHost using the Aspire.Hosting.Testing.DistributedApplicationTestingBuilder like this:
IDistributedApplicationTestingBuilder appHostBuilder =
await DistributedApplicationTestingBuilder.CreateAsync<My_Aspire_TestAppHost>();
Next create DistributedApplication and start it:
DistributedApplication app = await appHostBuilder.BuildAsync(); await app.StartAsync();
After the DistributedApplication is started, you can invoke an extension method to create a HttpClient which can be used to communicate with your .NET Aspire Resource:
using var httpClient = app.CreateHttpClient("my-api-service");
Now you can use that HttpClient to call your own resource and check the result
// Act
var weatherForecasts = await httpClient.GetFromJsonAsync("/weatherforecast");
// Assert
weatherForecasts1.Should().BeEquivalentTo(. . .);
At the end of the unit-test, it’s best to dispose the distributable application, like this:
await app.DisposeAsync();
Or even better, wrap the code in a await using like the xUnit-test example below:
[Fact]
public async Task Test()
{
// Arrange
var appHostBuilder = await DistributedApplicationTestingBuilder
.CreateAsync<My_Aspire_TestAppHost>()
await using var app = await appHostBuilder.BuildAsync();
await app.StartAsync();
using var httpClient = app.CreateHttpClient("my-api-service");
// Act
var weatherForecasts1 = await httpClient
.GetFromJsonAsync("/weatherforecast");
// Assert
weatherForecasts1.Should().BeEquivalentTo(new[]
{
new WeatherForecast(new DateOnly(2024, 5, 24), -10, "Freezing"),
new WeatherForecast(new DateOnly(2024, 5, 25), +33, "Hot")
});
}
Now you can unit-test your .NET Aspire Resource locally and in your pipeline. See next chapter for more details on how to enable and integrate an .NET Aspire unit-test in the pipeline.
π DevOps (CI/CD)
The .NET Aspire workload is default installed when using the latest Visual Studio, however when building and testing your Aspire component in a GitHub Workflow or an Azure Pipeline, this Aspire workload is not installed by default on the runners.
Locally
To install the .NET Aspire workload locally on your system, use this command:
dotnet workload install aspire
GitHub Actions
Use the following task in your GitHub workflow to install the .NET Aspire workload:
name: Build with Tests
jobs:
linux-build-and-run:
runs-on: ubuntu-latest
steps:
- name: Install .NET Aspire workload
run: dotnet workload install aspire
Azure Pipeline
Use the following task in your Azure Pipeline to install the .NET Aspire workload:
- task: CmdLine@2
displayName: 'Install .NET Aspire workload'
inputs:
script: 'dotnet workload install aspire'
When the .NET Aspire workload is correctly installed on the runners, you can just use the normal dotnet test command to run the unit-tests for your .NET Aspire Resource.
π Challenge
When developing a new frontend and backend application and orchestrate these using .NET Aspire, you are depending on the progress from the real backend implementation (which can be a ProjectResource or also an ContainerResource). Therefor it would be very useful to have a mocking service which can be switched in place of the backend application to stub and simulate the calls from the frontend so that the frontend development can be done in parallel.
Also configuring this new mocking service as a distributed .NET Aspire resource should be easy and straightforward.
To meet these requirements, I did create an .NET Aspire extension for WireMock.Net which makes this possible. In the next chapter I’ll zoom-in on the details and how this is implemented.
π» WireMock.Net.Aspire
Class Overview
Code
- WireMockServerResource: This is the main resource that represents a WireMock.Net Server running as a Container (this means that it extends the ContainerResource). Note that this resource also implements the IResourceWithServiceDiscovery interface in order to expose the http endpoint where this WireMock.Net Server is accessible.
- WireMockServerArguments: The minimal required argument to construct a .NET Aspire Resource is the “name”. However for this solution it’s also required to provide the settings for the WireMock.Net Server, for this reason I’ve created a simple class WireMockServerArguments which contains some settings like the HTTP listen port and the optional admin username and password.
The main purpose from this WireMockServerArguments class is to define the settings and to convert these settings to command-line arguments which are passed to the container. - WireMockServerBuilderExtensions: This class provides extension methods for adding WireMock.Net Server resources to the application model. Like the
AddWireMock(this IDistributedApplicationBuilder builder, string name, int? port = null)which will add the WireMock.Net Server resource to the distributed application builder. - WireMockServerLifecycleHook: In case the Fluent Mapping Builder is used to build the mappings in C# code, a custom Lifecycle-hook on the AfterResourcesCreatedAsync is required to wait until the WireMock.Net Container has been fully started. Once it’s started (the
/__admin/healthendpoint does return success), the defined mappings in C# can be send towards the running WireMock.Net Container to define the mappings which need to be used.
Usage
The next code-snippet shows how you can setup you .NET Aspire project to use WireMock.Net with some defined mappings to mock the requests and responses.
So instead of this code for Program.cs:
using AspireApp1.AppHost;
var builder = DistributedApplication.CreateBuilder(args);
IResourceBuilder apiService = builder.AddProject("apiservice");
builder.AddProject("webfrontend")
.WithExternalHttpEndpoints()
.WithReference(apiService);
builder.Build().Run();
Use the next code for Program.cs to replace the real ApiService with a WireMock.Net instance.
using AspireApp1.AppHost;
var builder = DistributedApplication.CreateBuilder(args);
var apiService = builder
.AddWireMock("apiservice", WireMockServerArguments.DefaultPort)
.WithApiMappingBuilder(adminApiBuilder =>
{
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
adminApiBuilder.Given(b => b
.WithRequest(request => request
.UsingGet()
.WithPath("/weatherforecast2")
)
.WithResponse(response => response
.WithHeaders(h => h.Add("Content-Type", "application/json"))
.WithBodyAsJson(() => Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray())
)
);
return Task.CompletedTask;
});
builder.AddProject("webfrontend")
.WithExternalHttpEndpoints()
.WithReference(apiService);
builder.Build().Run();
π Summary
This blog post takes you through the process of building and testing your own .NET Aspire component, focusing on how to mock API services using WireMock.Net within a distributed .NET Aspire application. Starting with an introduction to .NET Aspire and its orchestration model, the essential resources and references that help manage cloud-native, distributed applications are explored.
Testing .NET Aspire and your own .NET Aspire components is fairly easy by using the DistributedApplicationTestingBuilder to create robust unit tests. By setting up a simple AppHost and utilizing lifecycle hooks, you can simulate various scenarios and ensure your application behaves as expected. This blog also discussed integrating these tests into a DevOps CI/CD pipelines using GitHub Actions and Azure Pipelines.
Addressing the challenge of parallel frontend and backend development, I introduced the WireMock.Net.Aspire extension. This useful tool allows you to replace real backend services with mocked ones, enabling frontend development to progress without dependency bottlenecks. By incorporating WireMock.Net into your .NET Aspire orchestration, you can define custom mappings and simulate API responses, facilitating a more flexible and efficient development process.
π Links
- GitHub project WireMock.Net
- BLog from Anthony Simmon about LifecycleHooks, ILogger and ResourceLoggerService.
π Notes
Some content in this blog is created with the help of an AI. I did review and revise the content where needed.
Written by: Stef Heyenrath
Stef started writing software for the Microsoft .NET framework in 2007. Over the years, he has developed into a Microsoft specialist with experience in: backend technologies such as .NET, NETStandard, ASP.NET, Ethereum, Azure, and other cloud providers. In addition he worked with several frontend technologies such as Blazor, React, Angular, Vue.js.
He is the author from WireMock.Net.
Mission: Writing quality and structured software with passion in a scrum team for technically challenging projects.
Want to know more about our experts? Contact us!


