如何在 asp.net core 中验证 JSON 请求体为有效的 JSON?

3
在asp.net core 2.1中,当控制器动作设置为:
    [HttpPost]
    public JsonResult GetAnswer(SampleModel question)
    {               
        return Json(question.Answer);
    }

其中SampleModel被定义为:

public class SampleModel
{
    [Required]
    public string Question { get; set; }

    public string Answer { get; set; }
}

这仍被视为有效请求:
{
  "question": "some question",
  "question": "some question 2",
  "answer": "some answer"
}

在控制器中,我可以看到第二个问题是模型的值,而且模型是有效的。
问题是如何在模型绑定之前验证请求体仅为有效的JSON?

尝试捕获异常,并测试您期望的对象,如果不是,则不是有效的模型。 - JohnnBlade
如果您想获取第一个数据,则可以使用Request["SampleModel.answer"]或者您称之为的方式,但是您也可以为此创建一个特殊的处理程序,但是对于测试,您可以使用Request["nameofyrinput"]。 - JohnnBlade
@JohnnBlade 如果反序列化成功,我该如何测试对象是否与我的预期不同? - Zaak
1个回答

4

根据Timothy Shields的回答,如果我们有重复的属性键,很难说这将是一个无效的JSON。

当使用ASP.NET Core 2.1时,它根本不会抛出异常。

截至版本12.0.1Newtonsoft.Json具有DuplicatePropertyNameHandling设置。如果我们设置DuplicatePropertyNameHandling.Error并传递了重复的属性,则它将抛出异常。因此,我能想到的最简单的方法是创建自定义模型绑定器。我们可以反序列化JSON并更改ModelState(如果它抛出异常)。

首先,安装最新的Newtonsoft.Json

  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
  </ItemGroup>

然后注册一个JsonLoadSettings选项作为单例服务,以便稍后重复使用:

services.AddSingleton<JsonLoadSettings>(sp =>{
    return new JsonLoadSettings { 
        DuplicatePropertyNameHandling =  DuplicatePropertyNameHandling.Error,
    };
});

现在我们可以创建一个自定义模型绑定器来处理重复的属性:
public class XJsonModelBinder: IModelBinder
{
    private JsonLoadSettings _loadSettings;
    public XJsonModelBinder(JsonLoadSettings loadSettings)
    {
        this._loadSettings = loadSettings;
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); }
        var modelName = bindingContext.BinderModelName?? "XJson";
        var modelType = bindingContext.ModelType;

        // create a JsonTextReader
        var req = bindingContext.HttpContext.Request;
        var raw= req.Body;
        if(raw == null){ 
            bindingContext.ModelState.AddModelError(modelName,"invalid request body stream");
            return Task.CompletedTask;
        }
        JsonTextReader reader = new JsonTextReader(new StreamReader(raw));

        // binding 
        try{
            var json= (JObject) JToken.Load(reader,this._loadSettings);
            var o  = json.ToObject(modelType);
            bindingContext.Result = ModelBindingResult.Success(o);
        }catch(Exception e){
            bindingContext.ModelState.AddModelError(modelName,e.ToString()); // you might want to custom the error info
            bindingContext.Result = ModelBindingResult.Failed();
        }
        return Task.CompletedTask;
    }
}

为了实现多次读取 Request.Body,我们还可以创建一个虚拟的 Filter
public class EnableRewindResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.HttpContext.Request.EnableRewind();
    }
    public void OnResourceExecuted(ResourceExecutedContext context) { }
}

最后,使用[ModelBinder(typeof(XJsonModelBinder))]EnableRewindResourceFilter来装饰操作方法:
    [HttpPost]
    [EnableRewindResourceFilter]
    public JsonResult GetAnswer([ModelBinder(typeof(XJsonModelBinder))]SampleModel question)
    {               
        if(ModelState.IsValid){
            return Json(question.Answer);
        }
        else{
            // ... deal with invalid state
        }
    }

演示:

在此输入图片描述

(注:以上内容涉及IT技术)

谢谢你的回答,不过让我有点惊讶的是,在同一级别上有重复键并不在技术上构成无效的 JSON,但再想想我的背景大多是强类型语言,所以这可能是我的思维方式的一部分。 - Zaak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接