在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?
答案 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
}
}
演示: