如何在 asp.net 核心中将 json 请求主体验证为有效 json

How to validate json request body as valid json in asp.net core

在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?

根据 Timothy Shields's answer,如果我们有重复的 属性 键,很难说 json 是无效的。

而使用ASP.NET Core 2.1时,完全不会抛出。

12.0.1 起,Newtonsoft.Json has a DuplicatePropertyNameHandling settings。如果我们设置 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修饰action方法:

    [HttpPost]
    [EnableRewindResourceFilter]
    public JsonResult GetAnswer([ModelBinder(typeof(XJsonModelBinder))]SampleModel question)
    {               
        if(ModelState.IsValid){
            return Json(question.Answer);
        }
        else{
            // ... deal with invalid state
        }
    }

演示: