AspNet Identity and IoC Container Registration
2016-06-09 17:44
911 查看
https://github.com/trailmax/IoCIdentitySample
TL;DR: Registration code for Autofac, for SimpleInjector, for Unity.
Tony Mackay has an alternative walk-through of a very similar process but with AutofacPart 2: Sending Emails in Asp.Net Identity using Dependency Injection, Sendgrid and debugging it with Mailtrap.io
Warning: If you don’t know what Dependency Injection is or you don’t know why you need this, don’t waste your time on this article. This approach is not recommended for cases when you don’t need a IoC container and have only a handful of controllers. Visual Studio template with Identity framework works great out of the box. You don’t need DI to enjoy full power of Asp.Net Identity. This article does not explain what DI is, how it works and why you need it. So proceed with caution and only if you know the difference between constructor injection and lifetime scope.
I regularly monitor StackOverflow for questions related to AspNet Identity framework. And one question keeps popping up over and over again: how do you use Inversion of Control containers with this framework.
If you know the main principles of Dependency Injection, things are very simple. Identity is very DI-friendly and I’ve done a number of projects with Identity and injected all Identity components via Autofac or SimpleInjector. The principles behind DI are applicable to all the containers, despite the differences in API’s for registration.
For the sake of this blog-post I’ll start with the standard VS2013 template and use Unity container, just because I’ve never used Unity before. You can view registrations for SimpleInjector in my test-project.
Projects where I used Autofac are far too complex to show as an example and none of them are open-source, so Autofac fans will have to figure out themselves from my examples.
Create project, install dependencies
For this sample I’ve used standard MVC project template from Visual Studio 2013.First thing I’ve done – updated all Nuget packages. Just to keep new project really up to date.
Next thing I do is install container nuget packages:
PM> Install-Package Unity.Mvc
In default template I don’t like the way Identity classes are all in one file and in
App_Startfolder, so I move them to
/Identityfolder and place a separate classes into separate folders. This is my preference, not really a requirement -) (Did you know with Reshrper you can place caret on class name and Ctr+R+O -> Move To Folder and get all the classes into separate files, into different folder)
Configure DI
Now, once we have installed Unity, let’s register components with it. I see that Unity nuget package have created 2 files for me in/App_Start: UnityConfig.cs and UnityMvcActivator.cs.
UnityConfig.cs looks like this (I’ve removed comments):
public class UnityConfig { private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() => { var container = new UnityContainer(); RegisterTypes(container); return container; }); public static IUnityContainer GetConfiguredContainer() { return container.Value; } public static void RegisterTypes(IUnityContainer container) { // TODO: Register your types here // container.RegisterType<IProductRepository, ProductRepository>(); } }
This is pretty standard class most containers have for registration, but I’ve never seen Lazy initialisation of the container before. I’ll keep it that way – when in Rome, do as Romans -) However I’ve got strong feeling that
RegisterTypemethod should be private, as it makes no sense to expose it to the world. And here the registrations of components should go. I’ll come back to this class later.
Now I’d like to have a look on another unity class
UnityWebActivator:
using System.Linq; using System.Web.Mvc; using IoCIdentity; using Microsoft.Practices.Unity.Mvc; [assembly: WebActivatorEx.PreApplicationStartMethod(typeof(UnityWebActivator), "Start")] [assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(UnityWebActivator), "Shutdown")] namespace IoCIdentity { public static class UnityWebActivator { public static void Start() { var container = UnityConfig.GetConfiguredContainer(); FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First()); FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container)); DependencyResolver.SetResolver(new UnityDependencyResolver(container)); // TODO: Uncomment if you want to use PerRequestLifetimeManager // Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule)); } public static void Shutdown() { var container = UnityConfig.GetConfiguredContainer(); container.Dispose(); } } }
I’ve included namespaces and attribute, as these are pretty important here. The two assembly attributes come fromWebActivator project and tells the application to execute methods before application starts and on application shut-down.
Start()method calls to the
GetConfiguredContainer()from UnityConfig class that we’ve seen earlier – this is just initiates the DI container with registered types.
Next two lines de-register standard MVC filter instead registers Unity filter provider. I presume this is to enable injections into filter objects.
This line
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
Sets Unity container as MVC standard dependency resolver, so in your static methods you can go
DependencyResolver.Current.GetService<MyService>();and this way Unity will be called into action and will resolve an instance of type
MyService.
I think this is good enough initialisation code. Let’s move on.
Register components
Now let’s register components we need. Despite the fact, that Unity can resolve concrete types without registration (as per comments I’ve deleted), I’ll register them anyway. Here is the basic list of registrations I have. This is not a complete list, I’ll add more types there when I need them:private static void RegisterTypes(IUnityContainer container) { container.RegisterType<ApplicationDbContext>(); container.RegisterType<ApplicationSignInManager>(); container.RegisterType<ApplicationUserManager>(); }
Now, lets look on end consumers of our components – controllers. I’m starting with
AccountControlleras it has a use of
UserManagerand
ApplicationSignInManager. By default it looks like this:
public class AccountController : Controller { private ApplicationUserManager _userManager; public AccountController() { } public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager ) { UserManager = userManager; SignInManager = signInManager; } public ApplicationUserManager UserManager { get { return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>(); } private set { _userManager = value; } } private ApplicationSignInManager _signInManager; public ApplicationSignInManager SignInManager { get { return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>(); } private set { _signInManager = value; } } // this is hidden on the very bottom - go find it and delete later private IAuthenticationManager AuthenticationManager { get { return HttpContext.GetOwinContext().Authentication; } } // list of actions
Let’s follow the DI principles and remove all the crap.
HttpContext.GetOwinContext().Get<ApplicationUserManager>()is a prime example of service locator as an anti-pattern. Also this does not use our Unity container, this uses Owin for object resolution. We don’t want to mix Owin registrations with Unity registrations for many reasons (the biggest reason is lack of lifetime management between 2 containers). So now my
AccountControllerlooks like this:
private readonly ApplicationUserManager userManager; private readonly ApplicationSignInManager signInManager; private readonly IAuthenticationManager authenticationManager; public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager, IAuthenticationManager authenticationManager) { this.userManager = userManager; this.signInManager = signInManager; this.authenticationManager = authenticationManager; } // actions
If you noticed, previously we have not registered
IAuthenticationManagerwith the container. Let’s do this now:
container.RegisterType<IAuthenticationManager>( new InjectionFactory(c => HttpContext.Current.GetOwinContext().Authentication));
If you run your application now, you’ll get exception that “IUserStore can’t be constructed” – that is correct, we have not registered it anywhere and it is required for
ApplicationUserManager. Register now
IUserStore:
container.RegisterType<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>( new InjectionConstructor(typeof(ApplicationDbContext)));
I specify what type I’d like to use as a parameter, otherwise Unity will try to resolve
DbContextwhich is wrong. But instead we need our
ApplicationDbContext.
ApplicationUserManager class
Now let’s look onUserManagerclass. The constructor does not have much in it, only takes
IUserManager. But there is static method
Createthat creates an instance of
UserManagerand configures the settings:
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) { // configuration stuff here }
This method creates instances of
ApplicationUserManager. Let’s copy all the code from
Createmethod into constructor:
public ApplicationUserManager(IUserStore<ApplicationUser> store, IdentityFactoryOptions<ApplicationUserManager> options) : base(store) { // Configure validation logic for usernames this.UserValidator = new UserValidator<ApplicationUser>(this) { AllowOnlyAlphanumericUserNames = false, RequireUniqueEmail = true }; // Configure validation logic for passwords this.PasswordValidator = new PasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = false, RequireDigit = false, RequireLowercase = false, RequireUppercase = false, }; // 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")); } }
Now we have constructor parameter
IdentityFactoryOptions<ApplicationUserManager> options. Previously this was supplied by Owin when we executed this code:
HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
This
GetUserManagerdoes magic tricks (I tried following their source code, but I’m not brainy enough-) and adds parameter you see above. But we’d like to avoid registration through Owin.
IdentityFactoryOptions<ApplicationUserManager>provides
IDataProtectionProviderneeded to generate user tokens to confirm account registration and for password reset tokens. It is used in constructor like this:
var dataProtectionProvider = options.DataProtectionProvider; if (dataProtectionProvider != null) { IDataProtector dataProtector = dataProtectionProvider.Create("ASP.NET Identity"); manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtector); }
We can’t resolve ourselves
IdentityFactoryOptions, we need Owin for that. And we can’t resolve
IDataProtectorourselves as well. But after looking on source code of Owin I have found where this
dataProtectoris coming from:
IAppBuilder.GetDataProtectionProvider(). But
IAppBuilderis not available anywhere apart from
Startup.Auth.csfile. So we can resolve this class where we can and save as static reference. And then use this reference where needed:
public partial class Startup { // add this static variable internal static IDataProtectionProvider DataProtectionProvider { get; private set; } public void ConfigureAuth(IAppBuilder app) { // add this assignment DataProtectionProvider = app.GetDataProtectionProvider(); // other stuff goes here unchanged } }
and in
ApplicationUserManagerI rewrite part with assigning
UserTokenProviderlike this:
// here we reuse the earlier assigned static variable instead of var dataProtectionProvider = Startup.DataProtectionProvider; // this is unchanged if (dataProtectionProvider != null) { IDataProtector dataProtector = dataProtectionProvider.Create("ASP.NET Identity"); this.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtector); }
Now we can remove dependency of
IdentityFactoryOptions<ApplicationUserManager>from ApplicationUserManager constructor. And the whole class now looks like this:
public class ApplicationUserManager : UserManager<ApplicationUser> { public ApplicationUserManager(IUserStore<ApplicationUser> store) : base(store) { // Configure validation logic for usernames this.UserValidator = new UserValidator<ApplicationUser>(this) { AllowOnlyAlphanumericUserNames = false, RequireUniqueEmail = true }; // Configure validation logic for passwords this.PasswordValidator = new PasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = false, RequireDigit = false, RequireLowercase = false, RequireUppercase = false, }; // 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 = Startup.DataProtectionProvider; if (dataProtectionProvider != null) { IDataProtector dataProtector = dataProtectionProvider.Create("ASP.NET Identity"); this.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtector); } } }
At this point the application should be usable and you should be able to register as a user, login and log out.
Clean-Up
Now that our app works with DI, let’s clean up some static calls that we don’t want around.First of all, let’s fix
ManageController, remove all empty controllers and initialisation of
UserManagerfrom OwinContext. The constructor should look like this:
private readonly ApplicationUserManager userManager; private readonly IAuthenticationManager authenticationManager; public ManageController(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) { this.userManager = userManager; this.authenticationManager = authenticationManager; }
Now let’s go into
Startup.Auth.csand look on bits we don’t want:
// Configure the db context, user manager and signin manager to use a single instance per request app.CreatePerOwinContext(ApplicationDbContext.Create); app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create); app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
I for sure know that Identity resolves
ApplicationUserManagerthrough Owin context and it uses it in certain cases. All the other Owin-resolved objects can be removed from this list:
// Configure the db context, user manager and signin manager to use a single instance per request app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
Then remove static object creation for
UserManagerand
SignInManagerto Unity resolution:
app.CreatePerOwinContext(() => DependencyResolver.Current.GetService<ApplicationUserManager>());
And remove static methods
ApplicationUserManager.Createand
ApplicationSignInManager.Create– no longer needed or used anywhere.
By now you should have all Identity components injected into your controllers via Unity container. I was able to register and login into the application. So the goal of this article is complete.
For your reference full source code of the solution is available on GitHub
Posted in Uncategorized.Tagged asp.net, di, Identity, mvc, recipe.
相关文章推荐
- CocoaPods和版本控制小技巧
- asp.net简单网站的实现
- Web Server 在iis下部署asp网站在iis下
- C#(asp.net)多线程用法示例(可用于同时处理多个任务)
- asp.net实现DropDownList,TreeView,ListBox的无限极分类目录树
- asp.net DataTable相关操作集锦(筛选,取前N条数据,去重复行,获取指定列数据等)
- asp.net提取多层嵌套json数据的方法
- 使用Aspose组件将WORD、PDF、PPT转为图片
- Asp.net MVC 移除不用的视图引擎
- ASP.NET Core 中文文档 第二章 指南(1)用 Visual Studio Code 在 macOS 上创建首个 ASP.NET Core 应用程序
- ASP.NET Core 中文文档 第一章 入门
- asp.net DataTable相关操作集锦(筛选,取前N条数据,去重复行,获取指定列数据等)
- 用Swashbuckle给ASP.NET Core的项目自动生成Swagger的API帮助文档
- Asp.Net中的控件(一)验证控件 和DropList选择控件
- asp.net 自带的缓存
- Spring3系列12-Spring AOP AspectJ
- asp.net中Page.ClientScript.RegisterStartupScript用法小结
- ASP.NET MVC 的分部视图
- ASP.NET MVC 4中如何为不同的浏览器自适应布局和视图
- RaspberryPi cProfile使用