web-dev-qa-db-fra.com

Comment injecter UserManager et SignInManager

J'essaie de comprendre comment injecter UserManager et SignInManager. J'ai installé Ninject dans mon application et je l'utilise de la manière suivante:

Veuillez considérer qu'il s'agit d'un tout nouveau projet. Dans Startup.cs, j'ai les éléments suivants:

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        ConfigureAuth(app);

        app.UseNinjectMiddleware(CreateKernel);
    }

    private static IKernel CreateKernel()
    {
        var kernel = new StandardKernel();
        kernel.Load(Assembly.GetExecutingAssembly());


        return kernel;
    }
}

maintenant, si je devais créer une classe factice et essayer de l'injecter en fonction de son interface qui fonctionne. Je l'ai testé. Ce que j'essaie de comprendre, c'est comment puis-je maintenant supprimer les éléments suivants de Startup.Auth.cs et l'injecter. N'ayant aucune interface sur laquelle je peux compter, je ne sais pas comment cela se fait:

app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

Juste pour clarifier une fois de plus, ma question est: comment puis-je instancier ApplicationUserManager et ApplicationSignInManager et l'injecter dans les paramètres de mon contrôleur. Voici le contrôleur dans lequel j'essaie d'injecter ceci:

public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager)
{
    UserManager = userManager;
    SignInManager = signInManager;
}

ÉDITER:

Voici ce que j'ai essayé:

private static IKernel CreateKernel()
{
    var kernel = new StandardKernel();
    kernel.Load(Assembly.GetExecutingAssembly());

    kernel.Bind<IUserStore<ApplicationUser>>().To<UserStore<ApplicationUser>>();
    kernel.Bind<UserManager<ApplicationUser>>().ToSelf();

    return kernel;
}

Mais avec cela, je reçois une erreur de référence nulle

17
Bojan

Pour donner une réponse exacte à ce que ma question a dit, voici le code et les instructions:

Étape 1: créer un magasin d'utilisateurs personnalisé

public class ApplicationUserStore : UserStore<ApplicationUser>
{
    public ApplicationUserStore(ApplicationDbContext context)
        : base(context)
    {
    }
}

Étape 2: Mettez à jour ApplicationUserManager et déplacez le code de la méthode Create vers le constructeur

public class ApplicationUserManager : UserManager<ApplicationUser>
{
    public ApplicationUserManager(IUserStore<ApplicationUser> store, IdentityFactoryOptions<ApplicationUserManager> options)
        : base(store)
    {
        this.UserValidator = new UserValidator<ApplicationUser>(this)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };

        // Configure validation logic for passwords
        this.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 6,
            RequireNonLetterOrDigit = true,
            RequireDigit = true,
            RequireLowercase = true,
            RequireUppercase = true,
        };

        // Configure user lockout defaults
        this.UserLockoutEnabledByDefault = true;
        this.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
        this.MaxFailedAccessAttemptsBeforeLockout = 5;

        // Register two-factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
        // You can write your own provider and plug it in here.
        this.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider<ApplicationUser>
        {
            MessageFormat = "Your security code is {0}"
        });
        this.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider<ApplicationUser>
        {
            Subject = "Security Code",
            BodyFormat = "Your security code is {0}"
        });
        this.EmailService = new EmailService();
        this.SmsService = new SmsService();
        var dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null)
        {
            this.UserTokenProvider =
                new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
        }
    }
}

Étape 3: modifiez la classe Startup.Auth et commentez le code suivant

//app.CreatePerOwinContext(ApplicationDbContext.Create);
//app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
//app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

Étape 4: Mettre à jour le contrôleur de compte (ou tout autre contrôleur en question) et ajouter le constructeur suivant

public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager, IAuthenticationManager authManager)
{
    _userManager = userManager;
    _signInManager = signInManager;
    _authManager = authManager;
}

