原文: JSON Patch With ASP.NET Core
作者:.NET Core Tutorials
譯文:如何在ASP.NET Core中使用JSON Patch
地址:https://www.cnblogs.com/lwqlun/p/10433615.html
譯者:Lamond Lu
JSON Patch是一種使用API顯式更新檔案的方法。它本身是一種契約,用於描述如何修改檔案(例如:將欄位的值替換成另外一個值),而不必同時傳送其他未更改的屬性值。
一個JSON Patch請求是什麼樣的?
你可以在以下連結(http://jsonpatch.com/)中找到JSON Patch的官方檔案,但是這裡我們將進一步研究一下如何在ASP.NET Core中實現JSON Patch。
為了演示JSON Patch, 我建立了以下C#物件類, 後續我將使用JSON Patch請求來操作這個物件類的實體。
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public List<string> Friends { get; set; }
}
每個JSON Patch請求都是遵循一個相似的結構。它有一個固定的“操作”串列。每個操作本身擁有3個屬性:
-
“op” – 定義了你要執行何種操作,例如add, replace, test等。
-
“path” – 定義了你要操作物件屬性路徑。用前面的
Person
類為例,如果你希望修改FirstName
屬性,那麼你使用的操作路徑應該是”/FirstName”。 -
“value” – 在大部分情況下,這個屬性表示你希望在操作中使用的值。
現在讓我們來看一下每一個的操作如何使用。
Add
Add操作通常意味著你要向物件中新增屬性,或者向陣列中新增專案。對於前者,在C#中是沒有用的,因為C#是強型別語言,所以不能將屬性新增到編譯時尚未定義的物件上。
所以這裡如果想往陣列中新增專案,PATCH請求的內容應該如下所示。
{ "op": "add", "path": "/Friends/1", "value": "Mike" }
這將在Friends陣列的索引1處插入一個”Mike”值。
或者你還可以使用”-“在陣列尾部插入記錄。
{ "op": "add", "path": "/Friends/-", "value": "Mike" }
Remove
與Add操作類似,刪除操作意味著你希望刪除物件中屬性,或者從資料中刪除某一項。但是因為在C#中你無法移除屬性,實際操作時,它會將屬性的值變更為default(T)。在某些情況下,如果屬性是可空的,則會設定屬性值為NULL。但是需要小心,因為當在值型別上使用時,例如int, 則該值實際上會重置為”0″。
如果要在物件上刪除某一屬性以達到重置的效果,你可以使用一下命令。
{ "op": "remove", "path": "/FirstName"}
當然你也可以使用刪除命令刪除陣列中的某一項。
{ "op": "remove", "path": "/Friends/1" }
這將刪除陣列索引為1的專案。但是有時候使用索引從陣列中刪除資料是非常危險的,因為這裡沒有一個”where”條件來控制刪除, 有可能在刪除的時候,資料庫中對應陣列已經發生變化了。實際上有一個JSON Patch操作可以幫助解決這個問題,後面我會描述它。
Replace
Replace操作和它的字面意思完全一樣,可以使用它來替換已有值。針對簡單屬性,你可以使用如下的命令。
{ "op": "replace", "path": "/FirstName", "value": "Jim" }
你同樣可以使用它來替換陣列中的物件。
{ "op": "replace", "path": "/Friends/1", "value": "Bob" }
你甚至可以用它來替換整個陣列。
{ "op": "replace", "path": "/Friends", "value": ["Bob", "Bill"] }
Copy
Copy操作可以將值從一個路徑複製到另一個路徑。這個值可以是屬性,物件,或者資料。在下麵的例子中,我們將FirstName屬性的值複製到了LastName屬性上。這個命令的使用場景不是很多。
{ "op": "copy", "from": "/FirstName", "path" : "/LastName" }
Move
Move操作非常類似於Copy操作,但是正如它的字面意思,”from”欄位的值將被移除。如果你看一下ASP.NET Core的JSON Patch的底層程式碼,你會發現,它實際上它會在”from”路徑上執行Remove操作,在”path”路徑上執行Add操作。
{ "op": "move", "from": "/FirstName", "path" : "/LastName" }
Test
在當前的ASP.NET Core公開發行版中沒有Test操作,但是如果你在Github上檢視原始碼,你會發現微軟已經處理了Test操作。Test操作是一種樂觀鎖定的方法,或者更簡單的說,它會檢測資料物件從伺服器讀取之後,是否發生了更改。
我們以如下操作為例。
[
{ "op": "test", "path": "/FirstName", "value": "Bob" }
{ "op": "replace", "path": "/FirstName", "value": "Jim" }
]
這個操作首先會檢查”/FirstName”路徑的值是否”Bob”, 如果是,就將它改為”Jim”。 如果不是,則什麼事情都不會發生。這裡你需要註意,在一個Test操作的請求體內可以包含多個Test操作,但是如果其中任何一個Test操作驗證失敗,所以的變更操作都不會被執行。
為什麼要使用JSON PATCH
JSON Patch的一大優勢在於它的請求操作體很小,只傳送物件的更改內容。 但是在ASP.NET Core中使用JSON Patch還有另一個很大的好處,就是C#是一種強型別語言,無法區分是要將模型的值設定為NULL,還是忽略該屬性, 而使用JSON Patch可以解決這個問題。
這裡如果沒有好的例子,很難解釋。 所以想象一下我從API請求一個“Person”物件。 在C#中,模型可能如下所示:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
當從API傳回Json物件時,它看起來可能像這樣。
{
"firstName" : "James",
"lastName" : "Smith"
}
現在在前端,如果不使用JSON Patch, 如果我只想更新FirstName, 我可能在請求中附帶一下請求體。
{
"firstName" : "Jim"
}
現在當我在C#中反序列化這個模型時,問題就出現了。不要看下麵的程式碼,想一下此時我們的模型中的屬性值是什麼?
public class Person
{
public string FirstName { get; set; } //Jim
public string LastName { get; set; } //
}
因為我們傳送LastName屬性的值,所以它被反序列化為Null。 但這很簡單,我們可以忽略NULL的值,只更新我們實際傳遞的欄位。 但這不一定是正確的,如果該欄位實際上可以為空呢? 如果我們發送了以下請求體怎麼辦?
{
"firstName" : "Jim",
"lastName" : null
}
所以現在我們實際上已經指定我們想要取消該欄位。但是因為C#是強型別的,所以我們無法在伺服器端進行模型系結的時候,我們無法確定它是否要將該欄位的值設定為NULL。
這似乎是一個奇怪的場景,因為前端可以始終傳送完整的資料模型,永遠不會省略欄位。並且在大多數情況下,前端Web庫的模型將始終與API的模型匹配。但有一種情況並非如此,那就是移動應用程式。通常向蘋果應用商店提交手機應用,可能需要數周時間才能獲得批准。在這個時候,你可能還需要在Web或Android應用程式中使用新模型。在不同平臺之間實現同步非常困難,而且通常是不可能。雖然API版本確實對解決這個問題有很長的路要走,但我仍然認為JSON Patch在解決這個問題方面具有很大的實用性。
最後,讓我們使用JSON Patch!我們可以使用以下JSON Patch請求更新Person物件
[
{
"op": "replace",
"path": "/FirstName",
"value": "Jim"
}
]
這明確表示我們想要更改名字而不是其他內容。 它準確的告訴我們到底將要發生什麼。
在ASP.NET Core專案中啟用JSON Patch
在Visual Studio中,我們可以在Package Manage Console中安裝官方的Json Patch庫(預設建立的ASP.NET Core專案中沒有該庫)。
Install-Package Microsoft.AspNetCore.JsonPatch
為了演示,我將新增如下的一個控制器類。這裡需要註意我們使用的HTTP verb是HttpPatch, 請求引數的型別是JsonPatchDocument
。 為了更新物件,我們只需要簡單呼叫ApplyTo
方法,並傳入了需要更新的物件。
在以上示例中,我們只是使用了存放在控制器中的簡單物件並對其進行了更新,但是在正式的API中,我們需要從資料庫中拉取資料物件,更新物件,並重新儲存到資料庫。
當我們使用如下請求體傳送JSON Patch請求時:
[
{"op" : "replace", "path" : "/FirstName", "value" : "Bob"}
]
我們可以得到如下響應內容:
{
"firstName": "Bob",
"lastName": "Smith"
}
真棒! 我們的名字改為Bob! 使用JSON Patch啟動和執行真的很簡單。
使用Automapper處理JSON Patch請求
針對JSON Patch的使用,最大的問題是,你經常需要從API傳回View Model或者DTO, 並生成PATCH請求。但是如果將這些修改請求應用於資料庫物件上?大部分情況下,開發人員都掙紮在與此。這裡我們可以使用Automapper來幫助完成這個轉換的工作。
例如如下程式碼: