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
});
}

View C# snippet source on GitHub

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();
});
}

View C# snippet source on GitHub

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

using Cratis.Chronicle;
using Microsoft.Extensions.Configuration;
public class CustomNamespaceResolver : IEventStoreNamespaceResolver
{
readonly IConfiguration _configuration;
public CustomNamespaceResolver(IConfiguration configuration)
{
_configuration = configuration;
}
public EventStoreNamespaceName Resolve() =>
_configuration["Tenant:Namespace"] ?? EventStoreNamespaceName.Default;
}

View C# snippet source on GitHub

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)));
}

View C# snippet source on GitHub

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);
});
}

View C# snippet source on GitHub

  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();
}
}

View C# snippet source on GitHub

For non-web contexts, see DotNET client usage.