Как подключить FluentValidator к веб-API?

Я пытаюсь подключить Fluent Validation к моему MVC WEB-проекту Api, и он не хочет работать.

Когда я использую MyController : Controller → отлично работает (ModelState.IsValid возвращает False)

но когда я использую MyController :ApiController... ничего.

Есть ли у кого-нибудь опыт в том, как их подключить?

Ответ 1

последняя версия Fluent Validation (5.0.0.1) поддерживает веб-api

Просто установите его из Nuget и зарегистрируйте в Global.asax так:

using FluentValidation.Mvc.WebApi;

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

Ответ 2

Ответ на этот запрос на перенос.

В основном вам необходимо реализовать пользовательский ModelValidation Provider.

И еще несколько замечаний:

Ответ 3

Поскольку я пытался решить эту проблему, я хотел сделать так, чтобы один и тот же экземпляр validator мог использоваться для MVC и Web API. Я смог выполнить это, сделав две фабрики и используя их вместе.

MVC Factory:

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 Factory:

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;

Извините, это было немного долго, но, надеюсь, это помогает кому-то выйти.

Ответ 4

Я нашел еще одно простое решение для использования FluentValidation в Web API, но ему не хватает интеграции с ModelState и метаданными. Однако при создании API, который не должен возвращать весь ModelState клиенту (как это требуется в MVC для перестройки страницы), я нашел компромисс для простоты целесообразным. Всякий раз, когда ввод API недействителен, я возвращаю код состояния 400 Bad Request со списком идентификаторов свойств и сообщений об ошибках. Для этого я использую простой 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);
        }
    }
}

Этот атрибут можно добавить как глобальный фильтр, отдельные контроллеры/действия или базовый класс.

Этот код, безусловно, может быть улучшен, но он до сих пор мне помог, поэтому я хотел сделать его доступным для других. Вот некоторые из его недостатков:

  • Нулевые входы не проверяются. Я думал, что это будет скорее проблемой, но на практике это просто не так много (если вообще) в нашем приложении. Мои контроллеры бросают ArgumentNullExceptions для пустых входов, которые возвращают 500 клиенту, информируя клиента о том, что вход не может быть нулевым.
  • Я не могу использовать ModelState в своих контроллерах. Но после того, как проверка требуемых входных данных не равна нулю, я уже знаю, что ModelState действителен, поэтому это может фактически упростить код. Но важно, чтобы разработчики знали, что не использовать его.
  • В настоящее время эта реализация жестко закодирована для AttributedValidatorFactory. Это должно быть абстрагировано, но до сих пор оно было довольно низким в моем списке приоритетов.

Ответ 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/

Ответ 6

Последняя версия Fluent Validation не поддерживает Mvc 4 или Web Api. Прочитайте это.