Étape 5: Mettez à jour le contrôleur de compte et rendez les propriétés accessibles uniquement:

public ApplicationSignInManager SignInManager
{
    get
    {
        return _signInManager;
    }
}

public ApplicationUserManager UserManager
{
    get
    {
        return _userManager;
    }
}

private IAuthenticationManager AuthenticationManager
{
    get
    {
        return _authManager;
    }
}

Étape 6: Mettre à jour Startup.cs

public partial class Startup
{
    private IAppBuilder _app;
    public void Configuration(IAppBuilder app)
    {
        ConfigureAuth(app);
        _app = app;
        app.UseNinjectMiddleware(CreateKernel);
    }

    private IKernel CreateKernel()
    {
        var kernel = new StandardKernel();
        kernel.Load(Assembly.GetExecutingAssembly());

        kernel.Bind<ApplicationDbContext>().ToSelf().InRequestScope();
        kernel.Bind<IUserStore<ApplicationUser>>().To<ApplicationUserStore>();
        kernel.Bind<ApplicationUserManager>().ToSelf();
        kernel.Bind<ApplicationSignInManager>().ToSelf();
        kernel.Bind<IAuthenticationManager>().ToMethod(x => HttpContext.Current.GetOwinContext().Authentication);
        kernel.Bind<IDataProtectionProvider>().ToMethod(x => _app.GetDataProtectionProvider());

        return kernel;
    }
}

Pour développer davantage la réponse à cette question, sur la base des commentaires que j'ai reçus:

Ces gestionnaires ne doivent pas être injectés en tant que classes, car vous n'accomplissez pas DI. Ce qui devrait être fait à la place est de créer plusieurs interfaces qui séparent et groupent davantage les méthodes de UserManager selon vos besoins. Voici un exemple:

public interface IUserManagerSegment
{
    Task<IdentityResult> CreateAsync(ApplicationUser user, string password);
    Task<IdentityResult> CreateAsync(ApplicationUser user);
    Task<IdentityResult> ConfirmEmailAsync(string userId, string token);
    Task<ApplicationUser> FindByNameAsync(string userName);
    Task<bool> IsEmailConfirmedAsync(string userId);
    Task<IdentityResult> ResetPasswordAsync(string userId, string token, string newPassword);
    Task<IList<string>> GetValidTwoFactorProvidersAsync(string userId);
    Task<IdentityResult> AddLoginAsync(string userId, UserLoginInfo login);
    void Dispose(bool disposing);
    void Dispose();
}

La méthode ci-dessus a une liste de quelques méthodes aléatoires que j'ai choisies juste pour illustrer le point. Cela dit, nous allons maintenant injecter la méthode basée sur l'interface comme celle-ci:

kernel.Bind<IUserManagerSegment>().To<ApplicationUserManager>();

Et maintenant, notre constructeur AccountController ressemblerait à ceci:

public AccountController(IUserManagerSegment userManager, ApplicationSignInManager signInManager, IAuthenticationManager authManager)  
{
    _userManager = userManager;
    _signInManager = signInManager;
    _authManager = authManager;
}

La même chose doit être faite pour SignInManager et AuthenticationManager.

Le code ci-dessus a été testé et fonctionne. Assurez-vous simplement d'avoir référencé les DLL suivantes:

Ninject.dll
Ninject.Web.Common
Ninject.Web.Common.OwinHost
Ninject.Web.Mvc

18
Bojan

Conditions préalables

Démarrez une nouvelle application MVC5 à l'aide du modèle MVC. Cela installera toutes les dépendances nécessaires et déploiera le Startup.Auth.cs fichier contenant le code bootstrap pour Microsoft.AspNet.Identity (il inclut également toutes les références pour Microsoft.AspNet.Identity).

Installez les packages suivants et mettez-les à jour ultérieurement.

Install-Package Ninject
Install-Package Ninject.MVC5

Configuration

