ASP.NET Core Web API:我可以使用 [FromBody] 对象使用 ModelState.IsValid 进行验证吗

问题描述 投票:0回答:1

想知道是否可以将

Model.IsValid
与 ASP.NET Core Web API 一起使用,该 API 从控制器参数中接收 JSON 正文作为
[FromBody]
对象。

或者我应该编写自己的验证方法?往什么方向走?我如何处理这种风格的 Web API 的验证,实际上不使用 MVC 或 Blazor;它只接受来自客户端 UI Web 模块的

GET
POST
请求(很有可能)。

当然,这个 API 的创建是为了与框架无关,从某种意义上说,它不期望模型来自视图......而且我没有提出这个设计,所以不幸的是我坚持了下来。我们基本上采用原始 JSON 并使用 Newtonsoft 方法将请求正文解析为 C# 类(DTO、COJO 或任何您想调用的名称)。

  • 我应该在项目中的某个地方编写自己的验证方法吗?
  • 是否有第三方库允许我在类中注释参数...类似于模型验证概念/机制。
  • 或者是否有处理这种类型的 ASP.NET Core Web API 的模式或最佳实践? 我一直被教导应该在 UI 以及中间件/API/Web 服务级别中进行参数/字段验证。

这是我正在处理的项目中控制器方法的基本示例:

[HttpPost]
[Route("SubmitNewRecord")]
public ActionResult SubmitNewRecord([FromBody] object PostValues)
{
    ExampleAPI.Model.MyInfo submitNewRecordDataRequest = null;

    if (PostValues != null)
    {
        // Newtonsoft.Json.Linq;
        JObject PostObject = (JObject)PostValues;

        // Converting the raw incoming object PostValues to MyInfo class object:
        submitNewRecordDataRequest = JsonConvert.DeserializeObject<ExampleAPI.Model.MyInfo>(PostObject.ToString());
    }
    else
    {
        response.ResultCode = "-12";
        response.ResultMessage = "Incoming message was empty (null) - " + DateTime.Now;

        _logger.LogCritical("Incoming message was empty (null)- " + DateTime.Now);
        return new JsonResult(response);
    }

    // HERE I would like to use Model.IsValid...
    // just putting a Model.IsValid check here is not working obviously.
    if (ModelState.IsValid)  
    {  
        // Do stuff with the new MyInfo DTO (submitNewRecordDataRequest)
    } 
    else 
    {
        // throw error and send special response message.
    }        
}

这是一个示例数据传输对象(DTO),我之所以这样称呼它是因为我们没有使用实体框架或任何其他 ORM 框架;只是从数据库中获取存储过程。

using System.ComponentModel.DataAnnotations;

namespace MyApi.DTOs
{
    public class MyInfo 
    {
        public int Id { get; set; }

        [Required]
        public string Name { get; set; }

        public decimal Price { get; set; }

        [Range(0, 999)]
        public double Weight { get; set; }

        [Required]
        [StringLength(1000)]
        public string Annotation{ get; set; }
    }
}

无论如何......我不知道最初的设计者在想什么,他也没有记录在API中进行对象验证的具体方法。像这样使用 Newtonsoft 的 API 的最佳实践是什么?抱歉,我对这种风格的 API 很陌生……我可能使用了一些错误的术语。

c# .net-core json.net asp.net-core-webapi
1个回答
0
投票

我建议采用更通用的方法,例如使用基本控制器来使所有错误响应相同:

namespace App.Controllers
  public abstract class BaseController : Controller
    {
        protected HttpContext CurrentHttpContext;
        protected ILogger Logger;
        protected ILoggerFactory LoggerFactory;

        public BaseController(
                                IHttpContextAccessor httpContextAccessor, 
                                ILoggerFactory loggerFactory, 
            )
        {
            CurrentHttpContext = httpContextAccessor.HttpContext;
            LoggerFactory = loggerFactory;
            _cacheOperation = cacheOperation;
            _customErrors = customErrors;
        }

         /// <summary>
        /// Generate response with list of custom errors
        /// </summary>
        /// <param name="CustomErrors"></param>
        /// <param name="method"></param>
        /// <param name="route"></param>
        /// <param name="errorCode"></param>
        /// <returns></returns>
        protected ObjectResult ReturnApiCustomError(List<string> CustomErrors, string method = "", string route = "", int errorCode = 2000) =>
            StatusCode(500, new Model.ApiError
            {
                ErrorCode = errorCode,
                ErrorMessages = CustomErrors.ToArray(),
                Method = method,
                Route = route,
                ErrorCount = CustomErrors.Count,
            });

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            if (!ModelState.IsValid)
            {
                var modelerrors = new List<string>();
                ModelState
                   .ToList()
                   .ForEach(m =>
                   {
                       List<string> errors = ModelState
                                                   .Keys.Where(k => k == m.Key)
                                                   .Select(k => ModelState[k].Errors)
                                                   .First().Select(e => e.ErrorMessage).ToList();
                       errors.ForEach(er => modelerrors.Add($"{m.Key} : {er}"));
                   });
                context.Result = ReturnApiCustomError(modelerrors, $"{this.GetType().Name}.{this.ControllerContext.RouteData.Values["action"].ToString()}", context.HttpContext.Request.Path.Value);
            }
            base.OnActionExecuting(context);
        }

    }

然后在你的控制器中

[Route("api/[controller]")]
    [ApiController]
    [AllowAnonymous]
    public class StackOverflowYourController : BaseController
{

}

   [HttpPost]
        public async Task<IActionResult> SubmitNewRecord(MyInfo request)
        {
          if (ModelState.IsValid)
            {
                return await Task.Run(()=> Ok("Validation pass"));
            }
            else
            {
                return BadRequest();
            }
        }

现在。有了这个要求:

{
  "Id": 0,
  "Name": null,
  "Price": 0,
  "Weight": 999,
  "Annotation": "string"
}

响应将是

{
  "ErrorCode": 2000,
  "Route": "/api/StackOverflow",
  "Method": "StackOverflow.SubmitNewRecord",
  "ExceptionMessage": null,
  "ErrorCount": 1,
  "ErrorMessages": [
    "Name : The Name field is required."
  ]
}
© www.soinside.com 2019 - 2024. All rights reserved.