【问题标题】:using IdentityServer4 with custom Configration DBContext将 IdentityServer4 与自定义配置 DBContext 一起使用
【发布时间】:2017-11-20 22:36:46
【问题描述】:

我创建了一个自定义的IConfigurationDbContext,以便将 IDS4 与 Oracle 一起使用。

  public class IdentityConfigurationDbContext :  DbContext, IConfigurationDbContext {
        private readonly ConfigurationStoreOptions storeOptions;

        public IdentityConfigurationDbContext(DbContextOptions<IdentityServerDbContext> options)
         : base(options) {
    }

    public IdentityConfigurationDbContext(DbContextOptions<ConfigurationDbContext> options, ConfigurationStoreOptions storeOptions)
        : base(options) {
        this.storeOptions = storeOptions ?? throw new ArgumentNullException(nameof(storeOptions));
    }

    public DbSet<Client> Clients { get; set; }
    public DbSet<IdentityResource> IdentityResources { get; set; }
    public DbSet<ApiResource> ApiResources { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder) {
        modelBuilder.ConfigureClientContext(storeOptions);
        modelBuilder.ConfigureResourcesContext(storeOptions);

        base.OnModelCreating(modelBuilder);
    }
  }

在配置服务中:

 services.AddIdentityServer()
                .AddTemporarySigningCredential()
                .AddAspNetIdentity<ApplicationUser>();

我也有我的自定义IClientStore,它像这样添加到容器中:

services.AddScoped<IClientStore, ClientStore>();

当我运行 IdentityConfigurationDbContext 迁移时,我收到此错误:

System.InvalidOperationException: No database provider has been configured for this DbContext.

我试过这样做:

services.AddDbContext<IdentityConfigurationDbContext>(builder => builder.UseOracle(connectionString, options => {
                options.MigrationsAssembly(migrationsAssembly);
                options.MigrationsHistoryTable("EF_MIGRATION_HISTORY");
            }));

这是在 IDS4 中使用自定义 dbcontext 的正确方法吗?以及如何解决此问题并完成我的迁移工作?

