ASP.NET MVC 框架中的身份使用 Identity Core

Identity in ASP.NET MVC Framework using Identity Core

我在部分切换到 .NET Standard 时遇到问题。

我正在将 class 库迁移到 .NET Standard,在这个库中我有存储库和数据库通信。我已经成功迁移它以使用 AspNetCore.Identity.EntityFrameworkCore。我试图实现的目标是最终让 1 个 .NET Standard 项目负责数据库,其中 1 个 MVC .NET Framework、1 API .NET Framework 和 1 个新的 .NET Core 应用程序将使用它。除此之外,其他一些 .NET Framework class 库也依赖于它。基本上 .NET Core 应用程序已经制作完成,但后端尚未 'merged' 重叠功能。

小概览:

不将 MVC/API 转换为 .Core 的原因是目前有太多其他库依赖于 .NET Framework,有些还不能转换,但是对数据库使用相同的库是一个根本性的变化,它将避免某些存储库的双重实现。

我也已经转换了实现 Microsoft.AspNetCore.Identity.IdentityUserMicrosoft.AspNetCore.Identity.IdentityRole 等的实体。 所以我的 DbContext class 看起来像这样:

public class DatabaseContext : IdentityDbContext<ApplicationUser, ApplicationRole, string, ApplicationUserClaim, ApplicationUserRole, ApplicationUserLogin, ApplicationRoleClaim, ApplicationUserToken>
{
    private IConfiguration _config;
    public DatabaseContext(IConfiguration config) : base()
    {
        _config = config;
    }
    //all DbSet, OnModelCreating
}

我已成功 运行 EFCore 代码首次迁移。

现在我正在尝试在我的 MVC 应用程序中配置标识(然后也在 API 项目中)。

我只有标准 IdentityConfig.csStartup.Auth.cs,所有配置都已完成。我试过查看此文档 (migration identity)。我所能做的就是添加这个,其中 AddMvc() 不存在,因此会引发编译错误:

Startup.cs

using System;
using System.IO;
using Babywatcher.Core.Data.Database;
using Babywatcher.Core.Data.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Owin;
using Owin;

[assembly: OwinStartupAttribute(typeof(MyProject.MVC.Startup))]
namespace MyProject.MVC
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var services = new ServiceCollection();
            ConfigureAuth(app);
            ConfigureServices(services);
        }

        public void ConfigureServices(IServiceCollection services)
        {
            // Add EF services to the services container.
            services.AddDbContext<DatabaseContext>(options =>
                options.UseSqlServer(""));//Configuration trying to refer to above method: Configuration.GetConnectionString("DefaultConnection")

            services.AddIdentity<ApplicationUser, ApplicationRole>()
                .AddEntityFrameworkStores<DatabaseContext>()
                .AddDefaultTokenProviders();

            services.AddMvc();
        }
    }
}

好吧,我想这并没有多大作用,而且我在两个 .NET Framework 项目中都使用了 SimpleInjector,我希望尽可能继续使用它,而不是使用默认的依赖项注入器。

通过上面的添加,我真的不知道如何使用 ConfigureAuth 方法以及将所有配置放在哪里。

当我尝试调整 IdentityManager 以尝试引用 AspNetCore.Identity 中的相同类型时,我在尝试更改 ApplicationUserManager:

时开始遇到问题

