我有以下模型:
public class Resource
{
[DataMember(IsRequired = true)]
[Required]
public bool IsPublic { get; set; }
[DataMember(IsRequired = true)]
[Required]
public ResourceKey ResourceKey { get; set; }
}
public class ResourceKey
{
[StringLength(50, MinimumLength = 1)]
[Required]
public string SystemId { get; set; }
[StringLength(50, MinimumLength = 1)]
[Required]
public string SystemDataIdType { get; set; }
[StringLength(50, MinimumLength = 1)]
[Required]
public string SystemEntityType { get; set; }
[StringLength(50, MinimumLength = 1)]
[Required]
public string SystemDataId { get; set; }
}
我有以下动作方法签名:
public HttpResponseMessage PostResource(Resource resource)
我在正文中使用JSON发送以下请求(属性"IsPublic"的故意无效值(:
Request Method:POST
Host: localhost:63307
Connection: keep-alive
Content-Length: 477
User-Agent: Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.97 Safari/537.22
Origin: chrome-extension://hgmloofddffdnphfgcellkdfbfbjeloo
Content-Type: application/json
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
{
"IsPublic": invalidvalue,
"ResourceKey":{
"SystemId": "asdf",
"SystemDataIdType": "int",
"SystemDataId": "Lorem ipsum",
"SystemEntityType":"EntityType"
},
}
这是无效的JSON-通过JSONLint运行它,它会告诉您:
第2行分析错误:
{"IsPublic":无效值,
…………..^应为"STRING"、"NUMBER"、"NULL"、"TRUE"、"FALSE"、"{"、"[">
ModelState.IsValid属性为"true"-为什么
此外,格式化程序似乎放弃了反序列化,而不是抛出验证错误,只是将"resource"参数作为null传递给操作方法!
请注意,如果我为其他属性输入无效值,也会发生这种情况,例如替换:
"SystemId": notAnObjectOrLiteralOrArray
然而,如果我发送下面的JSON,其中包含";SystemId";属性:
{
"IsPublic": true,
ResourceKey:{
"SystemId": undefined,
"SystemDataIdType": "int",
"SystemDataId": "Lorem ipsum",
"SystemEntityType":"EntityType"
},
}
然后我得到以下合理的异常抛出:
Exception Type: Newtonsoft.Json.JsonReaderException
Message: "Error reading string. Unexpected token: Undefined. Path 'ResourceKey.SystemId', line 4, position 24."
Stack Trace: " at Newtonsoft.Json.JsonReader.ReadAsStringInternal()
at Newtonsoft.Json.JsonTextReader.ReadAsString()
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ReadForType(JsonReader reader, JsonContract contract, Boolean hasConverter)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)"
问:Newtonsoft.Json库中发生了什么,导致了部分Json验证???
PS:可以将JSON名称/值对发布到Web API,而无需将名称括在引号中。。。
{
IsPublic: true,
ResourceKey:{
SystemId: "123",
SystemDataIdType: "int",
SystemDataId: "Lorem ipsum",
SystemEntityType:"EntityType"
},
}
这也是无效的JSON!
OK-所以问题的一部分似乎是我自己造成的。
我在控制器上有两个过滤器:
- 检查是否有任何null操作参数传递给操作方法,如果有,则返回"400错误请求"响应,规定参数不能为null
- ModelState检查过滤器,检查ModelState的错误,如果发现任何错误,则在"400错误请求"响应中返回
我犯的错误是把null参数过滤器放在模型状态检查过滤器之前。
在模型绑定之后,第一个JSON示例的序列化将正确失败,并将相关的序列化异常放在ModelState中,操作参数将保持为null,这是理所当然的
然而,由于第一个过滤器检查空参数,然后返回"404错误请求"响应,因此ModelState过滤器从未启动…
因此,似乎没有进行验证,而事实上是这样,但结果却被忽视了!
重要信息:在模型绑定期间发生的序列化异常被放置在ModelState KeyValue对Value的"Exception"属性中。。。不在ErrorMessage属性中!
为了帮助其他人做到这一点,这里是我的ModelValidationFilterAttribute:
public class ModelValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid) return;
// Return the validation errors in the response body.
var errors = new Dictionary<string, IEnumerable<string>>();
foreach (KeyValuePair<string, ModelState> keyValue in actionContext.ModelState)
{
var modelErrors = keyValue.Value.Errors.Where(e => e.ErrorMessage != string.Empty).Select(e => e.ErrorMessage).ToList();
if (modelErrors.Count > 0)
errors[keyValue.Key] = modelErrors;
// Add details of any Serialization exceptions as well
var modelExceptions = keyValue.Value.Errors.Where(e => e.Exception != null).Select(e => e.Exception.Message).ToList();
if (modelExceptions.Count > 0)
errors[keyValue.Key + "_exception"] = modelExceptions;
}
actionContext.Response =
actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, errors);
}
}
下面是操作方法,过滤器按正确顺序排列:
[ModelValidationFilter]
[ActionArgNotNullFilter]
public HttpResponseMessage PostResource(Resource resource)
现在,以下JSON结果如下:
{
"IsPublic": invalidvalue,
"ResourceKey":{
"SystemId": "asdf",
"SystemDataIdType": "int",
"SystemDataId": "Lorem ipsum",
"SystemEntityType":"EntityType"
},
}
{
"resource.IsPublic_exception": [(2)
"Unexpected character encountered while parsing value: i. Path 'IsPublic', line 2, position 21.",
"Unexpected character encountered while parsing value: i. Path 'IsPublic', line 2, position 21."
]-
}
然而,所有这些并不能解释为什么JsonMediaTypeFormatter仍然解析无效的JSON,例如,它不要求名称是字符串。
与其说是一个答案,不如说是一种变通方法,但我能够使用发布在http://aspnetwebstack.codeplex.com/workitem/609.基本上,与其让Post方法的签名采用Resource实例,不如让它不采用参数,然后使用JSon.Net(或JsonMediaTypeFormatter的新实例(进行反序列化。
public void Post()
{
var json = Request.Content.ReadAsStringAsync().Result;
var resource = Newtonsoft.Json.JsonConvert.DeserializeObject<Resource>(json);
//Important world saving work going on here
}