You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无法从ISchemaFilter的ServiceProvider获取FastEndpoint的IValidator<T>实例

在FastEndpoints中获取IValidator实例始终为null的解决方案

问题背景

使用FastEndpoints处理请求验证时,需要在Swagger的ISchemaFilter实现中通过反射获取IValidator<T>实例,但每次获取结果都是null。相关代码如下:

SwaggerFluentValidationRules.cs

using FluentValidation;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using PakEnergy.Services.SharedKernel.Request;
using PakEnergy.Services.SharedKernel.Validator;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace PakEnergy.Services.SharedKernel.Swagger;

public class SwaggerFluentValidationRules : ISchemaFilter
{
    private readonly IServiceProvider _serviceProvider;

    public SwaggerFluentValidationRules(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void Apply(OpenApiSchema model, SchemaFilterContext context)
    {
        var genericType = typeof(IValidator<>).MakeGenericType(context.Type);
        // 此处validator始终为null
        var validator = _serviceProvider.GetService(genericType) as IValidator; 

        // 后续逻辑
    }
}

Startup.cs

namespace PakEnergy.Services.CompanyService.Api;

[ExcludeFromCodeCoverage]
public abstract class Startup
{
    const string CorsPolicyName = "CorsPolicy";

    public static void Run(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
        builder.Host.UseSerilog((ctx, config) =>
            {
                var configuration = new ConfigurationBuilder().AddConfiguration(ctx.Configuration).Build();
                config.ReadFrom.Configuration(configuration);
            })
            .ConfigureContainer<ContainerBuilder>(containerBuilder =>
            {
                containerBuilder.RegisterModule(new DefaultCoreModule());
                containerBuilder.RegisterModule(new DefaultInfrastructureModule());
                containerBuilder.RegisterModule(new SharedPolicyHandlersModule());
            });

        ConfigureServices(builder);

        var app = builder.Build();
        app.UseSerilogRequestLogging();
        app.UseDefaultExceptionHandler(logStructuredException: true);

        if (app.Environment.IsEnvironment("PreProduction") || app.Environment.IsProduction())
        {
            app.UseHttpsRedirection();
        }

        if (!app.Environment.IsProduction())
        {
            app.UseSwagger();
            app.UseSwaggerUI(
                c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "PakEnergy.Services.CompanyService.Api V1"); });
        }

        app.UseCors(CorsPolicyName);
        app.UseRouting();
        app.UseFastEndpoints(c =>
        {
            c.Endpoints.RoutePrefix = "api";
            c.Endpoints.Configurator = ep =>
            {
                ep.PreProcessors(Order.Before, new IfMatchHeaderValidator<Company>());
                ep.ResponseInterceptor(new ETagResponseInterceptor());
            };

            c.Binding.ValueParserFor<ProductIdEnum>(ProductIdEnumParser.Parse);
        });

        app.UseAuthorization();

        using var scope = app.Services.CreateScope();
        var services = scope.ServiceProvider;

        var context = services.GetRequiredService<AppDbContext>();
        context.Database.Migrate();

        if (app.Environment.IsDevelopment() || app.Environment.IsEnvironment("QA"))
            app.UseDbSeeding(context);

        var separator = new string('#', 51);
        Console.WriteLine(separator);
        Console.WriteLine(
            $"### Starting PakEnergy.Services.CompanyService.Api in {builder.Environment.EnvironmentName}\t\t###");
        Console.WriteLine(separator);