创建 UserStore 不是问题,但是尝试创建 UserManager 比较困难,我也试过在我的 UserRepository 中使用它,就像我以前做的那样,我可以现在不用了:(.

旧用户存储库

private readonly UserManager<ApplicationUser, string> _userManager = null;
private readonly RoleManager<ApplicationRole, string> _roleManager = null;

internal static IDataProtectionProvider DataProtectionProvider { get; private set; }
public UserRepository(DatabaseContext dbContext) : base(dbContext, c => c.contactId, m => m.ContactId)
{
    var userStore =
        new UserStore<ApplicationUser, ApplicationRole, string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>(dbContext);
    var roleStore = new RoleStore<ApplicationRole, string, ApplicationUserRole>(dbContext);
    _userManager = new UserManager<ApplicationUser, string>(userStore);
    _roleManager = new RoleManager<ApplicationRole, string>(roleStore);
    _userManager.UserValidator = new UserValidator<ApplicationUser>(_userManager) { AllowOnlyAlphanumericUserNames = false };
    if (DataProtectionProvider == null)
    {
        DataProtectionProvider = new MachineKeyProtectionProvider();
    }
    _userManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser, string>(DataProtectionProvider.Create("Identity"));
}

尝试再次执行上述操作会在创建 UserManager 时出现问题,因为它要求 9 个参数,所以我有点觉得不应该这样做......可能会做 让他们到达那里当然我首先要解决根本问题。

最后但同样重要的是:请记住,这些应用程序都已投入生产,因此特别是在用户周围,我需要确保所有登录仍然有效。启用此版本时,由于其他一些迁移问题,我需要将数据迁移到新数据库。

好吧,这是一个很长的答案,准备好第二天花在你的头发上吧:)。

首先是 IApplicationUser 的一个接口,由 2 个不同的 类:

实现
public interface IApplicationUser
{
    string Id { get; set; }

    /// <summary>Gets or sets the user name for this user.</summary>
    string UserName { get; set; }
}

实施 1,这是我的数据库实体的一部分:

public class ApplicationUser :  IdentityUser<string>, IApplicationUser
{
    public DateTime? LockoutEndDateUtc { get; set; }
    public bool RequiresPasswordCreation { get; set; }
    public string TemporaryToken { get; set; }
}

实施 2,对于我的 .NET Framework 项目:

public class ApplicationUserMvc : IdentityUser<string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>, IApplicationUser
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUserMvc, string> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here

        return userIdentity;
    }
 ...all the other things
}

然后我创建了自己的身份管理器(界面)

public interface IIdentityManager
{
    //User manager methods
    Task<IIdentityResult> CreateAsync(IApplicationUser user);
    //..all methods needed
}

public interface IIdentityResult
{
    bool Succeeded { get; set; }
    List<string> Errors { get; set; }
}

然后是我的实际实现,所以这是我的 .NET Core 项目。

public class IdentityManagerCore : IIdentityManager
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly RoleManager<ApplicationRole> _roleManager;

    public IdentityManagerCore(UserManager<ApplicationUser> userManager, RoleManager<ApplicationRole> roleManager)
    {
        _userManager = userManager;
        _roleManager = roleManager;
    }

    public async Task<IIdentityResult> CreateAsync(IApplicationUser user)
    {
        ApplicationUser realUser = new ApplicationUser()
        {
            Id = user.Id,
            TemporaryToken = user.TemporaryToken,
            AccessFailedCount = user.AccessFailedCount,
            ConcurrencyStamp = user.ConcurrencyStamp,
            Email = user.Email,
            EmailConfirmed = user.EmailConfirmed,
            LockoutEnabled = user.LockoutEnabled,
            LockoutEnd = user.LockoutEnd,
            NormalizedEmail = user.NormalizedEmail,
            NormalizedUserName = user.NormalizedUserName,
            PasswordHash = user.PasswordHash,
            PhoneNumber = user.PhoneNumber,
            PhoneNumberConfirmed = user.PhoneNumberConfirmed,
            RequiresPasswordCreation = user.RequiresPasswordCreation,
            SecurityStamp = user.SecurityStamp,
            TwoFactorEnabled = user.TwoFactorEnabled,
            UserName = user.UserName
        };
        var result = await _userManager.CreateAsync(realUser);
        return ConvertToInterface(result);
    }

    private IIdentityResult ConvertToInterface(IdentityResult result)
    {
        IIdentityResult realResult = new IdentityResultCore();
        realResult.Succeeded = result.Succeeded;
        realResult.Errors = result.Errors?.Select(x => x.Description).ToList();
        return realResult;
    }
}

