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
Rinnovare il token di una GitHub App durante l'esecuzione di un workflow
Eliminare una project wiki di Azure DevOps
Gestire progetti NPM in .NET Aspire
Il nuovo controllo Range di Blazor 9
Scrivere selettori CSS più semplici ed efficienti con :is()
Utilizzare i variable font nel CSS
Ridurre il reflow ottimizzando il CSS
Escludere alcuni file da GitHub Secret Scanning
Montare Azure Blob Storage su Linux con BlobFuse2
Esporre i propri servizi applicativi con Semantic Kernel e ASP.NET Web API
Creare una libreria CSS universale: i bottoni
Fissare una versione dell'agent nelle pipeline di Azure DevOps
I più letti di oggi
- Sfruttare i nuovi overload di TimeSpan.From* per creare timespan usando numeri interi
- Documentare i servizi REST con Swagger e OpenAPI con .NET 9
- ecco tutte le novità pubblicate sui nostri siti questa settimana: https://aspit.co/wkly buon week-end!
- ecco tutte le novità pubblicate sui nostri siti questa settimana: https://aspit.co/wkly buon week-end!