自定义页面过滤器中的 ASP .NET Core 注入服务

Jac*_*kal 6 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())));
         })
Run Code Online (Sandbox Code Playgroud)

过滤器。

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

错误是

InvalidOperationException: 没有为此 DbContext 配置数据库提供程序。可以通过覆盖 DbContext.OnConfiguring 方法或在应用程序服务提供者上使用 AddDbContext 来配置提供者。如果使用 AddDbContext,则还要确保您的 DbContext 类型在其构造函数中接受 DbContextOptions 对象并将其传递给 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?}");
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

我的背景

 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();
    }
 ...
}
Run Code Online (Sandbox Code Playgroud)

Kir*_*kin 5

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

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

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

InvalidOperationException: 没有为此 DbContext 配置数据库提供程序。可以通过覆盖 DbContext.OnConfiguring 方法或在应用程序服务提供者上使用 AddDbContext 来配置提供者。如果使用 AddDbContext,则还要确保您的 DbContext 类型在其构造函数中接受 DbContextOptions 对象并将其传递给 DbContext 的基本构造函数。

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

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

这里一个明显的解决方案是将 DI 用于这些服务,但这并不是那么简单,因为在创建LockdownFilter. 这是TypeFilterAttributeServiceFilterAttribute进来,这是证据充分的在ASP.NET中的核心过滤器:依赖注入。这是我调用的代码的更新版本,它使用TypeFilterAttribute

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

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