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
Evidenziare una porzione di testo in un pagina dopo una navigazione
Anonimizzare i dati sensibili nei log di Azure Front Door
Centralizzare gli endpoint AI Foundry con Azure API Management
Importare un servizio esterno in .NET Aspire
Collegare applicazioni server e client con .NET Aspire
Supportare la crittografia di ASP.NET Core con Azure Container App
Arricchire l'interfaccia di .NET Aspire
Evitare memory leaks nelle closure JavaScript
Importare repository da Bitbucket a GitHub Enterprise Cloud
Controllare la velocità di spostamento su una pagina HTML
Recuperare le subissue e il loro stato di completamento in GitHub
Utilizzare Hybrid Cache in .NET 9