Supprimez le constructeur par défaut sur AccountController afin qu'il ne reste que le constructeur paramétré. Il doit avoir la signature suivante.

public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager)

Cela garantira que vous obtiendrez une erreur si l'injection échoue, ce que nous voulons.

Configuration de NInject

Le déploiement du package NInject NuGet aura créé un fichier nommé NinjectWebCommon.cs où l'enregistrement de la plaque de la chaudière NInject a lieu. Cela a une méthode avec la signature suivante que vous pouvez étendre avec vos inscriptions.

private static void RegisterServices(IKernel kernel)

Nous allons maintenant ajouter le code suivant à cette méthode pour que NInject injecte automatiquement les instances ApplicationSignInManager et ApplicationUserManager.

private static void RegisterServices(IKernel kernel) {
    kernel.Bind<IUserStore<ApplicationUser>>().To<UserStore<ApplicationUser>>();
    kernel.Bind<UserManager<ApplicationUser>>().ToSelf();

    kernel.Bind<HttpContextBase>().ToMethod(ctx => new HttpContextWrapper(HttpContext.Current)).InTransientScope();

    kernel.Bind<ApplicationSignInManager>().ToMethod((context)=>
    {
        var cbase = new HttpContextWrapper(HttpContext.Current);
        return cbase.GetOwinContext().Get<ApplicationSignInManager>();
    });

    kernel.Bind<ApplicationUserManager>().ToSelf();
}

C'est ça. Vous devriez maintenant pouvoir accéder aux liens de connexion ou d'enregistrement et l'injection se produira.

Approche alternative

Je préfère une approche proxy qui expose des fonctionnalités limitées pour les instances ApplicationSignInManager et ApplicationUserManager. J'injecte ensuite ce proxy dans les contrôleurs nécessaires. Il permet d'abstraire certaines des informations d'identité loin des contrôleurs, ce qui facilite la modification à l'avenir. Ce n'est en aucun cas un nouveau concept et le fait de le faire ou non dépend de la taille et de la complexité de votre projet ainsi que de la manière dont vous souhaitez gérer les dépendances. Les avantages sont donc (communs à tout proxy):

  • Vous pouvez retirer certaines dépendances de votre code
  • Vous pouvez rationaliser certains des appels dans l'API
  • Vous ne pouvez exposer que les fonctionnalités que vous souhaitez utiliser, y compris les pièces configurables
  • La gestion des changements devrait être plus facile si les interfaces changent jamais, maintenant vous modifiez les appels dans votre proxy au lieu de tout le code d'appel sur vos contrôleurs.

Exemple de code:

public interface IAuthManager
{
    Task<SignInStatus> PasswordSignInAsync(string userName, string password, bool rememberMe);
}

public class AuthManager : IAuthManager
{
    private ApplicationUserManager _userManager;
    ApplicationSignInManager _signInManager;

    public AuthManager(ApplicationUserManager userManager, ApplicationSignInManager signInManager)
    {
        this._userManager = userManager;
        this._signInManager = signInManager;
    }

    public Task<SignInStatus> PasswordSignInAsync(string userName, string password, bool rememberMe)
    {
        return _signInManager.PasswordSignInAsync(userName, password, rememberMe, true);
    }
}

Ajoutez la ligne suivante dans votre enregistrement de dépendance NInject.

kernel.Bind<IAuthManager>().To<AuthManager>();

Modifiez votre constructeur AccountController pour prendre une instance de IAuthManager. Enfin, modifiez vos méthodes pour faire référence à ce proxy au lieu des classes d'identité ASP.NET directement.

Avis de non-responsabilité - Je n'ai pas envoyé un appel complexe, juste un appel très simple pour illustrer mon propos. Ceci est également entièrement facultatif et que vous le fassiez ou non, cela devrait dépendre de la portée et de la taille de votre projet et de la façon dont vous prévoyez d'utiliser le framework d'identité ASP.NET

23
Igor