CoreOne.ModelPatch.Tenants
1.0.1
dotnet add package CoreOne.ModelPatch.Tenants --version 1.0.1
NuGet\Install-Package CoreOne.ModelPatch.Tenants -Version 1.0.1
<PackageReference Include="CoreOne.ModelPatch.Tenants" Version="1.0.1" />
<PackageVersion Include="CoreOne.ModelPatch.Tenants" Version="1.0.1" />
<PackageReference Include="CoreOne.ModelPatch.Tenants" />
paket add CoreOne.ModelPatch.Tenants --version 1.0.1
#r "nuget: CoreOne.ModelPatch.Tenants, 1.0.1"
#:package CoreOne.ModelPatch.Tenants@1.0.1
#addin nuget:?package=CoreOne.ModelPatch.Tenants&version=1.0.1
#tool nuget:?package=CoreOne.ModelPatch.Tenants&version=1.0.1
CoreOne.ModelPatch.Tenants
Optional multi-tenancy plugin for CoreOne.ModelPatch. Automatically validates and injects the current tenant key into every PATCH operation so your EF Core entities stay properly scoped without any manual work in your controllers or services.
📦 Installation
dotnet add package CoreOne.ModelPatch.Tenants
Requirements: net9.0 or net10.0, CoreOne.ModelPatch registered in DI
How It Works
- You mark your entities with
[TenantOwned]and one of their properties with[TenantKey]. - On every patch call the
TenantPrePatchPluginruns before the delta is applied. - It reads the current tenant ID from the configured
ITenantProvider. - If the delta already contains a tenant key, it validates it matches the current tenant.
- If the delta does not contain a tenant key, it injects one automatically.
- For updates it also verifies the existing record in the database belongs to the same tenant.
Quick Start
1. Mark Your Entities
using CoreOne.Identity.Attributes;
[TenantOwned]
public class Invoice
{
public Guid Id { get; set; }
[TenantKey]
public string TenantId { get; set; }
public string Description { get; set; }
public decimal Amount { get; set; }
}
2. Register Services
// Program.cs
services.AddModelPatch(); // Register the core patch service first
services.AddTenantSupport(options =>
{
// Choose how the tenant ID is extracted from the incoming HTTP request
options.TenatAccessor = new UserClaimTenantAccessor("tenant_id");
options.ThrowOnTenantMismatch = true; // Throw when a cross-tenant write is attempted
});
3. Use Normally — Tenant Isolation Is Automatic
public class InvoiceService(IDataModelService<AppDbContext> patchService)
{
public Task<PatchResult> Update(Delta<Invoice> delta, CancellationToken ct)
{
// No need to set TenantId manually — the plugin injects it for you
return patchService.Patch(delta, ct);
}
}
Tenant Accessor Options
The TenatAccessor property on TenantPluginOptions controls how the tenant ID is extracted from the incoming HTTP request. Choose the strategy that matches your architecture:
User Claim (most common for JWT)
options.TenatAccessor = new UserClaimTenantAccessor("tenant_id");
Reads the tenant ID from the authenticated user's claims. The argument is the claim type name.
HTTP Header
options.TenatAccessor = new AuthorizationHeaderTenantAccessor("X-Tenant-Id");
Reads the tenant ID from a request header. Useful for service-to-service calls.
Route Parameter
options.TenatAccessor = new RouteParameterTenantAccessor("tenantId");
Reads the tenant ID from a URL route segment, e.g. /api/{tenantId}/invoices.
Custom Tenant Provider
If none of the built-in accessors fit, implement ITenantProvider directly:
public class MyTenantProvider(IHttpContextAccessor http) : ITenantProvider
{
public ValueTask<object?> GetTenantKey()
{
var tenantId = http.HttpContext?.Session.GetString("TenantId");
return ValueTask.FromResult<object?>(tenantId);
}
}
// Register with the generic overload
services.AddTenantSupport<MyTenantProvider>(options =>
{
options.ThrowOnTenantMismatch = true;
});
TenantPluginOptions Reference
| Property | Type | Default | Description |
|---|---|---|---|
TenatAccessor |
HttpContexTenatAccessor? |
null |
Strategy for extracting tenant ID from HttpContext |
ThrowOnTenantMismatch |
bool |
true |
Throw when the incoming tenant key doesn't match the current tenant |
MultiTenantEntityTypes |
HashSet<Type> |
empty | Restrict tenant enforcement to specific entity types (empty = all [TenantOwned] types) |
Plugin Execution Order
The TenantPrePatchPlugin runs with Order = 9999, which means it executes first in the pre-patch pipeline — before strict field validation, concurrency checks, and attribute restrictions. This ensures the tenant key is always present and valid before any other processing occurs.
Related Packages
| Package | Description |
|---|---|
| CoreOne.ModelPatch | Core PATCH implementation — required |
| CoreOne.ModelPatch.Abstract | Interfaces and models — useful for plugin or shared-library projects |
License
MIT — see LICENSE on GitHub.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- CoreOne (>= 1.4.0.6)
- CoreOne.Identity (>= 1.2.2)
- CoreOne.ModelPatch (>= 1.5.2)
- Microsoft.AspNetCore.Http (>= 2.2.2)
- Microsoft.AspNetCore.Routing (>= 2.2.2)
- Microsoft.EntityFrameworkCore (>= 10.0.5)
-
net9.0
- CoreOne (>= 1.4.0.6)
- CoreOne.Identity (>= 1.2.2)
- CoreOne.ModelPatch (>= 1.5.2)
- Microsoft.AspNetCore.Http (>= 2.2.2)
- Microsoft.AspNetCore.Routing (>= 2.2.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.