首页 > 解决方案 > JObject.ToObject 与模型状态错误

问题描述

在一个 ASP.NET 核心项目中,我已经从使用切换ResourceModel到使用JObject作为我的[FromBody]参数。然后我传递JObject.ToObject<ResourceModel>()到一个服务中,但想为类似的功能维护它JObject本身。ContainsKey

如果JObject.ToObject<T>成功,我可以使用TryValidateModel(),如果失败,则BadRequest(ModelState)足够简单地返回。但是,我遇到的问题是何时JObject.ToObject<T>引发异常 - 我不确定如何在ModelState.

例子:

public class Person {
    public string FirstName {get;set;}
    public string LastName {get;set;}
    public uint Age {get;set;}
}
public class CommonParameters {
    [FromQuery] public string? Fields {get;set;}
    public string UserName {get;set;}
    public IPAddress? RequestorIp {get;set;}
    public JObject? JsonBody {get;set;}
}
public class PersonController : ControllerBase {
    private readonly PersonService _personService;
    // this is how I had been doing it
    [HttpPatch("obsolete/{id}")] // this method doesn't actually exist, but is shown for the example
    public async Task<IActionResult> UpdatePerson([FromRoute] int id, [FromBody] Person parameters, [FromHeader] CommonParameters commonParameters) {
         SetCommonParameters(commonParameters, parameters);
         // if parameters.Age in the json body was a string, BadRequest is returned before this method even starts
         _personService.Set(parameters);
         await _personService.SaveChangesAsync();
         return NoContent();
    }
    // this is how I'm trying to do it now, so that I only update Age if JObject.ContainsKey("Age")
    // instead of parameters.Age != default, etc.
    [HttpPatch("{id}")]
    public async Task<IActionResult> UpdatePerson([FromRoute] int Id, [FromBody] JObject parameters, [FromHeader] CommonParameters commonParameters) {
         SetCommonParameters(commonParameters, parameters);
         // if parameters.Age is a string in this version, a JsonReaderException 
         // or JsonSerializationException is thrown when I call JObject.ToObject<>()
         // and I have to validate the model separately after that
         // what this means is, a BadRequest with the ModelState errors is not
         // returned if there's an issue with the json.
         var model = parameters.ToObject<Person>();
         if (!TryValidateModel(model)) return BadRequest(ModelState);
         _personService.Set(person);
         await _personService.SaveChangesAsync();
         return NoContent();
    }
    private void SetCommonParameters(CommonParameters commonParameters, JObject? jsonBody = null) {
        commonParameters.JsonBody = jsonBody;
        commonParameters.UserName = User.Identity?.Name;
        commonParameters.RequestorIp = Request.HttpContext.Connection.RemoteIpAddress;
    }
}

编辑:正如 Guru Stron 所问的,这是一个无效模型/json 对象的示例。

模型:

public class ContactInfo {
  // PhoneNumber being a struct representing an 11-digit phone number
  // PhoneType being an enum of Cell, Work, Home, etc.
  public Dictionary<PhoneType, PhoneNumber[]> PhoneNumbers {get;set;} = new();
  public string? Apartment {get;set;}
  public int AddressNumber {get;set;}
  public string StreetName {get;set;}
  public string City {get;set;}
  // State being an enum of the 50 United States of America
  public State State {get;set;}
}

无效的 json

{
  "phoneNumbers": {
    "work": [ "1-111-111-1111" ],
    "home": [ "2-222-222-2222" ],
    "cell": [ "1-800-CALL-NOW" ]
  },
  "apartment": null,
  "addressNumber": "3a",
  "streetName": "Imagination St.",
  "city": "Atlantis",
  "state": "Atlantic"
}

在这种情况下,将上述 json for 传递ContactInfo给请求参数的控制器方法ContactInfo将返回一个错误请求,指示状态无效、地址号码无效电话号码无效 - 无需专门对此进行测试,例如以下。

{
  "errors": {
    "phoneNumbers": [
      "Could not parse phone number \"1-800-CALL-NOW\". The value must be an 11-digit numeric value."
    ],
    "addressNumber": [
      "The value could not be parsed as int."
    ],
    "state": [
      "\"Atlantic\" is an invalid value for State. The following values are valid: ..."
    ]
  }
}

相反,如果控制器要求,JObject然后我调用JObject.ToObject并捕获异常,我将得到其中一个错误 - 例如Could not parse phone number "1-800-CALL-NOW". The value must be an 11-digit numeric value.其他错误将被忽略,直到该错误被修复并且用户再次尝试。

标签: c#jsonasp.net-corejson.netbad-request

解决方案


我终于弄明白了……我需要做的就是提供一个 JsonSerializerJObject.ToObject<>并包含一个错误处理程序,将错误标记为已处理。例如:

var serializer = new JsonSerializer();
serializer.Error += (sender, args) =>
{
    if (args.ErrorContext.Error is JsonException)
    {
        ModelState.AddModelError(args.ErrorContext.Path, args.ErrorContext.Error.Message);
        args.ErrorContext.Handled = true;
    }
};

var value = parameters.ToObject<Person>(serializer);
if (!TryValidateModel(value)) return ValidationProblem(ModelState);

我的理解是默认Microsoft.AspNetCore.Mvc.Formatters.NewtonsoftJsonInputFormater执行类似的错误处理;我仍然不确定如何直接使用该处理程序,但我在这里所拥有的正在满足我的需要。


推荐阅读