Skip to content

Orleans and Aspire integration

The Aspire.Hosting.Orleans package models an Orleans cluster and its backing services in an Aspire AppHost. Aspire supplies cluster identity, endpoints, provider configuration, service discovery, dependency ordering, and observability context to silo and client projects.

Use Aspire when you want a repeatable local distributed environment or already use an AppHost to describe deployment resources. Aspire orchestrates Orleans; it doesn’t replace Orleans clustering, storage, reminder, or stream providers. See Install the Aspire CLI for the supported toolchain.

Reference Aspire.Hosting.Orleans and the Aspire integrations for the resources you use:

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.4.6" />
<PackageReference Include="Aspire.Hosting.Orleans" Version="13.4.6" />
<PackageReference Include="Aspire.Hosting.Redis" Version="13.4.6" />
</ItemGroup>

Define a clustering resource and an Orleans resource, then reference Orleans from the silo project:

public static void BasicOrleansCluster(string[] args)
{
var builder = DistributedApplication.CreateBuilder(args);
// Add Redis for Orleans clustering
var redis = builder.AddRedis("orleans-redis");
// Define the Orleans resource with Redis clustering
var orleans = builder.AddOrleans("cluster")
.WithClustering(redis);
// Add the Orleans silo project
builder.AddProject<Projects.Silo>("silo")
.WithReference(orleans)
.WaitFor(redis)
.WithReplicas(3);
builder.Build().Run();
}

.WithReplicas(3) starts three local silo replicas. .WaitFor(redis) prevents the silo project from starting before Redis is ready.

Add only the capabilities the application needs:

public static void OrleansWithStorageAndReminders(string[] args)
{
var builder = DistributedApplication.CreateBuilder(args);
var redis = builder.AddRedis("orleans-redis");
var orleans = builder.AddOrleans("cluster")
.WithClustering(redis)
.WithGrainStorage("Default", redis)
.WithGrainStorage("PubSubStore", redis)
.WithReminders(redis);
builder.AddProject<Projects.Silo>("silo")
.WithReference(orleans)
.WaitFor(redis)
.WithReplicas(3);
builder.Build().Run();
}

The named grain storage resources correspond to named Orleans providers such as Default and PubSubStore.

Register the keyed Aspire client for every backing resource consumed by Orleans, then call parameterless UseOrleans:

public static void BasicSiloConfiguration(string[] args)
{
var builder = Host.CreateApplicationBuilder(args);
// Add Aspire service defaults (OpenTelemetry, health checks, etc.)
builder.AddServiceDefaults();
// Add the Aspire Redis client for Orleans
builder.AddKeyedRedisClient("orleans-redis");
// Configure Orleans - Aspire injects all configuration automatically
builder.UseOrleans();
builder.Build().Run();
}

The AppHost injects the Orleans configuration hierarchy. Orleans binds cluster identity, endpoints, clustering, reminders, streaming, grain storage, and grain directory configuration from it.

Use the UseOrleans delegate only for configuration that the AppHost doesn’t model, such as application-specific options or custom services.

Create a client-only view of the Orleans resource with .AsClient():

public static void SeparateSiloAndClient(string[] args)
{
var builder = DistributedApplication.CreateBuilder(args);
var redis = builder.AddRedis("orleans-redis");
var orleans = builder.AddOrleans("cluster")
.WithClustering(redis)
.WithGrainStorage("Default", redis);
// Backend Orleans silo cluster
var silo = builder.AddProject<Projects.Silo>("backend")
.WithReference(orleans)
.WaitFor(redis)
.WithReplicas(5);
// Frontend web project as Orleans client
builder.AddProject<Projects.Client>("frontend")
.WithReference(orleans.AsClient()) // Client-only reference
.WaitFor(silo);
builder.Build().Run();
}

In the client project, register the keyed resource client and call parameterless UseOrleansClient:

