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
Eliminare record doppi in Sql Server
Gestire progetti .NET + React in .NET Aspire
Utilizzare i variable font nel CSS
Ottenere un token di accesso per una GitHub App
Sfruttare GPT-4o realtime su Azure Open AI per conversazioni vocali
Rendere le variabili read-only in una pipeline di Azure DevOps
Fornire parametri ad un Web component HTML
Utilizzare l'espressione if inline in una pipeline di Azure DevOps
Usare i servizi di Azure OpenAI e ChatGPT in ASP.NET Core con Semantic Kernel
Ottimizzare le pull con Artifact Cache di Azure Container Registry
Path addizionali per gli asset in ASP.NET Core MVC
Persistere la ChatHistory di Semantic Kernel in ASP.NET Core Web API per GPT
I più letti di oggi
- Usare i settings di serializzazione/deserializzazione di System.Text.Json di ASP.NET all'interno di un'applicazione non web
- The Agentic Day - Milano
- .NET Conference Italia 2025 - Milano
- Gestione ciclo di vita in .NET Aspire
- 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!
- Gestione CSS in Blazor con .NET 9
- Gestione file Javascript in Blazor con .NET 9