如何在 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
}
}
演示:
在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
}
}
演示: