2013-02-26 28 views
3

我有以下型號:的Web API/JsonMediaTypeFormatter接受無效JSON和傳遞空參數動作

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運行它,它會告訴你:

{ 「IsPublic」:

第2行解析錯誤一個InvalidValue,

........ ......... ^期待 'STRING', 'NUMBER', 'NULL', 'TRUE', '假', '{', '['

的ModelState.IsValid屬性是'真' - 爲什麼?

此外,不是拋出驗證錯誤,格式化程序似乎放棄反序列化,並簡單地將'resource'參數傳遞給action方法爲null!

請注意,如果我爲其他屬性輸入無效值,也會發生這種情況。代:

"SystemId": notAnObjectOrLiteralOrArray 

但是,如果我用了 「SYSTEMID」 屬性特殊未定義值發送以下JSON:

{ 
    "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)" 

SO:Newtonsoft.Json庫中發生了什麼,它導致了什麼看起來像部分JSON驗證?

PS:這是可以張貼JSON名稱/值對到Web API,而不放入引號內的名字......

{ 
    IsPublic: true, 
    ResourceKey:{  
     SystemId: "123", 
     SystemDataIdType: "int", 
     SystemDataId: "Lorem ipsum", 
     SystemEntityType:"EntityType" 
    },  
} 

這也是無效的JSON!

+0

我能夠用一個大大簡化的例子複製這個問題 - http://pastebin.com/ehDgWQBu - 從這個輸出是:200 - 好 - 400 - BadRequest - {「Message」:「模型數據是空,但它並沒有驗證失敗!「} 我在另一個項目最近遇到這個問題我自己,並駁回其作爲一個自定義的媒體類型格式我與當時工作的古怪,但這個例子中只使用了標準格式化和展示這個問題,所以我很好奇這是什麼意思... – Snixtor 2013-02-27 03:15:55

+0

我想這個謎語的答案在默認模型綁定或參數綁定配置的某處,甚至可能在媒體類型格式化程序('JsonMediaTypeFormatter') 。 – Snixtor 2013-02-27 03:49:08

回答

3

好的 - 所以看起來問題的一部分是由我自己做的。

我有兩個過濾器的控制器上:

  1. 檢查是否有被傳遞到操作方法,如果是任何空操作參數,返回「400錯誤的請求」的響應,規定的參數不能空值。該檢查ModelState中的錯誤,如果任何被發現,在「400錯誤的請求」響應返回他們
  2. 一個的ModelState檢查過濾器。

我犯的錯誤是將null參數過濾器放在模型狀態檢查過濾器之前。

模型綁定後,系列化將正確失敗的第一個JSON例子,會把在ModelState中的相關序列的異常和操作參數將保持爲空,這是理所當然的。

然而,由於第1過濾器檢查空參數,然後返回「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." 
    ]- 
} 

但是,所有這些都不能解釋w hy無效的JSON仍然由JsonMediaTypeFormatter分析,例如它不要求名稱是字符串。

+1

作爲參考,404是「未找到」。您應該使用400「錯誤的請求」 - http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_Error – Snixtor 2013-02-27 21:37:44

+0

@Snixtor:正確。對不起'404'是一個錯字。我確實返回400. – JTech 2013-02-28 10:18:45

+0

同樣的例外被添加出現兩次作爲迴應。 – 2016-09-30 14:30:17

1

更多的解決方法不是答案,但我能夠使用發佈在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 
}