How to register properly a custom IdentityServer ConfigurationDbContext in Asp.Net Core 2? - asp.net-core

I am trying to create my own ConfigurationDbContext from IdentityServer.
public class IdSrvConfigurationDbContext : ConfigurationDbContext<ConfigurationDbContext>
{
public IdSrvConfigurationDbContext(DbContextOptions<IdSrvConfigurationDbContext> options, ConfigurationStoreOptions storeOptions) : base(options.ChangeOptionsType<ConfigurationDbContext>(), storeOptions)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//mylogic here
base.OnModelCreating(modelBuilder);
}
}
Now in the Startup.cs I tried the following
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddConfigurationStore(options =>
{
// (DbContextOptionsBuilder) paramBuilder
options.ConfigureDbContext = paramBuilder =>
paramBuilder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationAssembly));
});
Now when I am trying to run migrations over my project, it goes through all the startup logic injection and ending with this error:

You will need to set up your IdSrvConfigurationDbContext type to expect a DbContextOptions<ConfigurationDbContext> instead. That’s the type the underlying ConfigurationDbContext expects and that’s also what the IdentityServer is going to pass down.
Usually, you should always use a typed DbContextOptions<T> matching to the context. But when inheriting from existing contexts, this can be a bit difficult. But in those cases, you don’t need to worry much: The typed options are only used to differentiate between the various configured options. So as long as each context in your application still uses a separate type, there shouldn’t be any problems.

The main reason for me to define custom ConfigurationDbContext/PersistedGrantDbContext was to set HasMaxLength over some properties. That's because in production we use NDBCLUSTER engine and if length is not defined or it's too big default type within MySQL will be longtext. This causes a problem, because NDBCLUSTER doesn't support text family data-types.
Within extension method I am plugging custom db context and at the same time setting the common ConfigurationDbContext/PersistedGrantDbContext. I guess IdentityServer uses them for the internal stuff.
services
.AddIdentityServer(options => {})
.AddConfigurationStore<ApplicationConfigurationDbContext>()
.AddConfigurationStoreCache()
.AddOperationalStore<ApplicationPersistedGrantDbContext>();
services.AddDbContext<PersistedGrantDbContext>(options => ContextBuilder(options, connectionString));
services.AddDbContext<ConfigurationDbContext>(options => ContextBuilder(options, connectionString));
services.AddDbContext<ApplicationPersistedGrantDbContext>(options => ContextBuilder(options, connectionString));
services.AddDbContext<ApplicationConfigurationDbContext>(options => ContextBuilder(options, connectionString));
static public void ContextBuilder(DbContextOptionsBuilder options, string connectionString)
{
options.UseMySql(connectionString: connectionString,
ServerVersion.AutoDetect(connectionString),
dbOptions => dbOptions.MigrationsAssembly(DataExtensions.MigrationAssemblyName));
}
That way migrations are created and applied properly and I didn't face any problems during the runtime.

Related

ILogger in Asp.net Core and Serilog

