【问题标题】:How to hook FluentValidator to a Web API?如何将 FluentValidator 连接到 Web API?
【发布时间】:2012-10-19 13:27:06
【问题描述】:

我正在尝试将 Fluent Validation 与我的 MVC WEB Api 项目挂钩,但它无法正常工作。

当我使用MyController : Controller -> 工作正常(ModelState.IsValid 返回False

但是当我使用MyController :ApiController ...什么都没有。

有没有人有如何连接这些的经验?

【问题讨论】:

标签: c# asp.net-web-api fluentvalidation


【解决方案1】:

最新版 Fluent Validation (5.0.0.1) 支持 web api

只需从 Nuget 安装并在 Global.asax 中注册,如下所示:

using FluentValidation.Mvc.WebApi;

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        ...
        FluentValidationModelValidatorProvider.Configure();
    }
}

【讨论】:

  • 我提交了从 MVC 5 中提取 Web API 实现的建议,因为它们完全不相关。看看作者回复什么。
【解决方案2】:

答案就在这个pull request

基本上你需要实现自定义ModelValidationProvider。

还有几点需要注意:

  1. Web API 不适用于 System.Web.Mvc 命名空间中的 modelValidator,仅适用于 System.Web.Http 中的那些,如下所述:

    Server side validation with custom DataAnnotationsModelValidatorProvider

  2. 你不要这样添加:

    ModelValidatorProviders.Providers.Add(new WebApiFluentValidationModelValidatorProvider());`
    

    但是像这样:

    GlobalConfiguration.Configuration.Services.Add(typeof(System.Web.Http.Validation.ModelValidatorProvider), new WebApiFluentValidationModelValidatorProvider());`
    

【讨论】:

  • 还有一点需要注意:无法在 WebApi 控制器中调用“Revalidate”或“TryValidate”。所以首先考虑一下你是否需要这个连接。
  • 请注意有更好的 web api 集成实现:fluentvalidation.codeplex.com/SourceControl/network/forks/… 也没有合并。
  • 只是一个评论。如果您使用的是像 Ninject 这样的 DI 容器,上述方法将不起作用。您必须改为在您的 DI 配置中添加绑定。将类似于以下内容:kernel.Bind<System.Web.Http.Validation.ModelValidatorProvider>().To<WebApiFluentValidationModelValidatorProvider>(); kernel.Bind<IValidatorFactory>().To<NinjectValidatorFactory>().InSingletonScope();
  • 你能发一个WebApiFluentValidationModelValidatorProvider的例子吗?
  • @Dmitry 我认为它应该是 FluentValidation.WebApi.FluentValidationModelValidatorProvider
【解决方案3】:

我找到了另一个在 Web API 中使用 FluentValidation 的简单解决方案,但它缺乏与 ModelState 和元数据的集成。然而,当构建一个不需要将整个 ModelState 返回给客户端的 API 时(正如 MVC 中重建页面所需要的那样),我发现为了简单而进行权衡是值得的。每当 API 输入无效时,我都会返回 400 Bad Request 状态代码以及属性 ID 和错误消息列表。为此,我使用了一个简单的 ActionFilterAttribute:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ValidateInputsAttribute : ActionFilterAttribute
{
    private static readonly IValidatorFactory ValidatorFactory = new AttributedValidatorFactory();

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        base.OnActionExecuting(actionContext);
        var errors = new Dictionary<string, string>();
        foreach (KeyValuePair<string, object> arg in actionContext.ActionArguments.Where(a => a.Value != null))
        {
            var argType = arg.Value.GetType();
            IValidator validator = ValidatorFactory.GetValidator(argType);
            if (validator != null)
            {
                var validationResult = validator.Validate(arg.Value);
                foreach (ValidationFailure error in validationResult.Errors)
                {
                    errors[error.PropertyName] = error.ErrorMessage;
                }
            }
        }
        if (errors.Any())
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, errors);
        }
    }
}

此属性可以作为全局过滤器添加到单个控制器/动作或基类中。

这段代码当然可以改进,但到目前为止它对我很有帮助,所以我想把它提供给其他人。以下是它的一些缺点:

  1. 空输入未验证。我认为这将是一个更大的问题,但实际上它在我们的应用程序中根本不会发生太多(如果有的话)。我的控制器为空输入抛出 ArgumentNullExceptions,这将向客户端返回 500,通知客户端输入不能为空。
  2. 我无法在我的控制器中使用 ModelState。但是,在验证所需的输入是非空的之后,我已经知道 ModelState 是有效的,所以这实际上可以用来简化代码。但对于开发者来说,知道不要使用它很重要。
  3. 现在这个实现是为 AttributedValidatorFactory 硬编码的。这应该是抽象的,但到目前为止,它在我的优先级列表中非常低。

【讨论】:

  • 您的实现实际上可以拆分/抽象为几个部分:查找验证器(基于 DI 容器的工厂)、验证本身和处理验证结果(将错误设置为上下文)。
【解决方案4】:

当我正在寻找解决这个问题时,我想将它制作成可以将相同的验证器实例用于 MVC 和 Web API。我能够通过制作两个工厂并将它们一起使用来实现这一点。

MVC 工厂:

public class MVCValidationFactory : ValidatorFactoryBase
{
    private readonly IKernel _kernel;

    public MVCValidationFactory(IKernel kernel)
    {
        _kernel = kernel;
    }

    public override IValidator CreateInstance(Type validatorType)
    {
        var returnType = _kernel.TryGet(validatorType);

        return returnType as IValidator;
    }
}

API 工厂:

public class WebAPIValidationFactory : ModelValidatorProvider
{
    private readonly MVCValidationFactory _mvcValidationFactory;

    private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

    public WebAPIValidationFactory(MVCValidationFactory mvcValidationFactory)
    {
        _mvcValidationFactory = mvcValidationFactory;
    }

