使用DbContext接口注册AspNetCore 2.1身份系统

问题描述 投票:3回答:2

我正在开发AspNetCore 2.1 WebApi。我正在使用Microsoft.Extensions.DependencyInjectionAspNetCore.IdentityEntityFrameworkCore

在我的DI注册中,我打电话给:

services.AddDbContext<IMyDbContext, MyDbContext>();

好,对吗?绕过合同。但后来我遇到了一个异常,即Identity Manager类(UserManagerRoleManager等)无法在依赖注入容器中重新解析,因为他们使用的标识存储(UserStoreRoleStore等)无法解析其DI参数(MyDbContext)。

设置身份时,这一切都指向这一行:

builder.AddEntityFrameworkStores<MyDbContext>();

...因为身份商店正在寻找具体的背景并且没有在DI注册,所以这是令人讨厌的。扩展期望它可以解析为classDbContext - 并且我不能向接口IMyDbContext添加隐式运算符以给它DI扩展方法可以使用的隐式转换。

所有这些让我进行了相当难看的DI注册:

services
        .AddDbContext<IMyDbContext, MyDbContext>()
        .AddDbContext<MyDbContext>();
  • DI注册可能导致我尚未遇到的其他问题(线程安全,事务)下游。

我的猜测是我可以通过滚动我自己的IServiceCollection扩展和/或自定义身份存储来清理DI注册 - 这看起来真的有点过分,因为我没有必要超越默认的内置身份存储。

我也不想删除<interface, concrete>上下文注册,因为它将逐渐渗透到我的所有构造函数注入,并且似乎错了。

有没有人已经解决了这个问题并找到了解决办法?或者任何人都可以至少确认/否认DI中的双重(界面&&具体)上下文注册不会导致其他问题?

提前致谢!

c# entity-framework asp.net-core dependency-injection asp.net-identity
2个回答
5
投票

默认情况下,当您调用AddDbContext时,您正在注册一个作用域的DbContext实例。这意味着在处理单个请求的任何地方,通过DI请求所述DbContext将为您提供相同的实例。通过双重注册,DI系统将根据您是否要求IMyDbContextMyDbContext为您提供不同的实例。

为了指示DI系统为这两种类型提供相同的实例,您可以使用以下方法:

services.AddDbContext<MyDbContext>();
services.AddScoped<IMyDbContext>(sp => sp.GetRequiredService<MyDbContext>());

第一个调用注册MyDbContext,第二个调用只是将IMyDbContext的请求转发给同一个范围的MyDbContext实例。


0
投票

基于身份的数据库上下文应如下所示:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

namespace MyNamespaseForIdentityDatabase
{
    public class MyDbContext : IdentityDbContext<User, Role, int, UserClaim, UserRole, UserLogin, RoleClaim, UserToken>
    {
        public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
        {
        }

        // DB sets for Identity entities like Users, Roles, etc. are defined in base class so you can add here your custom sets like: 
        // public virtual DbSet<Company> Companies { get; set; }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            ConfigureUserTable(builder);
            ConfigureRoleClaimTable(builder);
            ConfigureUserRoleTable(builder);
            ConfigureUserLoginTable(builder);
            ConfigureUserClaimTable(builder);
            ConfigureUserTokenTable(builder);

            // ConfigureCompaniesTable(builder);
        }

        private static void ConfigureUserTable(ModelBuilder builder)
        {
            builder.Entity<User>(
                entity =>
                {
                    entity.ToTable("Users");
                    entity.HasMany(user => user.UserRoles)
                        .WithOne(userRole => userRole.User)
                        .HasForeignKey(userRole => userRole.UserId)
                        .IsRequired()
                        .OnDelete(DeleteBehavior.Cascade);

                    entity.HasMany(user => user.Claims)
                        .WithOne()
                        .HasForeignKey(userClaim => userClaim.UserId)
                        .IsRequired()
                        .OnDelete(DeleteBehavior.Cascade);

                    entity.Property(e => e.FirstName)
                        .IsRequired();

                    entity.Property(e => e.LastName)
                        .IsRequired();
                });
        }

