使用 Owin 的 FluentValidation 忽略 Asp.Net WebApi2 中的 [FromUri] Dtos
FluentValidation with Owin ignores [FromUri] Dtos in Asp.Net WebApi2
我正在开发 ASP.NET WebApi2 (.NET 4.7.2),将 Owin 和 Autofac 作为 IOC 容器。在到达控制器之前,Dto 应使用 FluentValidation 进行验证。这是我的项目配置:
public class Startup
{
public void Configuration(IAppBuilder app)
{
...
var config = new HttpConfiguration();
config.Filters.Add(new ValidateDtoStateFilter());
...
FluentValidationModelValidatorProvider.Configure(config);
...
}
}
验证 dto 状态过滤器:
public class ValidateDtoStateFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
throw new ValidationException(actionContext.ModelState
.SelectMany(ms => ms.Value.Errors
.Select(er => new ValidationFailure(ms.Key.Split('.')[1].FromPascalToCamelCase(), er.ErrorMessage))));
}
}
}
Autofac 生成器配置:
builder.RegisterAssemblyTypes(typeof(MyDtoValidator).Assembly)
.Where(t => t.Name.EndsWith("Validator"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.RegisterType<FluentValidationModelValidatorProvider>().As<ModelValidatorProvider>();
builder.RegisterType<AutofacValidatorFactory>().As<IValidatorFactory>().SingleInstance();
Autofac 验证器工厂:
public class AutofacValidatorFactory : ValidatorFactoryBase
{
private readonly IComponentContext _context;
public AutofacValidatorFactory(IComponentContext context)
{
_context = context;
}
public override IValidator CreateInstance(Type validatorType)
{
object instance;
return _context.TryResolve(validatorType, out instance) ? instance as IValidator : null;
}
}
它适用于 POST 或请求负载中 dto 出现 [FromBody]
的 PUT 端点。它 not 与 [FromUri]
dtos 一起工作,例如在 GET 请求中。验证器将由 Autofac 创建,但在 ValidateDtoStateFilter
的 OnActionExecuting
中,无论提供的数据是否有效,actionContext.ModelState
始终为真。
我怎样才能实现 [FromUri]
dto 也得到 FluentValidation 中间件的验证?
更新
如果我没有做任何更改,问题就不会再出现了。
我无法重现此行为。使用 Autofac 6.0.0
和 FluentValidation 8.6.1
进行测试。但是,当模型为 null(没有传递查询字符串参数或请求正文为空)时,[FromUri]
和 [FromBody]
dtos 都将 actionContext.ModelState.IsValid
设置为 true。在这种情况下,不会生成任何错误,因此验证通过。
This is actually by design. The purpose of FluentValidation is to validate properties on objects, which by definition requires a non-null instance in order to work.
https://github.com/FluentValidation/FluentValidation/issues/486
我可以想出两种方法来解决这个限制。
- 在您的控制器中,只需测试 dto 是否为空:
[HttpGet]
public IHttpActionResult Get([FromUri] MyDto dto)
{
if (dto == null) return BadRequest();
...
}
- 更新
ValidateDtoStateFilter
class 以手动触发已定义验证器的空 dto 的验证:
public class ValidateDtoStateFilter : ActionFilterAttribute
{
private readonly IDependencyResolver _resolver;
public ValidateDtoStateFilter(IDependencyResolver resolver)
{
_resolver = resolver;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
var parameters = actionContext.ActionDescriptor.GetParameters();
var nullDtosWithValidatorDefined = actionContext.ActionArguments
.Where(argument => argument.Value == null)
.Select(argument => new
{
argument.Key,
// you may need to account for p.IsOptional and p.DefaultValue here
Type = parameters.FirstOrDefault(p => p.ParameterName == argument.Key)?.ParameterType
})
.Where(argument => argument.Type != null)
.Select(argument => new {
argument.Key,
Validator = (IValidator)_resolver.GetService(typeof(IValidator<>).MakeGenericType(argument.Type))
})
.Where(argument => argument.Validator != null)
.Select(argument => argument.Key);
foreach (var dto in nullDtosWithValidatorDefined)
{
actionContext.ModelState.AddModelError(dto, $"'{dto}' must not be null.");
}
if (!actionContext.ModelState.IsValid)
{
var errors = actionContext
.ModelState
.SelectMany(ms => ms.Value.Errors
.Select(er => new ValidationFailure(ms.Key, er.ErrorMessage))
);
throw new ValidationException(errors);
}
}
}
// in the Startup.cs file the ValidateDtoStateFilter must be added after the DependencyResolver is set to Autofac:
config.DependencyResolver = new AutofacWebApiDependencyResolver(ConfigureAutofac());
config.Filters.Add(new ValidateDtoStateFilter(config.DependencyResolver));
虽然这可能无法解决您的具体问题,但如果您将 link 分享到您的项目或您的 Startup.cs
文件的片段会更容易,因为它可能与您的 api配置。
我正在开发 ASP.NET WebApi2 (.NET 4.7.2),将 Owin 和 Autofac 作为 IOC 容器。在到达控制器之前,Dto 应使用 FluentValidation 进行验证。这是我的项目配置:
public class Startup
{
public void Configuration(IAppBuilder app)
{
...
var config = new HttpConfiguration();
config.Filters.Add(new ValidateDtoStateFilter());
...
FluentValidationModelValidatorProvider.Configure(config);
...
}
}
验证 dto 状态过滤器:
public class ValidateDtoStateFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
throw new ValidationException(actionContext.ModelState
.SelectMany(ms => ms.Value.Errors
.Select(er => new ValidationFailure(ms.Key.Split('.')[1].FromPascalToCamelCase(), er.ErrorMessage))));
}
}
}
Autofac 生成器配置:
builder.RegisterAssemblyTypes(typeof(MyDtoValidator).Assembly)
.Where(t => t.Name.EndsWith("Validator"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.RegisterType<FluentValidationModelValidatorProvider>().As<ModelValidatorProvider>();
builder.RegisterType<AutofacValidatorFactory>().As<IValidatorFactory>().SingleInstance();
Autofac 验证器工厂:
public class AutofacValidatorFactory : ValidatorFactoryBase
{
private readonly IComponentContext _context;
public AutofacValidatorFactory(IComponentContext context)
{
_context = context;
}
public override IValidator CreateInstance(Type validatorType)
{
object instance;
return _context.TryResolve(validatorType, out instance) ? instance as IValidator : null;
}
}
它适用于 POST 或请求负载中 dto 出现 [FromBody]
的 PUT 端点。它 not 与 [FromUri]
dtos 一起工作,例如在 GET 请求中。验证器将由 Autofac 创建,但在 ValidateDtoStateFilter
的 OnActionExecuting
中,无论提供的数据是否有效,actionContext.ModelState
始终为真。
我怎样才能实现 [FromUri]
dto 也得到 FluentValidation 中间件的验证?
更新
如果我没有做任何更改,问题就不会再出现了。
我无法重现此行为。使用 Autofac 6.0.0
和 FluentValidation 8.6.1
进行测试。但是,当模型为 null(没有传递查询字符串参数或请求正文为空)时,[FromUri]
和 [FromBody]
dtos 都将 actionContext.ModelState.IsValid
设置为 true。在这种情况下,不会生成任何错误,因此验证通过。
This is actually by design. The purpose of FluentValidation is to validate properties on objects, which by definition requires a non-null instance in order to work. https://github.com/FluentValidation/FluentValidation/issues/486
我可以想出两种方法来解决这个限制。
- 在您的控制器中,只需测试 dto 是否为空:
[HttpGet]
public IHttpActionResult Get([FromUri] MyDto dto)
{
if (dto == null) return BadRequest();
...
}
- 更新
ValidateDtoStateFilter
class 以手动触发已定义验证器的空 dto 的验证:
public class ValidateDtoStateFilter : ActionFilterAttribute
{
private readonly IDependencyResolver _resolver;
public ValidateDtoStateFilter(IDependencyResolver resolver)
{
_resolver = resolver;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
var parameters = actionContext.ActionDescriptor.GetParameters();
var nullDtosWithValidatorDefined = actionContext.ActionArguments
.Where(argument => argument.Value == null)
.Select(argument => new
{
argument.Key,
// you may need to account for p.IsOptional and p.DefaultValue here
Type = parameters.FirstOrDefault(p => p.ParameterName == argument.Key)?.ParameterType
})
.Where(argument => argument.Type != null)
.Select(argument => new {
argument.Key,
Validator = (IValidator)_resolver.GetService(typeof(IValidator<>).MakeGenericType(argument.Type))
})
.Where(argument => argument.Validator != null)
.Select(argument => argument.Key);
foreach (var dto in nullDtosWithValidatorDefined)
{
actionContext.ModelState.AddModelError(dto, $"'{dto}' must not be null.");
}
if (!actionContext.ModelState.IsValid)
{
var errors = actionContext
.ModelState
.SelectMany(ms => ms.Value.Errors
.Select(er => new ValidationFailure(ms.Key, er.ErrorMessage))
);
throw new ValidationException(errors);
}
}
}
// in the Startup.cs file the ValidateDtoStateFilter must be added after the DependencyResolver is set to Autofac:
config.DependencyResolver = new AutofacWebApiDependencyResolver(ConfigureAutofac());
config.Filters.Add(new ValidateDtoStateFilter(config.DependencyResolver));
虽然这可能无法解决您的具体问题,但如果您将 link 分享到您的项目或您的 Startup.cs
文件的片段会更容易,因为它可能与您的 api配置。