Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Structure Map and Aspnet.Identity UserManager static class issue

I've got an issue with getting correct instance of UserManager in my Account Controller. Currently, I cannot get password reset to work as my provider and other settings are being ignored.

In Startup - void ConfigureAuth(IAppBuilder app) I've got following:

app.CreatePerOwinContext<ViewingBookerDatabaseContext>(ViewingBookerDatabaseContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

Then in AccountManager class, method Create referenced above contains following:

public class ApplicationUserManager : UserManager<ApplicationUser>
{
    // Configure the application user manager
    public ApplicationUserManager(ApplicationUserStore store)
        : base(store)
    {
    }

public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
    {
        var manager = new ApplicationUserManager(new ApplicationUserStore(new ViewingBookerDatabaseContext()));

        // Configure validation logic for usernames
        manager.UserValidator = new UserValidator<ApplicationUser>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };
        // Configure validation logic for passwords
        manager.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 6,
            RequireNonLetterOrDigit = false,
            RequireDigit = false,
            RequireLowercase = false,
            RequireUppercase = false,
        };
        // Configure user lockout defaults
        manager.UserLockoutEnabledByDefault = true;
        manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
        manager.MaxFailedAccessAttemptsBeforeLockout = 5;

        var dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null)
        {
            manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
        }
        return manager;
    }

Now, this worked great before I installed IoC - in this case StrcutureMap. IoC for controllers is registered in following pattern:

Scan(scan =>
        {
            scan.TheCallingAssembly();
            scan.With(new ControllerConvention());
        });

public class ControllerConvention : IRegistrationConvention
{
    public void Process(Type type, Registry registry)
    {
        if (type.CanBeCastTo(typeof(Controller)) && !type.IsAbstract)
        {
            registry.For(type).LifecycleIs(new UniquePerRequestLifecycle());
        }
    }
}

UserManager is then referenced in my AccountController as follows:

private ApplicationUserManager _userManager;
    public ApplicationUserManager UserManager
    {
        get
        {
            return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
        }
        private set
        {
            _userManager = value;
        }
    }

    private IAuthenticationManager _authenticationManager
    {
        get
        {
            return HttpContext.GetOwinContext().Authentication;
        }
    }

    public AccountController() 
    {
    }

    public AccountController(ApplicationUserManager userManager, ViewingBookerDatabaseContext context, CurrentUser currentUser, Logger logger, EmailService emailService)
    {
        UserManager = userManager;
        _context = context;
        _currentUser = currentUser;
        _logger = logger;
        _emailService = emailService;
    }

Now, UserManager is being used for my password reset, account creation etc. After installing IoC, I get "No ITokenProvider is registered" for password reset, account names with @ are being returned as invalid when creating user etc. Looks like AccountController's instance of ApplicationUserManager is not created via Create() method of ApplicationUserManager class.

Can anyone point me to a correct implementation of strucutre map, so it calls Create to get an instance of AccountUserManager rather than returning a generic instance of that class that contains no ITokenProvider spec. etc?

Thanks for your help.

like image 698
Paul Mieczkowski Avatar asked Jun 02 '14 09:06

Paul Mieczkowski


2 Answers

Ok guys. Since nobody replied for so long, had to do some further research and come up with following, probably not the most elegant, tho a working solution:

1. Startup.cs - remove following lines

app.CreatePerOwinContext<ViewingBookerDatabaseContext>   (ViewingBookerDatabaseContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

2. Introduce new variable in Startup.cs

public static IDataProtectionProvider DataProtectionProvider { get; private set; }

then, set it in public void ConfigureAuth(IAppBuilder app) to following:

DataProtectionProvider = app.GetDataProtectionProvider();

3. ApplicationUserManager or whatever you call it, remove static Create method and change its constructor to following:

         public ApplicationUserManager(ApplicationUserStore store)
        : base(store)
    {
        // Configure validation logic for usernames
        UserValidator = new UserValidator<ApplicationUser>(this)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };

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

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

For password reset ITokenProvider, simply reference Startup's DataProtectionProvider variable like this:

    if (Startup.DataProtectionProvider != null)
            UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(Startup.DataProtectionProvider.Create("ASP.NET Identity"));

And finally...

in AccountController.cs - remove any reference to the getter of ApplicationUserManager and reuse the instance that is supplied in controller's constructor by StructureMap.

 public AccountController(ApplicationUserManager userManager)
    {
        _userManager = userManager;

so you'll be calling _userManager object methods instead

   REMOVE IF YOU HAVE IT IN YOUR CONTROLLER:
   public ApplicationUserManager UserManager
{
    get
    {
        return _userManager ??    HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
    }
    private set
    {
        _userManager = value;
    }
}
like image 151
Paul Mieczkowski Avatar answered Oct 11 '22 16:10

Paul Mieczkowski


You can register creation callbacks to set these properties outside of the static create. Here's how I accomplished the same thing:

        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create, (options, manager) =>
        {
            options.DataProtectionProvider = app.GetDataProtectionProvider();
            manager.UserTokenProvider =
                new DataProtectorTokenProvider<ApplicationUser>(options.DataProtectionProvider.Create("AspNet.com"));
        });
        app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
        app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
like image 35
tofortier Avatar answered Oct 11 '22 17:10

tofortier