Web API 實測 CRUD

昨天有針對使用 Scaffolding 自動產生的地址的 CRUD 動作方法,配合 RESTful 的說明稍微地了解了一下,今天會再進一步對照程式碼,使用 Postman 實際驗證一下執行的結果。

功能對照表

為了方面對照,特別將地址 API 的各個功能整理成下表:

請回想一下昨天談到的 RESTfull Web Service 的說明,在《前端》與《後端》之間的溝通,當《後端》接收到《前端》的請求(Request),知道要做什麼事,在執行完工後之後,還需要回報(Response)給《前端》並告知執行的結果,而這執行結果是以稱為 Status Code 的數字來表示,各個數字的 Status Code 所代表的意義,請參考這一篇的說明。

新增

因為目前資料庫中還沒有地址資料,所以先使用 POST 動作新增一筆地址資料如下:

照理來講 areaId 應該是讓使用者由使用者介面中先選出縣市,再由鄉鎮區中選出對應的 AreaId 才對,但是因為使用者介面還沒有做,所以該 AreaId 就從資料庫的資料表中直接查詢。當填選完適當的資料後在步驟 7 按下【Send】向《後端》送出請求,當後端執行完後回傳新增的地址資料(JSON 格式),以及 201 的 Status Code 表示資料新增成功。

執行並體驗完了新增地址的操作,再來對照一下程式碼,應該更容易明白程式碼的含意:

[HttpPost]
public async Task<IActionResult> PostAddress([FromBody] Address address)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    _context.Addresses.Add(address);
    await _context.SaveChangesAsync();

    return CreatedAtAction("GetAddress", new { id = address.Id }, address);
}

查詢

已經有新增一筆地址資料,且從回傳的資料中確定新地址的 Id 為 1 了,所以接下來以 GET 動作,在網址以參數 1 進行查詢。(鼓勵讀者以不存在的 Id 試著查詢看看有什麼結果發生):

當按下步驟 3 的【Send】向《後端》送出請求,《後端》接收到該 Id 後,以該 Id 向資料庫查詢是否有該 Id 的地址資料,若有則回傳查詢到的地址資料,以及 200 的 Status Code 表示查詢成功。

執行並體驗完了查詢地址的操作,再來對照一下程式碼,應該更容易明白程式碼的含意:

[HttpGet("{id}")]
public async Task<IActionResult> GetAddress([FromRoute] int id)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var address = await _context.Addresses.SingleOrDefaultAsync(m => m.Id == id);

    if (address == null)
    {
        return NotFound();
    }

    return Ok(address);
}

Eager loading

讀者有沒有發現,前面所查詢的地址資訊,其中 "area": null 表示沒有地區的資料,但是有 "areaId": 29  理應可以對應到 Area 才對,怎麼會是 null 呢?因為,定訂從資料庫中載入資料時不會將相關聯的 Entity 資料一起載入,但是可以使用 Include 方法指示查詢時需要包含哪些關聯資料:

  var address = await _context.Addresses.Include(a=>a.Area).SingleOrDefaultAsync(m => m.Id == id);

修改程式加入 .Include(a=>a.Area)  之後,再試著查詢一次,這次 Area 的資料就一起讀進來了,但是 City 呢?

防止序例化循環參考

在包含再上一層 city 資料之前,請在 Demae.Api 專案的 Startup.cs 中加入如下的程式碼,以防止無限循環參考的錯誤發生:

public void ConfigureServices(IServiceCollection services)
{
    ......
    ......
    ......        

    services.AddMvc().AddJsonOptions(opt =>
    {
        opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
    });
}

再加入 .ThenInclude(c=>c.City) 即可將 City 資料也包含進來:

var address = await _context.Addresses.Include(a=>a.Area).ThenInclude(c=>c.City).SingleOrDefaultAsync(m => m.Id == id);

修改程式加入 .ThenInclude(c=>c.City)  之後,再試著查詢一次,這次 City 的資料就一起讀進來了,但是 City 底下的 Areas 呢?那就繼續 .ThenInclude() 下去啊,留做家庭作業好了。

列表

不帶 Id 參數的 GET 動作為表列出資料庫中所有地址資料,因為目前只有一筆,因此看起來與查詢的結果很相似,但請再留意一下,住址資料外有用 [         ] 括起來,這代表為多筆資料(雖然目前只有一筆)的陣列。

執行並體驗完了表列地址的操作,再來對照一下程式碼,應該更容易明白程式碼的含意:

[HttpGet]
public IEnumerable<Address> GetAddresses()
{
    return _context.Addresses;
}

當然讀者也可以試著加入 .Include() 以及 .ThenInclude() 將相關聯的資料載入,也是一樣留做家庭作業,請讀者自己試一下。

刪除

同樣的,要刪除一筆資料,也是給 Id 參數,並使用 DELETE 動作即可:

執行並體驗完了刪除地址的操作,再來對照一下程式碼,應該更容易明白程式碼的含意:

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteAddress([FromRoute] int id)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var address = await _context.Addresses.SingleOrDefaultAsync(m => m.Id == id);
    if (address == null)
    {
        return NotFound();
    }

    _context.Addresses.Remove(address);
    await _context.SaveChangesAsync();

    return Ok(address);
}

刪除後再以同一個 Id 查詢,不出所料的傳回的是 404 的沒有這筆資料的 Status Code

好吧!今天就體驗到此,明天再繼續吧。可是 ~~~ 好像修改還沒有體驗齁!因為修改牽涉到並行(Concurrency)處理的問題,改天再一起解釋說明好了。