从 Action Filter 中的 FluentValidation 检索错误代码
Retrieve ErrorCode from FluentValidator in ActionFilter
我正在使用 FluentValidation 库自动验证工作正常的模型 - 但是 - 需要在验证器中使用 WithErrorCode()
方法设置错误代码 (AbstractValidator<T>
) .这也可以正常工作,然后问题是从 ASP.NET MVC Core Action Filter 中检索该代码,定义如下:
public class ActionModelValidationAttribute : ActionFilterAttribute
{
readonly ILogger<ActionModelValidationAttribute> log;
public ActionModelValidationAttribute (ILogger<ActionModelValidationAttribute> log) => this.log = log;
public override void OnActionExecuting (ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var routeName = context.RouteData.Values["action"] ?? "unknown";
log.LogDebug($"model validation failed for {routeName}");
var errors = context.ModelState.Values.Where(state => state.Errors.Count > 0)
.SelectMany(errs => errs.Errors)
.Select(e => new BaseErrorResponse(){
Code = 404, // <<-- this is where I would like the code from WithErrorCode()
Details = e.Exception?.Message ?? "",
Message = e.ErrorMessage,
Field = "field"
}).ToList();
var response = new ValidationErrorResponseModel()
{
Message = "Bad Request",
Errors = errors
};
context.Result = new JsonResult(response)
{
StatusCode = (int)HttpStatusCode.BadRequest
};
}
}
}
错误的类型是Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateEntry
e的类型是Microsoft.AspNetCore.Mvc.ModelBinding.ModelError
这是我的验证器:
public class ViewModelValidator : AbstractValidator<ViewModel>
{
public ViewModelValidator() {
RuleFor(m => m.DistributorId)
.NotNull().WithErrorCode("910000")
.NotEmpty().WithErrorCode("910001");
}
}
FluentValidation 库似乎无法自行处理此问题。解决方法是在 AbstractValidator<T>
具体实现上实现 IValidatorInterceptor
接口。内存缓存可用于存储唯一的请求 ID,然后可以从操作过滤器中的缓存中检索 ID。将返回一个 ValidationResult
对象,其中包含所有丰富的验证信息。
代码示例如下:
public abstract class BaseModelValidator<T> : AbstractValidator<T>, IValidatorInterceptor
{
protected readonly IMemoryCache cache;
protected readonly ILogger<BaseModelValidator<T>> log;
protected string RequestId { get; set; }
public BaseModelValidator(IMemoryCache cache, ILogger<BaseModelValidator<T>> log)
{
this.cache = cache;
this.log = log;
}
public virtual ValidationContext BeforeMvcValidation(ControllerContext controllerContext, ValidationContext validationContext)
{
RequestId = controllerContext.HttpContext.TraceIdentifier;
return validationContext;
}
public virtual ValidationResult AfterMvcValidation(ControllerContext controllerContext, ValidationContext validationContext, ValidationResult result)
{
cache.Set(RequestId, result, TimeSpan.FromMinutes(1));
return result;
}
}
全局动作过滤器:
public class ActionModelValidationAttribute : ActionFilterAttribute
{
readonly ILogger<ActionModelValidationAttribute> log;
readonly IMemoryCache cache;
public ActionModelValidationAttribute(IMemoryCache cache, ILogger<ActionModelValidationAttribute> log)
{
this.log = log;
this.cache = cache;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var key = context.HttpContext.TraceIdentifier;
cache.TryGetValue<ValidationResult>(key, out var result);
if (result == null) ReturnError(context, key); // impl ReturnError however you like
cache.Remove(key);
var count = result.Errors.Count();
var controllerName = context.RouteData.Values["Controller"] ?? "unknown";
var routeName = context.RouteData.Values["Action"] ?? "unknown";
var response = result.AsBaseResponse();
log.LogDebug($"Model validation failed. {count} errors in model for {controllerName}.{routeName}");
context.Result = new JsonResult(response)
{
StatusCode = (int)HttpStatusCode.BadRequest
};
}
}
}
我正在使用 FluentValidation 库自动验证工作正常的模型 - 但是 - 需要在验证器中使用 WithErrorCode()
方法设置错误代码 (AbstractValidator<T>
) .这也可以正常工作,然后问题是从 ASP.NET MVC Core Action Filter 中检索该代码,定义如下:
public class ActionModelValidationAttribute : ActionFilterAttribute
{
readonly ILogger<ActionModelValidationAttribute> log;
public ActionModelValidationAttribute (ILogger<ActionModelValidationAttribute> log) => this.log = log;
public override void OnActionExecuting (ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var routeName = context.RouteData.Values["action"] ?? "unknown";
log.LogDebug($"model validation failed for {routeName}");
var errors = context.ModelState.Values.Where(state => state.Errors.Count > 0)
.SelectMany(errs => errs.Errors)
.Select(e => new BaseErrorResponse(){
Code = 404, // <<-- this is where I would like the code from WithErrorCode()
Details = e.Exception?.Message ?? "",
Message = e.ErrorMessage,
Field = "field"
}).ToList();
var response = new ValidationErrorResponseModel()
{
Message = "Bad Request",
Errors = errors
};
context.Result = new JsonResult(response)
{
StatusCode = (int)HttpStatusCode.BadRequest
};
}
}
}
错误的类型是Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateEntry
e的类型是Microsoft.AspNetCore.Mvc.ModelBinding.ModelError
这是我的验证器:
public class ViewModelValidator : AbstractValidator<ViewModel>
{
public ViewModelValidator() {
RuleFor(m => m.DistributorId)
.NotNull().WithErrorCode("910000")
.NotEmpty().WithErrorCode("910001");
}
}
FluentValidation 库似乎无法自行处理此问题。解决方法是在 AbstractValidator<T>
具体实现上实现 IValidatorInterceptor
接口。内存缓存可用于存储唯一的请求 ID,然后可以从操作过滤器中的缓存中检索 ID。将返回一个 ValidationResult
对象,其中包含所有丰富的验证信息。
代码示例如下:
public abstract class BaseModelValidator<T> : AbstractValidator<T>, IValidatorInterceptor
{
protected readonly IMemoryCache cache;
protected readonly ILogger<BaseModelValidator<T>> log;
protected string RequestId { get; set; }
public BaseModelValidator(IMemoryCache cache, ILogger<BaseModelValidator<T>> log)
{
this.cache = cache;
this.log = log;
}
public virtual ValidationContext BeforeMvcValidation(ControllerContext controllerContext, ValidationContext validationContext)
{
RequestId = controllerContext.HttpContext.TraceIdentifier;
return validationContext;
}
public virtual ValidationResult AfterMvcValidation(ControllerContext controllerContext, ValidationContext validationContext, ValidationResult result)
{
cache.Set(RequestId, result, TimeSpan.FromMinutes(1));
return result;
}
}
全局动作过滤器:
public class ActionModelValidationAttribute : ActionFilterAttribute
{
readonly ILogger<ActionModelValidationAttribute> log;
readonly IMemoryCache cache;
public ActionModelValidationAttribute(IMemoryCache cache, ILogger<ActionModelValidationAttribute> log)
{
this.log = log;
this.cache = cache;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var key = context.HttpContext.TraceIdentifier;
cache.TryGetValue<ValidationResult>(key, out var result);
if (result == null) ReturnError(context, key); // impl ReturnError however you like
cache.Remove(key);
var count = result.Errors.Count();
var controllerName = context.RouteData.Values["Controller"] ?? "unknown";
var routeName = context.RouteData.Values["Action"] ?? "unknown";
var response = result.AsBaseResponse();
log.LogDebug($"Model validation failed. {count} errors in model for {controllerName}.{routeName}");
context.Result = new JsonResult(response)
{
StatusCode = (int)HttpStatusCode.BadRequest
};
}
}
}