Extend configuration and operational data contexts of Identity Server 4 - entity-framework-core

I want to customize the configuration and operational data contexts of Identity Server 4 .
I let you see the code just for the configuration store, because the code is really similar.
Here my custom store:
internal class MyConfigurationDbContext : ConfigurationDbContext
{
public MyConfigurationDbContext(DbContextOptions<ConfigurationDbContext> options, ConfigurationStoreOptions storeOptions)
: base(options, storeOptions)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("configuration");
}
}
Here I have the first doubt. I think the signature of the constructor should be
public MyConfigurationDbContext(DbContextOptions<MyConfigurationDbContext> options, ConfigurationStoreOptions storeOptions)
but in this case it cannot convert DbContextOptions<MyConfigurationDbContext> in DbContextOptions<ConfigurationDbContext>
Well, in my sturtup I have this code:
builder.AddConfigurationStore<MyConfigurationDbContext>(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(MIGRATION_ASSEMBLY));
});
Then, I try to generate first migration:
Add-Migration InitialIdentityServerPersistedGrantDbMigration -Context MyConfigurationDbContext -OutputDir Data/Migrations/IdentityServer/PersistedGrantDb
But in this case, I get this error:
Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions`1[IdentityServer4.EntityFramework.DbContexts.ConfigurationDbContext]' while attempting to activate 'My.IdentityServer.DataLayer.Repository.Contexts.MyConfigurationDbContext'.
How can I solve it?
Thank you

As you correctly guessed, you have to use DbContextOptions<MyConfigurationDbContext> type for options argument in your context constructor.
But in order to be able to call the base constructor, instead of the default non generic ConfigurationDbContext you should inherit your context from the generic ConfigurationDbContext<TContext> using your context type as a generic type argument:
internal class MyConfigurationDbContext : ConfigurationDbContext<MyConfigurationDbContext>
{
public MyConfigurationDbContext(DbContextOptions<MyConfigurationDbContext> options, ConfigurationStoreOptions storeOptions)
: base(options, storeOptions)
{
}
// ...
}

Related

Unable to add migrations to EF Core on Azure Function

I have an Azure function running on .NET Core 3.1. I have a .NET Standard 2.1 library that contains an EF Core 3.1 DbContext.
I'm trying to add migrations from Visual Studio and I'm getting the following errors:
If I run PM> Add-Migration Initial selecting as default the function project I get the error 'No DbContext was found in assembly 'SimonApp' (this is my function project). Ensure that you're using the correct assembly and that the type is neither abstract nor generic.'
If I run the same command against the library where EF Core is installed, I get No parameterless constructor defined for type 'SimonApp.Core.Data.ClinikoEntitiesContext'.
I have found lots of posts and questions that are similar on SO but none of them fix my problem.
I have tried creating a parameterless constructor on the context without luck, I get the same errors. My context looks like this:
public class ClinikoEntitiesContext : DbContext
{
public ClinikoEntitiesContext()
{}
public ClinikoEntitiesContext(DbContextOptions<ClinikoEntitiesContext> options)
: base(options)
{ }
My Startup.cs looks like:
class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var configuration = builder.Services.BuildServiceProvider().GetService<IConfiguration>();
var IsDevelopment = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT")?.Equals("Development", StringComparison.OrdinalIgnoreCase);
var connString = configuration.GetConnectionString("SqlCliniko");
builder.Services.AddDbContext<ClinikoEntitiesContext>(options => options.UseSqlServer(connString)
.UseLazyLoadingProxies()
.EnableSensitiveDataLogging(IsDevelopment.HasValue && IsDevelopment.Value == true));
builder.Services.AddLogging(loggingBuilder =>
{
loggingBuilder.AddConsole()
.AddFilter(DbLoggerCategory.Database.Command.Name, LogLevel.Warning);
});
}
}

DbContext class - ASP.Net.Core

How can I apply the :base("name=connectionstring_name") in ASP.NET Core?
Because my Visual Studio shows cannot convert from 'string' to 'Microsoft.EntityFrameworkCore.DbContextOptions' .
namespace SchoolDataLayer
{
public class Context: DbContext
{
public SchoolDBContext() : base("name=SchoolDBConnectionString")
{
}
}
}
public SchoolDBContext() : base("name=SchoolDBConnectionString")
As error says you should pass DbContextOptions class instead of connection string.
The DbContextOptions instance carries configuration information such as:
The database provider to use, typically selected by invoking a method such as UseSqlServer or UseSqlite. These extension methods require the corresponding provider package, such as Microsoft.EntityFrameworkCore.SqlServer or Microsoft.EntityFrameworkCore.Sqlite. The methods are defined in the Microsoft.EntityFrameworkCore namespace.
Any necessary connection string or identifier of the database instance, typically passed as an argument to the provider selection method mentioned above
Any provider-level optional behavior selectors, typically also chained inside the call to the provider selection method
Any general EF Core behavior selectors, typically chained after or before the provider selector method
here is an example:
public class Context: SchoolDbContext
{
public SchoolDbContext(DbContextOptions<SchoolDbContext> options)
: base(options)
{
}
}
for more information read https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext

Ef core abstract HasQueryFilter from DbContext

I'm trying to build some generic authorize stuff ontop of DbContext so that my devs do not need to care about authorization in the repos/domain.
Simple example like
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().Property<string>("TenantId").HasField("_tenantId");
// Configure entity filters
modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "TenantId") == _tenantId);
modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);
}
Which is MS example works. _tenantId will be used to create an expression and for each instance of DbContext it will use correct value of _tenantId.
But I do not want all our authorizen configured from our DB context, I want to inject it. Something like
public class AgreementAuthorization : IEntityAuthorization
{
private readonly string _legalEntityNumber;
public AgreementAuthorization(IAuthScope scope)
{
_legalEntityNumber = scope.LegalEntityNumber;
}
public void Build(ModelBuilder builder)
{
builder
.Entity<Agreement>()
.HasQueryFilter(a => _legalEntityNumber == null || a.LegalEntity.Number == _legalEntityNumber);
}
}
public class MyDbContext : DbContext
{
private IEnumerable<IEntityAuthorization> _entityAuthorization;
MyDbContext(IEnumerable<IEntityAuthorization> entityAuthorization)
{
_entityAuthorization = entityAuthorization;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
builder.ApplyConfigurationsFromAssembly(typeof(MyDbContext).Assembly);
_entityAuthorization.ForEach(a => a.Build(builder));
}
}
This does not work, query willl test against null every time and pass. If I move the code directly to DbContext it will work. Meaning _entityAuthorization lies directly on DbContext.
From https://learn.microsoft.com/en-us/ef/core/querying/filters
Filters cannot contain references to navigation properties.
You are referencing navigation property LegalEntity.
And this could potentially affect you as well as you're passing in an IEnumerable<IEntityAuthorization>:
It is currently not possible to define multiple query filters on the
same entity - only the last one will be applied. However, you can
define a single filter with multiple conditions using the logical AND
operator (&& in C#).
Update:
My guess would then be that you need to reference a field/property of the MyDbContext directly and not within another object. Inject a class/interface that gives access to the values by which to filter then configure the filters at the end of the OnModelCreating method. You can interate over the entity configurations and their properties to apply the desired filter(s) based on the presence of the applicable property.

How change tables's schema of Identity Server 4?

By default the tables's schema of Identity Server 4 is dbo, i want change it to security, so i create ConfigurationContext which inherit from ConfigurationDbContext:
public class ConfigurationContext : ConfigurationDbContext
{
public ConfigurationContext(DbContextOptions<ConfigurationDbContext> options, ConfigurationStoreOptions storeOptions) : base(options, storeOptions)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("Security");
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var relationalOptions = RelationalOptionsExtension.Extract(optionsBuilder.Options);
relationalOptions.MigrationsHistoryTableSchema = "Security";
}
}
and in add-migration i use ConfigurationContext :
Add-Migration -c ConfigurationContext
but i got this error:
No parameterless constructor was found on 'ConfigurationContext'. Either add a parameterless constructor to 'ConfigurationContext' or add an implementation of 'IDbContextFactory' in the same assembly as 'ConfigurationContext'.
what is the problem?
IdentityServer4 provides this option. In ConfigureServices,
services.AddIdentityServer()
.AddOperationalStore(builder => builder.UseSqlServer(cnStr, options =>
options.MigrationsAssembly(migAssembly)),
storeOption => storeOption.DefaultSchema = "security")
This way, you can continue to use the IDbContextFactory as suggested in the quickstarts.
I know this is quite an old question, but I recently had a similar issue; June Lau's answer does provide some of the info you need to resolve this, but the important part is that migrations don't inspect the database context at runtime, so you need to define the schema before you create your database migration.
Don't worry about extending ConfigurationDbContext either, as that's not needed, just add something like this to your ConfigureServices method in Startup.cs:
var identityServerBuilder = services.AddIdentityServer(options =>
{
// ...
});
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
identityServerBuilder.AddConfigurationStore(options =>
{
options.DefaultSchema = "config";
options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
});
Once you've added that code, create a migration for the relevant database context:
Add-Migration CreateInitialSchema -Context ConfigurationDbContext
You should see that the created migration starts like this:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: "config");
migrationBuilder.CreateTable(
name: "ApiResources",
schema: "config",
columns: table => new ...
The problem is that Add-Migration -c ConfigurationContext command does not startup your application and thus does not know how to resolve the classes in your constructor:
public ConfigurationContext( //How do i resolve this, i dont know?
DbContextOptions<ConfigurationDbContext> options,
ConfigurationStoreOptions storeOptions)
: base(options, storeOptions)
{ }
You need to add a parameterless constructor, as the error suggests:
public ConfigurationContext()
: base(/* todo default static logic here */)
{ /* and here */ }
Why
The database migration tries to create an instance of the ConfigurationContext to determine the 'desired' state (the state you want your database to be after the database migration has been executed).
This migration is a static file inside your project saying which Columns and which indexes etc need to be added or removed to the database to create the 'desired' state.
This Add-Migration command simply reflects your code to find the right context, it does not go through your startup class to see which dependencies you have the find (this would become way to complex since there could also be runtime dependencies or dependencies based on App-settings, etc)

How to define a BaseConfigration for all other configuration in EF7 code first?

I want to define a base configuration (for some reason) for all model configuration(which using fluent api on them), so i create BaseConfigration class:
public class BaseConfigration<T> : EntityTypeBuilder<T> where T : class
{
public BaseConfigration(InternalEntityTypeBuilder builder) :base(builder)
{
}
}
and all of other configuration inherit from it:
public class LoyaltyActivityConfig : BaseConfigration<LoyaltyActivity>
{
public LoyaltyActivityConfig(InternalEntityTypeBuilder builder) : base(builder)
{
this.Property(x => x.Title).HasMaxLength(100);
}
}
in this point all thing goes right, but when i want to introduce this configuration in OnModelCreating method:
new LoyaltyActivityConfig(modelBuilder.Entity<NotificationPlatform>());
it gives me error:
cannot convert from 'Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder' to 'Microsoft.EntityFrameworkCore.Metadata.Internal.InternalEntityTypeBuilder'
How can i do it?
As the exception message says, you are using incorrect type. During OnModelCreating you have EntityTypeBuilder<T> object. InternalEntityTypeBuilder should not be used in user code as its internal to EF core as said in documentation.
To define a BaseConfiguration which will be applied to each entity type in the model and derived by EntityTypeConfiguration, code should be organized in following manner:
public class BaseConfiguration<T> where T : class
{
public BaseConfiguration(EntityTypeBuilder<T> entityTypeBuilder)
{
// Write fluent API code here which will be applied to all entityTypes in the model
entityTypeBuilder.HasKey("Id");
}
}
public class LoyaltyActivityConfig : BaseConfiguration<LoyaltyActivity>
{
public LoyaltyActivityConfig(EntityTypeBuilder<LoyaltyActivity> entityTypeBuilder)
: base(entityTypeBuilder)
{
// Write fluent API code here which will be applied to EntityType LoyaltyActivity only
entityTypeBuilder.Property(x => x.Title).HasMaxLength(100);
}
}
Then call above code from OnModelCreating like this
new LoyaltyActivityConfig(modelBuilder.Entity<LoyaltyActivity>());
Above method will call BaseConfiguration constructor followed by EntityTypeConfiguration constructor, applying all fluent API code.
Few things to remember here,
In BaseConfiguration even if you have generic EntityTypeBuilder you will not be able to use generic methods since T is unknown type. You can define an interface which will be implemented by every T (all entity types in the model) and use generic methods afterwards. Using non-generic methods will also get you same result though. Its just matter of readability.
You can also use method calls instead of constructor in Config classes to achieve the same.
To organize code like above, IEntityTypeConfiguration<TEntity> feature from EF6 has been implemented in EF core and will be available in 2.0 release. More info https://github.com/aspnet/EntityFramework/issues/2805