Skip to content
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

How to use Simple Injector in ASP.NET 5 with ASP.NET Identity #93

Closed
kawazoe opened this issue Aug 17, 2015 · 5 comments
Closed

How to use Simple Injector in ASP.NET 5 with ASP.NET Identity #93

kawazoe opened this issue Aug 17, 2015 · 5 comments
Labels

Comments

@kawazoe
Copy link

kawazoe commented Aug 17, 2015

I've been trying to implement a vnext website with SimpleInjector using the pattern recommended in issue #41. I really like the separation it brings between first-party and third-party services, but I'm hitting a wall when trying to add support for Asp.Net Identity v3 using a custom UserStore.

You see, Asp.Net Identity normally gets registered through the DNX DI system during service configuration.

services.AddIdentity<FooIdentityUser, FooIdentityRole>()
    .AddDefaultTokenProviders()
    .AddUserStore<FooUserStore>()
    .AddRoleStore<FooRoleStore>();

This means that the entire Asp.Net Identity dependencies, including the custom FooUserStore and FooRoleStore, class will be registered on the the default DNX container. Already, this breaks the nice separation between the two containers, but the main issue comes from the AccountController class. Since this class needs access to Asp.Net Identity's UserManager and SignInManager, then it needs to access classes registered on the DNX container.

With the suggested integration method, controller activation is completely handled by SimpleInjector through the SimpleInjectorControllerActivator. There is no place to inject DNX registered services from there.

I thought about registering the Asp.Net Identity types in the DNX container and use SimpleInjector as a forwarder to the DNX's container.

container.Register(app.ApplicationServices.GetRequiredService<UserManager<FooIdentityUser>>)

The main problem with this solution is that the DNX container now have to know about my data access layer which means more forwarding; and in both direction this time.

To me, this feels like a road to hell. I'm sure there must be a simpler way to get this working. What do you recommend to do in this case?

@dotnetjunkie
Copy link
Collaborator

There are a several possible paths to walk here, depending on to what extend you would like the SOLID principles guide you here.

If you're just interested to get this working quickly, you can add the following configuration to vNext's Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) method:

IServiceProvider provider = app.ApplicationServices;

container.Options.AllowOverridingRegistrations = true;
container.Register<AccountController>(provider.GetRequiredService<AccountController>);
container.Options.AllowOverridingRegistrations = false;

What this does is simply telling Simple Injector to delegate the construction of the AccountController to ASP.NET's built-int DI container. This will work, because with the default template, the AccountController can already be resolved.

This does mean however that you will have to register the IEmailSender and ISmsSender abstractions in the ASP.NET container. This is weird of course, because although these abstractions are defined by the VS template, they are application-specific services and it's likely that other parts of your application will want to use these abstractions as well.

So instead, you can let the AccountController and AuthMessageSender to be created by Simple Injector, while the UserManager, SignInManager and ApplicationDbContext are redirected to the vNext container:

IServiceProvider provider = app.ApplicationServices;

container.Register<IEmailSender, AuthMessageSender>();
container.Register<ISmsSender, AuthMessageSender>();
container.Register(provider.GetRequiredService<UserManager<ApplicationUser>>);
container.Register(provider.GetRequiredService<SignInManager<ApplicationUser>>);
container.Register(provider.GetRequiredService<ApplicationDbContext>);

The decision we made with the first option is to see the AccountController as framework type, or at least, we don't really care about the maintainability of this type, even though its code in our application. This might be reasonable way of looking at this, although I would argue that after the code got injected into our project, it has become our problem and responsibility to maintain.