    public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, IEnumerable<ModelValidatorProvider> validatorProviders)
    {
        try
        {
            var type = GetType(metadata);

            if (type != null)
            {
                var fluentValidator =
                    _mvcValidationFactory.CreateInstance(typeof(FluentValidation.IValidator<>).MakeGenericType(type));

                if (fluentValidator != null)
                {
                    yield return new FluentValidationModelValidator(validatorProviders, fluentValidator);
                }
            }
        }
        catch (Exception ex)
        {
            Log.Error(ex);
        }

        return new List<ModelValidator>();
    }

    private static Type GetType(ModelMetadata metadata)
    {
        return metadata.ContainerType != null ? metadata.ContainerType.UnderlyingSystemType : null;
    }

接下来的诀窍是弄清楚如何对 MVC 和 Web API 运行验证。我最终为 IValidator 创建了一个与 ModelValidator 签名一起使用的包装器。

public class FluentValidationModelValidator : ModelValidator
{
    public IValidator innerValidator { get; private set; }

    public FluentValidationModelValidator(
        IEnumerable<ModelValidatorProvider> validatorProviders, IValidator validator)
        : base(validatorProviders)
    {
        innerValidator = validator;
    }

    public override IEnumerable<ModelValidationResult> Validate(ModelMetadata metadata, object container)
    {
        if (InnerValidator != null && container != null)
        {
            var result = innerValidator.Validate(container);

            return GetResults(result);
        }

        return new List<ModelValidationResult>();
    }

    private static IEnumerable<ModelValidationResult> GetResults(FluentValidation.Results.ValidationResult result)
    {
        return result.Errors.Select(error =>
            new ModelValidationResult
            {
                MemberName = error.PropertyName,
                Message = error.ErrorMessage
            }));
    }
}

最后一部分是连接 Global.asax 中的验证器:

MVCValidationFactory mvcValidationFactory = new MVCValidationFactory(KernelProvider.Instance.GetKernel());

GlobalConfiguration.Configuration.Services.Add(
    typeof(ModelValidatorProvider),
    new WebAPIValidationFactory(mvcValidationFactory));

ModelValidatorProviders.Providers.Add(new FluentValidationModelValidatorProvider(mvcValidationFactory));

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

对不起,这有点长,但希望它可以帮助别人。

【讨论】:

  • 你能从控制器调用“验证”吗?对我来说,问题是我需要在“MVC 控制器方法”(例如 POST)中间执行验证。因此,将其连接到 MVC 工厂对 API 不起作用,因为它会在不输入方法的情况下拒绝发布的对象(而在我的情况下,该方法会分配一些需要有效的值)
  • 我必须说你的回答太棒了。
  • 事实证明这非常复杂,并且有一些非常奇怪的行为。您可以通过将工厂传递给这些FluentValidation.Mvc.FluentValidationModelValidatorProvider.Configure(FluentValidation.WebApi.FluentValidationModelValidatorProvider.Configure( 来使用标准的 Ninject 工厂(在您的示例中为 MVCFactory) - 然后过滤器也只是覆盖 System.Net.Http onActionExecuting 并且适用于 MVC 和 WebAPI -这很好,因为 MVC6 只使用 System.Net,而 MVC 命名空间几乎已被根除
【解决方案5】:

在 WebApiConfig 中添加两行

public static class WebApiConfig
{
   public static void Register(HttpConfiguration config)
   {
       // snip...
       //Fluent Validation
       config.Filters.Add(new ValidateModelStateFilter());
       FluentValidationModelValidatorProvider.Configure(config);
   }
}

如下创建模型和验证器 -

[Validator(typeof(PersonCreateRequestModelValidator))] 
public class PersonCreateRequestModel
{
    public Guid PersonId { get; set; }
    public string Firstname { get; set; }
    public string Lastname { get; set; }
}


public class PersonCreateRequestModelValidator : AbstractValidator
{
    //Simple validator that checks for values in Firstname and Lastname
    public PersonCreateRequestModelValidator()
    {
        RuleFor(r => r.Firstname).NotEmpty();
        RuleFor(r => r.Lastname).NotEmpty();
    }
}

这就是你所需要的。只需像往常一样编写控制器即可。

public IHttpActionResult Post([FromBody]PersonCreateRequestModel requestModel)
{
    //snip..
    //return Ok(some new id);
}

如果您想要完整的源代码示例,可以在此处获取 - http://NoDogmaBlog.bryanhogan.net/2016/12/fluent-validation-with-web-api-2/

【讨论】:

  • 我不知道为什么,但这就是行不通:(即使我有像你展示的那样的代码......我知道它应该工作,但它只是没有。哪个令人沮丧。虽然我同时拥有 MVC 和 WebAPI.. 所以我想这一定是某个地方的问题。
  • @ppumkin 这篇博文有完整的源代码。下载并尝试。
  • 我最终使用了其他东西 FluentValidation.Mvc.FluentValidationModelValidatorProvider.Configure 然后 FluentValidation.WebApi.FluentValidationModelValidatorProvider.Configure 没有任何复杂的工厂和代理,只有一个 FilterAttribute。不知道为什么我很难找到它,但它不适用于 MVC 和 WebAPI 中的 3 行代码:D
【解决方案6】:

Fluent Validation 的最新版本不支持 Mvc 4 或 Web Api。 阅读this.

【讨论】:

  • 不是真的。检查nuget。 MVC3 和 MVC4 都有 FV
猜你喜欢
  • 2020-02-28
  • 1970-01-01
  • 1970-01-01
  • 2014-10-28
  • 1970-01-01
  • 2018-05-08
  • 1970-01-01
  • 2018-08-15
  • 2019-12-03
相关资源
最近更新 更多