Fluent ValidationをMVC WEB Apiプロジェクトにフックしようとしていますが、機能しません。
MyController : Controller
を使用すると、正常に機能します(ModelState.IsValid
はFalse
を返します)
しかし、MyController :ApiController
を使用すると...何も起こりません。
誰かがそれらを接続する方法の経験がありますか?
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();
}
}
答えはこの pull request にあります。
基本的に、カスタムModelValidation
プロバイダーを実装する必要があります。
さらに、注意すべきことがいくつかあります。
Web APIは、System.Web.Mvc名前空間のmodelValidatorでは機能せず、System.Web.Httpからのものは次のように機能します。
次のように追加しないでください。
ModelValidatorProviders.Providers.Add(new WebApiFluentValidationModelValidatorProvider());`
しかし、このように:
GlobalConfiguration.Configuration.Services.Add(typeof(System.Web.Http.Validation.ModelValidatorProvider), new WebApiFluentValidationModelValidatorProvider());`
Web APIでFluentValidationを使用するための別の簡単な解決策を見つけましたが、ModelStateおよびMetadataとの統合が欠如しています。ただし、ModelState全体をクライアントに返す必要のないAPIを構築する場合(MVCでページを再構築するために必要)、シンプルさのトレードオフに価値があることがわかりました。 API入力が無効な場合は常に、プロパティIDとエラーメッセージのリストを含む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);
}
}
}
この属性は、グローバルフィルターとして、個々のコントローラー/アクション、または基本クラスに追加できます。
このコードは確かに改善できますが、これまでのところうまくいきましたので、他の人にも利用できるようにしたいと思いました。ここにいくつかの欠点があります:
私はこれを解決しようとしていたので、MVCとWeb APIに同じバリデーターインスタンスを使用できるようにしたかったのです。私は2つの工場を作り、それらを一緒に使用することによってこれを達成することができました。
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の両方の検証を実行する方法を理解することでした。最終的に、ModelValidatorシグネチャで動作するIValidator <>のラッパーを作成しました。
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;
申し訳ありませんがこれは少し長かったですが、うまくいけば誰かを助けるのに役立ちます。
WebApiConfigに2行を追加します
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/