With the second option, we decided that the AccountController is actually part of our code base and is our control to maintain. Still we (implicitly) chose to violate the SOLID principles (especially DIP, SRP, and ISP) by injecting UserManager<T>, SignInManager<T> and ApplicationDbContext into the AccountController. OurAccountController` is:

  • violating the Dependency Inversion Principle (DIP), because it takes a hard dependency on concrete types that are defined by an external tool;
  • it violates the Interface Segregation Principle (ISP), because it is forced to depend on methods on UserManager<T>, SignInManager<T> and ApplicationDbContext that it doesn't use;
  • and it violates Single Responsibility Principle (SRP), because it implements lots of logic should not be part of the AccountController.

So the third option is to actually refactor the AccountController into a SOLID class. How this looks will completely depend on your application, because the generated code supplies us with a full implementation that almost no one will ever need. So you as application developer will have to strip out the code that you don't use and from that proper application-specific abstraction can be distilled.

I won't be able to post an example of how a SOLID AccountController might look like right now (because of time constraints), but I will update this answer within a few days with an example. Please stay tuned...

@kawazoe
Copy link
Author

kawazoe commented Aug 18, 2015

I see what you are suggesting there and I agree with you on the fact that it is problematic that Identity Framework, by default, uses a strong dependency on the managers in the AccountController class. Though, this only answers one part of my question.

Let's ignore your first suggestion for a minute and assume I am going down the proper route. If I understood you correctly, it is essentially going to be based on the second solution, but with an additional set of wrappers for which I control the interfaces. This solves the issue about injecting the right dependencies inside AccountController, but this doesn't take care of the second layer of the problem.

I still need to inject a custom DAL inside my UserStore. This dependency is registered inside SimpleInjector, but even if I use your second solution and forward the registration to the DNX container. I will still be stuck when it comes to the UserStore.

IServiceProvider provider = app.ApplicationServices;

container.Register<IEmailSender, AuthMessageSender>();
container.Register<ISmsSender, AuthMessageSender>();
container.Register(provider.GetRequiredService<UserManager<ApplicationUser>>);
container.Register(provider.GetRequiredService<SignInManager<ApplicationUser>>);

container.Register<IDataAccessLayer, FooDataAccessLayer>();

class FooUserStore : ...
{
    FooUserStore(IDataAccessLayer dal)
    {
        ...
    }
}

How should I register this custom store in such a situation?

@kawazoe kawazoe closed this as completed Aug 18, 2015
@kawazoe kawazoe reopened this Aug 18, 2015
@dotnetjunkie
Copy link
Collaborator

I'm sorry, I had to reread your question a couple of times and had to dive a bit into Identity Framework to understand what the problem is.

This is where you start to see the cracks in the new vNext DI eco system. The problem is not so much with the DI implementation itself, but with all the assumptions around it, and especially how other tools will start building upon this new API (as Identity is already doing). This causes trouble for people who like to keep application code isolated from framework code.

The AddUserStore<T>() extension method that you are calling is actually really simple under the covers. It is making the following registration in the vNext container:

AddScoped(typeof(IUserStore<FooIdentityUser>), typeof(FooUserStore))

I would view this FooUserStore as an adapter to allow customizing framework code. I therefore don't see this UserStore as core application code (especially since it takes a hard dependency on Identity). So I would probably register it in the vNext container, while I would register the IDataAccessLayer in Simple Injector. This means that you should probably replace the AddUserStore<FooUserStore>() call with something as follows:

services.AddScoped<IUserStore<FooIdentityUser>>(
    _ => new FooUserStore(this.container.GetInstance<IDataAccessLayer>));

Note that I'd advice to change FooUserStore's depedency from IDataAccessLayer to Func<IDataAccessLayer>. This makes it clear that Simple Injector is asked for an IDataAccessLayer instance when ever required. Because we register FooUserStore in vNext, it becomes impossible to detect any captive dependencies/lifestyle mismatches here and injecting a Func<T> removes the possibility of causing such captive dependency.

@kawazoe
Copy link
Author

kawazoe commented Aug 19, 2015

Wow, ok thanks a lot. This pretty much answers all of my questions.

So the solution is pretty much to setup forwarding between the two containers to make the various services accessible.

Regarding your SOLID AccountController example, I think you should still post it here if you have some time. This will most definitely be useful to people looking to cleanly integrate IdentityFramework inside their application.

Is there a sample library for SimpleInjector? Once I'm done setting up this project, I could publish this code as a sample that other people could follow.

@dotnetjunkie
Copy link
Collaborator

So the solution is pretty much to setup forwarding between the two containers

Yes. There will probably be just a few services where this forwarding is required, although we can expect this will happen more in the future, since every many framework developers will invalidly assume that their framework needs to integrate with the vNext container.

I think you should still post it here if you have some time

I will. Last days I was quite busy finalizing v3, but no that it is out the door, I'll try to create a SOLID AccountController example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants