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
Usare i generics di C# con la clausola nameof in modo semplificato
Configurare OpenAI in .NET Aspire
Effettuare un clone parziale di un repository di GitHub
Supporto semplificato per le left join in Entity Framework 10
Response streaming con Blazor e .NET 10
Ospitare n8n su Azure App Service
Microsoft Fabric: la piattaforma unificata per l'ecosistema dati moderno
Agentic Workflows in GitHub
Come automatizzare il download dei report di billing da GitHub Enterprise
Utilizzare le librerie native di .NET per decomprimere un file zip
Impostare il tipo di supporto dei campi Json in Entity Framework con Sql Server
Disabilitare la telemetria nella CLI di GitHub


