New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MVC 5 template (ASP.NET Identity 2.0) combined with Simple Injector #597

Closed
dotnetjunkie opened this Issue Aug 1, 2018 · 5 comments

Comments

1 participant
@dotnetjunkie
Collaborator

dotnetjunkie commented Aug 1, 2018

Migrated from Codeplex: (discussion 564822)

NOTE: This post describes a solution to combining Simple Injector with ASP.NET Identity.

When you start a new MVC 5 project using the newly added templates in VS 2013 you’ll you get to use the new ASP.net Identity 2 framework. The ASP.Net Identity framework 2.0 depends heavily on the Microsoft implementation of Owin. Therefore the template will add a load of files together with some specific Owin startup files, as Owin defines his own startup, working next to the also existing Appplication_Start() event in the Global.asax file. When you first run the application, all works out-of-box. You can even add users and directly authenticate using this newly add user.

But then, as soon as you add Simple injector, all stops… It took me quite some time to figure out which adjustments I needed to make, to keep using the new identity framework together with Simple Injector. Therefore I’m sharing my solution, hoping to help somebody, struggling with the same questions.
First of all let us take a look at what the template gives us (only the items that needs changing for use with Simple Injector):

Startup_auth.cs: Some configuration of the poor man’s D.I. container functionality of the OWIN appcontext. This will create a ApplicationUserManager per web request using the Create() function of the ApplicationUserManager class.

AccountController.cs: The MVC controller for account management. This class has 2 constructors. A default constructor and one where ApplicationUserManager class can be injected. Therefore the templates defines a property to fix the issue of a null value for the usermanager when the default constructor was called.

When you get Simple Injector from NuGet and run the application, verifying of the container throws an exception:

SimpleInjector.ActivationException: For the container to be able to create AccountController, it should contain exactly one public constructor, but it has 2

Solution:

Off course the solution is to delete the default constructor from the AccountController. But then Simple Injector needs to know about the ApplicationUserManager. So let’s start to do some changes:
First the DbContext (ApplicationDbContext). Remove the Factory method Create(). And change the constructor to:

public ApplicationDbContext(string connectionstring)
    : base(connectionstring, throwIfV1Schema: false)
{
}

You want to change the constructor in this way, because in this way you can do your configuration in the composition root instead of the ugly solution where the configuration is in the constructor, as is default in the template.

Secondly you can remove the factory method Create() from the ApplicationUserManager class.
Third remove these two lines from the OWIN startup file (we add one of them back in later):

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

If you used the Simple Injector MVC quick start remove the WebActivator attribute from the SimpleInjectorInitializer class and replace the code with:

namespace MVC5_SI.App_Start
{
    using System.Reflection;
    using System.Web.Mvc;
    using Microsoft.AspNet.Identity;
    using Microsoft.AspNet.Identity.EntityFramework;
    using Microsoft.AspNet.Identity.Owin;
    using Microsoft.Owin.Security.DataProtection;
    using MVC5_SI.Models;
    using Owin;
    using SimpleInjector;
    using SimpleInjector.Advanced;
    using SimpleInjector.Integration.Web.Mvc;
 
    public static class SimpleInjectorInitializer
    {
        public static Container Initialize(IAppBuilder app)
        {
            var container = GetInitializeContainer(app);
 
            container.Verify();
 
            DependencyResolver.SetResolver(
                new SimpleInjectorDependencyResolver(container));
       
            return container;
        }
 
        public static Container GetInitializeContainer(
                  IAppBuilder app)
        {
            var container = new Container();
 
            container.RegisterSingle<IAppBuilder>(app);
 
            container.RegisterPerWebRequest<
                   ApplicationUserManager>();
            
            container.RegisterPerWebRequest<ApplicationDbContext>(() 
              => new ApplicationDbContext(
               "Your constring goes here"));
            
            container.RegisterPerWebRequest<IUserStore<
              ApplicationUser>>(() => 
                new UserStore<ApplicationUser>(
                  container.GetInstance<ApplicationDbContext>()));
 
            container.RegisterInitializer<ApplicationUserManager>(
                manager => InitializeUserManager(manager, app));
 
            container.RegisterMvcControllers(
                    Assembly.GetExecutingAssembly());
 
            return container;
        }

        private static void InitializeUserManager(
            ApplicationUserManager manager, IAppBuilder app)
        {
            manager.UserValidator = 
             new UserValidator<ApplicationUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };

            //Configure validation logic for passwords
            manager.PasswordValidator = new PasswordValidator()
            {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = true,
                RequireLowercase = true,
                RequireUppercase = true,
            };
 
            var dataProtectionProvider = 
                 app.GetDataProtectionProvider();
            
            if (dataProtectionProvider != null)
            {
                manager.UserTokenProvider = 
                 new DataProtectorTokenProvider<ApplicationUser>(
                  dataProtectionProvider.Create("ASP.NET Identity"));
            }
        }
    }
}

Then change the code of the OWIN Configuration method in the startup class to:

public void Configuration(IAppBuilder app)
{
    var container = SimpleInjectorInitializer.Initialize(app);
    ConfigureAuth(app, container);
}

Now go back to the ConfigureAuth method and change the signature to:

public void ConfigureAuth(IAppBuilder app, Container container)
{
//...code
}

Then recreate the line to register the ApplicationUserManager within the OwinContext like this:

app.CreatePerOwinContext(() => container.GetInstance<ApplicationUserManager>());

This is necessary because Owin internally also uses the ApplicationUserManager and tries to get this from the OwinContext. (see for reference Trailmax Tech)

Last but not least, do not forget to delete the default constructor from the AccountController. And while you’re at it, remove the UserManager property and rename the private field from _userManager to UserManager.

Now you should be good to go.

original: Thread #564822 | Message #1297744 | 2014-09-01

Repository owner locked as resolved and limited conversation to collaborators Aug 1, 2018

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Aug 2, 2018

ApplicationSignInManager:

Asuming the template implementation for the ApplicationSignInManager class, you can register this class as:

container.RegisterPerWebRequest<SignInManager<ApplicationUser, string>, ApplicationSignInManager>();

And for the second constructor parameter IAuthenticationManager the registration is somewhat different:

container.RegisterPerWebRequest<IAuthenticationManager>(() => 
    container.IsVerifying
        ? new OwinContext(new Dictionary<string, object>()).Authentication 
        : HttpContext.Current.GetOwinContext().Authentication); 

Because the IAuthenticationManager is depending upon an OwinContext, verifying the container will fail, because no such context will be there at verifying time. Therefore we will create a new OwinContext during verifying the container and return the Authentication component from this newly created OwinContext. Note that when the container isn't verifying the OwinContext should exist and we just return the 'normal' Authentication component from the OwinContext.

With above registration you can inject the ApplicationSignInManager in your controller or other classes as:

SignInManager<ApplicationUser, string> signInManager 

and then use it directly.

original: Thread #564822 | Message #1303157 | 2014-09-10

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Aug 2, 2018

Original question asked by @mhead11

Can you please help me register the RoleManager? I have everything else you listed done.

//Identity.config

public class ApplicationRoleManager : RoleManager<IdentityRole>
{
    public ApplicationRoleManager(IRoleStore<IdentityRole, string> roleStore)
    : base(roleStore) { }

