【问题标题】:FluentValidation with Owin ignores [FromUri] Dtos in Asp.Net WebApi2使用 Owin 进行 FluentValidation 忽略 Asp.Net WebApi2 中的 [FromUri] Dtos
【发布时间】:2021-07-02 13:40:57
【问题描述】:

我正在开发一个使用 Owin 和 Autofac 作为 IOC 容器的 ASP.NET WebApi2 (.NET 4.7.2)。 Dtos 应在到达控制器之前使用 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 或 PUT 端点,其中 dto 来自 requets 有效负载中的[FromBody]。它确实[FromUri] dtos 一起工作,例如在 GET 请求中。验证器将由 Autofac 创建,但在 ValidateDtoStateFilterOnActionExecuting 中,actionContext.ModelState 始终为 true,无论提供的数据是否有效。

我怎样才能使[FromUri] dtos 也得到 FluentValidation 中间件的验证?

更新

如果我没有进行任何更改,问题就不会再发生了。

【问题讨论】:

    标签: c# asp.net-web-api2 owin autofac fluentvalidation


    【解决方案1】:

    我无法重现此行为。用Autofac 6.0.0FluentValidation 8.6.1 测试。但是,[FromUri][FromBody] dtos 在模型为空(未传递查询字符串参数或请求正文为空)时将 actionContext.ModelState.IsValid 设置为 true。在这种情况下,不会产生任何错误,因此验证通过。

    这实际上是设计使然。 FluentValidation 的目的是验证对象的属性,根据定义,它需要一个非空实例才能工作。 https://github.com/FluentValidation/FluentValidation/issues/486

    我可以想出两种方法来解决这个限制。

    1. 在您的控制器中,只需测试 dto 是否为空:
    [HttpGet]
    public IHttpActionResult Get([FromUri] MyDto dto)
    {
        if (dto == null) return BadRequest();
        ...
    }
    
    1. 更新 ValidateDtoStateFilter 类以手动触发对已定义验证器的空 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));
    

    虽然这可能无法解决您的具体问题,但如果您共享项目链接或Startup.cs 文件的 sn-p 会更容易,因为它可能与您的 api 配置有关。

    【讨论】:

      猜你喜欢
      • 2016-07-09
      • 2014-10-14
      • 2014-11-10
      • 1970-01-01
      • 1970-01-01
      • 2016-09-12
      • 2015-03-16
      • 2018-06-20
      • 1970-01-01
      相关资源
      最近更新 更多