ASP.NET Core Web API基于RESTFul APIs的集合结果过滤和分页

译者荐语:如何在RESTFul APIs中进行集合结果分页?还是用客户端来拼接链接地址么?

原文来自互联网,由长沙DotNET技术社区【邹溪源】翻译。如译文侵犯您的版权,请联系小编,小编将在24小时内删除。

在ASP.NET Core WebApi项目中分页响应数据

REST API的分页响应和通过REST API端点筛选返回的数据(它们经常一起出现)同样重要。

就像过滤一样,分页会限制从端点返回的数据量,从而节省了客户端和服务器端资源。想象一下,如果你想返回一个客户的数据,但是却返回了所有客户的数据,或者你返回了所有的分页数据,而你搜索的数据实际上就在前几条记录中。

这将仅导致服务器上处理能力和网络带宽的浪费,也给客户端的实现带来了不必要的复杂度。

有许多技术可以解决这两个问题,例如OData[1]或 GraphQL[2]。但是,也可以仅使用依赖于过滤和分页API数据的定制解决方案来解决这些问题。

为什么要考虑使用定制解决方案而不是现有技术?原因很简单,因为它可以避免您的团队浪费时间学习新技术,而他们已经可以在没有新技术的情况下解决这些问题。而且,这些技术将强加于客户端的适应能力并限制它们的相应客户端的使用。依赖于过滤和分页技术也不是没有复杂性。有时这些简单的方法会变得非常复杂,最终会成为消费者/客户的一个问题。

我将提到REST API设计中自定义筛选和分页的一些支柱,特别是在使用.NET Core WEB API在REST服务中实现筛选和分页时。

使过滤器易于扩展

我在WEB API项目中实现自定义筛选和分页时发现的常见错误之一是将值作为单独的参数传递给MVC Controller Action方法。且别说他是不是易于扩展,这样做使得方法签名变得更加复杂,并且有向端点添加更多过滤选项的趋势,从而变得更加复杂。

 [HttpGet]  # 
public IActionResult Get(String term, int page, int limit)  # 
{  # //Handle filtering and paging  # 
}  #

假设一段时间后,您必须扩展端点以接收DateTime和Boolean参数,这些参数将参与过滤。方法签名将更改并变为:

 [HttpGet]  # 
public IActionResult Get(String term, DateTime minDate, Boolean includeInactive, int page, int limit)  # 
{  # //Handle filtering and paging  # 
}  #

您已经看到,一次更新后,您的方法签名变得更加复杂,而且为了提高阅读这段代码,你还得将其分成两行显示。除非您有版本控制,否则将很难与现有端点使用者保持一致,而且由于开发者不知道增加了新的参数,而由于MVC不知道如何路由请求,MVC将不会匹配方法签名只会自动给出404响应。这意味着您必须将新的参数设置为可选参数,并将其移动到参数列表的末尾。

 [HttpGet]  # 
public IActionResult Get(String term, int page, int limit, DateTime? minDate = null, Boolean includeInactive=false)  # 
{  # //Handle filtering and paging  # 
}  #

现在,参数分散在签名中,使方法签名具有混合过滤和分页参数,没有逻辑分组或顺序,因为除了方法签名中参数列表的末尾,您不能拥有其他可选参数。当您意识到必须将这些更改应用到不止一种方法时,复杂性就会大大增加。您可能必须同时对多个端点进行更改,这不仅使一种方法变得困难,而且几乎使整个应用程序外观都难以维护。

我认为我们已经提出了足够多的观点,可以得出结论,对过滤方法使用多个参数是一个坏主意。更好的方法是使用模型,并将所有参数作为POCO类的属性。尽管方法仍然是HTTP GET,但是MVC可以通过使用模型的[FromQuery]关键字从查询字符串中为您绑定模型。

 public class FilterModel  # 
{  # public String Term { get; set; }  # public DateTime MinDate { get; set; }  # public Boolean IncludeInactive { get; set; }  # public int Page { get; set; }  # public int Limit { get; set; }  # 
}  # # 
[HttpGet]  # 
public IActionResult Get([FromQuery] FilterModel filter)  # 
{  # //Handle filtering and paging  # 
}  #

现在,扩展过滤器是单个类的责任,如果您的过滤器在整个项目中是通用的,或者它具有公共属性(例如页码和页面大小/限制),则可以将其带到基类,并且如果需要扩展跨多个Actions甚至跨多个Controller的过滤器模型,您只需扩展基本模型过滤器类即可。您仍然必须在过滤逻辑中处理新参数,但是方法签名将保持不变,而无需扩展它。

