Quando dobbiamo sviluppare applicazioni multitenant, una delle cose fondamentali è assicurarci che un dato inserito da un utente di quel tenant, sia effettivamente inserito per il tenant e successivamente visibile solo agli utenti di quel tenant. Questo requisito è talmente importante da non poter essere delegato a chi sviluppa le singole funzioni, ma va accentrato in un unico punto così da non ammettere possibilità di errore. Per centralizzare questo requisito ci basta agire sul contesto dell'applicazione. I passi da eseguire per gestire questa funzionalità non sono molti e li vediamo di seguito.
Per prima cosa, creiamo un'interfaccia che contiene il TenantId e che le entity devono implementare.
public interface IMultiTenantEntity
{
int TenantId { get; set; }
}
public class Person : IMultiTenantEntity
{
public int TenantId { get; set; }
}Il secondo passaggio consiste nel passare al costruttore del contesto il principal dell'utente corrente. Il contesto memorizza quest'utente e lo utilizza per le fasi di scrittura e lettura dati.
public class MyDbContext : DbContext
{
public DbSet<Person> People { get; set; }
private ClaimsPrincipal _principal;
public MultitenantDbContext(DbContextOptions<MultitenantDbContext> options,
ClaimsPrincipal principal) : base(options)
{
_principal = principal;
}
}A questo punto dobbiamo innanzitutto assicurarci che in fase di scrittura la proprietà TenantId sia correttamente popolata con quello dell'utente corrente. A questo scopo eseguiamo l'override del metodo SaveChanges e per ogni entity in stato di Added che implementa IMultiTenantEntity impostiamo la proprietà TenantId con il tenant dell'utente corrente (recuperato dai claim principal).
Per le entity in stato di Modified o Deleted solleviamo invece un'eccezione se il tenantId è diverso da quello dell'utente.
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
ChangeTracker.Entries<IMultiTenantEntity>()
.Where(e => e.State == EntityState.Added)
.ToList()
.ForEach(entry => entry.Entity.TenantId = _principal.GetTenantId());
var any = ChangeTracker.Entries<IMultiTenantEntity>()
.Any(e =>
(e.State == EntityState.Modified || e.State == EntityState.Deleted) &&
e.Entity.TenantId != _principal.GetTenantId()
);
if (any)
{
throw new InvalidOperationException("Invalid tenant");
}
}Per quanto riguarda la fase di lettura, possiamo ricorrere a un filtro globale impostato in fase di mapping.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Person>()
.HasQueryFilter(p => p.TenantId == _principal.GetTenantId());
}Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Nuova modale riconnessione Blazor
Esporre un server MCP con Azure API Management
Definire il metodo di rilascio in .NET Aspire
Arricchire l'interfaccia di .NET Aspire
Personalizzare le pagine di errore su Azure App Service
Ospitare n8n su Azure App Service
Eseguire i pre-commit hook di git con dependabot
Utilizzare il top layer in HTML
Escludere alcuni file da GitHub Copilot
Gestire gli errori nelle Promise JavaScript con try()
Mappare una complex property di una entity su un campo JSON
Abilitare il rolling update su Azure Functions flex consumption




