Skip to content

Examples: Generated vs Manual

Preview Aspire ResourceKit Reviewed 2026-09-21 purview-dev/aspire-resourcekit apphost aspire cloud-native code-generation csharp developer-tools distributed-systems dotnet dotnet-aspire integration-testing resource-management roslyn source-generators testing

The repository ships two example AppHosts that compose the same set of resources in two different styles:

  • src/src/Example.AppHost — the source-generated pattern (attributes, no hand-written wiring).
  • src/src/Example.ManualAppHost — the manual pattern (hand-written HostKitBase and ResourceKitBase composition, no generator).

Both hosts build the same logical resources through Purview.Aspire.ResourceKit.Example constants in Example.ServiceDefaults/Platform.cs:

ResourceNameAspire type
Postgrespostgres (+ db database)AzurePostgresFlexibleServerResource
Azure Storageazure-storage (+ blob)AzureStorageResource
RedisredisAzureManagedRedisResource
Key VaultkvAzureKeyVaultResource
APIapiProjectResource (Projects.Example_Service)
Publish markerpublish-markerParameterResource

Key Vault and the publish marker are publish-only (IsResourceEnabled returns builder.ExecutionContext.IsPublishMode).

AppHost.cs registers everything with a single generated call:

var builder = DistributedApplication.CreateBuilder(args);
builder.AddAspireResourceKit();
var app = builder.Build();
await app.RunAsync();

The host kit is a one-line attribute declaration:

[HostKit]
sealed partial class ExampleHostKit;

Each resource is a [ResourceDefinition<TResource>] partial class that supplies the overrides. For example, the API kit also wires dependencies in ConfigureResource() and can now forward a populated options object to a destination resource:

[ResourceDefinition<Projects.Example_Service>(Platform.ResourceKits.API)]
sealed partial class ExampleAPIKit
{
protected override IResourceBuilder<ProjectResource> BuildResource(IDistributedApplicationBuilder builder) =>
builder.AddProject<Projects.Example_Service>(Name).WithUrl("/health", "Health");
protected override void ConfigureResource()
{
if (HostKit.PublishMarker.IsEnabled)
ResourceBuilder.WithEnvironment(Options.PublishEnvironmentVariableName, HostKit.PublishMarker);
ResourceBuilder.WithEnvironment(
OptionsHelper.Environment(
new DemoServiceEnvironmentOptions
{
Service = new()
{
Name = Name,
Enabled = true,
Labels =
{
["region"] = "west",
},
},
Replicas = [1, 2, 3],
Routes =
{
["health"] = "/health",
},
}
)
.Override(static o => o.Service.Name = "api")
.Ignore(static o => o.Service.Labels)
.Build()
);
ResourceBuilder.WithReference(HostKit.Postgres.Database).WaitFor(HostKit.Postgres.Database);
ResourceBuilder.WithReference(HostKit.AzureStorage.Blobs).WaitFor(HostKit.AzureStorage.Blobs);
if (HostKit.KeyVault.IsEnabled)
ResourceBuilder.WithReference(HostKit.KeyVault).WaitFor(HostKit.KeyVault);
if (HostKit.Redis.IsEnabled)
ResourceBuilder.WithReference(HostKit.Redis).WaitFor(HostKit.Redis);
}
partial class ExampleAPIKitOptions
{
[Required(AllowEmptyStrings = false)]
public string PublishEnvironmentVariableName { get; set; } = "PUBLISH_MARKER";
}
sealed class DemoServiceEnvironmentOptions
{
public DemoServiceOptions Service { get; set; } = new();
public List<int> Replicas { get; set; } = [];
public Dictionary<string, string> Routes { get; set; } = [];
}
sealed class DemoServiceOptions
{
public string Name { get; set; } = string.Empty;
public bool Enabled { get; set; }
public Dictionary<string, string> Labels { get; set; } = [];
}
}

The generator emits the host kit members, options, ResourceKitBase<TResource> base classes, and the extension method. See Generated Output.

AppHost.cs registers a host kit instance directly via the generic extension:

builder.AddAspireResourceKit<ExampleHostKit>();

The host kit is hand-written and composes the resource kits:

sealed class ExampleHostKit : HostKitBase<ExampleHostKit>
{
public AzureStorageKit AzureStorage { get; init; }
public ExampleAPIKit ExampleAPI { get; init; }
// ...
public ExampleHostKit()
{
AzureStorage = new(this);
ExampleAPI = new(this);
// ...
AddResource(AzureStorage);
AddResource(ExampleAPI);
// ...
}
}

Each resource kit is a hand-written partial class deriving from the runtime base, with a primary constructor that takes the host kit:

sealed partial class ExampleAPIKit(ExampleHostKit hostKit)
: ResourceKitBase<ExampleHostKit, ProjectResource>(hostKit, Platform.ResourceKits.API)
{
protected override IResourceBuilder<ProjectResource> BuildResource(IDistributedApplicationBuilder builder) =>
builder.AddProject<Projects.Example_Service>(Name);
protected override void ConfigureResource()
{
if (HostKit.PublishMarker.IsEnabled)
ResourceBuilder.WithEnvironment("PUBLISH_MARKER", HostKit.PublishMarker);
ResourceBuilder.WithReference(HostKit.Postgres.Database).WaitFor(HostKit.Postgres.Database);
// ...
}
}
ConsiderationGeneratedManual
BoilerplateMinimal (attributes + overrides)Explicit (base classes, constructors, AddResource)
OptionsGenerated, typed, bindableYou manage options yourself
DiagnosticsAnalyzer checks model rulesNo generator diagnostics
TestabilityBoth test wellLifecycle is directly unit-testable
Best forMost AppHostsLearning the model, or when you want full control

Both patterns produce the same runtime behavior: instantiate resources, Build enabled resources, Configure enabled resources.