public static void BasicClientConfiguration(string[] args)
{
var builder = Host.CreateApplicationBuilder(args);
builder.AddServiceDefaults();
builder.AddKeyedRedisClient("orleans-redis");
// Configure Orleans client - Aspire injects clustering configuration automatically
builder.UseOrleansClient();
builder.Build().Run();
}

The client receives the same cluster identity and clustering provider settings as the silos, but doesn’t receive silo hosting capabilities.

This compiled example uses Azurite for local Azure Storage development:

public static void AzureStorageWithAspire(string[] args)
{
var builder = DistributedApplication.CreateBuilder(args);
// Add Azure Storage for Orleans
var storage = builder.AddAzureStorage("orleans-storage")
.RunAsEmulator(); // Use Azurite emulator for local development
var tables = storage.AddTables("orleans-tables");
var orleans = builder.AddOrleans("cluster")
.WithClustering(tables)
.WithGrainStorage("Default", tables)
.WithReminders(tables);
builder.AddProject<Projects.Silo>("silo")
.WithReference(orleans)
.WaitFor(storage)
.WithReplicas(3);
builder.Build().Run();
}

Register the matching Azure Tables client in the silo:

public static void AzureStorageConfiguration(string[] args)
{
var builder = Host.CreateApplicationBuilder(args);
builder.AddServiceDefaults();
builder.AddKeyedAzureTableServiceClient("orleans-tables");
builder.UseOrleans();
builder.Build().Run();
}

.RunAsEmulator() is a local-development choice. For production, bind the Azure Storage resource to a real account and configure identity and access in the deployment environment. Don’t copy emulator configuration into a production AppHost.

The same principle applies to Redis and databases: the AppHost resource can launch a local container during development and bind to a managed service in deployment.

For automatically configured external resources, the AppHost needs the corresponding Aspire hosting integration. The silo or client needs both the Orleans provider package and the Aspire client integration, and it must register the client using the Aspire resource name.

ResourceApplication registrationSupported automatic Orleans configuration
RedisAddKeyedRedisClientClustering, grain storage, reminders, and grain directories
Azure TablesAddKeyedAzureTableServiceClientClustering, grain storage, reminders, and grain directories
Azure BlobsAddKeyedAzureBlobServiceClientGrain storage
ADO.NET databaseConfigure the Orleans provider from the injected connection stringClustering, grain storage, and reminders require manual configuration
In-memoryNoneDevelopment clustering, grain storage, reminders, and streaming

The resulting provider support matrix is:

CapabilityRedisAzure TablesAzure BlobsADO.NETIn-memory
ClusteringAutomaticAutomaticNoManualDevelopment only
Grain storageAutomaticAutomaticAutomaticManualDevelopment only
RemindersAutomaticAutomaticNoManualDevelopment only
Grain directoryAutomaticAutomaticNoNoNo

ADO.NET resource types don’t infer the AdoNet provider name expected by Orleans, and Aspire.Hosting.Orleans doesn’t expose an API to override the inferred name. Reference the database resource directly from the silo project so that Aspire injects its connection string:

public static void AdoNetAppHost(string[] args)
{
var builder = DistributedApplication.CreateBuilder(args);
// Add a SQL Server instance and database.
// Note: Aspire infers the Orleans provider type from the resource class name
// (SqlServerDatabaseResource → "SqlServerDatabase"), which does not match
// the Orleans provider name "AdoNet".
//
// There is no public API to override this inference in the current version
// of Aspire.Hosting.Orleans. As a workaround, configure the Orleans providers
// manually in the silo using UseOrleans(siloBuilder => {...}) and read the
// connection string from IConfiguration.
var sql = builder.AddSqlServer("sql");
var db = sql.AddDatabase("orleans-db");
// Pass the database resource so Aspire injects ConnectionStrings__orleans-db.
// Then configure Orleans manually in the silo (see silo example).
builder.AddProject<Projects.Silo>("silo")
.WithReference(db)
.WaitFor(sql);
builder.Build().Run();
}

Configure the Orleans ADO.NET providers from that connection string:

