Created
October 21, 2025 07:57
-
-
Save jmezach/08ec8e54aea2b531bdb661f0949bc0c3 to your computer and use it in GitHub Desktop.
.NET Aspire database-per-tenant
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| var builder = DistributedApplication.CreateBuilder(args); | |
| var configurationDatabase = builder.AddConnectionString("ConfigurationDatabase"); | |
| var databasePerTenant = builder.AddTenantedConnectionString("DatabasePerTenant") | |
| .WithReference(configurationDatabase); | |
| var api = builder.AddProject<Projects.Api>("api") | |
| .WithReference(databasePerTenant); | |
| builder.Build().Run(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using Microsoft.Data.SqlClient; | |
| namespace Aspire.Hosting; | |
| public static class TenantedConnectionStringBuilderExtensions | |
| { | |
| public static IResourceBuilder<TenantedConnectionStringResource> AddTenantedConnectionString(this IDistributedApplicationBuilder builder, string name) | |
| { | |
| ArgumentNullException.ThrowIfNull(builder); | |
| ArgumentException.ThrowIfNullOrEmpty(name); | |
| var tenantedConnectionString = new TenantedConnectionStringResource(name); | |
| return builder.AddResource(tenantedConnectionString); | |
| } | |
| public static IResourceBuilder<TenantedConnectionStringResource> WithReference<TSource>(this IResourceBuilder<TenantedConnectionStringResource> builder, IResourceBuilder<TSource> connectionStringResource) | |
| where TSource : IResourceWithConnectionString | |
| { | |
| ArgumentNullException.ThrowIfNull(builder); | |
| ArgumentNullException.ThrowIfNull(connectionStringResource); | |
| connectionStringResource.OnResourceReady(async (resource, @event, ct) => | |
| { | |
| try | |
| { | |
| Console.WriteLine($"Loading tenanted connection strings for {builder.Resource.Name}..."); | |
| await LoadTenantedConnectionStringsAsync(builder, resource, ct); | |
| Console.WriteLine($"Finished loading tenanted connection strings for {builder.Resource.Name}: {builder.Resource.TenantConnectionStrings.Count} tenants found."); | |
| } | |
| catch (Exception ex) | |
| { | |
| Console.WriteLine($"Error loading tenanted connection strings for {builder.Resource.Name}: {ex}"); | |
| throw; | |
| } | |
| }); | |
| return builder; | |
| } | |
| private static async Task LoadTenantedConnectionStringsAsync(IResourceBuilder<TenantedConnectionStringResource> tenantedBuilder, IResourceWithConnectionString sourceResource, CancellationToken ct) | |
| { | |
| // I'll leave this an exercise to the reader as it is highly application specific | |
| // What's import though is that all relevant connection strings are added to the TenantedConnectionStringResource like so: | |
| // tenantedBuilder.Resource.TenantConnectionStrings.Add($"{tenantedBuilder.Resource.Name}-{tenantId}", connectionString); | |
| } | |
| public static IResourceBuilder<TDestination> WithReference<TDestination>(this IResourceBuilder<TDestination> builder, IResourceBuilder<TenantedConnectionStringResource> tenantedConnectionStringResource) | |
| where TDestination : IResourceWithEnvironment | |
| { | |
| ArgumentNullException.ThrowIfNull(builder); | |
| ArgumentNullException.ThrowIfNull(tenantedConnectionStringResource); | |
| builder.WithEnvironment(async context => | |
| { | |
| tenantedConnectionStringResource.Resource.TenantConnectionStrings.ToList().ForEach(kvp => | |
| { | |
| context.EnvironmentVariables[$"ConnectionStrings__{kvp.Key}"] = kvp.Value; | |
| }); | |
| }); | |
| return builder; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| namespace Aspire.Hosting.ApplicationModel; | |
| public class TenantedConnectionStringResource([ResourceName]string name) : Resource(name) | |
| { | |
| public string Name { get; } = name; | |
| internal Dictionary<string, string> TenantConnectionStrings { get; } = new(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment