如何在ASP.NET Core中将JSON请求正文验证为有效JSON

时间:2019-03-14 02:52:50

标签: c# asp.net-core asp.net-core-2.1 model-validation

在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"
}

在控制器中,我可以看到第二个问题是model的值和model是否有效。

问题是如何甚至在模型绑定之前就将请求正文验证为有效JSON?

1 个答案:

答案 0 :(得分:1)

根据Timothy Shields's answer,如果我们复制了属性键,那么很难说这将是无效的json。

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

12.0.1起,Newtonsoft.Json具有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装饰动作方法:

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

演示:

enter image description here