I have a question,
Is there any concern if I use ILogger in Serilog on behalf of Microsoft logger?
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(Log.Logger);
}
Then use from ILogger in Serilog namespace.
_logger.Error(exception, "Error", exception.Message, exceptionId);
What is your idea?
Microsoft.Extensions.ILogger is an abstraction that decouples your application code (the code that writes log entries) from the underlying logging framework.
Using this abstraction brings the benefit, that you could easily change the Logging Framework underneath (e.g. replace Serilog with NLog) and don't have to update all references in your application code.
Also using Microsoft's abstractions also allows you to configure logging of your application code and logging of Microsoft SDKs you might use at a single place.
The downside of abstractions is that you have to aggree to a common minimum interface provided by all logging frameworks. It's not so easy to use Framework-Specific features this way.
So most of the time i would advise using the abstraction.
If you have very specific features from Serilog you would like to interact with you could think about using ILogger from Serilog directly.
However you can configure serilog in the provider registration as well to a high degree and probably get the best of both worlds.
You would configure Serilog factory interface to be used instead of built-in Logger factory for creating ILogger
First in program.cs, add the Serilog ILoggerFactory to your IHostBuilder with UserSerilog() method in CreateHostBuilder():
public static IHostBuilder CreateHostBuilder(string[] args) =>
new HostBuilder()
.ConfigureHostConfiguration(builder => { /* Host configuration */ })
.ConfigureAppConfiguration(builder => { /* App configuration */ })
.ConfigureServices(services => { /* Service configuration */})
.UseSerilog(); // <- Add this line
}
How the library works behind the scenes
On the face of it, completely replacing the default ASP.NET Core logging system to use a different one seems like a big deal. Luckily, thanks to the use of interfaces, loose coupling, and dependency injection, the code is remarkably simple! The whole extension method we used previously is shown below:
public static class SerilogHostBuilderExtensions
{
public static IHostBuilder UseSerilog(this IHostBuilder builder,
Serilog.ILogger logger = null, bool dispose = false)
{
builder.ConfigureServices((context, collection) =>
collection.AddSingleton<ILoggerFactory>(services => new
SerilogLoggerFactory(logger, dispose)));
return builder;
}
}
The UseSerilog() extension calls the ConfigureServices method on the IHostBuilder and adds an instance of the SerilogLoggerFactory as the application's ILoggerFactory. Whenever an ILoggerFactory is required by the app (to create an ILogger), the SerilogLoggerFactory will be used.
for more Information check this Link
"The downside of abstractions is that you have to aggree to a common minimum interface provided by all logging frameworks. It's not so easy to use Framework-Specific features this way."
I think we can always create extension methods to overcome this downside.

Asp.net core, when in debug, bypass Authorization

I have a test web site which uses the aspnetCore [AuthorizeAttribute] at the entire controller level to ensure only Authenticated Users can hit this site.
While we debug and test new features, we constantly have to comment out the attribute in each controller, which I know will be easy to forget and might get merged some day.
We've had good success with checking to see if a Debugger is attached before...I am wondering which AuthenticationScheme I should specify to allow anonymous, only if debugging.
I extend the base AuthorizeAttribute so I have an easy place to shim in some code.
public class MyAppAuthorizeAttribute : AuthorizeAttribute
{
public MyAppAuthorizeAttribute()
: base(Policies.MyAppAuthorize)
{
if (System.Diagnostics.Debugger.IsAttached)
{
Console.WriteLine("Skipping auth for debug"); //we hit this line but...
this.AuthenticationSchemes = AllowAnonymousAttribute //this setting does not work
}
else
{
this.AuthenticationSchemes = "IntegratedWindowsAuthentication";
}
}
}
Seems like a good candidate for IWebHostingEnvironment.IsDevelopment():
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (!env.IsDevelopment())
{
app.UseAuthentication();
app.UseAuthorization(); // maybe optional, depends on your case
}
...
Your requirement may be necessary in some cases where the user is required to be authenticated BUT not actually referenced in the code (e.g: the code does not access any info on the current identity, especially related to the business model of the user).
So I assume that you are aware of that because the following will just simply remove the requirement to check for authenticated user when the code runs in the development environment. There is another way to auto sign-in a dummy user which can be better in some scenarios.
Here is the first solution, it configures a default policy only which does not include the DenyAnonymousAuthorizationRequirement (which is the only requirement contained in the default policy). That means if you have multiple policies used somewhere (with AuthorizeAttribute), only the default will be ignored (while debugging). The second solution (shown later) may suit that scenario better.
//inject IHostingEnvironment in the Startup constructor
public Startup(IConfiguration configuration, IHostingEnvironment env){
HostingEnvironment = env;
}
public IHostingEnvironment HostingEnvironment { get; }
//in the ConfigureServices method in Startup.cs
services.AddAuthorization(o => {
if (HostingEnvironment.IsDevelopment()){
o.DefaultPolicy = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme)
//there should be at least 1 requirement
//here we add a simple always-passed assertion
.RequireAssertion(e => true).Build();
}
//...
});
We need to use IHostingEnvironment (in .net core 2.2, since 3.0 we have 2 alternatives IWebHostEnvironment and IHostEnvironment) so we inject it in the Startup constructor and store it in a readonly property (as you see above). There is another way is try to get the ASPNETCORE_ENVIRONMENT variable directly like this:
var isDevelopment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development";
Here is the second solution in which you use a custom global IAsyncAuthorizationFilter to auto sign-in a dummy user (so it's always authenticated for all requests).
public class AllowAnonymousFilterAttribute : Attribute, IAsyncAuthorizationFilter
{
readonly IHostingEnvironment _hostingEnvironment;
public AllowAnonymousFilterAttribute(IHostingEnvironment env){
_hostingEnvironment = env;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
if (_hostingEnvironment.IsDevelopment() && !context.HttpContext.User.Identity.IsAuthenticated)
{
//prepare a dummy user to auto sign-in
HttpContext.User = new ClaimsPrincipal(new[] {
new ClaimsIdentity(new []{ new Claim(ClaimTypes.NameIdentifier,"admin")},
CookieAuthenticationDefaults.AuthenticationScheme)
});
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
HttpContext.User);
}
}
}
Register the filter globally, here I write code for .net core 2.2:
services.AddMvc(o => {
//register the filter only for development (should be debugging)
if (HostingEnvironment.IsDevelopment()){
o.Filters.Add<AllowAnonymousFilterAttribute>();
}
//...
});
I'm sure there are still some other solutions but what I've introduced here are fairly simple and good enough for your purpose.
P/S: the first solution I've introduced above suits for .net core 2.2 (actually currently I do not have access to newer versions of .net core, it's a pity). For the newer versions, the Authorization middleware is separate so you may just simply not call .UseAuthorization() middleware in the Configure method (of course for development environment only) as one other answer suggests.

