OData WebAPI实践-兼容OData集合响应

WebAPI,OData · 浏览次数 : 268

小编点评

**OData 标准请求与返回形式** OData 是一种开放标准,其标准请求与返回形式可以在 oasis 组织标准化中查询到。对于实体对象和实体对象的集合,OData 提供了不同的返回格式,以确保兼容性。 **实体对象** 当返回单个实体对象时,OData 会返回一个 JSON 对象,其中包含以下内容: * `@odata.context`:指向 OData 路由的上下文链接。 * `timestamp`:当前时间戳。 * `max`、`min`、`avg`:实体对象的最大值、最小值和平均值。 **实体对象的集合** 当返回实体对象的集合时,OData 会返回一个 JSON 对象,其中包含以下内容: * `@odata.context`:指向 OData 路由的上下文链接。 * `timestamp`:当前时间戳。 * `items`:包含实体对象的数组。 **封装** 为了让前端访问非 OData 路由 API 的资源,可以考虑在返回对象时进行封装。例如,可以使用 `value` 作为 key 对齐进行封装,将原始数组对象转换为 JSON 对象。 **示例** ```csharp // 实体对象 [Serializable] public class DeviceDataDto { public string timestamp { get; set; } public double max { get; set; } public double min { get; set; } public double avg { get; set; } } // 实体对象集合 public class DeviceDataDtoCollection { public DeviceDataDto[] items { get; set; } } // 获取实体对象 [HttpGet("/api/v1/Current")] [ProducesResponseType(typeof(DeviceDataDto), Status200OK)] public IActionResult Current(string key) { // 封装实体对象 var deviceDataDto = _context.DeviceData.Find(key); return Ok(_mapper.Map(deviceDataDto)); } ``` **注意** * OData 标准提供了多种返回格式,根据特定需求可以选择合适的格式。 * 在返回非 OData 路由 API 的资源时,需要进行封装,例如使用 `value` 对齐进行封装。

正文

本文属于 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 的值获取数组对象。

与OData WebAPI实践-兼容OData集合响应相似的内容:

OData WebAPI实践-兼容OData集合响应

本文属于 OData 系列文章 引言 OData 是一个开放标准,已经在 oasis 组织标准化,因此我们可以在标准的官网查询到 OData 的标准请求与返回形式:OData JSON Format Version 4.01 (oasis-open.org) 针对不同的数据类型,输出返回的格式也不尽

OData WebAPI实践-OData与EDM

本文属于 OData 系列 引言 在 OData 中,EDM(Entity Data Model) 代表“实体数据模型”,它是一种用于表示 Web API 中的结构化数据的格式。EDM 定义了可以由 OData 服务公开的数据类型、实体和关系。 EDM 也提供了一些规则来描述数据模型中的实体之间的关

OData WebAPI实践-Non-EDM模式

本文属于OData系列文章 前文说到了 EDM 与 OData 之间的关系,具有 EDM 的 OData 提供了强大的查询能力,但是 OData 并不必须要配置 EDM,我们也可以使用 Non-EDM 方案。 Non-EDM 所谓 Non-EDM ,并不是说在 OData 运行时不需要 EDM 配置

OData WebAPI实践-与ABP vNext集成

本文属于 OData 系列文章 ABP 是一个流行的 ASP. NET 开发框架,旧版的的 ABP 已经能够非常好的支持了 OData ,并提供了对应的 OData 包。 ABP vNext 是一个重新设计的,面向微服务的框架,提供了一些非常有用的特性,包括分页查询等但是它并不能原生支持 OData

武装你的WEBAPI-OData之API版本管理

本文属于OData系列 Intro 对外提供WEBAPI时,如果遇上了版本升级,那么控制WEBAPI的版本也是非常必要的。OData官方提供了版本控制以及管理的解决方案,我个人是实践体会是不好用,好在社区提供了对应的nuget包,与.NET主版本同步更新。 介绍 ASP.NET API Versio

武装你的WEBAPI-OData与DTO

本文属于OData系列文章 Intro 前面写了很多有关OData使用的文章,很多读者会有疑问,直接将实体对象暴露给最终用户会不会有风险?$expand在默认配置的情况下,数据会不会有泄露风险? 答案是肯定的,由于OData的特性,提供给我们便捷同时也会带来一些风险。很多地方推荐使用DTO模式来隔离

武装你的WEBAPI-OData聚合查询

本文属于OData系列 Introduction ODATA v4提出了新的聚合查询功能,这对于ODATA的基本查询能力($expand等)是一个非常大的补充。ODATA支持的聚合查询功能,可以对数据进行统计分析,例如求和、平均值、最大/最小值、分组等。 聚合查询是通过$apply关键字实现的。使用

武装你的WEBAPI-OData使用Endpoint

本文属于 OData 系列文章 Introduction 更新: 由于新版的 OData 已经默认使用了 endpoint 模式(Microsoft.AspNetCore.OData 8.0.0),不再需要额外配置,本文已经过时(asp.net core 3.1)。 最近看 OData 的 devb

外键拆分手记

我习惯性使用OData,它的$expand与层级查询非常好用,这个功能非常依赖于数据库的导航属性,也就是外键结构。最近想着把一个单体的系统拆分为多个小系统,首先需要处理外键依赖的问题。 多个服务各自有各自的数据库,数据库层面并不互通,也就无法使用外键约束。 我使用EF Core来描述数据库的结构,有

ChatGPT与码农的机会

之前一篇博客已经写了有关AI在博客编写方面的优势与对未来博客的编写方面的思考。这篇文档我继续分享我在开发中的一个案例和相关的感想。 事件还原 我发现ChatGPT也可以帮助我编写OData,于是我也利用GPT帮助我编程。 OData如何将filter与apply字段联合使用?答案如下: GET /o