【问题讨论】:

    标签: c# entity-framework asp.net-core asp.net-identity identityserver4


    【解决方案1】:

    您无需创建自定义ConfigurationDbContext 或事件IDbContextFactory 即可切换到使用不同的数据库。使用IdentityServer4.EntityFramework 2.3.2 版,您可以:

    namespace DL.STS.Host
    {
        public class Startup
        {
            ...
    
            public void ConfigureServices(IServiceCollection services)
            {
                string connectionString = _configuration.GetConnectionString("appDbConnection");
    
                string migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly
                    .GetName().Name;
    
                services
                   .AddIdentityServer()
                   .AddConfigurationStore(options =>
                   {
                       options.ConfigureDbContext = builder =>
                           // I made up this extension method "UseOracle",
                           // but this is where you plug your database in
                           builder.UseOracle(connectionString,
                               sql => sql.MigrationsAssembly(migrationsAssembly));
                   })
                   ...;
    
                ...
            }
    
            ...
        }
    }
    

    将配置/操作存储分离到自己的项目/程序集中?

    如果您想很好地布置您的解决方案并希望将配置存储和操作存储(以及身份用户存储)分离到它们自己的类库/程序集中怎么办?

    根据文档,您可以使用 -o 指定输出迁移文件夹目标:

    dotnet ef migrations add InitialIdentityServerPersistedGrantDbMigration -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrantDb
    dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/IdentityServer/ConfigurationDb
    

    但谁喜欢在迁移时记住/输入这么长的路径?那么你可能会想:继承自 IdentityServer 的自定义 ConfigurationDbContext 和一个单独的项目怎么样:

    using IdentityServer4.EntityFramework.DbContexts;
    using IdentityServer4.EntityFramework.Options;
    using Microsoft.EntityFrameworkCore;
    
    namespace DL.STS.Data.ConfigurationStore.EFCore
    {
        public class AppConfigurationDbContext : ConfigurationDbContext
        {
            public AppConfigurationDbContext(DbContextOptions<ConfigurationDbContext> options, 
                ConfigurationStoreOptions storeOptions) : base(options, storeOptions)
            {
            }
        }
    }
    

    常见错误

    我认为这是人们遇到麻烦的地方。当您执行Add-Migration 时,您会遇到:

    无法创建AppConfigurationDbContext 类型的对象。有关设计时支持的不同模式,请参阅https://go.microsoft.com/fwlink/?linkid=851728

    尝试激活 DL.STS.Data.ConfigurationStore.EFCore.AppConfigurationDbContext 时无法解析类型 Microsoft.EntityFrameworkCore.DbContextOptions&lt;IdentityServer4.EntityFramework.DbContexts.ConfigurationDbContext&gt; 的服务。

    我不认为,目前,有办法解决它。

    还有其他方法吗?

    事实证明这实际上很容易。似乎您不能从 IdentityServer 继承自己的 DbContext。所以摆脱它,并在那个单独的库/程序集中创建一个扩展方法:

    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.DependencyInjection;
    using System.Reflection;
    
    namespace DL.STS.Data.ConfigurationStore.EFCore.Extensions
    {
        public static class IdentityServerBuilderExtensions
        {
            public static IIdentityServerBuilder AddEFConfigurationStore(
                this IIdentityServerBuilder builder, string connectionString)
            {
                string assemblyNamespace = typeof(IdentityServerBuilderExtensions)
                    .GetTypeInfo()
                    .Assembly
                    .GetName()
                    .Name;
    
                builder.AddConfigurationStore(options =>
                    options.ConfigureDbContext = b =>
                        b.UseSqlServer(connectionString, optionsBuilder =>
                            optionsBuilder.MigrationsAssembly(assemblyNamespace)
                        )
                );
    
                return builder;
            }
        }
    }
    

    然后在您的网络项目上Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        ...
    
        string connectionString = _configuration.GetConnectionString("appDbConnection");
    
        services
            .AddIdentityServer()
            .AddDeveloperSigningCredential()
            .AddEFConfigurationStore(connectionString)
            ...;
    
        ...
    }
    

    当你使用PM&gt; Add-Migration AddConfigurationTables -Context ConfigurationDbContext 时,默认项目是那个单独的库/程序集:

    【讨论】:

    • 我得到“找不到名为 'ConfigurationDbContext' 的 DbContext”。请检查我的问题stackoverflow.com/questions/61263843/…
    • 这个答案太棒了,正是我所需要的。我有一个 n 层项目,其中上下文和迁移与 Web 项目分开(完全如此处所述)。在遵循这个答案之后,唯一要添加的是我运行以生成和应用迁移的确切脚本。从 API 项目目录:dotnet ef migrations add AddConfigurationTables -s ../&lt;Api Project Folder&gt; --context ConfigurationDbContext -o Migrations\IdentityServer4dotnet ef database update AddConfigurationTables -s ../&lt;Api Project Folder&gt; --context ConfigurationDbContext
    【解决方案2】:

    在最近的版本中,Identityserver 框架确实支持配置存储、操作存储的自定义实现。这也适用于迁移

    例如见下文

                public class CustomPersistsDbContext : DbContext, IPersistedGrantDbContext
                    {
                    }
    

    在 OnModelCreating(ModelBuilder modelBuilder) 中,我必须手动添加关系:

                    protected override void OnModelCreating(ModelBuilder modelBuilder)
                    {
                        //Optional: The version of .NET Core, used by Ef Core Migration history table
                        modelBuilder.HasAnnotation("ProductVersion", "2.2.0-rtm-35687");
    
              //.. Your custom code
    
        //PersistentDbContext
    modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
                        {
                            b.Property<string>("UserCode")
                                .ValueGeneratedOnAdd()
                                .HasMaxLength(200);
    
                            b.Property<string>("ClientId")
                                .IsRequired()
                                .HasMaxLength(200);
    
                            b.Property<DateTime>("CreationTime");
    
                            b.Property<string>("Data")
                                .IsRequired()
                                .HasMaxLength(50000);
    
                            b.Property<string>("DeviceCode")
                                .IsRequired()
                                .HasMaxLength(200);
    
                            b.Property<DateTime?>("Expiration")
                                .IsRequired();
    
                            b.Property<string>("SubjectId")
                                .HasMaxLength(200);
    
                            b.HasKey("UserCode");
    
                            b.HasIndex("DeviceCode")
                                .IsUnique();
    
                            b.HasIndex("UserCode")
                                .IsUnique();
    
                            b.ToTable("DeviceCodes");
                        });
    
                        modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
                        {
                            b.Property<string>("Key")
                                .HasMaxLength(200);
    
                            b.Property<string>("ClientId")
                                .IsRequired()
                                .HasMaxLength(200);
    
                            b.Property<DateTime>("CreationTime");
    
                            b.Property<string>("Data")
                                .IsRequired()
                                .HasMaxLength(50000);
    
                            b.Property<DateTime?>("Expiration");
    
                            b.Property<string>("SubjectId")
                                .HasMaxLength(200);
    
                            b.Property<string>("Type")
                                .IsRequired()
                                .HasMaxLength(50);
    
                            b.HasKey("Key");
    
                            b.HasIndex("SubjectId", "ClientId", "Type");
    
                            b.ToTable("PersistedGrants");
                        });
                    }
    

    在服务启动时

     .AddOperationalStore<CustomPersistsDbContext>(options =>
    

    【讨论】:

      【解决方案3】:

      我尝试了不同的方法。而不是实现IConfigurationDbContext 我继承自IdentityServer4.EntityFramework.DbContexts.ConfigurationDbContext

      public class CustomConfigurationDbContext : ConfigurationDbContext
      {
          public CustomConfigurationDbContext(DbContextOptions<ConfigurationDbContext> options,
              ConfigurationStoreOptions storeOptions)
              : base(options, storeOptions)
          {
          }
      
          protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
          {
              if (!optionsBuilder.IsConfigured)
              {
                  //...
      
                  base.OnConfiguring(optionsBuilder);
              }
          }
      }
      

      在startup.cs中

      services.AddIdentityServer()
                      .AddTemporarySigningCredential()
                      .AddConfigurationStore(
                          builder => builder.UseSqlServer(connectionString, options => options.MigrationsAssembly(migrationsAssembly)))
                      .AddOperationalStore(
                          builder => builder.UseSqlServer(connectionString, options => options.MigrationsAssembly(migrationsAssembly)))
                      .AddAspNetIdentity<ApplicationUser>();
      

      它就像一个魅力。 免责声明:这不是我的想法。我只是不记得它的来源。

      【讨论】:

      • 我这样做了,但在迁移过程中出现此错误:在“IdentityConfigurationDbContext”上找不到无参数构造函数。将无参数构造函数添加到“IdentityConfigurationDbContext”或在与“IdentityConfigurationDbContext”相同的程序集中添加“IDbContextFactory”的实现。
      • 即使你继承自 ConfigurationDbContext
      • 是的,我还添加了一个 No parameterless constructor 同样的东西。
      • 找不到前任。方法。
      • hm 这不会使用 CustomConfigurationDbContext 除非 AddConfigurationStore 但随后会出现“无法解析类型 'Microsoft.EntityFrameworkCore .DbContextOptions`1[IdentityServer4.EntityFramework.DbContexts.ConfigurationDbContext] 的服务”错误尝试激活时..”
      【解决方案4】:

      添加IDbContextFactory 解决了这个问题。

      public class IdentityConfigurationDbContextFactory : IDbContextFactory<IdentityConfigurationDbContext> {
      
              public IdentityConfigurationDbContext Create(DbContextFactoryOptions options) {
                  var optionsBuilder = new DbContextOptionsBuilder<ConfigurationDbContext>();
                  var config = new ConfigurationBuilder()
                                   .SetBasePath(options.ContentRootPath)
                                   .AddJsonFile("appsettings.json")
                                    .AddJsonFile($"appsettings.{options.EnvironmentName}.json", true)
      
                                   .Build();
      
                  optionsBuilder.UseOracle(config.GetConnectionString("DefaultConnection"));
      
                  return new IdentityConfigurationDbContext(optionsBuilder.Options, new ConfigurationStoreOptions());
              }
          }
      

      【讨论】:

      • 哦,是的,你应该有一个IDbContextFactory&lt;CustomConfigurationDbContext&gt;的实现
      • 您是如何在 Startup.cs 中注册 CustomConfigurationDbContext 的?我收到此错误 System.InvalidOperationException: 'No service for type CustomConfigurationDbContext' has been registered。'
      • 检查我的答案 above 这是我在启动时添加的唯一内容
      【解决方案5】:

      我认为最简单的方法是使用 ConfigurationDbContext 的参数 T,如下所示。它适用于 net core 3.0

      public class ConfigurationDataContext : ConfigurationDbContext<ConfigurationDataContext>
      {
          public ConfigurationDataContext(DbContextOptions<ConfigurationDataContext> options, ConfigurationStoreOptions storeOptions)
          : base(options, storeOptions)
          {
          }
      
          protected override void OnModelCreating(ModelBuilder builder)
          {
              base.OnModelCreating(builder);
      
              builder.ApplyConfigurationsFromAssembly(typeof(MyConfigurationsAssemby).Assembly);
          }
      }
      

      【讨论】:

        【解决方案6】:

        我使用了您的解决方案,稍作改动。我附在代码下面。 在您的测试方法 OnModelCreating 中,您声明了两个方法

        ...
        modelBuilder.ConfigureClientContext(configurationStoreOptions);
        modelBuilder.ConfigureResourcesContext(configurationStoreOptions);
        
        modelBuilder.ConfigurePersistedGrantContext(operationalStoreOptions); // need to add
        

        指的是PersistedGrants和DeviceFlowCodes,这很好,但你需要添加

        ConfigurePersistedGrantContext also.

        here too there is info?

        public class MYCustomDbContext : DbContext, IPersistedGrantDbContext, IConfigurationDbContext
        
        
        public DbSet<PersistedGrant> PersistedGrants { get; set; }
        
        public DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; }
        
        ........
        
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
        
            if (modelBuilder is null)
               throw new ArgumentNullException(nameof(modelBuilder));
        
            ConfigurationStoreOptions storeConfigurationOptions = new ConfigurationStoreOptions();
            OperationalStoreOptions storeOperationalOptions = new OperationalStoreOptions();
        
            modelBuilder.ConfigureClientContext(configurationStoreOptions);
            modelBuilder.ConfigureResourcesContext(configurationStoreOptions);
            modelBuilder.ConfigurePersistedGrantContext(operationalStoreOptions);
        }
        
        Task<int> IPersistedGrantDbContext.SaveChangesAsync() => base.SaveChangesAsync();
        
        public Task<int> SaveChangesAsync() => base.SaveChangesAsync();
        
        public void ConfigureServices(IServiceCollection services)
        {
        ......
         services.AddOperationalStore<MYCustomDbContext>(options => {
                            // this enables automatic token cleanup.
                            options.EnableTokenCleanup = true;
                            options.TokenCleanupInterval = 3600; }
        .....
        }
        

        【讨论】:

          【解决方案7】:

          我用 IdentityServer4.EntityFramework (4.1.2) 的最新版本制作了这个:

          我创建了一个名为ApplicationConfigurationDbContext 的类继承自ConfigurationDbContext&lt;TContext&gt;,其中TContext 是我的类

          public class ApplicationConfigurationDbContext : ConfigurationDbContext<ApplicationConfigurationDbContext>
          {
              public ApplicationConfigurationDbContext(DbContextOptions<ApplicationConfigurationDbContext> options, ConfigurationStoreOptions storeOptions) : base(options, storeOptions)
              {
          
              }
          
              // My own entities...
          
              protected override void OnModelCreating(ModelBuilder modelBuilder)
              {
                  base.OnModelCreating(modelBuilder);
          
                  // My own configurations...
              }
          }
          

          然后在Startup 类中我注册了我的 DbContext,最后在调用 AddIdentityServer() 扩展方法之后,我也链接了 AddConfigurationStore() 扩展方法,就是这样!。

          public class Startup
          {
              // Hide for brevity
          
              public void ConfigureServices(IServiceCollection services)
              {
                  var builder = services.AddIdentityServer(options =>
                  {
                      // My options...
                  })
                  .AddConfigurationStore<ApplicationConfigurationDbContext>(options =>
                  {
                      options.ConfigureDbContext = b => b.UseSqlServer(connectionString,
                          sql => sql.MigrationsAssembly(migrationsAssembly));
                  })
                  .AddAspNetIdentity<ApplicationUser>();
              }
          }
          

          当我希望添加迁移时,我会这样做:

          dotnet ef migrations add "Add_New_Table" --context "ApplicationConfigurationDbContext" --startup-project "My.Start.Project" --project "Target.Project" --output-dir "Migrations/ConfigurationDb"
          

          通过 .NET CLI here 获得有关 Entity Framework Core 工具的更多信息。

          【讨论】:

            猜你喜欢
            • 2020-08-28
            • 1970-01-01
            • 2016-08-31
            • 1970-01-01
            • 2018-05-03
            • 2011-09-22
            • 1970-01-01
            • 2012-08-01
            • 1970-01-01
            相关资源
            最近更新 更多