不要让客户端为您分担工作

我看到的许多自定义实现都让客户端形成查询字符串以获取下一页。我认为这不是正确的方法。我看到的并且我真的很喜欢的一种实现[3]是ZenDesk API[4]使用的这种。除了实体集合以外,响应还包括结果下一页和上一页的URL。样本响应将是这样的

{  # persons:[  # {  # name: "John Smith",  # dob: "1984-10-31",  # email: "john@smith.test.com"  # },  # ...  # ],  # nextPage: "http://localhost:5000/api/persons?name=John&page=2&limit=100",  # previousPage: null  # 
}  #

这样,您的客户就不必确定下一个页面URL是什么,并且在采用当今的现代无限滚动方式的大多数UI实现(包括Web和移动)上,这种方法非常理想,因为每个页面滚动到底部都是一种新方法HTTP GET到下一页URL。在WEB API中,看起来像这样

 public class FilterModel  # 
{  # public String Term { get; set; }  # public DateTime MinDate { get; set; }  # public Boolean IncludeInactive { get; set; }  # public int Page { get; set; }  # public int Limit { get; set; }  # 
}  # # 
public class PagedCollectionResponse<T> where T : class  # 
{  # public IEnumerable<T> Items { get; set; }  # public Uri NextPage { get; set; }  # public Uri PreviousPage { get; set; }  # 
}  # # 
public class Person  # 
{  # public String Name { get; set; }  # public DateTime DOB { get; set; }  # public String Email { get; set; }  # 
}  # # 
[HttpGet]  # 
public ActionResult<PagedCollectionResponse<Person>> Get([FromQuery] FilterModel filter)  # 
{  # //Handle filtering and paging  # 
}  #

这只是过滤器动作签名的外观的浅层结构。接下来,我将用一段简单的代码解释如何在ASP.NET Core WEB API示例控制器中实现这种方法。

过滤和分页的简单示例

为了向您展示如何使用页面指针URL来实现上述分页方法,我将使用一个简单的控制器和字符串的静态集合。理想情况下,您将查询存储库中的数据,但是为了使事情保持简单并专注于生成所描述的响应结构,我将坚持简单的字符串集合。

在我们跳到逻辑之前,第一件事就是创建模型。由于我们项目中的所有过滤器都会接收页面和限制值,因此有必要将其设为抽象类,以便任何具有页面调度的过滤器都可以继承它。

 namespace Sample.Web.Api.Models  # 
{  # public abstract class FilterModelBase:ICloneable  # {  # public int Page { get; set; }        public int Limit { get; set; }  public FilterModelBase()  {  this.Page = 1;  this.Limit = 100;  }  public abstract object Clone();  }  
}  

我们有一个默认的构造函数,它将页面大小(Limit属性)设置为100,这意味着默认情况下,任何过滤器模型都将分页显示100个项目的集合中的值。我们还实现了ICloneable接口,但是实现保留为抽象,以允许继承的类处理克隆逻辑,因为它可能涉及继承的POCO类的其他属性。当我们开始实现分页逻辑时,您将明白为什么我们需要涉及ICloneable接口。

现在让我们通过继承FilterModelBase抽象类来实现过滤器

 public class SampleFilterModel:FilterModelBase  
{  public string Term { get; set; }  public SampleFilterModel():base()  {  this.Limit = 3;  }  public override object Clone()  {  var jsonString = JsonConvert.SerializeObject(this);  return JsonConvert.DeserializeObject(jsonString,this.GetType());  }  
}  

除了Page和Limit属性之外,我还添加了一个附加属性Term,该属性应用于过滤我们的字符串集合。我还希望在构造函数中将新页面大小设置为3,而不是在基类构造函数中分配的默认页面大小设置为100,这是因为希望查看少量数据集的分页。Clone方法表示过滤器模型实例的深层副本,使用Newtonsoft.Json[5]包通过简单的序列化/反序列化即可完成。这样,我们涵盖了Action方法的输入,但是现在我们需要注意输出。为了使响应通用,我将使用相同的结构模型,但是将根据控制器的需要更改集合的类型。为此,我使用了通用类型来声明输出模型,以便我们可以在多个Controller Action方法中重用它以返回不同类型的集合元素。

 namespace Sample.Web.Api.Models  
{  public class PagedCollectionResponse<T> where T:class  {  public IEnumerable<T> Items { get; set; }  public Uri NextPage { get; set; }  public Uri PreviousPage { get; set; }  }  
}  

