我们使用分页,我们有一个方便的小类可以帮助我们,其中一些看起来像这样:
public class PagedResponse<T>
{
public PagedResponse(HttpRequest request, IQueryable<dynamic> queryable,
string maxPageSizeKey, dynamic options, int pageNumber, int pageSize)
{
//code
}
public dynamic Data { get; set; }
在某个时刻,我们执行此操作并将其分配给Data:
List<dynamic> dataPage = queryable
.Skip(skip)
.Take(take)
.ToList();
因为这个类使用了动态类型,下面是Swagger生成的内容(假设我们传递给PagedResponse类的类型是Foo):
{
"PageNumber": 0,
"PageSize": 0,
"MaxPageSizeAllowed": 0,
"FirstPage": "string",
"NextPage": "string",
"LastPage": "string",
"TotalPages": 0,
"TotalRecords": 0,
"Data": {} <---- Here we want it to describe Foo
}
我还注意到,当你点击"schema"时,它会给它一个串联的名称FooPagedResponse。
事实上,swagger没有提供任何关于Data的信息,这正成为我们React开发人员的一个症结所在,他们利用一些实用程序来获取模式。
现在,事情是这样的,如果你用T代替我使用的任何动态,当然,swagger会很高兴。但这不是解决方案,因为我们必须使用动态。
为什么?OdataQueryOptions和使用odata$select命令的可能性。
在我们执行queryable(上面的.ToList)之前,我们要做这样的事情(以及其他odata命令):
if (options.SelectExpand != null)
{
queryable = options.SelectExpand.ApplyTo(queryable, settings) as IQueryable<dynamic>;
}
(选项是传递给构造函数的"动态选项",这是ODataQueryOptions)
一旦你应用了$select,你就不能再将.ToList
分配给List<T>
——它必须是List<dynamic>
,否则我会遇到一个无法转换类型的异常。
我不能为了获得合适的文档而取消$select的功能。我在这里能做什么?
虽然这不是对原始帖子的直接回答,但这里有很多东西需要解开,对于一个简单的评论来说太多了。
首先,最初的问题本质上是:当返回动态类型的响应时,如何自定义swagger文档
- 要直接对此做出回应,需要帖子包含swagger配置以及实现示例,而不仅仅是类型
扩展Swagger文档的一般机制是通过实现IDocumentFilter
或IOpertationFilter
,因此可以读取这些资源:
因为您使用的是dynamic
类型的数据响应,文档工具无法确定预期的类型,所以您需要提供该信息,如果可用的话,换句话说,有很多不同的插件和代码示例,但我还没有遇到这样的实现,所以无法提供具体的示例。大多数OData配置都是通过反射派生的,因此,如果您通过dynamic
混淆了特定的实现,那么大多数OData工具和内部工作都将失败。
现在,事情是这样的,如果你用T替换我使用动态的任何地方,swagger当然会很高兴。但这不是解决方案,因为我们必须使用动态。
您的代码要求您使用dynamic
类型的响应的唯一原因是,它是以要求的方式编写的。这是您在代码上强制执行的约束,与EF、OData或代码之外的任何其他约束无关。当$select
操作应用于项目查询结果时,我们实际上根本没有改变数据的形状,我们只是省略了未指定的列,在实际意义上,它们的值将为null。
OData对投影的概念非常具体,为了简化服务器端和客户端的处理,$select
(或$expand
)查询选项根本不能修改模式,它只提供了一个应用于模式的掩码。将响应从$select
投影取消序列化为基类型是完全有效的,但是丢失的字段将不会初始化。
- 在请求的基本类型中处理投影响应并不总是可行的,因此许多客户端,尤其是任何后期绑定的语言,都会接收到与在中传输的数据形状相同的数据
虽然您可以深入定制swagger输出,但这篇文章闻起来很像XY问题,OData有一个内置机制,用于分页IQueryable<T>
响应OOTB,是的,响应与您正在使用的PagedResponse<T>
类不同,它对于典型的数据分页场景来说已经足够了,而且它的实现与您的自定义实现非常相似。
如果您在OData中使用内置的分页机制,使用$skip
和$top
查询选项,那么您的代码实现将更简单,swagger文档也将是正确的
您已经承认自己是OData的新手,因此在盲目定制标准端点之前,首先了解默认行为并查看是否可以将您的需求与标准重新调整是一种宝贵的体验。
首先采用OData的一个关键驱动原因是要符合标准,这样客户就可以根据标准约定对API进行调用,代码生成工具可以创建可靠的客户端接口。一旦你开始定制内置行为,如果你想保持与这些类型的流程的兼容性,你就必须定制文档或$metadata。
默认情况下,当提供$top
查询选项时,响应将包括一个获取下一页结果的链接。如果未启用自动提供count
,则可以使用$count=true
查询选项启用在输出中提供的总计数。
自定义实现和OData实现之间的关键区别在于,客户端负责管理或维护页码的列表,并将它们转换为$top
和$skip
值。
这是OData团队有意推动的,旨在支持虚拟分页或按需加载滚动。期望的是,用户可能会到达列表的底部,然后点击查看更多,或者通过到达当前列表的末尾,更多的记录将被动态添加到前一组,成为一个延迟加载但连续的列表。
要选择第三个页面,如果页面大小为5,同时有投影和过滤器,我们可以使用以下网址:
~/OData/Companies?$top=5&$skip=10&$select=CompanyName,TradingName&$filter=contains(CompanyName,'Bu')&$count=true
{
"@odata.context": "~/odata/$metadata#Companies(CompanyName,TradingName)",
"@odata.count": 12,
"value": [
{
"CompanyName": "BROWERS BULBS",
"TradingName": "Browers Bulbs"
},
{
"CompanyName": "BUSHY FLOWERS",
"TradingName": "Bushy Flowers"
}
],
"@odata.nextLink": "~/OData/Companies?$top=5&$skip=10&$select=CompanyName%2CTradingName&$filter=contains%28CompanyName%2C%27Bu%27%29"
}
这个表有数千行和30多列,在这里显示的内容太多了,所以我可以演示$select
和$filter
操作,这太棒了。
这里有趣的是,这个响应表示最后一页,客户端代码应该能够很容易地解释这一点,因为返回的行数小于页面计数,但也因为匹配筛选条件的总行数是12,所以当按5分页时,第3页上应该有2行。
这在客户端仍然有足够的信息来构建到特定单个页面的链接,但大大减少了服务器端的处理和自定义实现中返回的重复内容。换句话说,如果您有对该结构的遗留需求,那么可以很容易地从标准响应创建来自自定义分页实现的确切响应。
不要重新发明轮子,只需重新调整。
- Anthony J.D'Angelo