无法使用 ASP.NET MVC HTML5 样板在存储库设置中正确使用 AutoFac

Unable to employ AutoFac correctly in a repository setting using the ASP.NET MVC HTML5 Boilerplate

当前项目:

我已尽最大努力遵循上面链接的文章中的存储库模式,因为我有 a distinct need for a repository pattern,并且希望将用户 ID 视为 GUID 而不仅仅是 nvarchar(128 ) 字符串。

本质上,我需要在多个站点之间共享 table 结构,因此我使用了一种存储库模式,该模式还允许我在这些多个站点之间共享登录信息。各个站点使用 ASP.NET MVC HTML5 样板模板,因此 AutoFac 被嵌入其中。但是也请注意,一般来说,我在依赖注入方面存在相当大的概念困难,类似于尝试闻蓝色的气味,或尝试品尝温度的味道。我只是不了解它。

我还设法扩展了该存储库模式以允许我启用迁移。如:我能够将数据库模型迁移到数据库。不幸的是,因为我正在处理基础设施层(我称我的存储库模式层为数据库模型的核心和其他一切的基础设施),我无法将主要管理员用户注入数据库,因为没有 Entity Framework 也没有App_Start 在基础设施层。因此,我无法在 Infrastructure:

中使用 ApplicationDbInitializer
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);
      }
    }
  }
}

注入主管理员。

因此,我试图通过其中一个网站注入主要管理员用户,但是当我尝试注册修改后的 UserManagerUserStore 时,AutoFac 似乎无法接收这些注册,即使它可以很好地接收模板附带的所有其他注册。

AutoFac 是通过 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));
    }
  }
}

我的家庭控制器的开头是这样的:

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;
    }

重点是我打算使用 Index 方法的初始 运行 将管理员用户注入到数据库中:

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);
}

这可以从任何页面完成,我只是选择了索引。一旦注入了管理员用户,代码将被停用并删除,因为……好吧,到那时工作就完成了。已创建管理员用户。

问题是,由于默认构造函数中存在 UserManager 条目,整个系统崩溃了。全部归锅:

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])'.

从默认构造函数中删除该条目,一切正常(当然,Index 中的用户创建代码被注释掉了)。把它放回去,DotNet 惊慌失措。

一段时间以来,我一直在为这个问题苦苦思索,现在已经束手无策了。任何帮助将不胜感激。我有 ,但他们没有为我提供解决方案。


编辑 1: UserStore.cs 的请求,在 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
  }
}

感谢 tdragon,我能够 bash 将足够多的随机岩石组合在一起 assemble 我自己进行了函数依赖注入。

因为我使用了 ASP.NET MVC HTML5 样板,实际的依赖设置可能与默认的 AutoFac 设置不同,但关键是如果你使用相同的 repository pattern tutorial 和我一样,您需要在 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();

从那里开始,无论您希望在哪个控制器中操纵用户,都需要添加以下内容:

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;
}

当然,我使用该存储库模式的工作还没有完成——实际的 User table 与默认值相比有很大的修改,而且由于验证问题,我的实际用户插入失败了(我我认为某些东西被 non-nullable 值绊倒了,我没有在我的用户插入中明确说明,即使数据库字段有默认值),但重要的是依赖注入不再是问题.