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
Utilizzare Copilot con Azure Cosmos DB
Eseguire un metodo asincrono dopo il set di una proprietà in Blazor 8
Creare una custom property in GitHub
Sostituire la GitHub Action di login su private registry
Garantire la provenienza e l'integrità degli artefatti prodotti su GitHub
Ottimizzare la latenza in Blazor 8 tramite InteractiveAuto render mode
Ottimizzare le performance delle collection con le classi FrozenSet e FrozenDictionary
Change tracking e composition in Entity Framework
Rinnovare il token di una GitHub App durante l'esecuzione di un workflow
Usare lo spread operator con i collection initializer in C#
Estrarre dati randomici da una lista di oggetti in C#
Triggerare una pipeline su un altro repository di Azure DevOps
I più letti di oggi
- Utilizzare angular-cli per creare una direttiva in #angular2 https://aspit.co/bft di @sm15455
- le impostazioni sono su #windowslive, quindi basta il liveid per avere tutte le ... http://aspitalia.com/build-win8 #BldWin
- ecco tutte le novità pubblicate sui nostri siti questa settimana: https://aspit.co/wkly buon week-end!
- la nostra app per #win8 è nello store che vi aspetta da qualche mese: provatela! contenuti, forum e push! https://aspit.co/pd