Skip to content

ASP.NET Core namespace resolution

The ASP.NET Core client provides namespace resolution designed for multi-tenant web applications. It integrates with HTTP context and offers built-in resolvers for common request patterns.

If your multi-tenant setup is based on Arc Tenancy, you can map the current tenant to a namespace. See Arc Tenancy.

The default resolver reads the namespace from an HTTP header. Configure it through ChronicleAspNetCoreOptions:

using Microsoft.AspNetCore.Builder;
public static class NamespacesAspNetCoreHttpHeaderResolver
{
public static void Configure(WebApplicationBuilder builder) =>
builder.AddCratisChronicle(options =>
{
options.EventStore = "my-event-store";
options.WithHttpHeaderNamespaceResolver("x-cratis-tenant-id"); // Default header name
});
}

When the header is present, its value becomes the namespace for the request. If the header is missing, the default namespace is used.

The subdomain resolver extracts the namespace from the request host (for example, tenant1.example.com).

using Microsoft.AspNetCore.Builder;
public static class NamespacesAspNetCoreSubdomainResolver
{
public static void Configure(WebApplicationBuilder builder) =>
builder.AddCratisChronicle(options =>
{
options.EventStore = "my-event-store";
options.WithSubdomainNamespaceResolver();
});
}

You can configure a custom resolver either by type (DI) or using the IChronicleBuilder fluent API.

Pass a resolver instance using the IChronicleBuilder configure callback:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
public static class NamespacesAspNetCoreBuilderCustomResolver
{
public static void Configure(WebApplicationBuilder builder, IConfiguration someConfiguration) =>
builder.AddCratisChronicle(
configureOptions: options => options.EventStore = "my-event-store",
configure: b => b.WithNamespaceResolver(new CustomNamespaceResolver(someConfiguration)));
}

Configure the resolver type through ChronicleAspNetCoreOptions to let the DI container resolve it:

using Cratis.Chronicle.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
public static class NamespacesAspNetCoreTypeBasedCustomResolver
{
public static void Configure(WebApplicationBuilder builder) =>
builder.Services.Configure<ChronicleAspNetCoreOptions>(options =>
{
options.EventStore = "my-event-store";
options.EventStoreNamespaceResolverType = typeof(CustomNamespaceResolver);
});
}
  1. Resolver set via IChronicleBuilder.WithNamespaceResolver() or IChronicleBuilder.NamespaceResolver
  2. Type configuration (EventStoreNamespaceResolverType) when provided
  3. Default HTTP header resolver
using Cratis.Chronicle.Events;
using Cratis.Chronicle.EventSequences;
using Microsoft.AspNetCore.Builder;
[EventType]
public record NamespacesAspNetCoreItemAddedToCart(string ProductId, int Quantity);
public static class NamespacesAspNetCoreWebAppExample
{
public static void ConfigureApp(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.AddCratisChronicle(options =>
{
options.EventStore = "production-store";
options.WithHttpHeaderNamespaceResolver("x-tenant-id");
});
var app = builder.Build();
app.MapPost("/api/cart/{cartId}/items", async (string cartId, IEventLog eventLog) =>
{
var itemAdded = new NamespacesAspNetCoreItemAddedToCart(ProductId: "product-123", Quantity: 1);
await eventLog.Append(cartId, itemAdded);
return Microsoft.AspNetCore.Http.Results.Ok();
});
app.Run();
}
}

For non-web contexts, see DotNET client usage.