在ActionFilter中从FluentValidator检索ErrorCode

时间:2019-02-13 02:26:26

标签: asp.net-core fluentvalidation

我正在使用FluentValidation库自动验证运行良好的模型-但是-需要在验证器(WithErrorCode())中使用AbstractValidator<T>方法设置错误代码。这样也可以很好地工作,然后问题出在从如下定义的ASP.NET MVC核心操作过滤器中检索该代码:

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");
    }
}

1 个答案:

答案 0 :(得分:0)

似乎FluentValidation库本身不能解决这个问题。一种解决方法是在IValidatorInterceptor具体实现上实现AbstractValidator<T>接口。内存缓存可用于存储唯一的请求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
            };
        }
    }
}