Fluent Validation を MVC WEB Api プロジェクトにフックしようとしていますが、うまくいきません。
私が使用するとMyController : Controller
->正常に動作します(ModelState.IsValid
戻りますFalse
)
しかし、私が使用するとMyController :ApiController
...何もありません。
誰もそれらを接続する方法について経験がありますか?
Fluent Validation を MVC WEB Api プロジェクトにフックしようとしていますが、うまくいきません。
私が使用するとMyController : Controller
->正常に動作します(ModelState.IsValid
戻りますFalse
)
しかし、私が使用するとMyController :ApiController
...何もありません。
誰もそれらを接続する方法について経験がありますか?
答えはこのプルリクエストにあります。
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との統合が欠けています。ただし、(ページを再構築するためにMVCで必要とされるように)ModelState全体をクライアントに返す必要のないAPIを構築する場合、単純さのトレードオフに価値があることがわかりました。API入力が無効な場合は常に、プロパティIDとエラーメッセージのリストを含む400BadRequestステータスコードを返します。これを行うには、単純な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とWebAPIに同じバリデーターインスタンスを使用できるようにしたいと思いました。これは、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とWebAPIの両方の検証を実行する方法を理解することでした。最終的に、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;
申し訳ありませんが、これは少し長かったですが、うまくいけば、誰かを助けることができます。
Fluent Validation の最新バージョンは、Mvc 4 または Web Api をサポートしていません。これを読んでください。