代码之家  ›  专栏  ›  技术社区  ›  phougatv

在ASP中识别模型绑定失败的最佳方法是什么。NET Core 6.0+

  •  2
  • phougatv  · 技术社区  · 3 年前

    我已经浏览了MSDN文档:

    我尝试创建一个场景,其中从swagger发送到API的值无法绑定到服务器上预期的模型。以下是场景的代码:

    OrderController.cs

    [HttpPost]
    public async Task<IActionResult> CreateAsync(OrderViewModel viewModel)
    {
        //map and add this model to the db
        //and return a 201 status code
    }
    

    还有我从招摇中发出的信息:

    {
        null
    }
    

    这导致了模型绑定失败,我有一个结果过滤器,用于处理这种情况,如下所示: FailedValidationResultFilter

    public class FailedValidationResultFilter : IResultFilter
    {
        public void OnResultExecuted(ResultExecutedContext context)
        {
        }
    
        public void OnResultExecuting(ResultExecutingContext context)
        {
            //When model-binding fails
            var hasModelBindingFailed = context.ModelState.Any(pair => String.IsNullOrEmpty(pair.Key));
            if (hasModelBindingFailed)
            {
                //do something when model-binding fails.
                //and return BadRequestObjectResult
            }
    
            //When validation attributes fails
            var invalidParams = new Dictionary<String, String[]>(context.ModelState.Count);
            foreach (var keyModelStatePair in context.ModelState)
            {
                var key = keyModelStatePair.Key;
                var modelErrors = keyModelStatePair.Value.Errors;
    
                if (modelErrors is not null && modelErrors.Count > 0)
                {
                    var errorMessages = modelErrors.Select(error => error.ErrorMessage).ToArray();
                    invalidParams.Add(key, errorMessages);
                }
            }
    
            var problemDetails = new ProblemDetails
            {
                Type = "123",
                Title = "Invalid parameters",
                Status = StatusCodes.Status400BadRequest,
                Detail = "Your request parameters didn't validate.",
                Instance = ""
            };
            problemDetails.Extensions.Add(nameof(invalidParams), invalidParams);
    
            context.Result = new BadRequestObjectResult(problemDetails);
        }
    }
    

    我在调试时观察到,每当此输入的模型绑定失败时,它都会返回2个键值对:

    • { "", "Some error message" }
    • { "viewModel", "Again some error message" }

    所以,我正在检查它们是否是一个带有空键的模型状态,如果是,则存在模型绑定错误。我不知道为什么,但这似乎不是发现模型绑定是否失败的正确方法。

    问题:识别模型绑定是否失败的正确方法是什么?可以传递的另一种输入类型是什么,这会导致模型绑定失败,然后在过滤器中,第一个属性可能不会像我预期的那样为空?

    0 回复  |  直到 3 年前
        1
  •  0
  •   Qiang Fu    3 年前

    ModelState.key是无效的属性名称。你发送 null 因此属性名称为空。但在我的测试中,关键是一个“$”
    您可以尝试以下代码。
    ViewModel.cs

        public class ViewModel
        {
            public int Number {  get; set; }
        }
    

    ProblemDetails.cs

        public class ProblemDetails
        {
            public string Type { get; set; }
            public string Title { get; set; }
            public int Status { get; set; }
            public string Detail { get; set; }
            public string Instance { get; set; }
            public Dictionary<string, string> Extensions { get; set; }
        }
    

    程序.cs

    builder.Services.Configure<ApiBehaviorOptions>(options
        => options.SuppressModelStateInvalidFilter = true);
    

    控制器

            [HttpPost("test")]
            public IActionResult test(ViewModel viewModel)
            {
                if (!ModelState.IsValid)
                {
                    var problemDetails = new ProblemDetails
                    {
                        Type = "123",
                        Title = "Invalid parameters",
                        Status = StatusCodes.Status400BadRequest,
                        Detail = "Your request parameters didn't validate.",
                        Instance = "",
                        Extensions = new Dictionary<string, string>()
                    };
                    foreach (var b in ModelState)
                    {
                        problemDetails.Extensions.Add(b.Key, b.Value.Errors.FirstOrDefault().ErrorMessage);
                    }
                    return Ok(problemDetails);
                }
                return Ok(viewModel);           
            }
    

    测验
    enter image description here
    enter image description here

    您可以将代码放在ActionFilter中。
    ValidationFilterAttribute.cs

        public class ValidationFilterAttribute : IActionFilter
        {
            public void OnActionExecuting(ActionExecutingContext context)
            {
                if (!context.ModelState.IsValid)
                {
                    var problemDetails = new ProblemDetails
                    {
                        Type = "123",
                        Title = "Invalid parameters",
                        Status = StatusCodes.Status400BadRequest,
                        Detail = "Your request parameters didn't validate.",
                        Instance = "",
                        Extensions = new Dictionary<string, string>()
                    };
                    foreach (var b in context.ModelState)
                    {
                        problemDetails.Extensions.Add(b.Key, b.Value.Errors.FirstOrDefault().ErrorMessage);
                    }
                    context.Result = new ObjectResult(problemDetails);
                }
            }
            public void OnActionExecuted(ActionExecutedContext context) { }
        }
    

    程序.cs

    builder.Services.AddScoped<ValidationFilterAttribute>();
    builder.Services.Configure<ApiBehaviorOptions>(options
        => options.SuppressModelStateInvalidFilter = true);
    

    控制器

            [HttpPost("test")]
            [ServiceFilter(typeof(ValidationFilterAttribute))]
            public IActionResult test(ViewModel viewModel)
            {
                return Ok(viewModel);           
            }
    

    结果过滤器与操作过滤器 ActionFilter and ResultFilter different and Examples

        2
  •  0
  •   phougatv    3 年前

    经过大量的测试和试验,我希望我有正确的答案。那么,让我们开始吧。

    场景一

    当请求有效载荷为

    null
    

    当我们在请求中发送此有效载荷时,模型验证无法生成2个密钥( 其中一个是空字符串 )带有以下错误消息:

    钥匙 错误消息
    需要一个非空的请求正文。
    视图模型 viewModel字段是必需的。

    场景二

    当请求有效载荷为

    {
        null
    }
    

    在这种情况下,将生成这2个密钥:

    钥匙 错误消息
    $ “n”是属性名称的无效开头。应为“”。路径:$|行号:1 | BytePositionInLine:2。
    视图模型 viewModel字段是必需的。

    现在,我已经使用以下代码来处理这两种场景:

    //When model-binding fails because input is an invalid JSON
    if (modelStateDictionary.Any(pair => pair.Key == DollarSign || String.IsNullOrEmpty(pair.Key)))
    {
        problemDetails.Detail = RequestFailedModelBinding;
        context.Result = GetBadRequestObjectResult(problemDetails);
        return;
    }
    

    完整代码:

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Filters;
    using WebApi.ErrorResponse.ViaFilterAndMiddleware.ViewModels;
    using static Microsoft.AspNetCore.Http.StatusCodes;
    
    public class MyModelValidationResultFilter : IResultFilter
    {
        #region Private Constants
        private const Char Dot = '.';
        private const String DollarSign = "$";
        private const String InvalidParameters = "Invalid parameters.";
        private const String RequestFailedModelBinding = "Your request failed model-binding.";
        private const String RequestPropertyFailedModelBinding = "Your request failed model-binding: '{0}'.";
        private const String RequestParametersDidNotValidate = "Your request parameters did not validate.";
        private const String MediaTypeApplicationProblemJson = "application/problem+json";
        #endregion Private Constants
    
        /// <summary>
        /// 
        /// </summary>
        /// <param name="context">The result executed context.</param>
        public void OnResultExecuted(ResultExecutedContext context)
        {
    
        }
    
        /// <summary>
        /// 
        /// </summary>
        /// <param name="context">The result executing context.</param>
        public void OnResultExecuting(ResultExecutingContext context)
        {
            if (context.ModelState.IsValid)
                return;
    
            var modelStateDictionary = context.ModelState;
            var problemDetails = new ProblemDetails
            {
                Title = InvalidParameters,
                Status = Status400BadRequest
            };
    
            //When model-binding fails because input is an invalid JSON
            if (modelStateDictionary.Any(pair => pair.Key == DollarSign || String.IsNullOrEmpty(pair.Key)))
            {
                problemDetails.Detail = RequestFailedModelBinding;
                context.Result = GetBadRequestObjectResult(problemDetails);
                return;
            }
    
            //When a specific property-binding fails
            var keyValuePair = modelStateDictionary.FirstOrDefault(pair => pair.Key.Contains("$."));
            if (keyValuePair.Key is not null)
            {
                var propertyName = keyValuePair.Key.Split(Dot)[1];
                problemDetails.Detail =
                    String.IsNullOrEmpty(propertyName) ? RequestFailedModelBinding : String.Format(RequestPropertyFailedModelBinding, propertyName);
                context.Result = GetBadRequestObjectResult(problemDetails);
                return;
            }
    
            //When one of the input parameters failed model-validation
            var invalidParams = new List<InvalidParam>(modelStateDictionary.Count);
            foreach (var keyModelStatePair in modelStateDictionary)
            {
                var key = keyModelStatePair.Key;
                var modelErrors = keyModelStatePair.Value.Errors;
                if (modelErrors is not null && modelErrors.Count > 0)
                {
                    IEnumerable<InvalidParam> invalidParam;
                    if (modelErrors.Count == 1)
                    {
                        invalidParam = modelErrors.Select(error => new InvalidParam(keyModelStatePair.Key, new[] { error.ErrorMessage }));
                    }
                    else
                    {
                        var errorMessages = new String[modelErrors.Count];
                        for (var i = 0; i < modelErrors.Count; i++)
                        {
                            errorMessages[i] = modelErrors[i].ErrorMessage;
                        }
    
                        invalidParam = modelErrors.Select(error => new InvalidParam(keyModelStatePair.Key, errorMessages));
                    }
    
                    invalidParams.AddRange(invalidParam);
                }
            }
    
            problemDetails.Detail = RequestParametersDidNotValidate;
            problemDetails.Extensions[nameof(invalidParams)] = invalidParams;
            context.Result = GetBadRequestObjectResult(problemDetails);
        }
    
        /// <summary>
        /// Creates <see cref="BadRequestObjectResult"/> instance.
        /// The content-type is set to: 'application/problem+json'
        /// </summary>
        /// <param name="problemDetails">The problem details instance.</param>
        /// <returns>The bad request object result instance.</returns>
        private static BadRequestObjectResult GetBadRequestObjectResult(ProblemDetails problemDetails)
        {
            var result = new BadRequestObjectResult(problemDetails);
            result.ContentTypes.Clear();
            result.ContentTypes.Add(MediaTypeApplicationProblemJson);
            return result;
        }
    }
    
    推荐文章