OData WebAPI實踐-相容OData集合響應

波多爾斯基發表於2023-05-15

本文屬於 OData 系列文章

引言

OData 是一個開放標準,已經在 oasis 組織標準化,因此我們可以在標準的官網查詢到 OData 的標準請求與返回形式:OData JSON Format Version 4.01 (oasis-open.org)

針對不同的資料型別,輸出返回的格式也不盡相同,涉及的內容非常多。日常使用 OData 的過程中,我們經常處理的是實體物件以及實體物件的集合。如果直接返回 IQueryable 用於 OData 查詢,那麼返回的資料大多是集合(陣列/列表)。

{
    "@odata.context": "http://localhost:9000/api/v2/$metadata#Collection(Datum_AggDto)",
    "@odata.count": 2,
    "value": [
        {
            "timestamp": 1682294400000,
            "max": 180.0,
            "min": 152.0,
            "avg": 161.7605633802817
        },
        {
            "timestamp": 1682985600000,
            "max": 281.0,
            "min": 180.0,
            "avg": 228.39583333333334
        }]
}

這個陣列物件也不是很純粹,它被 value 封裝,並且提供了 @odata.context 後設資料連結。如果我們的 API 沒有被 OData 路由解析,那麼預設 WEBAPI 會返回一個純粹的陣列物件:

[
{
	"timestamp": 1682294400000,
	"max": 180.0,
	"min": 152.0,
	"avg": 161.7605633802817
},
{
	"timestamp": 1682985600000,
	"max": 281.0,
	"min": 180.0,
	"avg": 228.39583333333334
}]

假設我們的對外的資料介面不完全被 OData 路由,會導致前端訪問的行為不一致:一些 API 可以直接解析,另外一些 API 則需要使用 value 封裝後處理。

封裝非 OData Route Mapping

由於 OData 有了標準,為了對外保持一致性,我們可以嘗試在返回非 OData 路由 API 時,將原始陣列物件進行封裝。

單實體物件

        [HttpGet("/api/v1/Current")]
        [ProducesResponseType(typeof(DeviceDataDto), Status200OK)]
        public IActionResult Current(string key)
        {
            key = key.Trim('\'');
            var data = _context.DeviceData.Where(w => w.DeviceId == key).OrderByDescending(w => w.Timestamp).FirstOrDefault();

            return Ok(_mapper.Map<DeviceDataDto>(datas));
        }

對於以上的程式碼,只返回單個實體物件,返回的形式與 OData 標準中返回單個實體物件的標準一致,因此不需要額外的轉換操作。

{
	//OData 返回會多一個context,普通API不會有。
	"@odata.context": "http://localhost:9000/api/v2/$metadata#Datum_AggDto",
	"timestamp": 1682985600000,
	"max": 281.0,
	"min": 180.0,
	"avg": 228.39583333333334
}

實體物件集合

        [HttpGet("/api/v1")]
        [ProducesResponseType(typeof(IEnumerable<DeviceDataDto>), Status200OK)]
        public async Task<IActionResult> Get(string key)
        {
            key = key.Trim('\'');
            return Ok(await _context.DeviceData.Where(w => w.DeviceId == key).ToListAsync());
        }

以上程式碼返回的型別是一個集合,並且被 OData 路由對映。我們使用 value 這個 key 對齊進行封裝:

        [HttpGet("/api/v1")]
        [ProducesResponseType(typeof(IEnumerable<DeviceDataDto>), Status200OK)]
        public async Task<IActionResult> Get(string key)
        {
            key = key.Trim('\'');
            var datas = await _context.DeviceData.Where(w => w.DeviceId == key).ToListAsync();

            var result = new { Value = datas };
            return Ok(result);
        }

注意這個 Value 我使用的是大寫,由於我啟用了 camelCase,所以會自動轉換為小寫。這樣前端訪問 API 時,不論是否為 OData API 都可以訪問 value 的值獲取陣列物件。

相關文章