According to Timothy Shields's answer, it's hard to say that would be an invalid json if we have duplicated property keys.
And when using ASP.NET Core 2.1
, it won't throw at all.
As of 12.0.1
, the Newtonsoft.Json has a DuplicatePropertyNameHandling settings. It will throw if we set DuplicatePropertyNameHandling.Error
and pass a duplicated property. So the easiest way I can come up is to create a custom model binder. We could deserialize the JSON and change the ModelState if it throws .
Fristly, install the latest Newtonsoft.Json
:
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
</ItemGroup>
and then register a JsonLoadSettings
option as a singleton service for later reuse :
services.AddSingleton<JsonLoadSettings>(sp =>{
return new JsonLoadSettings {
DuplicatePropertyNameHandling = DuplicatePropertyNameHandling.Error,
};
});
Now we can create a custom model binder to deal with duplicated properties :
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;
}
}
To enable read the Request.Body
multiple times, we could also create a dummy Filter
:
public class EnableRewindResourceFilterAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.HttpContext.Request.EnableRewind();
}
public void OnResourceExecuted(ResourceExecutedContext context) { }
}
Lastly, decorate the action method with [ModelBinder(typeof(XJsonModelBinder))]
and EnableRewindResourceFilter
:
[HttpPost]
[EnableRewindResourceFilter]
public JsonResult GetAnswer([ModelBinder(typeof(XJsonModelBinder))]SampleModel question)
{
if(ModelState.IsValid){
return Json(question.Answer);
}
else{
// ... deal with invalid state
}
}
Demo :
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…