Skip to content
Docs Try Aspire

Aspire custom resources

Creating custom resources allows you to extend Aspire to model components that aren’t covered by built-in integrations. This guide covers the patterns and APIs for building your own resource types.

All Aspire resources implement the IResource interface, which requires a Name property. The base Resource class provides a simple implementation you can inherit from.

The simplest custom resource is a class that inherits from Resource:

MailDevResource.cs
public sealed class MailDevResource(string name) : Resource(name);

This creates a resource that can be added to the app model but doesn’t have any special behavior. You’ll typically want to implement additional interfaces to add capabilities.

Aspire provides several interfaces that give resources specific capabilities:

InterfacePurpose
IResourceWithConnectionStringResource exposes a connection string for consumers
IResourceWithEndpointsResource has network endpoints
IResourceWithEnvironmentResource can configure environment variables
IResourceWithArgsResource accepts command-line arguments
IResourceWithWaitSupportResource supports WaitFor orchestration
IResourceWithParentResource has a parent resource (lifecycle binding)

Implementing IResourceWithConnectionString

Section titled “Implementing IResourceWithConnectionString”

Resources that expose connection strings for client applications should implement IResourceWithConnectionString:

MailDevResource.cs
public sealed class MailDevResource(string name, EndpointReference smtpEndpoint)
: Resource(name), IResourceWithConnectionString
{
public ReferenceExpression ConnectionStringExpression =>
ReferenceExpression.Create(
$"smtp://{smtpEndpoint.Property(EndpointProperty.Host)}:{smtpEndpoint.Property(EndpointProperty.Port)}");
}

For resources with authentication, include credentials in the connection string:

InfluxDbResource.cs
public sealed class InfluxDbResource : Resource, IResourceWithConnectionString
{
private readonly EndpointReference _endpoint;
private readonly ParameterResource _token;
public InfluxDbResource(
string name,
EndpointReference endpoint,
ParameterResource token) : base(name)
{
_endpoint = endpoint;
_token = token;
}
public ReferenceExpression ConnectionStringExpression =>
ReferenceExpression.Create(
$"Endpoint={_endpoint.Property(EndpointProperty.UriString)};" +
$"Token={_token}");
}

By convention, resources are added to the app model via extension methods on IDistributedApplicationBuilder:

MailDevExtensions.cs
public static class MailDevExtensions
{
public static IResourceBuilder<MailDevResource> AddMailDev(
this IDistributedApplicationBuilder builder,
[ResourceName] string name,
int? smtpPort = null)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(name);
var resource = new MailDevResource(name);
return builder.AddResource(resource)
.WithImage("maildev/maildev", "2.1.0")
.WithHttpEndpoint(targetPort: 1080, name: "http")
.WithEndpoint(targetPort: 1025, port: smtpPort, name: "smtp");
}
}

The [ResourceName] attribute enables IDE tooling and validation for resource names.

Resources are data containers by default. To add runtime behavior, use one of these approaches:

The recommended approach for adding lifecycle behavior is implementing IDistributedApplicationEventingSubscriber:

MailDevEventingSubscriber.cs
public sealed class MailDevEventingSubscriber(
ResourceNotificationService notification,
ResourceLoggerService loggerService)
: IDistributedApplicationEventingSubscriber
{
public Task SubscribeAsync(
IDistributedApplicationEventing eventing,
DistributedApplicationExecutionContext context,
CancellationToken cancellationToken)
{
eventing.Subscribe<AfterResourcesCreatedEvent>(async (@event, ct) =>
{
foreach (var resource in context.Model.Resources.OfType<MailDevResource>())
{
var logger = loggerService.GetLogger(resource);
logger.LogInformation("MailDev server ready at {Name}", resource.Name);
await notification.PublishUpdateAsync(resource, s => s with
{
State = KnownResourceStates.Running,
StartTimeStamp = DateTime.UtcNow
});
}
});
return Task.CompletedTask;
}
}

For the full list of available events and advanced patterns, see AppHost eventing.

Register the subscriber in your extension method:

MailDevExtensions.cs
public static IResourceBuilder<MailDevResource> AddMailDev(
this IDistributedApplicationBuilder builder,
[ResourceName] string name,
int? smtpPort = null)
{
builder.Services.TryAddEventingSubscriber<MailDevEventingSubscriber>();
var resource = new MailDevResource(name);
return builder.AddResource(resource);
}

For simpler cases, you can subscribe to events directly on the resource builder:

AppHost.cs
public static IResourceBuilder<MailDevResource> AddMailDev(
this IDistributedApplicationBuilder builder,
[ResourceName] string name)
{
var resource = new MailDevResource(name);
builder.Eventing.Subscribe<AfterResourcesCreatedEvent>(resource, async (@event, ct) =>
{
// Initialize mail server, create test mailboxes, etc.
});
return builder.AddResource(resource);
}

Use ResourceNotificationService to publish state updates that appear in the dashboard:

Publishing state updates
await notification.PublishUpdateAsync(resource, state => state with
{
State = KnownResourceStates.Running,
StartTimeStamp = DateTime.UtcNow
});

Aspire provides several well-known states in KnownResourceStates:

  • NotStarted - Resource hasn’t started yet
  • Starting - Resource is initializing
  • Running - Resource is operational
  • Stopping - Resource is shutting down
  • Exited - Resource has stopped
  • FailedToStart - Resource failed during startup
  • Waiting - Resource is waiting for dependencies
  • Hidden - Resource is hidden from the dashboard

You can create custom states using ResourceStateSnapshot:

Custom state example
await notification.PublishUpdateAsync(resource, state => state with
{
State = new ResourceStateSnapshot("Indexing", KnownResourceStateStyles.Info)
});

Set the initial dashboard appearance using WithInitialState:

Setting initial state
return builder.AddResource(resource)
.WithInitialState(new CustomResourceSnapshot
{
ResourceType = "MailDev",
CreationTimeStamp = DateTime.UtcNow,
State = KnownResourceStates.NotStarted,
Properties = [
new(CustomResourceKnownProperties.Source, "MailDev SMTP Server")
]
});

If your resource is for local development only, exclude it from deployment manifests:

Excluding from deployment
return builder.AddResource(resource)
.ExcludeFromManifest();

Use relationships to organize how resources appear in the dashboard:

Create custom relationships between resources for visual organization:

AppHost.cs
var api = builder.AddProject<Projects.Api>("api");
var worker = builder.AddProject<Projects.Worker>("worker")
.WithRelationship(api.Resource, "publishes-to");

Group related resources under a parent in the dashboard:

AppHost.cs
var postgres = builder.AddPostgres("postgres");
var catalogDb = postgres.AddDatabase("catalog");
// Custom resources can establish parent-child relationships:
var mailDev = builder.AddMailDev("mail")
.WithChildRelationship(catalogDb);

For more on how the Aspire Dashboard displays resources, see Dashboard overview.

Use WithIconName to display a custom icon for your resource in the dashboard. Any Fluent UI system icon can be used:

Setting custom dashboard icons
return builder.AddResource(resource)
.WithIconName("mail"); // Uses the "mail" Fluent UI icon
// Or specify a variant (Filled is default, Regular is outline-only)
return builder.AddResource(resource)
.WithIconName("mail", IconVariant.Regular);