Comment passer l'ID utilisateur / TenantId à un constructeur de référentiel à l'aide de l'injection de dépendance

asp.net-core asp.net-core-mvc asp.net-mvc dependency-injection entity-framework-core

Question

J'écris un service Web multi-locataires utilisant ASP.NET 5 et EF7. Les données de référentiel sont spécifiques à un locataire / utilisateur. Comme je vais utiliser le TenantId dans tous les appels, je voudrais initialiser le référentiel avec le TenantId actuel.

public class MyRepository {
    private int tenantId;
    public MyRepository(MyDbContext context, int tenantId) { 
        this.tenantId = tenantId;
        // ...
    }

    public Task<List<Data>> GetAllAsync() {
        return this.context.Data.Where(d => d.TenantId == this.tenantId).ToListAsync();
    }
}

public class Startup {
    public void ConfigureServices(IServiceCollection services) {
        // How do I pass the TenantId to the constructor?
        services.AddScoped<MyRepository>();
    }
}

Est-il possible d'initialiser mon référentiel une fois après l'authentification d'un utilisateur? Comment pourrais-je passer le TenantId avec le constructeur?

Réponse populaire

Les valeurs de votre locataire et de votre identifiant d'utilisateur sont des données d'exécution et vous ne devez pas injecter de données d'exécution dans les composants de votre système (votre référentiel dans ce cas), car cela compliquerait énormément votre racine de composition (comme vous l'avez déjà fait) et le rend presque impossible pour vérifier votre graphe d'objets. Les données d'exécution doivent passer par un graphe d'objet déjà construit. Il y a fondamentalement deux manières d'y parvenir. Soit vous transmettez les données via les appels de méthode de l'API publique de votre composant, soit vous injectez un composant responsable de l'extraction de la valeur d'exécution lorsqu'elle est demandée.

Dans votre cas, la dernière option est la meilleure. Mon conseil est donc d’injecter un composant permettant de récupérer cette information contextuelle:

public interface ITenantContext
{
    int CurrentTenantId { get; }
}

public interface IUserContext
{
    int CurrentUserId { get; }
}

public class MyRepository {
    private readonly Func<MyDbContext> contextProvider;
    private readonly ITenantContext tentantContext;

    public MyRepository(Func<MyDbContext> contextProvider, ITenantContext tentantContext){ 
        this.contextProvider = contextProvider;
        this.tentantContex = tentantContex;
    }

    public Task<List<Data>> GetAllAsync() {
        return this.contextProvider().Data
            .Where(d => d.TenantId == this.tenantContext.CurrentTenantId)
            .ToListAsync();
}

Cela résout le problème avec élégance, car vous pouvez maintenant définir une implémentation ITenantContext qui sait comment récupérer le bon ID de ITenantContext pour la demande en cours. Par exemple:

public sealed class AspNetSessionTenantContext : ITenantContext {
    public int CurrentTenantId {
        get { return (int)HttpContext.Current.Session["tenantId"]; }
    }
}

Cela vous permet même d'enregistrer tous vos composants en tant que singleton, ce qui peut améliorer les performances et réduire le changement des pièges DI courants, tels que les dépendances captives .




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi