想知道是否可以将
Model.IsValid
与 ASP.NET Core Web API 一起使用,该 API 从控制器参数中接收 JSON 正文作为 [FromBody]
对象。
或者我应该编写自己的验证方法?往什么方向走?我如何处理这种风格的 Web API 的验证,实际上不使用 MVC 或 Blazor;它只接受来自客户端 UI Web 模块的
GET
和 POST
请求(很有可能)。
当然,这个 API 的创建是为了与框架无关,从某种意义上说,它不期望模型来自视图......而且我没有提出这个设计,所以不幸的是我坚持了下来。我们基本上采用原始 JSON 并使用 Newtonsoft 方法将请求正文解析为 C# 类(DTO、COJO 或任何您想调用的名称)。
这是我正在处理的项目中控制器方法的基本示例:
[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 很陌生……我可能使用了一些错误的术语。
我建议采用更通用的方法,例如使用基本控制器来使所有错误响应相同:
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."
]
}