        app.Run();
    }

    private static void ConfigureServices(WebApplicationBuilder builder)
    {
        var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
        
        var services = builder.Services;
        builder.Services.AddOptions();
        builder.Services.Configure<ConnectionStringSettings>(builder.Configuration.GetSection("ConnectionStrings"));
        services.AddSingleton<IConnectionStringSettings>(provider =>
            provider.GetRequiredService<IOptions<ConnectionStringSettings>>().Value);
        builder.Services.Configure<ProvisioningSettings>(builder.Configuration.GetSection("Provisioning"));
        services.AddSingleton<IProvisioningSettings>(provider =>
            provider.GetRequiredService<IOptions<ProvisioningSettings>>().Value);

        services.AddDbContext<AppDbContext>((sp, optionsBuilder) =>
        {
            var auditInterceptor = sp.GetRequiredService<EntityBaseAuditInterceptor>();
            var softDeleteInterceptor = sp.GetRequiredService<EntityBaseSoftDeleteInterceptor>();
            optionsBuilder.UseSqlServer(connectionString)
                .AddInterceptors(auditInterceptor, softDeleteInterceptor);
        });
        
        services.AddHttpContextAccessor();
        services.AddAutoMapper(typeof(CreateCompanyMapper));
        services.AddCors(options =>
        {
            options.AddPolicy(CorsPolicyName, policyBuilder =>
            {
                policyBuilder
                    .AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowCredentials()
                    .WithExposedHeaders("content-disposition", "etag")
                    .SetIsOriginAllowed(_ => true); // 允许任意来源
            });
        });

        services.AddFastEndpoints();
        services.AddFastEndpointsApiExplorer();
        services.AddScoped<ISqlRunnerService, SqlRunnerService>();
        services.AddScoped<IDatabaseProvisioningService, DatabaseProvisioningService>();
        services.AddScoped<IStorageContainerProvisioningService, StorageContainerProvisioningService>();
        services.AddScoped<IProvisionStatusService, ProvisionStatusService>();
        services.AddScoped<ICompanyAccessService, CompanyAccessService>();
        services.AddScoped<ICompanyValidationService, CompanyValidationService>();
        services.AddScoped<ITaxValidationService, TaxValidationService>();
        services.AddScoped<ICompanyRepository, CompanyRepository>();
        services.AddScoped<IResourceNameGeneratorService, ResourceNameGeneratorService>();
        services.AddScoped<IBlobServiceClientFactory, BlobServiceClientFactory>();

        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1",
                new OpenApiInfo { Title = "PakEnergy.Services.CompanyService.Domain.Api", Version = "v1" });
            c.EnableAnnotations();
            c.OperationFilter<FastEndpointsOperationFilter>();
            c.OperationFilter<SwaggerOperationNotRequiredParametersFilter>();
            c.DocumentFilter<SwaggerEnumDocumentFilter>();
            c.SchemaFilter<SwaggerFluentValidationRules>();
            // 添加JWT认证的安全方案
            var jwtSecurityScheme = new OpenApiSecurityScheme
            {
                BearerFormat = "JWT",
                Name = "JWT Authentication",
                In = ParameterLocation.Header,
                Type = SecuritySchemeType.Http,
                Scheme = JwtBearerDefaults.AuthenticationScheme,
                Description = "请在下方输入框中仅填写你的JWT Bearer令牌!",

                Reference = new OpenApiReference
                {
                    Id = JwtBearerDefaults.AuthenticationScheme,
                    Type = ReferenceType.SecurityScheme
                }
            };

            c.AddSecurityDefinition("Bearer", jwtSecurityScheme);

            c.AddSecurityRequirement(new OpenApiSecurityRequirement()
            {
                { jwtSecurityScheme, new List<string>() }
            });
        });

        services
            .AddAuthentication()
            .AddJwtBearer(options =>
            {
                options.Authority = builder.Configuration.GetValue<string>("Auth0:Authority");
                options.Audience = builder.Configuration.GetValue<string>("Auth0:Audience");
            });

        builder.Services.AddAuthorization(options =>
        {
            options.AddPolicy(GlobalPolicies.RequiresMatchingProductId, policy =>
            {
                policy.AddRequirements(new RequiresMatchingProductIdRequirement());
            });
            options.AddPolicy(GlobalPolicies.RequiresMatchingCompanyId, policy =>
            {
                policy.AddRequirements(new RequiresMatchingCompanyIdRequirement());
            });
        });

        // 用于诊断的服务列表
        services.Configure<ServiceConfig>(config =>
        {
            config.Services = new List<ServiceDescriptor>(services);
            config.Path = "listservices";
        });
    }
}

问题原因

  1. Autofac容器未正确注册验证器:FastEndpoints默认会自动向ASP.NET Core内置DI容器注册验证器,但项目使用了Autofac作为服务提供者,Autofac不会自动发现这些验证器。
  2. 作用域问题:SchemaFilter执行时,直接使用根服务提供者获取作用域服务(验证器默认是Scoped)会失败,因为根容器不支持解析Scoped服务。
  3. 验证器注册开关未启用:FastEndpoints的验证自动注册可能被意外关闭。

解决方案

方案1:让Autofac手动注册所有验证器

在Autofac模块(比如DefaultCoreModule)中添加扫描注册逻辑,确保所有IValidator<T>实现被注册:

using Autofac;
using FluentValidation;
using System.Reflection;

public class DefaultCoreModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        // 扫描当前程序集及相关程序集中的验证器
        builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
               .Where(t => t.IsClosedTypeOf(typeof(IValidator<>)))
               .AsImplementedInterfaces()
               .InstancePerLifetimeScope(); // 与FastEndpoints默认生命周期一致

        // 其他现有注册逻辑...
    }
}

方案2:在SchemaFilter中使用作用域获取服务

修改SwaggerFluentValidationRules中的代码,通过创建作用域来获取Scoped生命周期的验证器:

public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
    var genericType = typeof(IValidator<>).MakeGenericType(context.Type);
    
    // 创建作用域获取服务
    using var scope = _serviceProvider.CreateScope();
    var validator = scope.ServiceProvider.GetService(genericType) as IValidator;

    // 后续逻辑...
}

方案3:确认FastEndpoints验证配置未被关闭

检查AddFastEndpoints的配置,确保验证自动注册功能处于启用状态(默认是启用的,若有自定义配置需确认):

services.AddFastEndpoints(c =>
{
    c.Validation.EnableValidation = true; // 确保此值为true
    // 其他配置...
});

内容的提问来源于stack exchange,提问作者Jorge Zapata

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 14:25:58