当我们要返回上述人员的集合时,我们可以使用相同的模型类来存储示例数据。

 namespace Sample.Web.Api.Models  
{  public class Person  {  public String Name { get; set; }  public DateTime DOB { get; set; }  public String Email { get; set; }  }  
}  

现在我们准备写下我们的页面处理。正如我提到的,我将使用Person类实例的集合,并且在此演示中,我将它们声明为在Controller构造中启动的集合。

 namespace Sample.Web.Api.Controllers  
{  [Route("api/[controller]")]  [ApiController]  public class PersonsController : ControllerBase  {  IEnumerable<Person> persons = new List<Person>() {  new Person() { Name = "Nancy Davolio", DOB = DateTime.Parse("1948-12-08"), Email = "nancy.davolio@test.com" },  new Person() { Name = "Andrew Fuller", DOB = DateTime.Parse("1952-02-19"), Email = "andrew.fuller@test.com" },  new Person() { Name = "Janet Leverling", DOB = DateTime.Parse("1963-08-30"), Email = "janet.leverling@test.com" },  new Person() { Name = "Margaret Peacock", DOB = DateTime.Parse("1937-09-19"), Email = "margaret.peacock@test.com" },  new Person() { Name = "Steven Buchanan", DOB = DateTime.Parse("1955-03-04"), Email = "steven.buchanan@test.com" },  new Person() { Name = "Michael Suyama", DOB = DateTime.Parse("1963-07-02"), Email = "michael.suyama@test.com" },  new Person() { Name = "Robert King", DOB = DateTime.Parse("1960-05-29"), Email = "robert.king@test.com" },  new Person() { Name = "Laura Callahan", DOB = DateTime.Parse("1958-01-09"), Email = "laura.callahan@test.com" },  new Person() { Name = "Anne Dodsworth", DOB = DateTime.Parse("1966-01-27"), Email = "anne.dodsworth@test.com" }  };  // GET api/values  [HttpGet]  public ActionResult<PagedCollectionResponse<Person>> Get([FromQuery] SampleFilterModel filter)  {  //Filtering logic  Func<SampleFilterModel, IEnumerable<Person>> filterData = (filterModel) =>  {  return persons.Where(p => p.Name.StartsWith(filterModel.Term ?? String.Empty, StringComparison.InvariantCultureIgnoreCase))  .Skip((filterModel.Page-1) * filter.Limit)  .Take(filterModel.Limit);  };  //Get the data for the current page  var result = new PagedCollectionResponse<Person>();  result.Items = filterData(filter);  //Get next page URL string  SampleFilterModel nextFilter = filter.Clone() as SampleFilterModel;  nextFilter.Page += 1;  String nextUrl = filterData(nextFilter).Count() <= 0 ? null : this.Url.Action("Get", null, nextFilter, Request.Scheme);  //Get previous page URL string  SampleFilterModel previousFilter = filter.Clone() as SampleFilterModel;  previousFilter.Page -= 1;  String previousUrl = previousFilter.Page <= 0 ? null : this.Url.Action("Get", null, previousFilter, Request.Scheme);  result.NextPage = !String.IsNullOrWhiteSpace(nextUrl) ? new Uri(nextUrl) : null;  result.PreviousPage = !String.IsNullOrWhiteSpace(previousUrl) ? new Uri(previousUrl) : null;  return result;  }  }  
}  

该示例代码非常原始,它不是生产代码,它需要一些处理才能在多个控制器中重复使用,但它的目的是在简单数据收集的小样本上生成所需的输出数据结构和分页逻辑。让我们一步一步地分析该方法的逻辑块

过滤逻辑 从源数据集合返回项目集合的Simple Func会根据传递的过滤器模型来获取一批对象。此实现很大程度上取决于您的过滤逻辑和您要应用过滤器的数据。Func主体特定于Action方法。•获取当前页面的数据 上述Func实现的简单用法 。将逻辑放在Func中的原因是供以后重用以确定下一页和上一页URL。•获取下一页URL字符串 在这里,我们正在创建具有更新的页码的新模型。还记得我们使用ICloneable作为过滤器POCO吗?现在,我们将使用它来创建深层副本并更新模型的页码,以便我们可以生成下一页的URL。在生成下一页的URL之前,我们需要知道下一页号是否返回任何元素。我们不想在下一页将客户端发送到空集合响应,因为我们希望客户端仅依赖NextPage和PreviousPage URL属性。•获取上一页URL字符串 与获取NextPage URL非常相似,但逻辑上略有不同。我们不需要将结果集集合计为下一页URL。我们只需要检查页码是否为1,这意味着没有更多的页面,PreviousPage URL为空值。

我们几乎涵盖了所有内容,因此让我们看一下它在POSTMAN中的实际工作方式。

图片

在具有默认页面参数的初始请求中,我们可以看到结果集合中有3个人,我们的NextPage URL指向页面号增加1的URL,而PreviousPage URL为null,因为我们在首页上并且没有之前的页面。

如果我们遵循NextPage URL并在POSTMAN中对其执行HTTP GET,我们将得到以下响应。

图片

现在您可以看到我们同时具有NextPage URL和PreviousPage URL。如果您注意到示例数据集合中有9个元素,这意味着对NextPage URL的请求应在结果集合中再给我们3个元素。

图片

我们的最后一页在结果集中返回3个人,但是您可以注意到NextPage URL为空。这是因为页数4的计数将在响应中不返回任何元素,并且我们正在通知使用者没有更多数据要返回。

我在一个简单的数据收集上演示了此WEB API响应分页,但实际情况将涉及数据过滤和查询数据存储库。我希望 不久的将来,我将能够通过使用存储库模式和可重复使用的逻辑(可以应用于多个控制器和操作而无需任何代码重复)的展示,以更加精细的实现编写更详细的文本。

References

[1] OData: https://www.odata.org/
[2] GraphQL: https://graphql.org/
[3] 实现: https://developer.zendesk.com/rest_api
[4] ZenDesk API: https://developer.zendesk.com/rest_api
[5] Newtonsoft.Json: https://www.newtonsoft.com/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/311466.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.net 微服务实践

l 前言本文记录了我的一次.net core 微服务架构实践经验&#xff0c;以及所用到的技术l 优点每个服务聚焦于一块业务&#xff0c;无论在开发阶段或是部署阶段都是独立的&#xff0c;更适合被各个小团队开发维护&#xff0c;团队对服务的整个生命周期负责&#xff0c;工作在独…

redis过期监听性能_基于Redis的延迟处理

延迟处理是一个非常常用的一个功能;例如, 下单成功后,在30分钟内没有支付,自动取消订单;延迟队列便是延迟处理中最常见的实现方式;先一起看下JDK中延迟队列是如何实现的.JUC的DelayQueue在JDK中, 提供了一套延迟队列的实现, 是JUC包中DelayQueue类.在使用时只需要让处理的元素对…

【译】来看看WebWindow,一个跨平台的.NET Core webview 库

本文翻译自 ASP.NET 项目组的 Steve Sanderson 的博客&#xff0c;发表于 2019 年 11 月 18 日。Steve Sanderson 是 Blazor 最早的创造者。它类似于 Electron&#xff0c;但没有捆绑 Node.js 和 Chromium&#xff0c;也没有大部分 API。我的上一篇文章研究了如何用 web 渲染的…

sql if 和insert_拼多多面试:Mybatis是如何实现SQL语句复用功能的?

在工作中&#xff0c;往往有这样的需求&#xff0c;对于同一个sql条件查询&#xff0c;首先需要统计记录条数&#xff0c;用以计算pageCount&#xff0c;然后再对结果进行分页查询显示&#xff0c;看下面一个例子。<sql id"studentProperties"><!--sql片段-…

代码演示C#各版本新功能

代码演示C#各版本新功能C#各版本新功能其实都能在官网搜到&#xff0c;但很少有人整理在一起&#xff0c;并通过非常简短的代码将每个新特性演示出来。代码演示C#各版本新功能C# 2.0版 - 2005泛型分部类型匿名方法可以为null的值类型迭代器协变和逆变C# 3.0版 - 2007自动实现的…

《C++ Primer》1.52节练习

练习1.23 #include <iostream> #include "Sales_item.h"using namespace std;int main() {Sales_item trans1, trans2;cout << "请输入若干销售记录:" << endl;if (cin >> trans1) {int num 1;while (cin >> trans2)if (t…

ASP.NET Core 反向代理部署知多少

引言最近在折腾统一认证中心&#xff0c;看到开源项目[IdentityServer4.Admin&#xff1a;https://github.com/skoruba/IdentityServer4.Admin]集成了IdentityServer4和管理面板&#xff0c;就直接拿过来用了。在尝试Nginx部署时遇到了诸如虚拟目录映射&#xff0c;请求头超长、…

函数传参string_JavaScript 高阶函数入门浅析

原文&#xff1a;https://www.freecodecamp.org/news/a-quick-intro-to-higher-order-functions-in-javascript-1a014f89c6b/译者&#xff1a;jingruzhang校对者&#xff1a;acusp高阶函数高阶函数可以接收函数作为参数&#xff0c;同时也可以返回一个新的函数。高阶函数之所以…

.NET Core开发实战(第13课:配置绑定:使用强类型对象承载配置数据)--学习笔记...

13 | 配置绑定&#xff1a;使用强类型对象承载配置数据要点&#xff1a;1、支持将配置值绑定到已有对象2、支持将配置值绑定到私有属性上继续使用上一节代码首先定义一个类作为接收配置的实例class Config {public string Key1 { get; set; }public bool Key5 { get; set; }pub…

工业互联网白皮书_发布|《工业互联网平台安全白皮书(2020)》发布

12月4日&#xff0c;2020年中国工业信息安全大会暨全国工控安全深度行(京津冀站)在北京国际会议中心举行。大会由国家工业信息安全发展研究中心、工业信息安全产业发展联盟主办&#xff0c;以“贯彻总体国家安全观&#xff0c;把牢工控安全基准线”为主题。会上&#xff0c;国家…

ASP.NET Core Razor 视图预编译、动态编译

0x01 前言ASP.NET Core在默认发布情况下&#xff0c;会启动预编译将试图编译成xx.Views.dll,也许在视图中打算修改一处很细小的地方我们需要再重新编译视图进行发布。下面我将从 ASP.NET Core 3 之前版本到 ASP.NET Core 3X 之后版本的一个配置列下下方供大家参考。0x02 预编译…

《C++ Primer》2.1.2节练习

练习2.3 #include <iostream> using namespace std;int main() {unsigned u 10, u2 42;cout << u2 - u << endl;cout << u - u2 << endl;int i 10, i2 42;cout << i2 - i << endl;cout << i - i2 << endl;cout <…

如何构建基于.NET Core和云环境下的微服务技术体系?

这个内核用处不大&#xff0c;但.NET 内核却666随着业务需求的增长&#xff0c;我们现在开发非常大型和复杂的项目&#xff0c;需要更多时间来构建和部署。每当质量检查报告任何问题时&#xff0c;我们都需要对其进行调试或修复&#xff0c;然后部署整个代码。为了降低这些复杂…

UVA - 11059 Maximum Product-暴力枚举

输入n个元素组成的序列s,找出一个乘积最大的连续子序列&#xff0c;如果这个子序列不是整数&#xff0c;则输出0. 解题思路&#xff1a; 枚举起点和终点&#xff0c;把中间的数相乘&#xff0c;然后找到最大的结果。 代码如下&#xff1a; #include <iostream> using…

好用的vp n推荐2020_哪个牌子的沐浴露好,2020年最新沐浴露选购测评,好用好闻易清洗沐浴露品牌推荐...

您好&#xff0c;感谢您关注并阅读本文。声明&#xff1a;本文系作者原创&#xff0c;未经作者授权不得转载、引用。如果您看完本文觉得对您有帮助&#xff0c;请点赞、收藏和关注&#xff0c;作者感激不尽。本文详细地介绍一下沐浴露使用方法和选购建议指南&#xff0c;以及沐…

【朝夕Net社区技术专刊】Core3.1 WebApi集群实战专题---WebApi环境搭建运行发布部署篇...

欢迎大家阅读《朝夕Net社区技术专刊》第1期我们致力于.NetCore的推广和落地&#xff0c;为更好的帮助大家学习&#xff0c;方便分享干货&#xff0c;特创此刊&#xff01;很高兴你能成为首期读者&#xff0c;文末福利不要错过哦&#xff01;本文通过5大部分进行解读&#xff1a…

【朝夕Net社区技术专刊】Core3.1 WebApi集群实战专题-Corre3.1WebApi配置集成日志/配置Swagger...

欢迎大家阅读《朝夕Net社区技术专刊》第2期我们致力于.NetCore的推广和落地&#xff0c;为更好的帮助大家学习&#xff0c;方便分享干货&#xff0c;特创此刊&#xff01;很高兴你能成为首期读者&#xff0c;文末福利不要错过哦&#xff01;本文通过3大部分进行解读&#xff1a…

一文读懂开源许可证异同

对开源许可证异同的对比并非源自担忧。对开源许可证进行比较并不容易&#xff0c;什么 copyleft 啦&#xff0c;什么宽松许可证啦&#xff0c;光 GNU 就有 GPL 2 和 GPL 3 之分&#xff0c;OSI 批准的许可证就有八十多个&#xff0c;而开源生态下存在了数百个许可证。对于我们这…