Core abstractions and contracts for building multi-tenant SaaS applications
$ dotnet add package SaasSuite.CoreCore abstractions and contracts for building multi-tenant SaaS applications in .NET.
SaasSuite.Core provides the foundational interfaces, models, and services for implementing multi-tenancy in .NET applications. It defines the core contracts for tenant resolution, isolation policies, and tenant context management without imposing specific implementation details.
dotnet add package SaasSuite.Core
var builder = WebApplication.CreateBuilder(args);
// Register SaasSuite.Core services
builder.Services.AddSaasCore();
var app = builder.Build();
// Add tenant resolution middleware
app.UseSaasResolution();
app.Run();
using SaasSuite.Core.Interfaces;
using SaasSuite.Core.Services;
// Resolve tenant from HTTP headers
builder.Services.AddSingleton<ITenantResolver>(sp =>
{
var httpContextAccessor = sp.GetRequiredService<IHttpContextAccessor>();
return new HttpHeaderTenantResolver(httpContextAccessor);
});
using SaasSuite.Core;
using SaasSuite.Core.Interfaces;
app.MapGet("/api/tenant-info", (ITenantAccessor tenantAccessor) =>
{
var tenant = tenantAccessor.TenantContext;
if (tenant == null)
return Results.BadRequest("No tenant context");
return Results.Ok(new
{
tenantId = tenant.TenantId.Value,
tenantName = tenant.Name
});
});
Strongly-typed identifier for tenants with implicit string conversions:
TenantId tenantId = new TenantId("tenant-123");
string id = tenantId; // Implicit conversion
Holds the current tenant information:
public class TenantContext
{
public TenantId TenantId { get; set; }
public string Name { get; set; }
public string Identifier { get; set; }
public Dictionary<string, object> Properties { get; set; }
}
Define how tenant data is isolated:
public class MyIsolationPolicy : IIsolationPolicy
{
public IsolationLevel GetIsolationLevel(TenantId tenantId)
{
// Enterprise tenants get dedicated databases
if (IsEnterpriseTenant(tenantId))
return IsolationLevel.Dedicated;
return IsolationLevel.Shared;
}
public Task<bool> ValidateAccessAsync(
TenantId tenantId,
string resourceId,
CancellationToken cancellationToken = default)
{
// Validate tenant can access resource
return Task.FromResult(true);
}
}
Resolves tenant ID from the current execution context:
public interface ITenantResolver
{
Task<TenantId?> ResolveTenantIdAsync(CancellationToken cancellationToken = default);
}
Persistence operations for tenant data:
public interface ITenantStore
{
Task<TenantInfo?> GetByIdAsync(TenantId tenantId, CancellationToken cancellationToken = default);
Task<TenantInfo?> GetByIdentifierAsync(string identifier, CancellationToken cancellationToken = default);
Task SaveAsync(TenantInfo tenant, CancellationToken cancellationToken = default);
Task RemoveAsync(TenantId tenantId, CancellationToken cancellationToken = default);
}
Access point for the current tenant context:
public interface ITenantAccessor
{
TenantContext? TenantContext { get; }
}
Schedule maintenance periods for tenants:
using SaasSuite.Core.Interfaces;
public class MaintenanceController : ControllerBase
{
private readonly IMaintenanceService _maintenanceService;
public MaintenanceController(IMaintenanceService maintenanceService)
{
_maintenanceService = maintenanceService;
}
[HttpPost("schedule")]
public async Task<IActionResult> ScheduleMaintenance(
string tenantId,
DateTime startTime,
int durationMinutes)
{
await _maintenanceService.ScheduleMaintenanceAsync(
new TenantId(tenantId),
startTime,
TimeSpan.FromMinutes(durationMinutes));
return Ok();
}
[HttpGet("is-under-maintenance")]
public async Task<IActionResult> CheckMaintenance(string tenantId)
{
var isUnderMaintenance = await _maintenanceService
.IsUnderMaintenanceAsync(new TenantId(tenantId));
return Ok(new { underMaintenance = isUnderMaintenance });
}
}
Enrich logs and metrics with tenant context:
public class TenantTelemetryEnricher : ITelemetryEnricher
{
public void Enrich(IDictionary<string, object> telemetryData, TenantContext context)
{
telemetryData["TenantId"] = context.TenantId.Value;
telemetryData["TenantName"] = context.Name;
telemetryData["IsolationLevel"] = context.Properties
.TryGetValue("IsolationLevel", out var level) ? level : "Unknown";
}
}
SaasSuite.Core targets:
This package is licensed under the Apache License 2.0. See the LICENSE file in the repository root for details.