Как подключить 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.
И еще несколько замечаний:
-
Веб-API не работает с modelValidator из пространства имен System.Web.Mvc, только с теми из System.Web.Http, как указано здесь:
Проверка на стороне сервера с помощью пользовательских DataAnnotationsModelValidatorProvider
-
Вы не добавляете его так:
ModelValidatorProviders.Providers.Add(new WebApiFluentValidationModelValidatorProvider());`
НО так:
GlobalConfiguration.Configuration.Services.Add(typeof(System.Web.Http.Validation.ModelValidatorProvider), new WebApiFluentValidationModelValidatorProvider());`
Ответ 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.
Прочитайте это.