public static void AdoNetSilo(string[] args)
{
var builder = Host.CreateApplicationBuilder(args);
builder.AddServiceDefaults();
// Configure Orleans manually because Aspire cannot automatically wire ADO.NET
// providers — provider type inference produces "SqlServerDatabase" instead of
// the "AdoNet" provider name Orleans expects.
builder.UseOrleans(siloBuilder =>
{
var connectionString = builder.Configuration.GetConnectionString("orleans-db")!;
siloBuilder.UseAdoNetClustering(options =>
{
options.Invariant = "Microsoft.Data.SqlClient";
options.ConnectionString = connectionString;
});
siloBuilder.AddAdoNetGrainStorageAsDefault(options =>
{
options.Invariant = "Microsoft.Data.SqlClient";
options.ConnectionString = connectionString;
});
siloBuilder.UseAdoNetReminderService(options =>
{
options.Invariant = "Microsoft.Data.SqlClient";
options.ConnectionString = connectionString;
});
});
builder.Build().Run();
}

Register an Aspire database client separately only when application code also consumes that database client. Orleans ADO.NET providers use their configured connection string directly.

Redis and Azure Tables can back named grain directories. This example configures a Redis grain directory in the AppHost:

public static void GrainDirectoryAppHost(string[] args)
{
var builder = DistributedApplication.CreateBuilder(args);
var redis = builder.AddRedis("orleans-redis");
var orleans = builder.AddOrleans("cluster")
.WithClustering(redis)
.WithGrainDirectory("MyDirectory", redis);
builder.AddProject<Projects.Silo>("silo")
.WithReference(orleans)
.WaitFor(redis);
builder.Build().Run();
}

Register the Redis client with the same resource name in the silo:

public static void GrainDirectorySilo(string[] args)
{
var builder = Host.CreateApplicationBuilder(args);
builder.AddServiceDefaults();
builder.AddKeyedRedisClient("orleans-redis");
builder.UseOrleans();
builder.Build().Run();
}

AddOrleans produces standard Orleans configuration. The application projects still call UseOrleans or UseOrleansClient, and Orleans validates the resulting provider configuration at startup. You can inspect injected environment variables in the Aspire dashboard when diagnosing a missing provider, keyed resource, or endpoint.

Common AppHost operations include:

OperationPurpose
AddOrleans(name)Define an Orleans cluster resource.
WithClustering(resource)Select the membership and gateway provider.
WithGrainStorage(name, resource)Add named grain storage.
WithReminders(resource)Add a durable reminder provider.
WithStreaming(name, resource)Add a named stream provider.
WithGrainDirectory(name, resource)Add a named grain directory.
AsClient()Reference the cluster from a client-only project.
WithReference(orleans)Inject Orleans configuration into a project.

Consult the Aspire Orleans integration reference for resource types and overloads supported by your Aspire version.

Set stable service and cluster identifiers for environments that must interoperate across restarts and rolling deployments:

public static void ExplicitClusterIds(string[] args)
{
var builder = DistributedApplication.CreateBuilder(args);
var redis = builder.AddRedis("orleans-redis");
var orleans = builder.AddOrleans("cluster")
// Set stable IDs for rolling deployments and cross-restart compatibility.
// If omitted, random IDs are generated per run — fine for development,
// but problematic in production because silos from different runs
// will not recognize each other.
.WithClusterId("my-cluster")
.WithServiceId("my-service")
.WithClustering(redis);
builder.AddProject<Projects.Silo>("silo")
.WithReference(orleans)
.WaitFor(redis)
.WithReplicas(3);
builder.Build().Run();
}
  • Treat the AppHost as a resource model, not as a substitute for durable services.
  • Use managed identities or workload identities instead of embedding secrets.
  • Keep ServiceId stable and isolate environments with ClusterId.
  • Run multiple silo replicas across failure domains.
  • Configure readiness, telemetry export, and graceful termination in each application project.
  • Match keyed service names exactly between the AppHost and application projects.