c# - 自定义页面过滤器中的 ASP .NET Core 注入(inject)服务

标签 c# asp.net-core razor-pages

我设置了一个过滤器来处理特定文件夹及其内的所有页面。我需要使用声明访问数据库。问题是我似乎无法在启动服务上向 DI 注册我的过滤器,因为它找不到数据库连接

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

过滤器。

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;
    }
}

错误是

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.

这是整个启动类

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?}");
        });
    }
}

我的背景

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

最佳答案

您的问题中有很多代码,因此我将首先突出显示感兴趣的代码:

options.Conventions.AddAreaFolderApplicationModelConvention("Production", "/FrontEnd", 
    model => model.Filters.Add(
        new LockdownFilter(
            new ProducaoRegistoService(new ProductionContext()), 
            new UrlHelperFactory(), 
            new HttpContextAccessor())));

现在,让我们再看一下错误消息:

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.

在您的情况下,在我调用的代码中创建的 ProductionContext 实例尚未配置。您可能认为它正在被配置,因为您在 ConfigureServices 方法中的其他地方使用了 AddDbContext,但事实并非如此。 p>

AddDbContext 设置 DI 所需的一切,为您提供一个 ProductionContext 实例,该实例根据您的设置进行配置(使用 SQL Server 和 DefaultConnection 连接字符串)。但是,通过创建您自己的 ProductionContext 实例并将其传递到过滤器中,根本不会使用 DI 配置的实例。

这里一个明显的解决方案是对这些服务使用 DI,但这并不是那么简单,因为在创建 LockdownFilter 实例时您无权访问 DI。这就是 TypeFilterAttributeServiceFilterAttribute 发挥作用的地方,Filters in ASP.NET Core: Dependency injection 中有详细记录。 。这是我调用的代码的更新版本,它使用 TypeFilterAttribute:

options.Conventions.AddAreaFolderApplicationModelConvention("Production", "/FrontEnd", 
    model => model.Filters.Add(new TypeFilterAttribute(typeof(LockdownFilter))));

使用这种方法,传入 LockdownFilter 构造函数的参数将从 DI 解析。从您的问题可以清楚地看出,这三个服务都已在 DI 容器中注册,因此应该按原样工作。

关于c# - 自定义页面过滤器中的 ASP .NET Core 注入(inject)服务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57539964/

相关文章:

c# - 如何识别列表中的任何项目是否作为另一个对象列表的元素存在

c# - 在 Linux 上时 Dotnet 测试忽略过滤器

asp.net-core - ASP.NET Identity 和 IdentityServer 有什么区别?

asp.net - 使用 Razor 页面禁用按钮

c# - 是否可以将 .exe 程序集安装到 GAC 中?

C# Autoupdater.net 无法提取

c# - 在应用程序中设置数据库的模式名称

c# - app.UseMigrationsEndPoint() 出现错误,如果无法解决可以指导我删除 asp.net 身份文件(用于创建登录)

c# - 无法注册 IRelationalTypeMappingSource

asp.net-core-2.0 - 更改 ASP.NET Core 2 Razor 页面的文化