将IdentityServer4与自定义Configration DBContext

时间:2017-06-18 18:34:15

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

我创建了一个自定义的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);
    }
  }
在ConfigureService中

 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的正确方法吗?以及如何解决此问题,并完成我的迁移工作?

6 个答案:

答案 0 :(得分: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>();

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

答案 1 :(得分:1)

添加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());
        }
    }

答案 2 :(得分:0)

在最近发布的版本中,Identityserver框架确实支持配置存储,操作存储的自定义实现。

见下文例如

public class CustomPersistsDbContext : DbContext, IPersistedGrantDbContext
    {
    }

在服务启动

 .AddOperationalStore<CustomPersistsDbContext>(options =>

答案 3 :(得分:0)

您无需创建自定义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

  

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

我不认为,目前有一种解决方法。

还有其他方法吗?

事实证明,这实际上很容易。看来您无法拥有从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;
        }
    }
}

然后在您的Web项目上的Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    ...

    string connectionString = _configuration.GetConnectionString("appDbConnection");

    services
        .AddIdentityServer()
        .AddDeveloperSigningCredential()
        .AddEFConfigurationStore(connectionString)
        ...;

    ...
}

当您使用默认项目是单独的库/程序集进行PM> Add-Migration AddConfigurationTables -Context ConfigurationDbContext时:

enter image description here

答案 4 :(得分:0)

我认为最简单的方法是使用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);
    }
}

答案 5 :(得分:0)

我用您的解决方案做了一点改动。我在代码下方附加。 在测试方法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; }
.....
}