I have set a filter to work upon a specific folder and all pages inside it. I need to access database using a claim. The problem is that I cannot seem to register my filter with DI on startup services cause it does not find the database connection
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.AllowAreas = true;
options.Conventions.AuthorizeAreaFolder("Administration", "/Account");
options.Conventions.AuthorizeAreaFolder("Production", "/Account");
options.Conventions.AuthorizeAreaFolder("Robotics", "/Account");
options.Conventions.AddAreaFolderApplicationModelConvention("Production", "/FrontEnd",
model => model.Filters.Add(
new LockdownFilter(
new ProducaoRegistoService(new ProductionContext()),
new UrlHelperFactory(),
new HttpContextAccessor())));
})
the filter.
public class LockdownFilter : IAsyncPageFilter
{
private readonly IProducaoRegistoService _producaoRegistoService;
private readonly IUrlHelperFactory _urlHelperFactory;
private readonly IHttpContextAccessor _httpContextAccessor;
public LockdownFilter(IProducaoRegistoService producaoRegistoService, IUrlHelperFactory urlHelperFactory, IHttpContextAccessor httpContextAccessor)
{
_producaoRegistoService = producaoRegistoService;
_urlHelperFactory = urlHelperFactory;
_httpContextAccessor = httpContextAccessor;
}
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
int registoId;
if(!int.TryParse(_httpContextAccessor.HttpContext.User.GetRegistoId(), out registoId))
{
// TODO
}
var registo = _producaoRegistoService.GetById(registoId);
await next.Invoke();
}
public async Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
await Task.CompletedTask;
}
}
the error is
InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext.
here is the whole startup class
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
})
.AddCookie("ProductionUserAuth", options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(1);
options.LoginPath = new PathString("/Production/FrontEnd/Login");
options.LogoutPath = new PathString("/Production/FrontEnd/Logout");
options.AccessDeniedPath = new PathString("/Production/FrontEnd/AccessDenied");
options.SlidingExpiration = true;
options.Cookie.Name = "NoPaper.ProductionUser";
options.Cookie.Expiration = TimeSpan.FromDays(1);
})
.AddCookie("ProductionAdminAuth", options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(1);
options.LoginPath = new PathString("/Production/BackOffice/Login");
options.LogoutPath = new PathString("/Production/BackOffice/Logout");
options.AccessDeniedPath = new PathString("/Production/BackOffice/AccessDenied");
options.SlidingExpiration = true;
options.Cookie.Name = "NoPaper.ProductionAdmin";
options.Cookie.Expiration = TimeSpan.FromDays(1);
})
.AddCookie("AdministrationAuth", options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(1);
options.LoginPath = new PathString("/Administration/Index");
options.LogoutPath = new PathString("/Administration/Logout");
options.AccessDeniedPath = new PathString("/Administration/AccessDenied");
options.SlidingExpiration = true;
options.Cookie.Name = "NoPaper.Administration";
options.Cookie.Expiration = TimeSpan.FromDays(1);
});
services.AddAuthorization();
services.AddMemoryCache();
services.AddAutoMapper(typeof(Startup));
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.AllowAreas = true;
options.Conventions.AuthorizeAreaFolder("Administration", "/Account");
options.Conventions.AuthorizeAreaFolder("Production", "/Account");
options.Conventions.AddAreaFolderApplicationModelConvention("Production", "/FrontEnd",
model => model.Filters.Add(
new LockdownFilter(
new ProducaoRegistoService(new ProductionContext(new DbContextOptions<ProductionContext>())),
new UrlHelperFactory(),
new HttpContextAccessor())));
})
.AddNToastNotifyToastr(new ToastrOptions()
{
ProgressBar = true,
TimeOut = 3000,
PositionClass = ToastPositions.TopFullWidth,
PreventDuplicates = true,
TapToDismiss = true
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddRouting(options =>
{
options.LowercaseUrls = true;
options.LowercaseQueryStrings = true;
});
services.AddDbContext<DatabaseContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 2,
maxRetryDelay: TimeSpan.FromSeconds(1),
errorNumbersToAdd: null);
sqlOptions.MigrationsHistoryTable("hEFMigrations", "Admin");
});
});
services.AddDbContext<ProductionContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), c => c.MigrationsHistoryTable("hEFMigrations", "Admin")
));
services.AddHttpContextAccessor();
services.AddSingleton<IFileProvider>(new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/files")));
services.AddTransient<IAuthorizationHandler, HasArranqueActivoHandler>();
services.AddTransient<IAuthorizationHandler, HasArranqueInactivoHandler>();
services.AddTransient<IAuthorizationHandler, IsParagemNotOnGoingHandler>();
services.AddTransient<IAuthorizationHandler, IsParagemOnGoingHandler>();
services.AddTransient<Services.Interfaces.IUserService, Services.UserService>();
#region AreaProduction
services.AddTransient<Production.Interfaces.IComponenteService, Production.ComponenteService>();
services.AddTransient<Production.Interfaces.IReferenciaService, Production.ReferenciaService>();
services.AddTransient<Production.Interfaces.IProducaoRegistoService, Production.ProducaoRegistoService>();
services.AddTransient<Production.Interfaces.IParagemService, Production.ParagemService>();
services.AddTransient<Production.Interfaces.ICelulaService, Production.CelulaService>();
services.AddTransient<Production.Interfaces.IUapService, Production.UapService>();
services.AddTransient<Production.Interfaces.ICelulaTipoService, CelulaTipoService>();
services.AddTransient<Production.Interfaces.IMatrizService, MatrizService>();
services.AddTransient<Production.Interfaces.IOperadorService, Production.OperadorService>();
services.AddTransient<Production.Interfaces.IEtiquetaService, Production.EtiquetaService>();
services.AddTransient<Production.Interfaces.IPokayokeService, Production.PokayokeService>();
services.AddTransient<Production.Interfaces.IGeometriaService, Production.GeometriaService>();
services.AddTransient<Production.Interfaces.IEmpregadoService, Production.EmpregadoService>();
services.AddTransient<Production.Interfaces.IPecaService, Production.PecaService>();
services.AddTransient<Production.Interfaces.IDefeitoService, Production.DefeitoService>();
services.AddTransient<Production.Interfaces.ITurnoService, Production.TurnoService>();
#endregion
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var exceptionHandlerPathFeature =
context.Features.Get<IExceptionHandlerPathFeature>();
// Use exceptionHandlerPathFeature to process the exception (for example,
// logging), but do NOT expose sensitive error information directly to
// the client.
if (exceptionHandlerPathFeature.Path.Contains("/Administration/") ||
exceptionHandlerPathFeature.Path.Contains("/administration/"))
{
context.Response.Redirect("/Administration/Error");
}
if (exceptionHandlerPathFeature.Path.Contains("/Production/") ||
exceptionHandlerPathFeature.Path.Contains("/production/"))
{
context.Response.Redirect("/Production/Error");
}
});
});
}
app.UseNToastNotify();
app.UseAuthentication();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "areas",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
);
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
my context
public class ProductionContext : DbContext
{
//static LoggerFactory object
public static readonly ILoggerFactory loggerFactory = new LoggerFactory(new[] {
new ConsoleLoggerProvider((_, __) => true, true)
});
public ProductionContext()
{
}
public ProductionContext(DbContextOptions<ProductionContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLoggerFactory(loggerFactory) //tie-up DbContext with LoggerFactory object
.EnableSensitiveDataLogging();
}
...
}
There's a lot of code in your question, so I'll highlight the code of interest first:
options.Conventions.AddAreaFolderApplicationModelConvention("Production", "/FrontEnd", model => model.Filters.Add( new LockdownFilter( new ProducaoRegistoService(new ProductionContext()), new UrlHelperFactory(), new HttpContextAccessor())));
Now, let's have another look at the error message:
InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext.
In your situation, the instance of ProductionContext
that's being created in the code I called out isn't being configured. You might think it is being configured because of how you've used AddDbContext
elsewhere in your ConfigureServices
method, but that's not the case.
AddDbContext
sets up DI with everything it needs to give you an instance of ProductionContext
that is configured according to your setup (using SQL Server with the DefaultConnection
connection-string). However, by creating your own instance of ProductionContext
and passing that into the filter, the DI-configured instance isn't being used at all.
An obvious solution here would be to use DI for those services, but that's not so straight forward as you don't have access to DI when creating your instance of LockdownFilter
. This is where TypeFilterAttribute
and ServiceFilterAttribute
come in, which are well-documented in Filters in ASP.NET Core: Dependency injection. Here's an updated version of the code I called out, which uses TypeFilterAttribute
:
options.Conventions.AddAreaFolderApplicationModelConvention("Production", "/FrontEnd",
model => model.Filters.Add(new TypeFilterAttribute(typeof(LockdownFilter))));
Using this approach, the arguments passed in to your LockdownFilter
constructor will be resolved from DI. It's clear from your quesiton that the three services are all registered with the DI container, so this should work as is.
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