public class IdentityResultCore : IdentityResult, IIdentityResult
{
       private IEnumerable<string> _errors;
    private bool _succeed;
    public new bool Succeeded
    {
        get => base.Succeeded || _succeed;
        set => _succeed = value;
    }

    public new List<string> Errors
    {
        get => base.Errors?.Select(x => x.Description).ToList() ?? _errors?.ToList();
        set => _errors = value;
    }
}

UserManagerRoleManager 在启动时注入,如下所示:

services.AddTransient<IIdentityManager, IdentityManagerCore>();

.NET Framework 实现:

public class IdentityManagerMvc : IIdentityManager
{
    private readonly UserManager<ApplicationUserMvc, string> _userManager = null;
    private readonly RoleManager<ApplicationRoleMvc, string> _roleManager = null;

    internal static IDataProtectionProvider DataProtectionProvider { get; private set; }
    public IdentityManagerMvc(DatabaseContextMvc dbContext)
    {
        var userStore =
            new UserStore<ApplicationUserMvc, ApplicationRoleMvc, string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>(dbContext);
        var roleStore = new RoleStore<ApplicationRoleMvc, string, ApplicationUserRole>(dbContext);
        _userManager = new UserManager<ApplicationUserMvc, string>(userStore);
        _roleManager = new RoleManager<ApplicationRoleMvc, string>(roleStore);
        _userManager.UserValidator = new UserValidator<ApplicationUserMvc>(_userManager) { AllowOnlyAlphanumericUserNames = false };
        if (DataProtectionProvider == null)
        {
            DataProtectionProvider = new MachineKeyProtectionProvider();
        }
        _userManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUserMvc, string>(DataProtectionProvider.Create("Identity"));
    }

    public async Task<IIdentityResult> CreateAsync(IApplicationUser user)
    {
        ApplicationUserMvc realUser = new ApplicationUserMvc()
        {
            Id = user.Id,
            TemporaryToken = user.TemporaryToken,
            AccessFailedCount = user.AccessFailedCount,
            ConcurrencyStamp = user.ConcurrencyStamp,
            Email = user.Email,
            EmailConfirmed = user.EmailConfirmed,
            LockoutEnabled = user.LockoutEnabled,
            LockoutEnd = user.LockoutEnd,
            NormalizedEmail = user.NormalizedEmail,
            NormalizedUserName = user.NormalizedUserName,
            PasswordHash = user.PasswordHash,
            PhoneNumber = user.PhoneNumber,
            PhoneNumberConfirmed = user.PhoneNumberConfirmed,
            RequiresPasswordCreation = user.RequiresPasswordCreation,
            SecurityStamp = user.SecurityStamp,
            TwoFactorEnabled = user.TwoFactorEnabled,
            UserName = user.UserName
        };
        var result = await _userManager.CreateAsync(realUser);
        return ConvertToInterface(result);
    }

    private IIdentityResult ConvertToInterface(IdentityResult result)
    {
        IIdentityResult realResult = new IdentityResultMvc();
        realResult.Succeeded = result.Succeeded;
        realResult.Errors = result.Errors?.ToList();
        return realResult;
    }
}


public class IdentityResultMvc : IdentityResult, IIdentityResult
{
    private IEnumerable<string> _errors;
    private bool _succeed;
    public new bool Succeeded
    {
        get => base.Succeeded || _succeed;
        set => _succeed = value;
    }

    public new List<string> Errors
    {
        get => base.Errors?.ToList() ?? _errors?.ToList();
        set => _errors = value;
    }
}

最后但同样重要的是,您的 .NET Framework 项目需要一个单独的 DatabaseContext,这个项目将仅用于 "identity" 目的,因此不会实际查询任何数据,仅用于认证,授权。

