Current project:
- ASP.NET 4.6.2
- MVC 5
- Repository pattern from Persistence-Ignorant ASP.NET Identity
- ASP.NET MVC HTML5 Boilerplate
I have done my best to follow the repository pattern in the article I linked to above, as I have a distinct need for a repository pattern, as well as a desire to see User IDs as GUIDs and not just as nvarchar(128) strings.
Essentially, I have a need to share table structures across multiple sites, so I used a repository pattern that also allowed me to share logins across these multiple sites. The individual sites make use of the ASP.NET MVC HTML5 Boilerplate template, so AutoFac comes baked into it. However please also note that I am having considerable conceptual difficulties with Dependency Injection in general, akin to trying to smell the colour blue, or trying to taste the flavour of temperature. I’m just not grokking it.
I have also managed to extend that repository pattern to allow me to enable migrations. As in: I am able to push a migration of the database model to the DB. Unfortunately, because I am dealing with the Infrastructure layer (I called my repository pattern layers Core for the DB model and Infrastructure for everything else), I cannot inject a primary admin user into the DB because there is no Entity Framework and no App_Start in the Infrastructure layer. As such, I cannot make use of ApplicationDbInitializer
within Infrastructure:
namespace Company.Infrastructure.Context {
using Identity;
using Microsoft.AspNet.Identity;
using System;
using Store;
using System.Data.Entity;
public class ApplicationDbInitializer : DropCreateDatabaseAlways<ApplicationDbContext> {
private readonly UserManager<IdentityUser, Guid> _userManager;
private readonly RoleManager<IdentityRole, Guid> _roleManager;
public ApplicationDbInitializer() { }
public ApplicationDbInitializer(UserManager<IdentityUser, Guid> userManager, RoleManager<IdentityRole, Guid> roleManager) {
_userManager = userManager;
_roleManager = roleManager;
}
// This example shows you how to create a new database if the Model changes
//public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext>
protected override void Seed(ApplicationDbContext context) {
InitializeIdentityForEf(context);
base.Seed(context);
}
//Create User=Admin@Admin.com with password=Admin@123456 in the Admin role
public void InitializeIdentityForEf(ApplicationDbContext db) {
const string username = "CompanyName";
const string email = "info@companyname.com";
const string password = "password";
const string roleName = "Administrator";
//Create Role Admin if it does not exist
var role = _roleManager.FindByName(roleName);
if(role == null) {
role = new IdentityRole(roleName);
var roleresult = _roleManager.Create(role);
}
var user = _userManager.FindByName(username);
if(user == null) {
user = new IdentityUser { UserName = username, Email = email };
var result = _userManager.Create(user, password);
result = _userManager.SetLockoutEnabled(user.Id, false);
}
// Add user admin to Role Admin if not already added
var rolesForUser = _userManager.GetRoles(user.Id);
if(!rolesForUser.Contains(role.Name)) {
var result = _userManager.AddToRole(user.Id, role.Name);
}
}
}
}
to inject the primary admin.
As such, I am attempting to inject a primary admin user through one of the websites, but when I try to register the modified UserManager
and UserStore
it seems that AutoFac cannot pick up on these registrations, even though it picks up on all the other registrations that came with the template just fine.
AutoFac is registered through App_Start\Startup.Container.cs
:
namespace Company.Website {
using System.Reflection;
using System.Web.Mvc;
using Autofac;
using Autofac.Integration.Mvc;
using Services;
using Owin;
using System;
using Microsoft.AspNet.Identity;
using Infrastructure.Identity;
using Infrastructure.Store;
using Core.Repositories;
using Infrastructure.Context;
/// <summary>
/// Register types into the Autofac Inversion of Control (IOC) container. Autofac makes it easy to register common
/// MVC types like the <see cref="UrlHelper"/> using the <see cref="AutofacWebTypesModule"/>. Feel free to change
/// this to another IoC container of your choice but ensure that common MVC types like <see cref="UrlHelper"/> are
/// registered. See http://autofac.readthedocs.org/en/latest/integration/aspnet.html.
/// </summary>
public partial class Startup {
public static void ConfigureContainer(IAppBuilder app) {
var container = CreateContainer();
app.UseAutofacMiddleware(container);
// Register MVC Types
app.UseAutofacMvc();
}
private static IContainer CreateContainer() {
var builder = new ContainerBuilder();
var assembly = Assembly.GetExecutingAssembly();
RegisterServices(builder);
RegisterMvc(builder, assembly);
var container = builder.Build();
SetMvcDependencyResolver(container);
return container;
}
private static void RegisterServices(ContainerBuilder builder) {
builder.RegisterType<BrowserConfigService>().As<IBrowserConfigService>().InstancePerRequest();
builder.RegisterType<CacheService>().As<ICacheService>().SingleInstance();
builder.RegisterType<FeedService>().As<IFeedService>().InstancePerRequest();
builder.RegisterType<LoggingService>().As<ILoggingService>().SingleInstance();
builder.RegisterType<ManifestService>().As<IManifestService>().InstancePerRequest();
builder.RegisterType<OpenSearchService>().As<IOpenSearchService>().InstancePerRequest();
builder.RegisterType<RobotsService>().As<IRobotsService>().InstancePerRequest();
builder.RegisterType<SitemapService>().As<ISitemapService>().InstancePerRequest();
builder.RegisterType<SitemapPingerService>().As<ISitemapPingerService>().InstancePerRequest();
// My additions:
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();
builder.RegisterType<UserManager<IdentityUser, Guid>>().As<UserManager<IdentityUser, Guid>>().InstancePerRequest();
builder.RegisterType<UserStore>().As<IUserStore<IdentityUser, Guid>>().InstancePerLifetimeScope();
}
private static void RegisterMvc(ContainerBuilder builder, Assembly assembly) {
// Register Common MVC Types
builder.RegisterModule<AutofacWebTypesModule>();
// Register MVC Filters
builder.RegisterFilterProvider();
// Register MVC Controllers
builder.RegisterControllers(assembly);
}
/// <summary>
/// Sets the ASP.NET MVC dependency resolver.
/// </summary>
/// <param name="container">The container.</param>
private static void SetMvcDependencyResolver(IContainer container) {
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
}
And the beginning of my Home controller is as such:
namespace Company.Website.Controllers {
using Boilerplate.Web.Mvc;
using Boilerplate.Web.Mvc.Filters;
using Constants;
using Identity;
using Infrastructure.Models;
using Infrastructure.Context;
using Infrastructure.Store;
using Microsoft.AspNet.Identity;
using Microsoft.Owin.Security;
using Recaptcha.Web;
using Recaptcha.Web.Mvc;
using Services;
using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Mvc;
public class HomeController : Controller {
private readonly IBrowserConfigService _browserConfigService;
private readonly IFeedService _feedService;
private readonly IManifestService _manifestService;
private readonly IOpenSearchService _openSearchService;
private readonly IRobotsService _robotsService;
private readonly ISitemapService _sitemapService;
private ApplicationDbContext _db;
private readonly UserManager<IdentityUser, Guid> _userManager;
public HomeController(
IBrowserConfigService browserConfigService,
IFeedService feedService,
IManifestService manifestService,
IOpenSearchService openSearchService,
IRobotsService robotsService,
ISitemapService sitemapService,
UserManager<IdentityUser, Guid> userManager) {
_browserConfigService = browserConfigService;
_feedService = feedService;
_manifestService = manifestService;
_openSearchService = openSearchService;
_robotsService = robotsService;
_sitemapService = sitemapService;
_userManager = userManager;
}
The whole point of this is that I intend to use the initial run of the Index method to inject an admin user into the DB:
var user = _userManager.FindByName(username);
if(user == null) {
user = new IdentityUser { UserName = username, Email = email };
var result = _userManager.Create(user, password);
result = _userManager.SetLockoutEnabled(user.Id, false);
}
This could have been done from any page, I just chose Index. Once an admin user has been injected, the code would be decommissioned and removed, because… well, the job is done at that point. Admin user created.
Problem is, the entire system collapses with the presence of the UserManager entries in the default constructor. It all goes to pot:
Autofac.Core.DependencyResolutionException: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'Company.Website.Controllers.HomeController' can be invoked with the available services and parameters: Cannot resolve parameter 'Microsoft.AspNet.Identity.UserManager
2[Company.Website.Identity.IdentityUser,System.Guid] userManager' of constructor 'Void .ctor(Company.Website.Services.IBrowserConfigService, Company.Website.Services.IFeedService, Company.Website.Services.IManifestService, Company.Website.Services.IOpenSearchService, Company.Website.Services.IRobotsService, Company.Website.Services.ISitemapService, Microsoft.AspNet.Identity.UserManager
2[Company.Website.Identity.IdentityUser,System.Guid])'.
Remove that entry from the default constructor, everything is fine (with the user creation code in Index commented out, of course). Put it back in, and DotNet panic attack extraordinaire.
I have been bashing my head against this issue for some time now, and am at my wit's end. Any help would be greatly appreciated. I have found other posts that are similar, but they did not lead to a resolution for me.
Edit 1: A request for UserStore.cs
, found under Company.Infrastructure.Store
:
namespace Company.Infrastructure.Store {
using Identity;
using Core.Repositories;
using Core.Models;
using Microsoft.AspNet.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Context;
public class UserStore : IUserStore<IdentityUser, Guid>, IUserLoginStore<IdentityUser, Guid>, IUserClaimStore<IdentityUser, Guid>, IUserRoleStore<IdentityUser, Guid>, IUserPasswordStore<IdentityUser, Guid>, IUserSecurityStampStore<IdentityUser, Guid>, IDisposable {
private readonly IUnitOfWork _unitOfWork;
public UserStore(IUnitOfWork unitOfWork) {
_unitOfWork = unitOfWork;
}
#region IUserStore<IdentityUser, Guid> Members
public Task CreateAsync(IdentityUser user) {
if(user == null) throw new ArgumentNullException(nameof(user));
var u = getUser(user);
_unitOfWork.UserRepository.Add(u);
return _unitOfWork.SaveChangesAsync();
}
public Task DeleteAsync(IdentityUser user) {
if(user == null) throw new ArgumentNullException(nameof(user));
var u = getUser(user);
_unitOfWork.UserRepository.Remove(u);
return _unitOfWork.SaveChangesAsync();
}
public Task<IdentityUser> FindByIdAsync(Guid userId) {
var user = _unitOfWork.UserRepository.FindById(userId);
return Task.FromResult(getIdentityUser(user));
}
public Task<IdentityUser> FindByNameAsync(string userName) {
var user = _unitOfWork.UserRepository.FindByUserName(userName);
return Task.FromResult(getIdentityUser(user));
}
public Task UpdateAsync(IdentityUser user) {
if(user == null) throw new ArgumentException("user");
var u = _unitOfWork.UserRepository.FindById(user.Id);
if(u == null) throw new ArgumentException("IdentityUser does not correspond to a User entity.", nameof(user));
PopulateUser(u, user);
_unitOfWork.UserRepository.Update(u);
return _unitOfWork.SaveChangesAsync();
}
#endregion
#region IDisposable Members
public void Dispose() {
// Dispose does nothing since we want Unity to manage the lifecycle of our Unit of Work
}
#endregion
#region IUserClaimStore<IdentityUser, Guid> Members
public Task AddClaimAsync(IdentityUser user, Claim claim) {
if(user == null) throw new ArgumentNullException(nameof(user));
if(claim == null) throw new ArgumentNullException(nameof(claim));
var u = _unitOfWork.UserRepository.FindById(user.Id);
if(u == null) throw new ArgumentException("IdentityUser does not correspond to a User entity.", nameof(user));
var c = new UserClaim {
ClaimType = claim.Type,
ClaimValue = claim.Value,
User = u
};
u.UserClaim.Add(c);
_unitOfWork.UserRepository.Update(u);
return _unitOfWork.SaveChangesAsync();
}
public Task<IList<Claim>> GetClaimsAsync(IdentityUser user) {
if(user == null) throw new ArgumentNullException(nameof(user));
var u = _unitOfWork.UserRepository.FindById(user.Id);
if(u == null) throw new ArgumentException("IdentityUser does not correspond to a User entity.", nameof(user));
return Task.FromResult<IList<Claim>>(u.UserClaim.Select(x => new Claim(x.ClaimType, x.ClaimValue)).ToList());
}
public Task RemoveClaimAsync(IdentityUser user, Claim claim) {
if(user == null) throw new ArgumentNullException(nameof(user));
if(claim == null) throw new ArgumentNullException(nameof(claim));
var u = _unitOfWork.UserRepository.FindById(user.Id);
if(u == null) throw new ArgumentException("IdentityUser does not correspond to a User entity.", nameof(user));
var c = u.UserClaim.FirstOrDefault(x => x.ClaimType == claim.Type && x.ClaimValue == claim.Value);
u.UserClaim.Remove(c);
_unitOfWork.UserRepository.Update(u);
return _unitOfWork.SaveChangesAsync();
}
#endregion
#region IUserLoginStore<IdentityUser, Guid> Members
public Task AddLoginAsync(IdentityUser user, UserLoginInfo login) {
if(user == null) throw new ArgumentNullException(nameof(user));
if(login == null) throw new ArgumentNullException(nameof(login));
var u = _unitOfWork.UserRepository.FindById(user.Id);
if(u == null) throw new ArgumentException("IdentityUser does not correspond to a User entity.", nameof(user));
var l = new UserLogin {
LoginProvider = login.LoginProvider,
ProviderKey = login.ProviderKey,
User = u
};
u.UserLogin.Add(l);
_unitOfWork.UserRepository.Update(u);
return _unitOfWork.SaveChangesAsync();
}
public Task<IdentityUser> FindAsync(UserLoginInfo login) {
if(login == null) throw new ArgumentNullException(nameof(login));
var identityUser = default(IdentityUser);
var l = _unitOfWork.LoginRepository.GetByProviderAndKey(login.LoginProvider, login.ProviderKey);
if(l != null) identityUser = getIdentityUser(l.User);
return Task.FromResult(identityUser);
}
public Task<IList<UserLoginInfo>> GetLoginsAsync(IdentityUser user) {
if(user == null) throw new ArgumentNullException(nameof(user));
var u = _unitOfWork.UserRepository.FindById(user.Id);
if(u == null) throw new ArgumentException("IdentityUser does not correspond to a User entity.", nameof(user));
return Task.FromResult<IList<UserLoginInfo>>(u.UserLogin.Select(x => new UserLoginInfo(x.LoginProvider, x.ProviderKey)).ToList());
}
public Task RemoveLoginAsync(IdentityUser user, UserLoginInfo login) {
if(user == null) throw new ArgumentNullException(nameof(user));
if(login == null) throw new ArgumentNullException(nameof(login));
var u = _unitOfWork.UserRepository.FindById(user.Id);
if(u == null) throw new ArgumentException("IdentityUser does not correspond to a User entity.", nameof(user));
var l = u.UserLogin.FirstOrDefault(x => x.LoginProvider == login.LoginProvider && x.ProviderKey == login.ProviderKey);
u.UserLogin.Remove(l);
_unitOfWork.UserRepository.Update(u);
return _unitOfWork.SaveChangesAsync();
}
#endregion
#region IUserRoleStore<IdentityUser, Guid> Members
public Task AddToRoleAsync(IdentityUser user, string roleName) {
if(user == null) throw new ArgumentNullException(nameof(user));
if(string.IsNullOrWhiteSpace(roleName)) throw new ArgumentException("Argument cannot be null, empty, or whitespace: roleName.");
var u = _unitOfWork.UserRepository.FindById(user.Id);
if(u == null) throw new ArgumentException("IdentityUser does not correspond to a User entity.", nameof(user));
var r = _unitOfWork.RoleRepository.FindByName(roleName);
if(r == null) throw new ArgumentException("roleName does not correspond to a Role entity.", nameof(roleName));
u.Role.Add(r);
_unitOfWork.UserRepository.Update(u);
return _unitOfWork.SaveChangesAsync();
}
public Task<IList<string>> GetRolesAsync(IdentityUser user) {
if(user == null) throw new ArgumentNullException(nameof(user));
var u = _unitOfWork.UserRepository.FindById(user.Id);
if(u == null) throw new ArgumentException("IdentityUser does not correspond to a User entity.", nameof(user));
return Task.FromResult<IList<string>>(u.Role.Select(x => x.Name).ToList());
}
public Task<bool> IsInRoleAsync(IdentityUser user, string roleName) {
if(user == null) throw new ArgumentNullException(nameof(user));
if(string.IsNullOrWhiteSpace(roleName)) throw new ArgumentException("Argument cannot be null, empty, or whitespace: role.");
var u = _unitOfWork.UserRepository.FindById(user.Id);
if(u == null) throw new ArgumentException("IdentityUser does not correspond to a User entity.", nameof(user));
return Task.FromResult(u.Role.Any(x => x.Name == roleName));
}
public Task RemoveFromRoleAsync(IdentityUser user, string roleName) {
if(user == null) throw new ArgumentNullException(nameof(user));
if(string.IsNullOrWhiteSpace(roleName)) throw new ArgumentException("Argument cannot be null, empty, or whitespace: role.");
var u = _unitOfWork.UserRepository.FindById(user.Id);
if(u == null) throw new ArgumentException("IdentityUser does not correspond to a User entity.", nameof(user));
var r = u.Role.FirstOrDefault(x => x.Name == roleName);
u.Role.Remove(r);
_unitOfWork.UserRepository.Update(u);
return _unitOfWork.SaveChangesAsync();
}
#endregion
#region IUserPasswordStore<IdentityUser, Guid> Members
public Task<string> GetPasswordHashAsync(IdentityUser user) {
if(user == null) throw new ArgumentNullException(nameof(user));
return Task.FromResult(user.PasswordHash);
}
public Task<bool> HasPasswordAsync(IdentityUser user) {
if(user == null) throw new ArgumentNullException(nameof(user));
return Task.FromResult(!string.IsNullOrWhiteSpace(user.PasswordHash));
}
public Task SetPasswordHashAsync(IdentityUser user, string passwordHash) {
user.PasswordHash = passwordHash;
return Task.FromResult(0);
}
#endregion
#region IUserSecurityStampStore<IdentityUser, Guid> Members
public Task<string> GetSecurityStampAsync(IdentityUser user) {
if(user == null) throw new ArgumentNullException(nameof(user));
return Task.FromResult(user.SecurityStamp);
}
public Task SetSecurityStampAsync(IdentityUser user, string stamp) {
user.SecurityStamp = stamp;
return Task.FromResult(0);
}
#endregion
#region Private Methods
private User getUser(IdentityUser identityUser) {
if(identityUser == null) return null;
var user = new User();
PopulateUser(user, identityUser);
return user;
}
private static void PopulateUser(User user, IdentityUser identityUser) {
user.UserId = identityUser.Id;
user.UserName = identityUser.UserName;
user.PasswordHash = identityUser.PasswordHash;
user.SecurityStamp = identityUser.SecurityStamp;
}
private IdentityUser getIdentityUser(User user) {
if(user == null) return null;
var identityUser = new IdentityUser();
PopulateIdentityUser(identityUser, user);
return identityUser;
}
private static void PopulateIdentityUser(IdentityUser identityUser, User user) {
identityUser.Id = user.UserId;
identityUser.UserName = user.UserName;
identityUser.PasswordHash = user.PasswordHash;
identityUser.SecurityStamp = user.SecurityStamp;
}
#endregion
}
}