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 Azure AI Studio per testare i modelli AI
Effettuare il log delle chiamate a function di GPT in ASP.NET Web API
Utilizzare QuickGrid di Blazor con Entity Framework
Conoscere il rendering Server o WebAssembly a runtime in Blazor
Fornire parametri ad un Web component HTML
Testare l'invio dei messaggi con Event Hubs Data Explorer
Combinare Container Queries e Media Queries
Esporre i propri servizi applicativi con Semantic Kernel e ASP.NET Web API
Managed deployment strategy in Azure DevOps
Rinnovare il token di una GitHub App durante l'esecuzione di un workflow
Gestire i dati con Azure Cosmos DB Data Explorer
Effettuare il refresh dei dati di una QuickGrid di Blazor