        private static void ConfigureRoleClaimTable(ModelBuilder builder)
        {
            builder.Entity<RoleClaim>(
                entity =>
                {
                    entity.HasKey(roleClaim => roleClaim.Id);
                    entity.ToTable("RoleClaims");
                });
        }

        private static void ConfigureUserRoleTable(ModelBuilder builder)
        {
            builder.Entity<UserRole>(
                userRole =>
                {
                    userRole.ToTable("UserRoles");
                    userRole.HasKey(
                        r => new
                        {
                            r.UserId,
                            r.RoleId
                        });
                });
        }

        private static void ConfigureUserLoginTable(ModelBuilder builder)
        {
            builder.Entity<UserLogin>().ToTable("UserLogins");
        }

        private static void ConfigureUserClaimTable(ModelBuilder builder)
        {
            builder.Entity<UserClaim>().ToTable("UserClaims");
        }

        private static void ConfigureUserTokenTable(ModelBuilder builder)
        {
            builder.Entity<UserToken>().ToTable("UserTokens");
        }
    }

    public class User : IdentityUser<int>
    {
        // Some additional custom properties for th euser
        [NotMapped] public string FullName => $"{FirstName} {LastName}";
        public string FirstName { get; set; }
        public string LastName { get; set; }
        // Some additional collections for related stuff to include by queries like dbContext.Users.Include(user=>user.UserRoles).ToList()
        public virtual ICollection<UserRole> UserRoles { get; set; }
        public virtual ICollection<UserClaim> Claims { get; } = new List<UserClaim>();
    }

    public class Role : IdentityRole<int>
    {
        public Role()
        {
            // Default constructor is used by the framework
        }

        public Role(string roleName)
            : base(roleName)
        {
        }

        // Custom property in addition to Identity base ones for the role
        public string Description { get; set; }

        public virtual ICollection<RoleClaim> Claims { get; } = new List<RoleClaim>();
    }

    public class RoleClaim : IdentityRoleClaim<int>
    {
    }

    public class UserClaim : IdentityUserClaim<int>
    {
    }

    public class UserLogin : IdentityUserLogin<int>
    {
    }

    public class UserRole : IdentityUserRole<int>
    {
        public virtual User User { get; set; }
        public virtual Role Role { get; set; }
    }

    public class UserToken : IdentityUserToken<int>
    {
    }

    public class UserClaimsFactory : UserClaimsPrincipalFactory<User, Role>
    {
        public UserClaimsFactory(
            UserManager<User> userManager,
            RoleManager<Role> roleManager,
            IOptions<IdentityOptions> optionsAccessor)
            : base(userManager, roleManager, optionsAccessor)
        {
        }

        protected override async Task<ClaimsIdentity> GenerateClaimsAsync(User user)
        {
            var userId = user.Id;
            user = await UserManager.Users.SingleAsync(u => u.Id == userId);

            // Add role claims
            var identity = await base.GenerateClaimsAsync(user);

            // Add custom claims for application user properties we want to store in claims (in cookies) which allows to get common values on UI without DB hit)
            identity.AddClaim(new Claim(ClaimTypes.GivenName, user.FirstName ?? ""));
            identity.AddClaim(new Claim(ClaimTypes.Surname, user.LastName ?? ""));
            identity.AddClaim(new Claim(ClaimTypes.Email, user.Email ?? ""));

            return identity;
        }
    }
}

在你的Startup课堂上使用类似的东西:

public void ConfigureServices(IServiceCollection services)
{
    // ... 
    services.AddDbContext<MyDbContext>(options =>
    {
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
    });
    services.AddIdentity<User, Role>()
        .AddEntityFrameworkStores<MyDbContext>()
        .AddDefaultTokenProviders();
    // ...
    services.Configure<IdentityOptions>(options =>
    {
        // Configure your password, lockout, user settings here via `options`
    });
    // ...
    services.AddScoped<IUserClaimsPrincipalFactory<User>, UserClaimsFactory>();
    // ...
    services.AddMvc(options =>{ /* ... */ });
}
© www.soinside.com 2019 - 2024. All rights reserved.