### Install Finbuckle.MultiTenant.AspNetCore package Source: https://www.finbuckle.com/multitenant/docs/AspNetCore Install the Finbuckle.MultiTenant.AspNetCore package for ASP.NET Core integration. This package depends on Finbuckle.MultiTenant and includes ASP.NET Core-specific strategies. ```bash dotnet add package Finbuckle.MultiTenant.AspNetCore ``` -------------------------------- ### Typical Configuration for ASP.NET Core with Finbuckle.MultiTenant Source: https://www.finbuckle.com/multitenant/docs/ConfigurationAndUsage A basic ASP.NET Core setup using AddMultiTenant with a host strategy and configuration store. This registers dependencies, adds middleware, and should be placed before other app services and middleware. ```csharp using Finbuckle.MultiTenant; var builder = WebApplication.CreateBuilder(args); // ...add app services // add MultiTenant services builder.Services.AddMultiTenant() .WithHostStrategy() .WithConfigurationStore(); var app = builder.Build(); // add the MultiTenant middleware app.UseMultiTenant(); // ...add other middleware app.Run(); ``` -------------------------------- ### Install Finbuckle.MultiTenant.AspNetCore package Source: https://www.finbuckle.com/MultiTenant Use this command to add the main Finbuckle.MultiTenant package to an ASP.NET Core application. ```dotnet-cli dotnet add package Finbuckle.MultiTenant.AspNetCore ``` -------------------------------- ### Configure WithPerTenantAuthentication() in ASP.NET Core Source: https://www.finbuckle.com/multitenant/docs/Authentication Shows the setup for using WithPerTenantAuthentication() after AddMultiTenant(). Defines an AppTenantInfo class with properties for authentication options, then configures authentication and MultiTenant services. The code includes using statements and the full Program.cs setup. ```csharp using Finbuckle.MultiTenant.Abstractions; using Finbuckle.MultiTenant.AspNetCore.Extensions; using Finbuckle.MultiTenant.Extensions; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.OpenIdConnect; public class AppTenantInfo : TenantInfo { public string? ChallengeScheme { get; set; } public string? CookieLoginPath { get; set; } public string? CookieLogoutPath { get; set; } public string? CookieAccessDeniedPath { get; set; } public string? OpenIdConnectAuthority { get; set; } public string? OpenIdConnectClientId { get; set; } public string? OpenIdConnectClientSecret { get; set; } public string? JwtAuthority { get; set; } } var builder = WebApplication.CreateBuilder(args); // ...add app services // add authentication services builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie() .AddOpenIdConnect(); // add MultiTenant services builder.Services.AddMultiTenant() .WithRouteStrategy() .WithConfigurationStore() .WithPerTenantAuthentication(); var app = builder.Build(); // add the MultiTenant middleware app.UseMultiTenant(); // ...add other middleware app.Run(); ``` -------------------------------- ### Per-tenant options in controller Source: https://www.finbuckle.com/multitenant/docs/Options The same controller example but now options are specific to the current tenant when using per-tenant configuration. ```csharp public class MyController : Controller { private readonly MyOptions _myOptions; public MyController(IOptionsMonitor optionsAccessor) { // _myOptions.Option1 and .Option2 will be specific to the current tenant. _myOptions = optionsAccessor.Value; } } ``` -------------------------------- ### Register MultiTenant services with AddMultiTenant Source: https://www.finbuckle.com/multitenant/docs/AspNetCore Register MultiTenant services in Program.cs using AddMultiTenant with at least one strategy and one store. This example uses WithHostStrategy() and WithConfigurationStore(). ```csharp using Finbuckle.MultiTenant; var builder = WebApplication.CreateBuilder(args); // ...add other app services builder.Services.AddMultiTenant() .WithHostStrategy() .WithConfigurationStore(); var app = builder.Build(); ``` -------------------------------- ### Configure MultiTenant with host strategy and configuration store Source: https://www.finbuckle.com/MultiTenant This is the minimal ASP.NET Core setup that registers MultiTenant services, adds the middleware, and uses the request host (subdomain) for resolution and appsettings.json for storage. Must be called before authentication/authorization middleware. ```csharp using Finbuckle.MultiTenant; var builder = WebApplication.CreateBuilder(args); // add app services... // add MultiTenant services builder.Services.AddMultiTenant() .WithHostStrategy() .WithConfigurationStore(); var app = builder.Build(); // add the MultiTenant middleware app.UseMultiTenant(); // add other middleware... app.Run(); ``` -------------------------------- ### ShortCircuitWhen() with custom predicate and optional redirect Source: https://www.finbuckle.com/multitenant/docs/AspNetCore Use the ShortCircuitWhen() extension on MultiTenantBuilder for advanced short-circuiting based on a custom predicate. The first example halts when an obsolete strategy is used or tenant is not resolved; the second adds a redirect to a relative URI. The predicate receives the HttpContext and can inspect StrategyInfo and IsResolved. ```csharp // Advanced short circuiting: if an obsolete strategy was used or when tenant not resolved. builder.Services.AddMultiTenant() .WithHostStrategy() .WithConfigurationStore() .ShortCircuitWhen(config => { config.Predicate = context => context.StrategyInfo?.Strategy is IMyCustomObsoleteStrategy || !context.IsResolved; }); // Including a redirect. builder.Services.AddMultiTenant() .WithHostStrategy() .WithConfigurationStore() .ShortCircuitWhen(config => { config.Predicate = context => context.StrategyInfo?.Strategy is IMyCustomObsoleteStrategy || !context.IsResolved; config.RedirectTo = new Uri("/tenant/notfound", UriKind.Relative); }); ``` -------------------------------- ### WithDelegateStrategy: Custom Tenant Resolution via Lambda Source: https://www.finbuckle.com/multitenant/docs/Strategies Configure a delegate strategy that determines the tenant identifier per request. The first example uses a simple lambda returning "initech". The second uses a typed lambda with HttpContext, extracting the "tenant" query parameter; it runs only when the runtime context is assignable to HttpContext, otherwise falls through to the next strategy. ```csharp // use custom logic to get the tenant identifier builder.Services.AddMultiTenant() .WithDelegateStrategy(context => Task.FromResult("initech"))... // or register with a typed lambda, HttpContext in this case; derived runtime types are also supported builder.Services.AddMultiTenant() .WithDelegateStrategy(httpContext => { var tenantIdentifier = httpContext.Request.Query["tenant"].FirstOrDefault(); return Task.FromResult(tenantIdentifier); })... ``` -------------------------------- ### Bypass with custom condition using BypassWhen Source: https://www.finbuckle.com/multitenant/docs/AspNetCore Use `BypassWhen()` to bypass tenant resolution based on a custom condition derived from `HttpContext`. This example bypasses for health check requests where the path starts with `/health`. ```csharp // Bypass resolution for health check requests. builder.Services.AddMultiTenant() .WithRouteStrategy() .WithConfigurationStore() .BypassWhen(options => { options.Predicate = ctx => ctx.Request.Path.StartsWithSegments("/health"); }); ``` -------------------------------- ### Exclude controller/action with ExcludeFromMultiTenantResolutionAttribute Source: https://www.finbuckle.com/multitenant/docs/AspNetCore Use the `ExcludeFromMultiTenantResolutionAttribute` attribute on controllers or action methods to exclude them from tenant resolution. This example excludes an entire controller and a specific action. Bypassing runs before tenant resolution. ```csharp // Exclude an entire controller. [ExcludeFromMultiTenantResolution] public class DashboardController : Controller { // Or exclude just a specific action. [ExcludeFromMultiTenantResolution] public ActionResult Index() { return View(); } } ``` -------------------------------- ### Exclude endpoints with ExcludeFromMultiTenantResolution Source: https://www.finbuckle.com/multitenant/docs/AspNetCore Use the `ExcludeFromMultiTenantResolution` extension method on `IEndpointConventionBuilder` to exclude specific endpoints from tenant resolution. This example excludes OpenAPI, a specific endpoint, a group, and static assets. Bypassing runs before tenant resolution. ```csharp var app = builder.Build(); // Exclude OpenApi endpoints. app.MapOpenApi() .ExcludeFromMultiTenantResolution(); // Exclude a specific endpoint. app.MapGet("/oops", () => "Oops! An error happened.") .ExcludeFromMultiTenantResolution(); // Exclude a group of endpoints. app.MapGroup("api/v{version:apiVersion}/dashboard") .ExcludeFromMultiTenantResolution(); // Exclude static asset endpoints. app.MapStaticAssets() .ExcludeFromMultiTenantResolution(); app.Run(); ``` -------------------------------- ### Build from source with git clone and dotnet build Source: https://www.finbuckle.com/MultiTenant/Docs Clone the Finbuckle.MultiTenant repository, change into the new directory, and compile with dotnet build. ```bash git clone https://github.com/Finbuckle/Finbuckle.MultiTenant.git cd Finbuckle.MultiTenant dotnet build ``` -------------------------------- ### Configure WithEchoStore Source: https://www.finbuckle.com/multitenant/docs/Stores Configure the Echo Store, a read-only store that returns a new tenant instance based on the identifier. AddAsync, UpdateAsync, RemoveAsync, and GetAllAsync throw NotImplementedException. ```csharp services.AddMultiTenant() .WithEchoStore(); ``` -------------------------------- ### Configure MyOptions with services.Configure Source: https://www.finbuckle.com/multitenant/docs/Options Show typical use of services.Configure with a delegate to set option values in a web application builder. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.Configure(options => options.Option1 = 1); // ...rest of app code ``` -------------------------------- ### Configure options per tenant Source: https://www.finbuckle.com/multitenant/docs/Options Shows the PerTenant variants of Configure, PostConfigure, and ConfigureAll methods. ```csharp using Finbuckle.MultiTenant.Extensions; // configure options per tenant builder.Services.ConfigurePerTenant((options, tenantInfo) => { options.Option1 = tenantInfo.Option1Value; options.Option2 = tenantInfo.Option2Value; }); // or configure named options per tenant builder.Services.ConfigurePerTenant("scheme2", (options, tenantInfo) => { options.Option1 = tenantInfo.Option1Value; options.Option2 = tenantInfo.Option2Value; }); // ConfigureAll options variant builder.Services.ConfigureAllPerTenant((options, tenantInfo) => { options.Option1 = tenantInfo.Option1Value; options.Option2 = tenantInfo.Option2Value; }); // can also configure post options, named post options, and all post options variants builder.Services.PostConfigurePerTenant((options, tenantInfo) => { options.Option1 = tenantInfo.Option1Value; options.Option2 = tenantInfo.Option2Value; }); builder.Services.PostConfigurePerTenant("scheme2", (options, tenantInfo) => { options.Option1 = tenantInfo.Option1Value; options.Option2 = tenantInfo.Option2Value; }); builder.Services.PostConfigureAllPerTenant((options, tenantInfo) => { options.Option1 = tenantInfo.Option1Value; options.Option2 = tenantInfo.Option2Value; }); ``` -------------------------------- ### Add Finbuckle.MultiTenant.EntityFrameworkCore package Source: https://www.finbuckle.com/multitenant/docs/EFCore Adds the Finbuckle.MultiTenant.EntityFrameworkCore package to the project, required for the multi-tenant functionality described in this section. ```bash dotnet add package Finbuckle.MultiTenant.EntityFrameworkCore ``` -------------------------------- ### Run unit tests with dotnet test Source: https://www.finbuckle.com/MultiTenant/Docs Run the unit tests from the command line using dotnet test from the solution directory. ```bash dotnet test ``` -------------------------------- ### Configure WithHeaderStrategy with default or custom header key Source: https://www.finbuckle.com/multitenant/docs/Strategies Configure the header strategy with the default header key `__tenant__` or a custom key. The first block uses the default, the second passes a custom header key. Both are called after `AddMultiTenant`. ```csharp // check for header "__tenant__" value builder.Services.AddMultiTenant() .WithHeaderStrategy()... ``` ```csharp // or check for custom header value builder.Services.AddMultiTenant() .WithHeaderStrategy("MyHeaderKey")... ``` -------------------------------- ### Configure a custom strategy with WithStrategy Source: https://www.finbuckle.com/multitenant/docs/Strategies Shows two ways to configure a custom IMultiTenantStrategy after AddMultiTenant. The first uses dependency injection with parameters, the second uses a factory method. The library decorates the strategy with logging and exception handling. ```csharp // configure a strategy with a given type builder.Services.AddMultiTenant() .WithStrategy(myParam1, myParam2)... // or configure a strategy with a factory method builder.Services.AddMultiTenant() .WithStrategy(sp => new MyStrategy())... ``` -------------------------------- ### Minimal appsettings.json configuration store entry Source: https://www.finbuckle.com/MultiTenant This JSON defines a tenant with an ID, identifier, and name. The identifier must match the one from the host strategy for the tenant to be resolved. ```json { "Finbuckle:MultiTenant:Stores:ConfigurationStore": { "Tenants": [ { "Id": "93f330717e5d4f039cd05da312d559cc", "Identifier": "initech", "Name": "Initech" } ] } } ``` -------------------------------- ### Access options via DI and service provider Source: https://www.finbuckle.com/multitenant/docs/Options Demonstrates injecting IOptionsMonitor in a controller constructor and accessing options via a service provider. ```csharp public class MyController : Controller { private readonly MyOptions _myOptions; public MyController(IOptionsMonitor optionsAccessor) { // same options regardless of the current tenant _myOptions = optionsAccessor.Value; } } ``` ```csharp var options = httpContext.RequestServices.GetRequiredService>().Value; ``` -------------------------------- ### Create tenant DbContext with MultiTenantDbContext.Create factory Source: https://www.finbuckle.com/multitenant/docs/EFCore Use the MultiTenantDbContext.Create factory to instantiate a tenant-specific DbContext without dependency injection, e.g., when looping through tenants. The factory supplies an IMultiTenantContextAccessor to the constructor; add DbContextOptions or other dependencies via optional arguments. Dispose the context when done, preferably with a using block or variable. ```csharp // create or otherwise obtain a tenant info instance var tenantInfo = new MyTenantInfo { Id = "id", Identifier = "identifier" }; // create a database context instance for the tenant var tenantDbContext = MultiTenantDbContext.Create(tenantInfo); // create a database context instance for the tenant with an instance of DbContextOptions var tenantDbContextWithOptions = MultiTenantDbContext.Create(tenantInfo, dbOptions); // create a database context instance for the tenant with an instance of other dependencies // the final parameter is params object[] so any number of dependency arguments can be used var tenantDbContextWithOptions = MultiTenantDbContext.Create(tenantInfo, dep1, dep2, dep3); // create a database context instance for the tenant with an instance pulled from a given service provider var tenantDbContextWithOptions = MultiTenantDbContext.Create(tenantInfo, serviceProvider); // create a database context instance for the tenant with an instance pulled from a given service provider // and provided explicitly via params object[] var tenantDbContextWithOptions = MultiTenantDbContext.Create(tenantInfo, serviceProvider, dep1, dep2, dep3); // loop through a bunch of tenant instances foreach (var tenant in tenants) { using var tenantDbContext = MultiTenantDbContext.Create(tenant); // do something with the database context } ``` -------------------------------- ### Configure per-tenant options with dependencies using OptionsBuilder Source: https://www.finbuckle.com/multitenant/docs/Options Use the OptionsBuilder API to configure per-tenant options with dependencies. The ConfigurePerTenant method takes a callback that receives the options, the dependency, and the tenant info. Note that MultiTenant supports up to four dependencies, unlike the standard OptionsBuilder which supports five. ```csharp // use OptionsBuilder API to configure per-tenant options with dependencies builder.Services.AddOptions("optionalName") .ConfigurePerTenant( (options, es, tenantInfo) => options.Option1 = DoSomethingWith(es, tenantInfo)); ``` -------------------------------- ### Configure Session Strategy with WithSessionStrategy Source: https://www.finbuckle.com/multitenant/docs/Strategies Configure the session strategy with the default session key '__tenant__' or a custom key. The strategy is configured as a singleton and requires the app to configure session state and set the session variable. It is typically used before a more expensive strategy to cache the tenant resolution. ```csharp // check for default "__tenant__" as the session key builder.Services.AddMultiTenant() .WithSessionStrategy()... // or check for a custom session key builder.Services.AddMultiTenant() .WithSessionStrategy("my-tenant-session-key")... ``` -------------------------------- ### Configure HttpRemoteStore with WithHttpRemoteStore Source: https://www.finbuckle.com/multitenant/docs/Stores Configure the HTTP remote store with WithHttpRemoteStore. The URI template must contain {__tenant__} or the identifier is appended. The store is read-only and throws NotImplementedException for write operations. A lambda can configure the HttpClient, including adding Polly resilience policies. ```csharp // append the identifier to the provided url builder.Services.AddMultiTenant() .WithHttpRemoteStore("https://remoteserver.com/")... ``` ```csharp // or template the identifier into a custom location builder.Services.AddMultiTenant() .WithHttpRemoteStore("https://remoteserver.com/{__tenant__}/getinfo")... ``` ```csharp // or modify the underlying `HttpClient` with a custom message handler and settings builder.Services.AddMultiTenant() .WithHttpRemoteStore("https://remoteserver.com/", httpClientBuilder => { httpClientBuilder.AddHttpMessageHandler(); httpClientBuilder.ConfigureHttpClient( client => { client.Timeout = TimeSpan.FromSeconds(10); }); }); ``` ```csharp // or add Polly support // via https://www.hanselman.com/blog/AddingResilienceAndTransientFaultHandlingToYourNETCoreHttpClientWithPolly.aspx builder.Services.AddMultiTenant() .WithHttpRemoteStore("https://remoteserver.com/", httpClientBuilder => { httpClientBuilder.AddTransientHttpErrorPolicy(policy => policyBuilder.RetryAsync(2)); }); ``` -------------------------------- ### Configuration store JSON format with Defaults and Tenants Source: https://www.finbuckle.com/multitenant/docs/Stores JSON configuration format for the configuration store. The section name is 'Finbuckle:MultiTenant:Stores:ConfigurationStore'. Fields in the 'Defaults' section are automatically copied into each tenant unless the tenant specifies its own value. For a custom implementation of TenantInfo, its properties are mapped from the JSON automatically. ```json { "Finbuckle:MultiTenant:Stores:ConfigurationStore": { "Defaults": { "ConnectionString": "default_connection_string" }, "Tenants": [ { "Id": "unique-id-0ff4daf", "Identifier": "tenant-1", "Name": "Tenant 1 Company Name", "ACustomProperty": "VIP Customer" }, { "Id": "unique-id-ao41n44", "Identifier": "tenant-2", "Name": "Name of Tenant 2", "ConnectionString": "tenant_specific_connection_string" } ] } } ``` -------------------------------- ### Register custom store with WithStore or factory method Source: https://www.finbuckle.com/multitenant/docs/Stores Shows two ways to register a custom IMultiTenantStore implementation: the generic WithStore method with constructor parameters, and the non-generic WithStore overload accepting a factory method. Both are called after AddMultiTenant in ConfigureServices. The library wraps the store with logging and exception handling, so custom stores should avoid adding their own logging or validation. ```csharp // register a custom store with the templated method builder.Services.AddMultiTenant() .WithStore(ServiceLifetime.Singleton, myParam1, myParam2)... // or register a custom store with the non-generic method, which accepts a factory method builder.Services.AddMultiTenant() .WithStore(ServiceLifetime.Singleton, sp => new MyStore())... ``` -------------------------------- ### Register WithConfigurationStore with default or custom path Source: https://www.finbuckle.com/multitenant/docs/Stores Registers the configuration store with the default root configuration and section name, or with a custom configuration path key. The store is case-insensitive when retrieving tenant information by tenant identifier and is read-only; calls to AddAsync, UpdateAsync, and RemoveAsync will throw a NotImplementedException. If the app is configured to reload its configuration when the source changes, the store will reflect the change. ```csharp builder.Services.AddMultiTenant() .WithConfigurationStore()... ``` ```csharp builder.Services.AddMultiTenant() .WithConfigurationStore("customConfigurationPathKey")... ``` -------------------------------- ### Configure Route Strategy with WithRouteStrategy Source: https://www.finbuckle.com/multitenant/docs/Strategies Configure the route strategy with default or custom route parameter names. The first block shows default and custom parameter usage, plus the optional UseRouting placement. The second block enables ambient route value promotion for link generation. ```csharp // use the default route parameter name "__tenant__" builder.Services.AddMultiTenant() .WithRouteStrategy()... // or set a different route parameter name of "MyTenantRouteParam" and do not add the tenant as an ambient route value builder.Services.AddMultiTenant() .WithRouteStrategy("MyTenantRouteParam", false)... // UseRouting is optional in ASP.NET Core, but if needed place before UseMultiTenant when the route strategy is used app.UseRouting(); app.UseMultiTenant(); ``` ```csharp builder.Services.AddMultiTenant() .WithRouteStrategy("tenant", useTenantAmbientRouteValue: true) .WithConfigurationStore(); ``` -------------------------------- ### Configure WithHostStrategy with default and custom patterns Source: https://www.finbuckle.com/multitenant/docs/Strategies Configure the host strategy in ASP.NET Core. The first call uses the default pattern (first host segment). The second uses a custom pattern to select the second-level domain segment. Both are chained after AddMultiTenant. ```csharp // check the first domain segment (e.g. subdomain) builder.Services.AddMultiTenant() .WithHostStrategy()... // check the second level domain segment (see 2nd example above) builder.Services.AddMultiTenant() .WithHostStrategy("*.__tenant__.?")... ``` -------------------------------- ### Configure EFCoreStore with WithEFCoreStore Source: https://www.finbuckle.com/multitenant/docs/Stores Configure the EFCore store by calling WithEFCoreStore after AddMultiTenant. The database context must derive from EFCoreStoreDbContext and is not itself multi-tenant. Case sensitivity is determined by the underlying EF Core database provider. ```csharp // configure dbcontext `MultiTenantStoreDbContext`, which derives from `EFCoreStoreDbContext` builder.Services.AddMultiTenant() .WithEFCoreStore()... ``` -------------------------------- ### Enable passkey support with Identity schema version 3 Source: https://www.finbuckle.com/multitenant/docs/Identity Configure IdentityOptions to set Stores.SchemaVersion to 3.0, enabling passkey support with multi-tenant configuration. No additional DbContext configuration is needed; MultiTenantIdentityDbContext detects the schema version. ```csharp services.AddOptions(); services.Configure(o => { // Use Identity schema version 3 to enable passkeys o.Stores.SchemaVersion = new Version(3, 0); }); ``` -------------------------------- ### ShortCircuitWhenTenantNotResolved() with optional redirect Source: https://www.finbuckle.com/multitenant/docs/AspNetCore Configure the MultiTenantMiddleware to short circuit the request pipeline when no tenant is resolved. The first call halts request handling; the second redirects to a relative URI. Use this after AddMultiTenant and note that endpoints not requiring a tenant must be excluded, otherwise they will never be reached. ```csharp // Simply short circuit the request, ending request handling. builder.Services.AddMultiTenant() .WithHostStrategy() .WithConfigurationStore() .ShortCircuitWhenTenantNotResolved(); // Short circuit and redirect to a specific Uri. builder.Services.AddMultiTenant() .WithHostStrategy() .WithConfigurationStore() .ShortCircuitWhenTenantNotResolved(new Uri("/tenant/notfound", UriKind.Relative)); ``` -------------------------------- ### Adding Data with MultiTenantDbContext and MultiTenantException Source: https://www.finbuckle.com/multitenant/docs/EFCore Shows how to add an entity to a tenant's context and then attempt to add the same entity to a different tenant, which throws a MultiTenantException on SaveChangesAsync. The entity's TenantId is set explicitly, and the context is created via MultiTenantDbContext.Create with a TenantInfo. ```csharp var myBlog = new Blog { TenantId = "1", Title = "My Blog" }; // Add the blog to a db context for a tenant. var myTenantInfo = new TenantInfo { Id = "1", Identifier = "tenant-1" }; var myDbContext = MultiTenantDbContext.Create(myTenantInfo); myDbContext.Blogs.Add(myBlog); myDbContext.SaveChanges(); // Try to add the same blog to a different tenant. var yourTenantInfo = new TenantInfo { Id = "2", Identifier = "tenant-2" }; var yourDbContext = MultiTenantDbContext.Create(yourTenantInfo); yourDbContext.Blogs.Add(myBlog); await yourDbContext.SaveChangesAsync(); // Throws MultiTenantException. ``` -------------------------------- ### Configure WithInMemoryStore with case sensitivity and tenants Source: https://www.finbuckle.com/multitenant/docs/Stores Configures the in-memory store with default case-insensitive matching or with case-sensitive matching and pre-populated tenants. The default store is empty and case-insensitive; the overload accepts an Action to set IsCaseSensitive and add tenants. ```csharp // set up a case-insensitive in-memory store. builder.Services.AddMultiTenant() .WithInMemoryStore()... // or make it case-sensitive and add tenants. builder.Services.AddMultiTenant() .WithInMemoryStore(options => { options.IsCaseSensitive = true; options.Tenants.Add(new TenantInfo { Id = "1", Identifier = "initech" }); options.Tenants.Add(new TenantInfo { Id = "2", Identifier = "acme" }); })... ``` -------------------------------- ### Define MyOptions class Source: https://www.finbuckle.com/multitenant/docs/Options Base options class with Option1 and Option2 properties. ```csharp public class MyOptions { public int Option1 { get; set; } public int Option2 { get; set; } } ``` -------------------------------- ### WithStaticStrategy: Always Use a Fixed Tenant Identifier Source: https://www.finbuckle.com/multitenant/docs/Strategies Configure a static strategy that always resolves to the tenant with identifier "MyTenant". This strategy runs last regardless of configuration order, useful for testing or fallback tenants. ```csharp builder.Services.AddMultiTenant() .WithStaticStrategy("MyTenant") ``` -------------------------------- ### Configure Claim Strategy with WithClaimStrategy Source: https://www.finbuckle.com/multitenant/docs/Strategies Configure the claim strategy with the default claim type '__tenant__' or a custom claim type. The strategy uses the default authentication scheme and does not set HttpContext.User, so authentication middleware should be used as normal. It does not work well with per-tenant cookie names. ```csharp // check for a claim type __tenant__ builder.Services.AddMultiTenant() .WithClaimStrategy()... // check for a custom claim type builder.Services.AddMultiTenant() .WithClaimStrategy("MyClaimType")... ``` -------------------------------- ### Retrieve a specific store from IEnumerable> Source: https://www.finbuckle.com/multitenant/docs/Stores Retrieves a specific store implementation from the injected collection of stores. Use when multiple stores are registered and you need to access a particular one, such as the ConfigurationStore. ```csharp public MyService(IEnumerable> stores) { _configurationStore = stores.OfType>().Single(); } ``` -------------------------------- ### Separate Databases: Per-Tenant Connection String in DbContext Source: https://www.finbuckle.com/multitenant/docs/EFCore Add a ConnectionString property to the TenantInfo implementation and inject IMultiTenantContextAccessor into the DbContext constructor to obtain the current tenant's connection string. Use it in OnConfiguring to connect to the per-tenant database. This approach avoids the complexity of shared-database query filtering but requires managing more database instances. ```csharp public class AppTenantInfo : TenantInfo { public string? ConnectionString { get; init; } } public class MyAppDbContext : DbContext { private AppTenantInfo? _tenantInfo; public MyAppDbContext(IMultiTenantContextAccessor multiTenantContextAccessor) { // get the current tenant info at the time of construction _tenantInfo = multiTenantContextAccessor.MultiTenantContext.TenantInfo; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { // use the connection string to connect to the per-tenant database optionsBuilder.UseSqlServer(_tenantInfo?.ConnectionString); } ... } ``` -------------------------------- ### Query Without the Tenant Filter using IgnoreQueryFilters Source: https://www.finbuckle.com/multitenant/docs/EFCore Bypasses the tenant global query filter using IgnoreQueryFilters with the Finbuckle.MultiTenant.Abstractions.Constants.TenantToken constant. Use when you need to query across all tenants. ```csharp // TenantBlogs will contain all blogs, regardless of tenant. var myTenantInfo = ...; var db = MultiTenantDbContext.Create(myTenantInfo); var tenantBlogs = db.Blogs .IgnoreQueryFilters(Finbuckle.MultiTenant.Abstractions.Constants.TenantToken) .ToList(); ``` -------------------------------- ### Configuration store tenant settings in appsettings.json Source: https://www.finbuckle.com/multitenant/docs/Authentication Defines tenant-specific authentication settings in appsettings.json, including cookie login/logout paths and per-tenant challenge schemes. The configuration store uses these settings to configure authentication for each tenant. ```json { "Finbuckle:MultiTenant:Stores:ConfigurationStore": { "Defaults": { "CookieLoginPath": "/__tenant__/home/login", "CookieLogoutPath": "/__tenant__/home/logout" }, "Tenants": [ { "Id": "93f330717e5d4f039cd05da312d559cc", "Identifier": "megacorp", "Name": "MegaCorp", "ChallengeScheme": "Cookies" }, { "Id": "505c5c97f4e2442394610c673ac91f61", "Identifier": "acme", "Name": "ACME", "ChallengeScheme": "OpenIdConnect", "OpenIdConnectAuthority": "https://finbuckle-acme.us.auth0.com", "OpenIdConnectClientId": "", "OpenIdConnectClientSecret": "" }, { "Id": "4ee609d6da0342e682012232566cff0e", "Identifier": "initech", "Name": "Initech", "ChallengeScheme": "OpenIdConnect", "OpenIdConnectAuthority": "https://finbuckle-initech.us.auth0.com", "OpenIdConnectClientId": "", "OpenIdConnectClientSecret": "" } ] } } ``` -------------------------------- ### Configure WithDistributedCacheStore with optional sliding expiration Source: https://www.finbuckle.com/multitenant/docs/Stores Configure the distributed cache store with either no expiration or a 5-minute sliding expiration. The store uses the distributed cache mechanism and supports Redis, SQL Server, NCache, or in-memory implementations. Tenant info must support JSON serialization via System.Text.Json. The store does not implement GetAllAsync. ```csharp // use the default configuration with no sliding expiration. services.AddMultiTenant() .WithDistributedCacheStore()... ``` ```csharp // or set a 5 minute sliding expiration. services.AddMultiTenant() .WithDistributedCacheStore(TimeSpan.FromMinutes(5)); ``` -------------------------------- ### Apply [MultiTenant] attribute to entity types Source: https://www.finbuckle.com/multitenant/docs/EFCore Shows how to designate entity types as multi-tenant using the [MultiTenant] attribute. The BlogPost class is isolated per tenant, while Roles is shared. The BloggingDbContext derives from MultiTenantDbContext and automatically respects the attribute. ```csharp // tenants will only see their own blog posts [MultiTenant] public class BlogPost { ... } // roles will be the same for all tenants public class Roles { ... } public class BloggingDbContext : MultiTenantDbContext { public BloggingDbContext(IMultiTenantContextAccessor multiTenantContextAccessor) : base(multiTenantContextAccessor) { } public DbSet BlogPosts { get; set; } // this will be multi-tenant! public DbSet Roles { get; set; } // not multi-tenant! } ``` -------------------------------- ### Derive BloggingDbContext from MultiTenantDbContext Source: https://www.finbuckle.com/multitenant/docs/EFCore Defines a database context that inherits from MultiTenantDbContext. It forwards the IMultiTenantContextAccessor and DbContextOptions to the base constructor for dependency injection, and provides ITenantInfo-based constructors for testing or non-DI scenarios. Using this context automatically filters queries to the current tenant. ```csharp public class BloggingDbContext : MultiTenantDbContext { // these constructors are called when dependency injection is used public BloggingDbContext(IMultiTenantContextAccessor multiTenantContextAccessor) : base(multiTenantContextAccessor) { } public BloggingDbContext(IMultiTenantContextAccessor multiTenantContextAccessor, DbContextOptions options) : base(multiTenantContextAccessor, options) { } // these constructors are useful for testing or other use cases where dependency injection is not used public BloggingDbContext(ITenantInfo tenantInfo) : base(tenantInfo) { } public BloggingDbContext(ITenantInfo tenantInfo, DbContextOptions options) : base(tenantInfo, options) { } public DbSet Blogs { get; set; } public DbSet Posts { get; set; } } ``` -------------------------------- ### Add MultiTenant middleware with UseMultiTenant Source: https://www.finbuckle.com/multitenant/docs/AspNetCore Add the MultiTenant middleware to the request pipeline with UseMultiTenant(). Place it before any middleware that requires per-tenant behavior, such as UseAuthentication() and UseAuthorization(). If UseRouting() is explicitly called, it must come before UseMultiTenant() when using the Route Strategy. ```csharp // add the MultiTenant middleware app.UseMultiTenant(); // ...add other middleware app.Run(); ``` -------------------------------- ### WithBasePathStrategy: Configure base path tenant resolution Source: https://www.finbuckle.com/multitenant/docs/Strategies Configure the Base Path strategy after AddMultiTenant. The first overload uses the default rebasing behavior; the second disables PathBase/Path adjustment by setting RebaseAspNetCorePathBase to false. Use the second when you do not want the PathBase and Path modified. ```csharp builder.Services.AddMultiTenant() .WithBasePathStrategy()... // or configure not to rebase PathBase and Path builder.Services.AddMultiTenant() .WithBasePathStrategy(options => { options.RebaseAspNetCorePathBase = false; })... ``` -------------------------------- ### Configure per-tenant cookie authentication options Source: https://www.finbuckle.com/multitenant/docs/Authentication Configures CookieAuthenticationOptions per tenant to give each tenant's cookie a different name, preserving existing tenant sign-ins when switching between requests on the same browser. Requires WithPerTenantAuthentication() to work as intended. Uses the default cookie authentication scheme; custom scheme names or ConfigureAllPerTenant can be used to scope options. ```csharp builder.Services.AddMultiTenant() .WithConfigurationStore() .WithRouteStrategy() .WithPerTenantAuthentication(); // WithPerTenantAuthentication, as shown above, is needed for this to work as intended. // Note the default cookie authentication scheme is used for the options name per ASP.NET Core defaults, // but you can use a custom authentication scheme name to scope the options or use ConfigureAllPerTenant // to impact all authentication schemes. builder.Services.ConfigurePerTenant(CookieAuthenticationDefaults.AuthenticationScheme, (options, tenantInfo) => { options.Cookie.Name = $"SignInCookie-{tenantInfo.Identifier}"; }); ``` -------------------------------- ### WithHttpContextStrategy: Resolve tenant from query string Source: https://www.finbuckle.com/multitenant/docs/Strategies Configure the HttpContext strategy after AddMultiTenant. The delegate receives the current HttpContext and returns the tenant identifier or null if not found. The query value is empty when the 'tenant' parameter is absent from the request. ```csharp builder.Services.AddMultiTenant() .WithHttpContextStrategy(async httpContext => { var identifier = httpContext.Request.Query["tenant"]; // query value will be empty if the value didn't exist in the request if(identifier == string.Empty) return null; return identifier; })... ``` -------------------------------- ### Access current tenant with GetMultiTenantContext Source: https://www.finbuckle.com/MultiTenant Use the GetMultiTenantContext extension method to access the current tenant's TenantInfo. The TenantInfo property type depends on the type passed to AddMultiTenant during configuration. If the current tenant could not be determined, TenantInfo will be null. ```csharp var tenantInfo = HttpContext.GetMultiTenantContext().TenantInfo; if (tenantInfo != null) { var tenantId = tenantInfo.Id; var identifier = tenantInfo.Identifier; var name = tenantInfo.Name; } ``` -------------------------------- ### AdjustKey and AdjustIndexes in OnModelCreating Source: https://www.finbuckle.com/multitenant/docs/EFCore Configures a multi-tenant entity type and adjusts its existing key and indexes to include the implicit TenantId. Use AdjustKey, AdjustIndexes, or AdjustUniqueIndexes on the MultiTenantEntityTypeBuilder returned by IsMultiTenant(). ```csharp protected override void OnModelCreating(ModelBuilder builder) { // Configure an entity type to be multi-tenant, adjust the existing keys and indexes var key = builder.Entity().Metadata.GetKeys().First(); builder.Entity().IsMultiTenant().AdjustKey(key, builder).AdjustIndexes(); } ``` -------------------------------- ### Define AppTenantInfo custom tenant info Source: https://www.finbuckle.com/multitenant/docs/Options A custom TTenantInfo type that derives from TenantInfo and adds per-tenant option values. ```csharp using Finbuckle.MultiTenant.Abstractions; public class AppTenantInfo : TenantInfo { public int Option1Value { get; set; } public int Option2Value { get; set; } } ``` -------------------------------- ### Call ConfigureMultiTenant in OnModelCreating Source: https://www.finbuckle.com/multitenant/docs/EFCore Configures a database context to respect the [MultiTenant] attribute by calling ConfigureMultiTenant in OnModelCreating. This is only needed when the context does not derive from MultiTenantDbContext. ```csharp protected override void OnModelCreating(ModelBuilder builder) { // not needed if database context derives from MultiTenantDbContext builder.ConfigureMultiTenant(); } ``` -------------------------------- ### Implement IMultiTenantDbContext on MyDbContext Source: https://www.finbuckle.com/multitenant/docs/EFCore Implements IMultiTenantDbContext on the DbContext, exposing the required properties for data isolation. The properties must be assigned values via constructors, setters, or defaults. ```csharp public class MyDbContext : DbContext, IMultiTenantDbContext { ... public ITenantInfo? TenantInfo { get; } public TenantMismatchMode TenantMismatchMode { get; } public TenantNotSetMode TenantNotSetMode { get; } ... } ``` -------------------------------- ### SetTenantInfo Extension Method Source: https://www.finbuckle.com/multitenant/docs/AspNetCore Use this method to explicitly override the TenantInfo set by the middleware. Setting resetServiceProviderScope to true causes scoped services to be resolved again under the new tenant; singleton and transient services are unaffected. This method also sets StoreInfo and StrategyInfo to null. ```csharp var newTenantInfo = new TenantInfo { Id = "new-id", Identifier = "new-identifier" }; HttpContext.SetTenantInfo(newTenantInfo, resetServiceProviderScope: true); // This will be the new tenant. var tenant = HttpContext.GetMultiTenantContext().TenantInfo; // This will regenerate the options class. var optionsProvider = HttpContext.RequestServices.GetService>(); ``` -------------------------------- ### Configure per-tenant JWT bearer options Source: https://www.finbuckle.com/multitenant/docs/Authentication Configures JwtBearerOptions per tenant to use a different authority for token validation. Requires WithPerTenantAuthentication() to work as intended. Uses the default JwtBearer authentication scheme; custom scheme names or ConfigureAllPerTenant can be used to scope options. ```csharp builder.Services.AddMultiTenant() .WithConfigurationStore() .WithRouteStrategy() .WithPerTenantAuthentication(); // WithPerTenantAuthentication, as shown above, is needed for this to work as intended. // Note the default JwtBearer authentication scheme is used for the options name per ASP.NET Core defaults, // but you can use a custom authentication scheme name to scope the options or use ConfigureAllPerTenant // to impact all authentication schemes. builder.Services.ConfigurePerTenant(JwtBearerDefaults.AuthenticationScheme, (options, tenantInfo) => { // assume tenants are configured with an authority string to use here. options.Authority = tenantInfo.JwtAuthority; }); ``` -------------------------------- ### Querying Data with MultiTenantDbContext Source: https://www.finbuckle.com/multitenant/docs/EFCore Shows how EF Core queries automatically filter results to the tenant associated with the TenantInfo passed to MultiTenantDbContext.Create. The global query filter is applied only at the root level; Include/ThenInclude entities are not filtered unless all involved entities have the [MultiTenant] attribute. ```csharp // Will only return "My Blog". var myTenantInfo = new TenantInfo { Id = "1", Identifier = "tenant-1" }; var myDbContext = MultiTenantDbContext.Create(myTenantInfo); var tenantBlog = myDbContext.Blogs.First(); // Will only return "Your Blog". var yourTenantInfo = new TenantInfo { Id = "2", Identifier = "tenant-2" }; var yourDbContext = MultiTenantDbContext.Create(yourTenantInfo); var yourBlogs = yourDbContext.Blogs.First(); ``` -------------------------------- ### Override OnModelCreating with ConfigureMultiTenant and IsMultiTenant Source: https://www.finbuckle.com/multitenant/docs/EFCore Overrides OnModelCreating to configure multi-tenant entities. Calls ConfigureMultiTenant for [MultiTenant]-marked types and IsMultiTenant for individual entity types. Base method should be called first if needed. ```csharp protected override void OnModelCreating(ModelBuilder builder) { // If necessary call the base class method. // Recommended to be called first. base.OnModelCreating(builder); // Configure all entity types marked with the [MultiTenant] data attribute builder.ConfigureMultiTenant(); // Configure an entity type to be multi-tenant. builder.Entity().IsMultiTenant(); } ``` -------------------------------- ### Configure an entity type as multi-tenant with IsMultiTenant Source: https://www.finbuckle.com/multitenant/docs/EFCore Call IsMultiTenant() on an entity type in OnModelCreating to enable multi-tenant functionality. This is more flexible than using the [MultiTenant] attribute, as it works for types without the attribute, such as those from another assembly. The method returns a MultiTenantEntityTypeBuilder for further configuration. ```csharp protected override void OnModelCreating(ModelBuilder builder) { // Configure an entity type to be multi-tenant. builder.Entity().IsMultiTenant(); } ``` -------------------------------- ### MultiTenantDbContext SaveChanges with MultiTenantException Source: https://www.finbuckle.com/multitenant/docs/EFCore Shows how SaveChanges and SaveChangesAsync throw MultiTenantException when an entity is attached to or deleted from a different tenant. The default behavior can be changed via TenantMismatchMode and TenantNotSetMode on IMultiTenantDbContext. ```csharp // Add a blog for a tenant. var myBlog = new Blog { TenantId = "1", Title = "My Blog" }; var myTenantInfo = new TenantInfo { Id = "1", Identifier = "tenant-1" }; var myDbContext = MultiTenantDbContext.Create(myTenantInfo); myDbContext.Blogs.Add(myBlog); myDbContext.SaveChanges(); // Modify and attach the same blog to a different tenant. var yourTenantInfo = new TenantInfo { Id = "2", Identifier = "tenant-2" }; var yourDbContext = MultiTenantDbContext.Create(yourTenantInfo); yourDbContext.Blogs.Attach(myBlog); myBlog.Title = "My Changed Blog"; await yourDbContext.SaveChangesAsync(); // Throws MultiTenantException. // Delete from the original tenant myDbContext.Blogs.Remove(myBlog); await myDbContext.SaveChangesAsync(); // Delete from the other tenant yourDbContext.Blogs.Remove(myBlog); await yourDbContext.SaveChangesAsync(); // Throws MultiTenantException. ``` -------------------------------- ### Bypass when no endpoint matched with BypassWhenEndpointNotResolved Source: https://www.finbuckle.com/multitenant/docs/AspNetCore Call `BypassWhenEndpointNotResolved()` after `AddMultiTenant` to skip tenant resolution when no endpoint is matched. This bypass does not require the route strategy. ```csharp builder.Services.AddMultiTenant() .WithRouteStrategy() .WithConfigurationStore() .BypassWhenEndpointNotResolved(); ``` -------------------------------- ### Exclude entities from multi-tenancy with IsNotMultiTenant Source: https://www.finbuckle.com/multitenant/docs/EFCore Use IsNotMultiTenant() to explicitly mark entities as non-multi-tenant, useful for shared entities in a multi-tenant database context. Entities marked this way will not have a TenantId property, will not be filtered by tenant, and will be accessible to all tenants. If previously configured with IsMultiTenant(), the tenant query filter is removed. ```csharp protected override void OnModelCreating(ModelBuilder builder) { // Configure an entity type to be explicitly non-multi-tenant. builder.Entity().IsNotMultiTenant(); builder.Entity().IsNotMultiTenant(); } ``` -------------------------------- ### Override SaveChanges and SaveChangesAsync with EnforceMultiTenant Source: https://www.finbuckle.com/multitenant/docs/EFCore Overrides SaveChanges and SaveChangesAsync to call EnforceMultiTenant before the base method, ensuring proper data isolation and behavior for the current tenant. ```csharp public override int SaveChanges(bool acceptAllChangesOnSuccess) { this.EnforceMultiTenant(); return base.SaveChanges(acceptAllChangesOnSuccess); } public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken)) { this.EnforceMultiTenant(); return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.