public class DatabaseContextMvc : IdentityDbContext<ApplicationUserMvc, ApplicationRoleMvc, string, ApplicationUserLogin,
    ApplicationUserRole, ApplicationUserClaim>
{
    public DatabaseContextMvc() : base("DatabaseContext")
    {
        Configuration.LazyLoadingEnabled = false;
        Configuration.ProxyCreationEnabled = false;

        //Database.SetInitializer<DatabaseContextMvc>(null);
    }

    public void SetTimeout(int minutes)
    {
        this.Database.CommandTimeout = minutes * 60;
    }

    public static DatabaseContextMvc Create()
    {
        return new DatabaseContextMvc();
    }
}

此时您应该具备在任何地方使用它所必需的所有 类。 因此,例如,在您的 .NET Framework 项目中,您可以像这样设置 ApplicationUserManager

public class ApplicationUserManager : UserManager<ApplicationUserMvc, string>
{
    public ApplicationUserManager(IUserStore<ApplicationUserMvc, string> store)
        : base(store)
    {//look at where i used applicationUserMvc
    }

    public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) 
    {
        var userStore =
            new UserStore<ApplicationUserMvc, ApplicationRoleMvc, string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>(context.Get<DatabaseContextMvc>());
        //ApplicationUserLogin,UserRole,UserClaim are self created but just override IdentityUserLogin (for example).
        var manager = new ApplicationUserManager(userStore);
    }
  ...
}

无论您在 .NET Framework 中使用什么依赖注入,请确保注册您的 DatabaseContextDatabaseContextMvc

这是我的 DatabaseContext,它位于 .NET Standard 库中并在 .NET Core 和 .NET Framework 中使用:

public class DatabaseContext : IdentityDbContext<ApplicationUser, ApplicationRole, string, ApplicationUserClaim, ApplicationUserRole, ApplicationUserLogin, ApplicationRoleClaim, ApplicationUserToken>
{
    private IConfiguration _config;
    public string ConnectionString { get; }
    public DatabaseContext(IConfiguration config) : base()
    {
        _config = config;
        var connectionString = config.GetConnectionString("DatabaseContext");
        ConnectionString = connectionString;
    }

    public void SetTimeout(int minutes)
    {
        Database.SetCommandTimeout(minutes * 60);
    }

    public virtual DbSet<Address> Addresses { get; set; }

    public static DatabaseContext Create(IConfiguration config)
    {
        return new DatabaseContext(config);
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        //setup code for the DbContextOptions
        optionsBuilder
            .UseSqlServer(ConnectionString, 
                providerOptions => providerOptions.CommandTimeout(60))
            .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
        base.OnConfiguring(optionsBuilder);
    }

经过这么长时间 post 或实施之后,您的注意力可能已经消失了,但再多说几句:

  • 我敢保证,有了这个你一定会用得上,这半年来一直用得很好。
  • 所以 .NET Standard(以及 EntityFrameworkCore)是进行数据库操作的主要方式,大部分代码都是为了应对 .NET Framework。
  • 您将努力安装正确的依赖项。它会导致运行时异常,你会很快遇到但很容易解决,.NET Framework 只需要为自己安装依赖项。确保版本一致,使用 Manage NuGet packages for Solution 下的 Consolidate 版本(右键单击解决方案)。
  • 您必须以网络核心的方式进行设置:因此您的 .NET Framework 项目中也需要 appsettings.json。另外在Web.Config中还是需要的,这主要是为了认证的一小部分。

    private IConfiguration GetConfiguartion()
    {
        var path = Server.MapPath("~/");
        var builder = new ConfigurationBuilder()
                         .SetBasePath(path)
                         .AddJsonFile("appsettings.json");
    
        return builder.Build();//inject the return IConfiguration in your DI
    }
    
  • 祝你好运。如果您认为这很困难并且会造成很多麻烦:正确,如果您有一个小型应用程序,您最好将所有内容都转换为 .NET Core / .NET Standard。