I created a customized IConfigurationDbContext
in order to using IDS4 with 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);
}
}
in ConfigureService:
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddAspNetIdentity<ApplicationUser>();
I also have my custom IClientStore
which is added to the container like this:
services.AddScoped<IClientStore, ClientStore>();
when I run IdentityConfigurationDbContext
migration, I get this error:
System.InvalidOperationException: No database provider has been configured for this DbContext.
I tried doing this:
services.AddDbContext<IdentityConfigurationDbContext>(builder => builder.UseOracle(connectionString, options => {
options.MigrationsAssembly(migrationsAssembly);
options.MigrationsHistoryTable("EF_MIGRATION_HISTORY");
}));
Is this the right way to use a custom dbcontext with IDS4? and How do I fix this issue, and complete my migration work?
You don't need to create a custom ConfigurationDbContext
or event IDbContextFactory
in order to switch to use different databases. With IdentityServer4.EntityFramework
version 2.3.2, you can do:
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));
})
...;
...
}
...
}
}
What if you want to lay out your solution nicely and would like to separate the configuration store and operational store (as well as the identity user store) into their own class library/assembly?
Per the documentation, you can use -o
to specify the output migration folder destination:
dotnet ef migrations add InitialIdentityServerPersistedGrantDbMigration -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrantDb
dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/IdentityServer/ConfigurationDb
But who likes to memorize/type such long path when doing migrations? Then you might think: how about a custom ConfigurationDbContext
inherited from IdentityServer's, and a separate project:
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)
{
}
}
}
I think this is where people get into troubles. When you do Add-Migration
, you would either encounter:
Unable to create an object of type
AppConfigurationDbContext
. For different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728.
or
Unable to resolve service for type
Microsoft.EntityFrameworkCore.DbContextOptions<IdentityServer4.EntityFramework.DbContexts.ConfigurationDbContext>
while attempting to activateDL.STS.Data.ConfigurationStore.EFCore.AppConfigurationDbContext
.
I don't think, for now, there is a way to fix it.
It turns out it's actually quite easy. It seems like you can't have your own DbContext
inherited from IdentityServer's. So get rid of that, and create an extension method in that separate library/assembly:
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;
}
}
}
Then on Startup.cs
on your web project:
public void ConfigureServices(IServiceCollection services)
{
...
string connectionString = _configuration.GetConnectionString("appDbConnection");
services
.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddEFConfigurationStore(connectionString)
...;
...
}
And when you do PM> Add-Migration AddConfigurationTables -Context ConfigurationDbContext
with the default project being that separate library/assembly:
with the recent release, the Identityserver framework does support custom implementation of configuration store, operation store. This will also work with migration
see below for instance
public class CustomPersistsDbContext : DbContext, IPersistedGrantDbContext
{
}
In the OnModelCreating(ModelBuilder modelBuilder) I had to add the relations manually:
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");
});
}
On the services startup
.AddOperationalStore<CustomPersistsDbContext>(options =>
I've tried a different approach. Instead of implementing IConfigurationDbContext
I have inherited from 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);
}
}
}
And in the 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>();
It works like a charm. Disclaimer: this is not my idea. I just cannot remember the source of that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With