0

Current project:

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.UserManager2[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.UserManager2[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
  }
}
Community
  • 1
  • 1
René Kåbis
  • 842
  • 2
  • 9
  • 28
  • Could you post the implementation of the `UserStore` class? I feel like you might be missing the registration for the `DbContext` that `UserStore` needs. – Mickaël Derriey Jan 17 '17 at 21:56
  • Provided. Although it is pretty well a cut-and-paste of the content from the Repository article. – René Kåbis Jan 17 '17 at 21:59
  • 1
    Could you try change `builder.RegisterType>().As>().InstancePerRequest();` to `builder.RegisterType>().InstancePerRequest();`? – tdragon Jan 18 '17 at 08:23
  • Well, bugger me with a pogo stick. That worked. Granted, I am still running into issues, but those (related specifically to User insert) are ones of Validation errors, probably due to my greatly expanded `User` table. I'm thinking one of those entries that don't have a default value are throwing the insert out of whack. As well, I am running into more issues with my `RoleManager` injection (which I did not cover in order to deal with one thing at a time), please keep an eye out for an Edit2 above for more details on that. – René Kåbis Jan 18 '17 at 18:42
  • Whelp, looks like I spoke too soon. `RoleManager` is now functional, now just have to deal with a pesky insert validation issue with the user itself. – René Kåbis Jan 18 '17 at 19:04

1 Answers1

0

Thanks to tdragon, I was able to bash together enough random rocks to assemble myself a functional dependency injection.

Because I used the ASP.NET MVC HTML5 Boilerplate, the actual dependency setup will probably be different than a default AutoFac setup, but the key thing is that if you use the same repository pattern tutorial as I did, you will need to create the following in your StartupContainer.cs:

builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();
builder.RegisterType<UserManager<IdentityUser, Guid>>().InstancePerRequest();
builder.RegisterType<UserStore>().As<IUserStore<IdentityUser, Guid>>().InstancePerLifetimeScope();
builder.RegisterType<RoleManager<IdentityRole, Guid>>().InstancePerRequest();
builder.RegisterType<RoleStore>().As<IRoleStore<IdentityRole, Guid>>().InstancePerLifetimeScope();

From there, in whatever controller you wish to manipulate users in, you need to add the following:

private readonly IUnitOfWork _unitOfWork;
private readonly UserManager<IdentityUser, Guid> _userManager;
private readonly RoleManager<IdentityRole, Guid> _roleManager;

public ControllerName(
    IUnitOfWork unitOfWork,
    UserManager<IdentityUser, Guid> userManager,
    RoleManager<IdentityRole, Guid> roleManager) {
  _unitOfWork = unitOfWork;
  _userManager = userManager;
  _roleManager = roleManager;
}

Granted, my job with that repository pattern is not complete -- the actual User table is significantly modified from the default, and my actual user insertion is failing due to validation issues (I am thinking that something is getting tripped up on non-nullable values that I am not explicitly stating in my user insertion, even though the DB fields have default values), but the important thing is that the Dependency Injection no longer is the issue.

Community
  • 1
  • 1
René Kåbis
  • 842
  • 2
  • 9
  • 28