ASP.Net Core - How use custom 'ModelBindingMessageProvider'

I have a simple ASP.Net Core app which uses razor. The user forms are generated by model and data annotation attributes. The error message of wrong input values are English by default. I knew that I can translate default message with help of 'ModelBindingMessageProvider'.
Below you can find a working way (in Startup.cs) by update the 'DefaultModelBindingMessageProvider':
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(o =>
{
// see: https://stackoverflow.com/questions/40828570/asp-net-core-model-binding-error-messages-localization
o.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(u => "My custom validation error message");
});
}
I would prefer not to configure the translation directly in the startup.cs. One way to do that is using a custom class which inherits from 'ModelBindingMessageProvider'.
public class MyModelBindingMessageProvider : ModelBindingMessageProvider
{
public override Func<string, string> ValueMustNotBeNullAccessor => o => "My custom validation error message";
}
Here is my question:
Where register My own class?
Is this a good way? What is best practice at the moment?
(the real app uses string resources of course)
Unfortunately it looks like it's not possible to provide a custom implementation of ModelBindingMessageProvider in ASP.NET Core 3.1.
The constructor of MvcOptions sets the ModelBindingMessageProvider property to an instance of DefaultModelBindingMessageProvider.
ModelBindingMessageProvider = new DefaultModelBindingMessageProvider();
And the property itself has no setter and is not of type ModelBindingMessageProvider.
public DefaultModelBindingMessageProvider ModelBindingMessageProvider { get; }
tl;dr Even though the option of having an own implementation would make sense here, you have to configure the messages in the Startup class.

Register OpenIddict entities into DbContext in another way

Is there another way to register the entity sets needed by OpenIddict onto a DbContext except calling
options.UseOpenIddict(); in services.AddDbContext<OpenIdDictDbContext>(options => {...}).
I have trouble with this approach, because I have more DbContexts, and I want to share DbContextOptions.
In .Net Core 2, if you can use non generic DbContextOptions for all DbContexts OR you must have nongeneric DbContextOptions<T> for all DbContexts. So, I would like the first approach if it possible.
You can directly register the OpenIddict entities from OnModelCreating:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions options)
: base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Register the entity sets needed by OpenIddict.
// Note: use the generic overload if you need
// to replace the default OpenIddict entities.
builder.UseOpenIddict();
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
If you don't see the extension, make sure you have a Microsoft.Extensions.DependencyInjection using and that your project references OpenIddict.EntityFrameworkCore.

