日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

从头编写 asp.net core 2.0 web api 基础框架 (2)

發布時間:2023/12/4 asp.net 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从头编写 asp.net core 2.0 web api 基础框架 (2) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

上一篇是:?從頭編寫 asp.net core 2.0 web api 基礎框架 (1)

Github源碼地址是:?https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-template-from-scratch

本文講的是里面的Step 2.

上一次, 我們使用asp.net core 2.0 建立了一個Empty project, 然后做了一些基本的配置, 并建立了兩個Controller, 寫了一些查詢方法.

下面我們繼續:

POST

POST一般用來表示創建資源, 也就是新增.

先看看Model, 其中的Id屬性, 一般是創建的時候服務器自動生成的, 所以如果客戶端在進行Post(創建)的時候, 它是不會提供Id屬性的.


public class Product{ ? ?
?? ? ?
public int Id { get; set; } ?
? ? ? ?
public string Name { get; set; } ? ?
? ? ??
public float Price { get; set; } ? ? ?
? ? ?
public ICollection<Material> Materials { get; set; }}


所以, 可以這樣做, 再建立一個Dto, 專門用于創建: ProductCreation.cs:?


namespace CoreBackend.Api.Dtos { ? ?
? ?
public class ProductCreation{ ? ? ?
? ??
public string Name { get; set; } ? ?
? ? ?
public float Price { get; set; }} }


這里去掉了Id和Materials這個導航屬性.

其實也可以使用同一個Model來做所有的操作, 因為它們的大部分屬性都是相同的, 但是,

還是建議針對查詢, 創建, 修改, 使用單獨的Model, 這樣以后修改和重構會簡單一些, 再說他們的驗證也是不一樣的.

創建Post Action

     [Route("{id}", Name = "GetProduct")] ? ?
? ? ? ?
public IActionResult GetProduct(int id){ ? ? ? ? ?
? ? ? ? ?
var product = ProductService.Current.Products.SingleOrDefault(x => x.Id == id); ? ? ? ? ?
? ? ? ? ? ?
if (product == null){ ? ? ? ? ? ?
? ? ? ? ? ?? ?
return NotFound();} ? ? ? ? ?
? ? ? ? ? ?
return Ok(product);}[HttpPost] ? ? ?
? ? ? ?
public IActionResult Post([FromBody] ProductCreation product){ ? ? ? ?
? ? ? ?? ?
if (product == null){ ? ? ? ?
? ? ? ? ? ? ? ? ?
return BadRequest();} ? ? ? ? ?
?
var maxId = ProductService.Current.Products.Max(x => x.Id); ? ? ? ? ?
??
var newProduct = new Product{Id = ++maxId,Name = product.Name,Price = product.Price};ProductService.Current.Products.Add(newProduct); ? ? ? ? ? ?return CreatedAtRoute("GetProduct", new { id = newProduct.Id }, newProduct);}


[HttpPost]?表示請求的謂詞是Post. 加上Controller的Route前綴, 那么訪問這個Action的地址就應該是: 'api/product'

后邊也可以跟著自定義的路由地址, 例如 [HttpPost("create")], 那么這個Action的路由地址就應該是: 'api/product/create'.

[FromBody]?,?請求的body里面包含著方法需要的實體數據, 方法需要把這個數據Deserialize成ProductCreation, [FromBody]就是干這些活的.

客戶端程序可能會發起一個Bad的Request, 導致數據不能被Deserialize, 這時候參數product就會變成null. 所以這是一個客戶端發生的錯誤, 程序為讓客戶端知道是它引起了錯誤, 就應該返回一個Bad Request?400 (Bad Request表示客戶端引起的錯誤)的 Status Code.

傳遞進來的model類型是 ProductCreation, 而我們最終操作的類型是Product, 所以需要進行一個Map操作, 目前還是挨個屬性寫代碼進行Map吧, 以后會改成Automapper.

返回?CreatedAtRoute:?對于POST, 建議的返回Status Code 是?201 (Created), 可以使用CreatedAtRoute這個內置的Helper Method. 它可以返回一個帶有地址Header的Response, 這個Location Header將會包含一個URI, 通過這個URI可以找到我們新創建的實體數據. 這里就是指之前寫的GetProduct(int id)這個方法. 但是這個Action必須有一個路由的名字才可以引用它, 所以在GetProduct方法上的Route這個attribute里面加上Name="GetProduct", 然后在CreatedAtRoute方法第一個參數寫上這個名字就可以了, 盡管進行了引用, 但是Post方法走完的時候并不會調用GetProduct方法. CreatedAtRoute第二個參數就是對應著GetProduct的參數列表, 使用匿名類即可,?最后一個參數是我們剛剛創建的數據實體.?

運行程序試驗一下, 注意需要在Headers里面設置Content-Type: application/json. 結果如圖:

返回的狀態是201.

看一下那一堆Headers:

里面的location 這個Header, 所以客戶端就知道以后想找這個數據, 就需要訪問這個地址, 我們可以現在就試試:

嗯. 沒什么問題.

?Validation 驗證

針對上面的Post方法,? 如果請求沒有Body, 參數product就會是null, 這個我們已經判斷了;?如果body里面的數據所包含的屬性在product中不存在, 那么這個屬性就會被忽略.

但是如果body數據的屬性有問題, 比如說name沒有填寫, 或者name太長, 那么在執行action方法的時候就會報錯, 這時候框架會自動拋出500異常, 表示是服務器的錯誤, 這是不對的. 這種錯誤是由客戶端引起的, 所以需要返回400 Bad Request錯誤.

驗證Model/實體, asp.net core 內置可以使用?Data Annotations進行:?


using System;
using System.ComponentModel.DataAnnotations;

namespace CoreBackend.Api.Dtos { ?
?
public class ProductCreation{ ? ? ?
? ? ? [Display(Name
= "產品名稱")][Required(ErrorMessage = "{0}是必填項")]// [MinLength(2, ErrorMessage = "{0}的最小長度是{1}")]// [MaxLength(10, ErrorMessage = "{0}的長度不可以超過{1}")]
     [StringLength(10, MinimumLength = 2, ErrorMessage = "{0}的長度應該不小于{2}, 不大于{1}")] ? ? ?
     ?
public string Name { get; set; } ? ? ? ?[Display(Name = "價格")][Range(0, Double.MaxValue, ErrorMessage = "{0}的值必須大于{1}")] ? ? ?
? ?
public float Price { get; set; }} }

這些Data Annotation (理解為用于驗證的注解), 可以在System.ComponentModel.DataAnnotation找到, 例如[Required]表示必填, [MinLength]表示最小長度, [StringLength]可以同時驗證最小和最大長度, [Range]表示數值的范圍等等很多.

[Display(Name="xxx")]的用處是, 給屬性起一個比較友好的名字.

其他的驗證注解都有一個屬性叫做ErrorMessage (string), 表示如果驗證失敗, 就會把ErrorMessage的內容添加到錯誤結果里面去. 這個ErrorMessage可以使用參數,?{0}表示Display的Name屬性,?{1}表示當前注解的第一個變量,?{2}表示當前注解的第二個變量.

在Controller里面添加驗證邏輯:


     [HttpPost] ? ?
     ?
public IActionResult Post([FromBody] ProductCreation product){ ? ? ? ? ?
? ? ? ? ??
if (product == null){ ? ? ? ? ?
? ? ? ? ?? ? ?
return BadRequest();} ? ? ? ? ?
? ? ? ? ? ??
if (!ModelState.IsValid){return BadRequest(ModelState);} ? ? ? ?
? ?
var maxId = ProductService.Current.Products.Max(x => x.Id); ? ? ? ? ?
?
var newProduct = new Product{Id = ++maxId,Name = product.Name,Price = product.Price};ProductService.Current.Products.Add(newProduct); ? ? ? ? ? ?return CreatedAtRoute("GetProduct", new { id = newProduct.Id }, newProduct);}


ModelState: 是一個Dictionary, 它里面是請求提交到Action的Name和Value的對們, 一個name對應著model的一個屬性, 它也包含了一個針對每個提交的屬性的錯誤信息的集合.

每次請求進到Action的時候, 我們在ProductCreationModel添加的那些注解的驗證, 就會被檢查. 只要其中有一個驗證沒通過, 那么ModelState.IsValid屬性就是False. 可以設置斷點查看ModelState里面都有哪些東西.

如果有錯誤的話, 我們可以把ModelState當作Bad Request的參數一起返回到前臺.

我們試試:

如果通過Data Annotation的方式不能實現比較復雜驗證的需求, 那就需要寫代碼了. 這時, 如果驗證失敗, 我們可以錯誤信息添加到ModelState里面,

if (product.Name == "產品"){ ? ? ? ? ? ? ? ?ModelState.AddModelError("Name", "產品的名稱不可以是'產品'二字");} ? ? ? ?

看看運行結果:?

Good.?

但是這種通過注解的驗證方式把驗證的代碼和Model的代碼混到了一起, 并不是很好的Separationg of Concern, 而且同時在Model和Controller里面為Model寫驗證相關的代碼也不太好.?

這是方式是asp.net core 內置的, 所以簡單的情況下還是可以用的. 如果需求比較復雜, 可以使用FluentValidation, 以后會加入這個庫.

PUT

put應該用于對model進行完整的更新.?

首先最好還是單獨為Put寫一個Dto Model, 盡管屬性可能都是一樣的, 但是也建議這樣寫, 實在不想寫也可以.

ProducModification.cs


public class ProductModification{[Display(Name = "產品名稱")][Required(ErrorMessage = "{0}是必填項")][StringLength(10, MinimumLength = 2, ErrorMessage = "{0}的長度應該不小于{2}, 不大于{1}")] ? ? ?
?
public string Name { get; set; }[Display(Name = "價格")][Range(0, Double.MaxValue, ErrorMessage = "{0}的值必須大于{1}")] ? ? ?
?
public float Price { get; set; }}


然后編寫Controller的方法:


     [HttpPut("{id}")] ? ? ?
?
public IActionResult Put(int id, [FromBody] ProductModification product){ ? ? ? ? ?
?
if (product == null){ ? ? ? ? ? ? ?
?
return BadRequest();} ? ? ? ? ?
?
if (product.Name == "產品"){ModelState.AddModelError("Name", "產品的名稱不可以是'產品'二字");} ? ? ? ? ?
?
if (!ModelState.IsValid){ ? ? ? ? ? ? ?
?
return BadRequest(ModelState);} ?
?
var model = ProductService.Current.Products.SingleOrDefault(x => x.Id == id); ? ? ? ? ?
??
if (model == null){ ? ? ? ? ?
? ? ?
return NotFound();} ? ? ? ? ? ?model.Name = product.Name;model.Price = product.Price; ? ? ? ? ? ?// return Ok(model);return NoContent();}

按照Http Put的約定, 需要一個id這樣的參數, 用于查找現有的model.

由于Put做的是完整的更新, 所以把ProducModification整個Model作為參數.

進來之后, 進行了一套和POST一摸一樣的驗證, 這地方肯定可以改進, 如果驗證邏輯比較復雜的話, 到處寫同樣驗證邏輯肯定是不好的, 所以建議使用FluentValidation.

然后, 把ProductModification的屬性都映射查詢找到給Product, 這個以后用AutoMapper來映射.

返回: PUT建議返回NoContent(), 因為更新是客戶端發起的, 客戶端已經有了最新的值, 無需服務器再給它傳遞一次, 當然了, 如果有些值是在后臺更新的, 那么也可以使用Ok(xxx)然后把更新后的model作為參數一起傳到前臺.兩種效果如圖:

注意: PUT是整體更新/修改, 但是如果只想修改部分屬性的時候, 我們看看會發生什么.

首先在Product相關Dto里面再加上一個屬性Description吧.

?View Code

然后在POST和PUT的方法里面映射那部分, 添加上相應的代碼, (如果有AutoMapper, 這不操作就不需要做了):

?View Code

然后我們用PUT進行實驗單個屬性修改:

這對這條數據:

我們修改name和price屬性:

然后再看一下修改后的數據:

Description被設置成null. 這就是HTTP PUT標準的本意: 整體修改, 更新所有屬性, 盡管你的代碼可能不這么做.

Patch 部分更新

?Http Patch 就是做部分更新的, 它的Request Body應該包含需要更新的屬性名 和 值, 甚至也可以包含針對這個屬性要進行的相應操作.

針對Request Body這種情況, 有一個標準叫做 Json Patch RFC 6092, 它定義了一種json數據的結構 可以表示上面說的那些東西.?

Json Patch定義的操作包含替換, 復制, 移除等操作.

這對我們的Product, 它的結構應該是這樣的:

op 表示操作, replace 是指替換; path就是屬性名, value就是值.

相應的Patch方法:

? ? ? ?[HttpPatch("{id}")] ? ?
? ? ? ?
public IActionResult Patch(int id, [FromBody] JsonPatchDocument<ProductModification> patchDoc){ ?
if (patchDoc == null){ ? ? ? ? ? ?
? ?
return BadRequest();} ? ? ? ? ?
??
var model = ProductService.Current.Products.SingleOrDefault(x => x.Id == id); ? ? ? ? ?
??
if (model == null){ ? ? ? ? ? ?
? ?
return NotFound();} ? ? ? ? ?
?
var toPatch = new ProductModification{Name = model.Name,Description = model.Description,Price = model.Price};patchDoc.ApplyTo(toPatch, ModelState); ? ? ? ? ? ?if (!ModelState.IsValid){return BadRequest(ModelState);}model.Name = toPatch.Name;model.Description = toPatch.Description;
       ?model.Price = toPatch.Price; ? ? ? ? ? ?
return NoContent();}

HttpPatch, 按約定方法有一個參數id, 還有一個JsonPatchDocument類型的參數, 它的泛型應該是用于Update的Dto, 所以選擇的是ProductionModification. 如果使用Product這個Dto的話, 那么它包含id屬性, 而id屬性是不更改的. 但如果你沒有針對不同的操作使用不同的Dto, 那么別忘了檢查傳入Dto的id 要和參數id一致才行.

然后把查詢出來的product轉化成用于更新的ProductModification這個Dto, 然后應用于Patch Document 就是指為toPatch這個model更新那些需要更新的屬性, 是使用ApplyTo方法實現的.

但是這時候可能會出錯, 比如說修改一個根本不存在的屬性, 也就是說客戶端可能引起了錯誤, 這時候就需要它進行驗證, 并返回Bad Request. 所以就加上ModelState這個參數. 然后進行判斷即可.

然后就是和PUT一樣的更新操作, 把toPatch這個Update的Dto再整體更新給model. 其實里面不管怎么實現, 只要按約定執行就好.

然后按建議,?返回NoContent().

試一下:

然后查詢一下:

與期待的結果一樣.

然后試一下傳入一個不存在的屬性:

結果顯示找不到這個屬性.

再試一下, ProductModification 這個model上的驗證: 例如刪除name這個屬性的值:

返回204, 表示成功, 但是name是必填的, 所以代碼還有問題.

我們做了ModelState檢查, 但是為什么沒有驗證出來呢? 這是因為, Patch方法的Model參數是JsonPatchDocument而不是ProductModification, 上面傳進去的參數對于JsonPatchDocument來說是沒有問題的.

所以我們需要對toPatch這個model進行驗證:

[HttpPatch("{id}")] ? ?
?
public IActionResult Patch(int id, [FromBody] JsonPatchDocument<ProductModification> patchDoc){ ? ? ? ? ?
?
if (patchDoc == null){ ? ? ? ? ? ?
? ?
return BadRequest();} ? ? ? ? ?
?
var model = ProductService.Current.Products.SingleOrDefault(x => x.Id == id); ? ? ? ?
? ?
if (model == null){ ? ? ? ? ?
? ? ?
return NotFound();} ? ? ? ? ?
?
var toPatch = new ProductModification{Name = model.Name,Description = model.Description,Price = model.Price};patchDoc.ApplyTo(toPatch, ModelState); ? ? ? ? ? ?if (!ModelState.IsValid){ ? ? ? ? ? ? ?
?
return BadRequest(ModelState);} ? ? ? ? ? ?if (toPatch.Name == "產品"){ModelState.AddModelError("Name", "產品的名稱不可以是'產品'二字");}TryValidateModel(toPatch);if (!ModelState.IsValid){return BadRequest(ModelState);}model.Name = toPatch.Name;model.Description = toPatch.Description;model.Price = toPatch.Price; ? ? ? ? ? ?return NoContent();}

使用TryValidateModel(xxx)對model進行手動驗證, 結果也會反應在ModelState里面.

再試一次上面的操作:

這回對了.

DELETE 刪除

這個比較簡單:

[HttpDelete("{id}")] ? ? ?
?
public IActionResult Delete(int id){ ? ? ? ? ?
??
var model = ProductService.Current.Products.SingleOrDefault(x => x.Id == id); ? ? ? ? ?
?
if (model == null){ ? ? ? ? ? ?
? ?
return NotFound();}ProductService.Current.Products.Remove(model); ? ? ? ? ? ?return NoContent();}

按Http Delete約定, 參數為id, 如果操作成功就回NoContent();

試一下:

成功.

目前, CRUD最基本的操作先告一段落.

相關文章:

  • 從頭編寫 asp.net core 2.0 web api 基礎框架 (1)

  • Asp.Net Core 2.0 多角色權限認證

  • 調試 ASP.NET Core 2.0 源代碼

原文地址:?http://www.cnblogs.com/sheng-jie/p/7640163.html?


.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注

總結

以上是生活随笔為你收集整理的从头编写 asp.net core 2.0 web api 基础框架 (2)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。