環境配置
Asp.Net core 2.2
Visual Studio 2017
nuget:FluentValidation.AspNetCore 8.4.0版
一般想使用Fluent Validation作傳入參數驗證可以在程式內使用Validate方法
現在為了節省程式碼改用actionfilterattribute的方式,掛在每個action方法上
但是發現每個request進來後都沒進到actionfilter
這個範例的程式碼我是參考軟體主廚的文章
https://dotblogs.com.tw/supershowwei/2016/04/30/005529
在開始之前先說明一下.net core filter的運作方式
黃色是正常處理流程
灰色是異常流程
資料參考https://blog.johnwu.cc/article/ironman-day14-asp-net-core-filters.html
request進來之前會先經過Model Binding
重點就是在這邊
看FluentValidation的文件說明他的驗證方式是依照.Net MVC 模型驗證的方式
所以跑到這一段的時候就會去自訂的Validator執行建構式
但是這邊驗證參數沒過的話不會往下走到action filter 而是會直接回傳錯誤訊息
用程式實際跑一次的流程如下
CustomAuthorizationFilter in.
CustomResourceFilter in.
CustomResultFilter in.
CustomResultFilter out.
CustomResourceFilter out.
回傳預設的格式是
{
"errors": {
"Amount": [
"Amount 必須大於 0。"
],
"UnitPrice": [
"Unit Price 必須大於等於 0。"
]
},
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "8000016b-0002-fc00-b63f-84710c7967bb"
}
如果沒有特別需求用這樣就可以
如果是像我需要自訂回傳格式就需要讓request進到Actionfilter
原本做法是在Startup.cs裡面註冊
services.AddMvc()
.AddFluentValidation(fvc =>fvc.RegisterValidatorsFromAssemblyContaining<Startup>())
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
這個做法會去找所有繼承AbstractValidator<T>的類別
在這邊就直接註冊
如果想改成手動的方式不走ModelBinding
就要把AddFluentValidation拿掉自己註冊
改成像這樣
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddTransient<IValidatorFactory, ServiceProviderValidatorFactory>();
services.AddTransient<IValidator<Order>, OrderValidator>();
2019.07.24補上另一個做法
services.AddMvc()
.AddFluentValidation(fvc => fvc.RegisterValidatorsFromAssemblyContaining<Startup>())
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
這邊意思是Model Binding驗證不過跳400error的話,會不理他繼續往下執行
範例ActionFilter
public class ValidateRequestParameterAttribute : ActionFilterAttribute
{
private string ParameterName;
private Type ParameterType;
/// <summary>
/// Initializes a new instance of the <see cref="ValidateRequestParameterAttribute" /> class.
/// </summary>
/// <param name="parameterType">Type of the parameter.</param>
/// <param name="parameterName">Name of the parameter.</param>
public ValidateRequestParameterAttribute(Type parameterType,
string parameterName = "parameter")
{
this.ParameterName = parameterName;
this.ParameterType = parameterType;
}
/// <summary>
/// 所有實作IRequestParameter介面的類別.
/// </summary>
private static Type[] _entryAssemblyParameterTypes;
private static Type[] EntryAssemblyParameterTypes
{
get
{
if (_entryAssemblyParameterTypes==null || _entryAssemblyParameterTypes.Any().Equals(false))
{
var type = typeof(IRequestParameter);
_entryAssemblyParameterTypes = type.Assembly
.GetTypes()
.Where(p => type.IsAssignableFrom(p))
.ToArray();
}
return _entryAssemblyParameterTypes;
}
}
/// <summary>
/// Called when [action executing].
/// </summary>
/// <param name="context">The context.</param>
/// <exception cref="System.NotSupportedException">wrong parameter type</exception>
/// <inheritdoc />
public override void OnActionExecuting(ActionExecutingContext context)
{
// 找到Controller設定對應的類別
var typeFullName = this.ParameterType.FullName;
var requestParameterType = EntryAssemblyParameterTypes.SingleOrDefault
(
x => x.FullName.Equals(typeFullName, StringComparison.OrdinalIgnoreCase)
);
// 取得傳進來 RequestParameter
var parameterObject = this.GetParameterFromActionContext(context, this.ParameterName);
if (parameterObject.GetType() != requestParameterType)
{
throw new NotSupportedException("wrong parameter type");
}
var validatorFactory = context.HttpContext.RequestServices.GetRequiredService<IValidatorFactory>();
var validator = validatorFactory.GetValidator(parameterObject.GetType());
// skip objects with no validators
if (validator!=null)
{
var result = validator.Validate(parameterObject);
// if there are errors, throw fluent validation exception.
if (!result.IsValid)
{
throw FluentValidationExtensions.GetFluentValidationException(result.Errors);
}
}
base.OnActionExecuting(context);
}
/// <summary>
/// Gets the parameter from action context.
/// </summary>
/// <param name="actionContext">The action context.</param>
/// <param name="parameterName">對應傳入參數名稱</param>
/// <returns>System.Object.</returns>
/// <exception cref="System.ArgumentNullException">actionContext - must input actionContext.</exception>
/// <exception cref="System.ArgumentException">must input parameter</exception>
private object GetParameterFromActionContext(ActionExecutingContext actionContext,
string parameterName)
{
if (actionContext==null)
{
throw new ArgumentNullException(nameof(actionContext), "must input actionContext.");
}
if (actionContext.ActionArguments.Any().Equals(false))
{
throw new ArgumentException("must input parameter");
}
var actionArguments = actionContext.ActionArguments;
var parameter = actionArguments[parameterName];
return parameter;
}
}
文件:https://fluentvalidation.net/aspnet#injecting-child-validators