The DbContext of type cannot be pooled because it does not have a single public constructor accepting a single parameter of type DbContextOptions

I am trying to upgrade our current .Net Core application from 1.1 to 2.0 and am getting this runtime error: "The DbContext of type 'CoreContext' cannot be pooled because it does not have a single public constructor accepting a single parameter of type DbContextOptions".
It is caused by using the new IServiceCollection.AddDbContextPool<> function. When I use IServiceCollection.AddDbContext<> it still works.
This application is DB-First, so I generate all our contexts using 'Scaffold-DbContext'. Due to that, and the need to inject other services I have an extension on every context like this:
public partial class CoreContext
{
public CoreContext(
DbContextOptions<CoreContext> options,
IUserService userService,
IAuditRepository auditRepository
) : base(options) {...}
}
Whenever I run the Scaffold-DbContext I just remove the autogenerated Constructor from CoreContext, but even if I put it in there I still get this error.
public partial class CoreContext : DbContext
{
public CoreContext(DbContextOptions<CoreContext> options) : base(options) {}
}
I've already updated Program.cs to the new style:
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
}
And the Startup.cs is pretty straightforward:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
...
services.AddDbContextPool<CoreContext>(options => options.UseSqlServer(absConnectionString));
...
}
I am using Autofac for DI if that helps. For now I'll default back to the non-Pooling alternative, but it would be nice to take advantage of this feature.
When using DbContext Pooling, your own state (e.g. private fields) in your derived DbContext class will be preserved. Which means the lifetime of your services is now singleton. That's why you shouldn't have other injected services here.
But it's possible to query the required services this way:
First we should use the UseInternalServiceProvider method on DbContextOptionsBuilder to tell EF which service provider to use for its services. This service provider must have all the services configured for EF and any providers. So we should register EF Services manually:
services.AddEntityFrameworkSqlServer();
And then introduce the application's services provider which now includes the EF Services too:
services.AddDbContextPool<ApplicationDbContext>((serviceProvider, optionsBuilder) =>
{
optionsBuilder.UseSqlServer("...");
optionsBuilder.UseInternalServiceProvider(serviceProvider);
});
After that define these namespaces:
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
And now you can access the registered services in the application within the
ApplicationDbContext class using the following methods
var siteSettings = this.GetService<IOptionsSnapshot<SiteSettings>>();
Or
var siteSettings = this.GetInfrastructure().GetRequiredService<IOptionsSnapshot<SiteSettings>>();
this is the current instance of the DbContext.
Remove the default constructor in the DbContext class, this worked for me
"because it does not have a single public constructor accepting a
single parameter of type DbContextOptions"
If you have any public constructors apart from one that accepts DbContextOptions, you need to remove them or make them non-public in order to use context pooling.
Also, there are restrictions on what can be done by overriding the OnConfiguring method. This is referenced in the documentation here but it isn't explicit about what those restrictions are: https://learn.microsoft.com/en-us/ef/core/what-is-new/index#dbcontext-pooling
This issue is mostly encountered when you "Scaffold-Dbcontext" and two constructors are generated.
Simple Solutions:
AddDbContextPool:
If you want to use AddDbContextPool, remove your empty constructor and maintain the one with the DbContextOptionsBuilder. Note that in this case you might have to provide the options, as suggested in the previous posts.
AddDbContext:
With AddDbContext, you can have both constructors/Overloads
Note: AddDbContextPool is preferred for performance reasons!
Try to use AddDbContext instead of AddDbContextPool. This helped me in the same situation.
services.AddDbContext<CoreContext>(options => options.UseSqlServer(absConnectionString));
in some case need to
remove the constractor with zero parameter
//public MyContext()
//{
//}
or use
"AddDbContext"
instead of
"AddDbContextPool"
in startup.cs => ConfigureServices()
services.AddDbContext(options =>
options.UseSqlServer(absConnectionString));