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.
Resource fundamentals
Section titled “Resource fundamentals”All Aspire resources implement the IResource interface, which requires a Name property. The base Resource class provides a simple implementation you can inherit from.
Basic resource definition
Section titled “Basic resource definition”The simplest custom resource is a class that inherits from Resource:
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.
Common resource interfaces
Section titled “Common resource interfaces”Aspire provides several interfaces that give resources specific capabilities:
| Interface | Purpose |
|---|---|
IResourceWithConnectionString | Resource exposes a connection string for consumers |
IResourceWithEndpoints | Resource has network endpoints |
IResourceWithEnvironment | Resource can configure environment variables |
IResourceWithArgs | Resource accepts command-line arguments |
IResourceWithWaitSupport | Resource supports WaitFor orchestration |
IResourceWithParent | Resource has a parent resource (lifecycle binding) |
Implementing IResourceWithConnectionString
Section titled “Implementing IResourceWithConnectionString”Resources that expose connection strings for client applications should implement IResourceWithConnectionString:
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:
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}");}Creating extension methods
Section titled “Creating extension methods”By convention, resources are added to the app model via extension methods on IDistributedApplicationBuilder:
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.
Adding resource behavior
Section titled “Adding resource behavior”Resources are data containers by default. To add runtime behavior, use one of these approaches:
Using eventing subscribers
Section titled “Using eventing subscribers”The recommended approach for adding lifecycle behavior is implementing IDistributedApplicationEventingSubscriber:
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:
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);}Using inline event subscriptions
Section titled “Using inline event subscriptions”For simpler cases, you can subscribe to events directly on the resource builder:
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);}Resource state management
Section titled “Resource state management”Use ResourceNotificationService to publish state updates that appear in the dashboard:
await notification.PublishUpdateAsync(resource, state => state with{ State = KnownResourceStates.Running, StartTimeStamp = DateTime.UtcNow});Well-known states
Section titled “Well-known states”Aspire provides several well-known states in KnownResourceStates:
NotStarted- Resource hasn’t started yetStarting- Resource is initializingRunning- Resource is operationalStopping- Resource is shutting downExited- Resource has stoppedFailedToStart- Resource failed during startupWaiting- Resource is waiting for dependenciesHidden- Resource is hidden from the dashboard
Custom states
Section titled “Custom states”You can create custom states using ResourceStateSnapshot:
await notification.PublishUpdateAsync(resource, state => state with{ State = new ResourceStateSnapshot("Indexing", KnownResourceStateStyles.Info)});Initial state configuration
Section titled “Initial state configuration”Set the initial dashboard appearance using WithInitialState:
return builder.AddResource(resource) .WithInitialState(new CustomResourceSnapshot { ResourceType = "MailDev", CreationTimeStamp = DateTime.UtcNow, State = KnownResourceStates.NotStarted, Properties = [ new(CustomResourceKnownProperties.Source, "MailDev SMTP Server") ] });Excluding from manifest
Section titled “Excluding from manifest”If your resource is for local development only, exclude it from deployment manifests:
return builder.AddResource(resource) .ExcludeFromManifest();Resource relationships
Section titled “Resource relationships”Use relationships to organize how resources appear in the dashboard:
WithRelationship
Section titled “WithRelationship”Create custom relationships between resources for visual organization:
var api = builder.AddProject<Projects.Api>("api");var worker = builder.AddProject<Projects.Worker>("worker") .WithRelationship(api.Resource, "publishes-to");WithChildRelationship
Section titled “WithChildRelationship”Group related resources under a parent in the dashboard:
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.
Custom icons
Section titled “Custom icons”Use WithIconName to display a custom icon for your resource in the dashboard. Any Fluent UI system icon can be used:
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);See also
Section titled “See also”- AppHost eventing - Lifecycle events and subscribers
- Resource annotations - Creating and using annotations
- Resource examples - Real-world custom resource examples
- Custom resource commands - Adding dashboard commands