    public static ApplicationRoleManager Create(
        IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
    {
        return new ApplicationRoleManager(new RoleStore<IdentityRole>(
            context.Get<ApplicationDbContext>()));
    }
}

I also added this in the startup.auth

app.CreatePerOwinContext(() => container.GetInstance<ApplicationRoleManager>());

My problem is here Trying to register this but it is failing in the SimpleInjectorInitializer. It won't compile.

container.RegisterPerWebRequest<RoleManager<IRoleStore<IdentityRole, string>>, ApplicationRoleManager>();

Thank you in advance.

original: Thread #564822 | Message #1304741 | 2014-09-15

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Aug 2, 2018

RoleManager

You can delete the Create() method from the Identity Config. Also no need to register the RoleManager with Owin, so remove the call to CreatePerOwinContext.

You can register the ApplicationRoleManager just like the UserManager. Like:

container.RegisterPerWebRequest<ApplicationRoleManager>();

container.RegisterPerWebRequest<IRoleStore<IdentityRole, string>>(
        () => new RoleStore<IdentityRole>(
              container.GetInstance<ApplicationDbContext>()));

And that should do it.

original: Thread #564822 | Message #1304771 | 2014-09-15

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Aug 2, 2018

Question: Is it possible to acquire and register IOwinContext without using HttpContext.Current?

original: Thread #564822 | Message #1326054 | 2014-11-25

Answer: No, the owin context is saved in the HttpContext. To get it you must access the context and use the extension method. The extension method is offcourse optional, but the most easy way.

original: Thread #564822 | Message #1326099 | 2014-11-25

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Aug 3, 2018

ApplicationDbContext's static constructor

Question:
On the [pre-release] IdentityFramework 2.1, inside the class ApplicationDbContext, there is a static constructor static ApplicationDbContext() that calls the ApplicationDbInitializer() to seed the database. How would I go about registering / setting that initializer as well? That ApplicationDbInitializer() references userManager and roleManager via HttpContext.Current.GetOwinContext().

Or do I just not need to do anything and whenever I request the ApplicationDbContext() instance it will run that initializer for me if the model has been changed (I have it inherit from DropCreateDatabaseIfModelChanges)?

original: Thread #564822 | Message #1329213 | 2014-12-05

Answer:
The ApplicationDbInitializer will seed your Db when you change the model. So the first thing you should ask yourself is, do I really need that functionality? And second, but I suppose you already know that, the code supplied by the sample is highly unlikely and undesirable to be used in production code. Creating a default admin user in the admin role like this is just evil, even for an example IMO.

That being said, [...] the static constructor in ApplicationDbContext only calls SetInitializer() on the static Database class. You can call this from anywhere in your application so the nasty static constructor from the template is really strange. They could have just as easily used the original 'Composition root' of the template: ConfigureAuth() and this solution would be much more straightforward to understand. Enough said, some code:

The composition root is the place to be for this code to run. Assuming your using a bootstrapper like mentioned in the first post, you can change the bootstrapper from the first post to

public static class SimpleInjectorInitializer
{
    public static Container Initialize(IAppBuilder app)
    {
        var container = GetInitializeContainer(app);

        container.Verify();

        DependencyResolver.SetResolver(
            new SimpleInjectorDependencyResolver(container));

        // set the db initializer after verifying the container
        // just ask the container for a new instance
        var dbInitializer = container.GetInstance<ApplicationDbInitializer>();
        Database.SetInitializer(dbInitializer);

        return container;
    }

    public static Container GetInitializeContainer(
        IAppBuilder app)
    {
        var container = new Container();
    
        // a singleton is perfect as it will be used once or never
        container.RegisterSingleton<ApplicationDbInitializer>();

        container.RegisterSingleton<Func<ApplicationUserManager>>(
            () => container.GetInstance<ApplicationUserManager>());
        container.RegisterSingleton<Func<ApplicationRoleManager>>(
            () => container.GetInstance<ApplicationRoleManager>());

        // rest of the code mentioned above
    }
}

Notice that I made registrations for UserManager and RoleManger factories. This is needed because the more or less ’static’ ApplicationDbInitializer class depends on UserManager and RoleManager, which both have a shorter lifetime definied (PerWebRequest). So when the Seed()method will be called we need to resolve the then current instances of theApplicationUserManagerandApplicationRoleManager`.

Now we have registrations for the ApplicationDbInitializer class and both factories we can setup our ApplicationDbInitializer using D.I. and remove the need for using the HttpContext and actually use the Service Locator anti-pattern. This would look something like:

public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext> 
{
    private readonly Func<ApplicationRoleManager> roleManagerFactory;
    private readonly Func<ApplicationUserManager> userManagerFactory;
 
    public ApplicationDbInitializer(Func<ApplicationUserManager> userManagerFactory, 
        Func<ApplicationRoleManager> roleManagerFactory)
    {
        this.userManagerFactory = userManagerFactory;
        this.roleManagerFactory = roleManagerFactory;
    }
    
    protected override void Seed(ApplicationDbContext context) {
        this.InitializeIdentityForEF(context);
        base.Seed(context);
    }
 
    //Create User=Admin@Admin.com with password=Admin@123456 in the Admin role        
    public void InitializeIdentityForEF(ApplicationDbContext db) {
        // get a instance from the factory
        var userManager = this.userManagerFactory.Invoke();
        var roleManager = this.roleManagerFactory.Invoke();
        
        // rest of code omitted for brevity...

original: Thread #564822 | Message #1329420 | 2014-12-06

@dotnetjunkie dotnetjunkie changed the title from MVC 5 template (ASP.Net Identity 2.0) combined with Simple Injector to MVC 5 template (ASP.NET Identity 2.0) combined with Simple Injector Aug 3, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.