Идентичность в ASP.NET MVC Framework с использованием Identity Core - PullRequest
0 голосов
/ 08 мая 2018

У меня проблемы с частичным переключением на .NET Standard.

Я нахожусь в процессе миграции библиотеки классов на .NET Standard, в этой библиотеке у меня есть хранилища и связь с базой данных. Я уже успешно перенес это для использования AspNetCore.Identity.EntityFrameworkCore. В конечном итоге я пытаюсь добиться того, чтобы один проект .NET Standard позаботился о базе данных, где его будут использовать 1 MVC .NET Framework, 1 API .NET Framework и 1 новое приложение .NET Core. Кроме того, от этого зависят несколько других библиотек классов .NET Framework. По сути, приложение .NET Core уже сделано, но бэкэнд не был «объединен» для перекрывающихся функций.

Небольшой обзор:

overview

Причина не преобразования MVC / API в .Core заключается в том, что слишком много других библиотек в настоящее время зависят от .NET Framework, некоторые еще не конвертируемы, но использование этой же библиотеки для базы данных является фундаментальным изменением, которое позволит избежать двойных реализаций некоторые репозитории.

Я также уже преобразовал свои сущности, которые реализуют Microsoft.AspNetCore.Identity.IdentityUser, Microsoft.AspNetCore.Identity.IdentityRole и т. Д. Так что мой класс DbContext выглядит так:

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.

Теперь я пытаюсь настроить Identity в своем приложении MVC (а затем и в проекте API).

У меня просто есть стандартные IdentityConfig.cs, Startup.Auth.cs, где все настройки выполнены. Я попытался просмотреть эту документацию (идентификация миграции) . Все, что я мог сделать, это добавить это, где 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();
        }
    }
}

Ну, я думаю, это не особо помогает, также я использую SimpleInjector в обоих проектах .NET Framework, который я предпочел бы продолжать использовать, если это возможно, вместо использования инжектора зависимостей по умолчанию.

Добавляя выше, я не знаю, что делать с методом ConfigureAuth и куда поместить всю конфигурацию.

Когда я пытаюсь настроить IdentityManager, чтобы попытаться сослаться на те же типы в AspNetCore.Identity, у меня появляются проблемы при попытке изменить ApplicationUserManager:

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 аргументов, которые он запрашивает, поэтому я чувствовал, что это не следует делать таким образом ... Вероятно, будет что-то вроде это чтобы получить их там, конечно, я сначала хочу исправить проблему с рутом.

И последнее, но не менее важное: имейте в виду, что все эти приложения уже работают, поэтому особенно пользователям необходимо убедиться, что все логины все еще работают. При переносе этой версии мне нужно будет перенести данные в новую базу данных из-за некоторых других проблем миграции.

1 Ответ

0 голосов
/ 12 декабря 2018

Хорошо, так что это длинный ответ, будьте готовы потратить следующий день, чтобы вырвать себе волосы из головы:).

Сначала интерфейс IApplicationUser, который реализуется двумя различными классами:

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
}

Затем я создал свой собственный Identity Manager (интерфейс)

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

UserManager и RoleManager вводятся при запуске следующим образом:

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

И последнее, но не менее важное: вам понадобится отдельный DatabaseContext для вашего проекта .NET Framework, этот будет использоваться только в целях «идентификации», чтобы фактически не запрашивать какие-либо данные, только для аутентификации и авторизации.

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, обязательно зарегистрируйте ваши DatabaseContext и DatabaseContextMvc.

Вот мой 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);
    }

ваш фокус, вероятно, уже ушел после этого длинного поста или после реализации, но еще несколько слов:

  • Я могу гарантировать, что с этим вы заставите его работать, он прекрасно работает в течение последнего полугода.
  • Так что .NET Standard (и, следовательно, EntityFrameworkCore) является основным способом выполнения операций над базой данных, большая часть кода справляется с .NET Framework.
  • Вы будете бороться с установкой правильных зависимостей. Это вызовет исключения во время выполнения, с которыми вы столкнетесь очень быстро, но их легко разрешить, .NET Framework просто нужно установить зависимости для себя. Убедитесь, что версии выровнены, используйте версию Consolidate в разделе «Управление пакетами NuGet для решения» (щелкните правой кнопкой мыши решение).
  • Вам нужно будет сделать это по-netcore-способу для настроек: так что вам нужен appsettings.json и в вашем .NET Framework проекте. Также вам все еще нужно это в 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.

...