无法从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"; }); } }
问题原因
- Autofac容器未正确注册验证器:FastEndpoints默认会自动向ASP.NET Core内置DI容器注册验证器,但项目使用了Autofac作为服务提供者,Autofac不会自动发现这些验证器。
- 作用域问题:SchemaFilter执行时,直接使用根服务提供者获取作用域服务(验证器默认是Scoped)会失败,因为根容器不支持解析Scoped服务。
- 验证器注册开关未启用: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
相关产品推荐
相关产品推荐

