参照 草根专栏- ASP.NET Core + Ng6 实战:https://v.qq.com/x/page/v07647j3zkq.html

翻页, 过滤, 排序等 – 如何传递参数?

Query String

  • http://localhost:5000/api/country?pageIndex=12&pageSize=10&orderBy=id

使用抽象父类 QueryParameters, 包含常见参数:

  • PageIndex, PageSize, OrderBy …

一、翻页:

1、在Core.Entity 中添加 QueryParameters.cs 类

namespace BlogDemo.Core.Entities
{
public abstract class QueryParameters : INotifyPropertyChanged
{
private const int DefaultPageSize = ;
private const int DefaultMaxPageSize = ; private int _pageIndex;
public int PageIndex
{
get => _pageIndex;
set => _pageIndex = value >= ? value : ;
} private int _pageSize = DefaultPageSize;
public virtual int PageSize
{
get => _pageSize;
set => SetField(ref _pageSize, value);
} private string _orderBy;
public string OrderBy
{
get => _orderBy;
set => _orderBy = value ?? nameof(IEntity.Id);
} private int _maxPageSize = DefaultMaxPageSize;
protected internal virtual int MaxPageSize
{
get => _maxPageSize;
set => SetField(ref _maxPageSize, value);
} public string Fields { get; set; } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
} protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
if (propertyName == nameof(PageSize) || propertyName == nameof(MaxPageSize))
{
SetPageSize();
}
return true;
} private void SetPageSize()
{
if (_maxPageSize <= )
{
_maxPageSize = DefaultMaxPageSize;
}
if (_pageSize <= )
{
_pageSize = DefaultPageSize;
}
_pageSize = _pageSize > _maxPageSize ? _maxPageSize : _pageSize;
}
}
}

2、在BlogDemo.Core.Entities 中添加  PostParameters.cs 类

namespace BlogDemo.Core.Entities
{
public class PostParameters:QueryParameters
{
public string Title { get; set; }
}
}

3、 修改 BlogDemo.Infrastructure.Repositories 文件夹 的 PostRepository类 中的 方法

        public async Task<PaginatedList<Post>> GetPostsAsync(PostParameters parameters)
{ var Query = _myContext.Posts.AsQueryable(); Query = Query.OrderBy(x => x.Id); var count = await Query.CountAsync();
var data = await Query.Skip(parameters.PageIndex * parameters.PageSize).Take(parameters.PageSize).ToListAsync();
return new PaginatedList<Post>(parameters.PageIndex, parameters.PageSize,count,data);
}

4、修改Controller中的Action

        public async Task<PaginatedList<Post>> GetPostsAsync(PostParameters parameters)
{ var Query = _myContext.Posts.AsQueryable();
Query = Query.OrderBy(x => x.Id);
var count = await Query.CountAsync();
var data = await Query.Skip(parameters.PageIndex * parameters.PageSize).Take(parameters.PageSize).ToListAsync();
return new PaginatedList<Post>(parameters.PageIndex, parameters.PageSize,count,data);
}

二、返回翻页元数据

  • 如果将数据和翻页元数据一起返回:

响应的body不再符合Accept Header了(不是资源的application/json), 这是一种新的media type.
           违反REST约束, API消费者不知道如何通过application/json这个类型来解释响应的数据.

  • 翻页数据不是资源表述的一部分, 应使用自定义Header (“X-Pagination”).
  • 存放翻页数据的类: PaginatedList<T>可以继承于List<T>.

1、添加存放翻页数据的类:PaginatedList<T>可以继承于List<T>:

namespace BlogDemo.Core.Entities
{
public class PaginatedList<T> : List<T> where T : class
{
public int PageSize { get; set; }
public int PageIndex { get; set; } private int _totalItemsCount;
public int TotalItemsCount
{
get => _totalItemsCount;
set => _totalItemsCount = value >= ? value : ;
} public int PageCount => TotalItemsCount / PageSize + (TotalItemsCount % PageSize > ? : ); public bool HasPrevious => PageIndex > ;
public bool HasNext => PageIndex < PageCount - ; public PaginatedList(int pageIndex, int pageSize, int totalItemsCount, IEnumerable<T> data)
{
PageIndex = pageIndex;
PageSize = pageSize;
TotalItemsCount = totalItemsCount;
AddRange(data);
}
}
}

2、修改PostRepository..cs 中的Get方法

        public async Task<PaginatedList<Post>> GetPostsAsync(PostParameters parameters)
{ var Query = _myContext.Posts.AsQueryable();
Query = Query.OrderBy(x => x.Id);
var count = await Query.CountAsync();
var data = await Query.Skip(parameters.PageIndex * parameters.PageSize).Take(parameters.PageSize).ToListAsync();
return new PaginatedList<Post>(parameters.PageIndex, parameters.PageSize,count,data);
}

3、修改Controller中的Action

        public async Task<IActionResult>  Get(PostParameters parameters)
{
var posts = await _postRepository.GetPostsAsync(parameters);
var postDto=_mapper.Map<IEnumerable<Post>,IEnumerable<PostDTO>>(posts); var meta = new
{
PageSize = posts.PageSize,
PageIndex = posts.PageIndex,
TotalItemCount = posts.TotalItemsCount,
PageCount = posts.PageCount, };
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(meta, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
})); return Ok(postDto);
}

4、PostMan测试

三、生成前后页的URI

1、ConfiguraServices注册IUrlHelper,IActionContextAccessor

            services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IUrlHelper>(factory =>
{
var actionContext = factory.GetService<IActionContextAccessor>().ActionContext;
return new UrlHelper(actionContext);
});

2、Controller 编写方法返回URL

        private string CreatePostUri(PostParameters parameters, PaginationResourceUriType uriType)
{
switch (uriType)
{
case PaginationResourceUriType.PreviousPage:
var previousParameters = new
{
pageIndex = parameters.PageIndex - ,
pageSize = parameters.PageSize,
orderBy = parameters.OrderBy,
fields = parameters.Fields
};
return _urlHelper.Link("GetPosts", previousParameters);
case PaginationResourceUriType.NextPage:
var nextParameters = new
{
pageIndex = parameters.PageIndex + ,
pageSize = parameters.PageSize,
orderBy = parameters.OrderBy,
fields = parameters.Fields
};
return _urlHelper.Link("GetPosts", nextParameters);
default:
var currentParameters = new
{
pageIndex = parameters.PageIndex,
pageSize = parameters.PageSize,
orderBy = parameters.OrderBy,
fields = parameters.Fields
};
return _urlHelper.Link("GetPosts", currentParameters);
}
}
        [HttpGet(Name = "GetPosts")]
        public async Task<IActionResult>  Get(PostParameters parameters)
{
var posts = await _postRepository.GetPostsAsync(parameters);
var postDto=_mapper.Map<IEnumerable<Post>,IEnumerable<PostDTO>>(posts);
var previousPageLink = posts.HasPrevious ?
CreatePostUri(parameters, PaginationResourceUriType.PreviousPage) : null; var nextPageLink = posts.HasNext ?
CreatePostUri(parameters, PaginationResourceUriType.NextPage) : null;
var meta = new
{
PageSize = posts.PageSize,
PageIndex = posts.PageIndex,
TotalItemCount = posts.TotalItemsCount,
PageCount = posts.PageCount,
previousPageLink,
nextPageLink
};
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(meta, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
})); return Ok(postDto);
}

3、Postman测试

四、 过滤和搜索

过滤: 对集合资源附加一些条件, 筛选出结果.

1、 在PostParameters.cs类中,添加过滤字段;

   public class PostParameters:QueryParameters
{
public string Title { get; set; }
}

2、修改 PostRepository.cs 中的方法:

        public async Task<PaginatedList<Post>> GetPostsAsync(PostParameters parameters)
{ var Query = _myContext.Posts.AsQueryable();
if (!string.IsNullOrEmpty(parameters.Title))
{
var title = parameters.Title.ToLowerInvariant();
Query = Query.Where(x => x.Title.ToLowerInvariant()==title); } Query = Query.OrderBy(x => x.Id); var count = await Query.CountAsync();
var data = await Query.Skip(parameters.PageIndex * parameters.PageSize).Take(parameters.PageSize).ToListAsync();
return new PaginatedList<Post>(parameters.PageIndex, parameters.PageSize,count,data);
}

3、Postman测试

五、排序

  • 翻页需要排序.
  • 让资源按照资源的某个属性或多个属性进行正向或反向的排序.
  • Resource Model的一个属性可能会映射到Entity Model的多个属性上
  • Resource Model上的正序可能在Entity Model上就是倒序的
  • 需要支持多属性的排序
  • 复用

1、在 BlogDemo.Infrastructure nuget包 添加 System.Linq.Dynamic.Core

2、添加映射属性、属性映射、容器等类;

namespace BlogDemo.Infrastructure.Services
{
public class MappedProperty
{
public string Name { get; set; }
public bool Revert { get; set; }
}
}
    public abstract class PropertyMapping<TSource, TDestination> : IPropertyMapping
where TDestination : IEntity
{
public Dictionary<string, List<MappedProperty>> MappingDictionary { get; } protected PropertyMapping(Dictionary<string, List<MappedProperty>> mappingDictionary)
{
MappingDictionary = mappingDictionary;
MappingDictionary[nameof(IEntity.Id)] = new List<MappedProperty>
{
new MappedProperty { Name = nameof(IEntity.Id), Revert = false}
};
}
}
namespace BlogDemo.Infrastructure.Services
{
public interface IPropertyMapping
{
Dictionary<string, List<MappedProperty>> MappingDictionary { get; }
}
}
namespace BlogDemo.Infrastructure.Services
{
public interface IPropertyMappingContainer
{
void Register<T>() where T : IPropertyMapping, new();
IPropertyMapping Resolve<TSource, TDestination>() where TDestination : IEntity;
bool ValidateMappingExistsFor<TSource, TDestination>(string fields) where TDestination : IEntity;
}
}
namespace BlogDemo.Infrastructure.Services
{
public class PropertyMappingContainer : IPropertyMappingContainer
{
protected internal readonly IList<IPropertyMapping> PropertyMappings = new List<IPropertyMapping>(); public void Register<T>() where T : IPropertyMapping, new()
{
if (PropertyMappings.All(x => x.GetType() != typeof(T)))
{
PropertyMappings.Add(new T());
}
} public IPropertyMapping Resolve<TSource, TDestination>() where TDestination : IEntity
{
var matchingMapping = PropertyMappings.OfType<PropertyMapping<TSource, TDestination>>().ToList();
if (matchingMapping.Count == )
{
return matchingMapping.First();
} throw new Exception($"Cannot find property mapping instance for <{typeof(TSource)},{typeof(TDestination)}");
} public bool ValidateMappingExistsFor<TSource, TDestination>(string fields) where TDestination : IEntity
{
var propertyMapping = Resolve<TSource, TDestination>(); if (string.IsNullOrWhiteSpace(fields))
{
return true;
} var fieldsAfterSplit = fields.Split(',');
foreach (var field in fieldsAfterSplit)
{
var trimmedField = field.Trim();
var indexOfFirstSpace = trimmedField.IndexOf(" ", StringComparison.Ordinal);
var propertyName = indexOfFirstSpace == - ? trimmedField : trimmedField.Remove(indexOfFirstSpace);
if (string.IsNullOrWhiteSpace(propertyName))
{
continue;
}
if (!propertyMapping.MappingDictionary.ContainsKey(propertyName))
{
return false;
}
}
return true;
}
}
}
namespace BlogDemo.Infrastructure.Extensions
{
public static class QueryableExtensions
{
public static IQueryable<T> ApplySort<T>(this IQueryable<T> source, string orderBy, IPropertyMapping propertyMapping)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
} if (propertyMapping == null)
{
throw new ArgumentNullException(nameof(propertyMapping));
} var mappingDictionary = propertyMapping.MappingDictionary;
if (mappingDictionary == null)
{
throw new ArgumentNullException(nameof(mappingDictionary));
} if (string.IsNullOrWhiteSpace(orderBy))
{
return source;
} var orderByAfterSplit = orderBy.Split(',');
foreach (var orderByClause in orderByAfterSplit.Reverse())
{
var trimmedOrderByClause = orderByClause.Trim();
var orderDescending = trimmedOrderByClause.EndsWith(" desc");
var indexOfFirstSpace = trimmedOrderByClause.IndexOf(" ", StringComparison.Ordinal);
var propertyName = indexOfFirstSpace == - ?
trimmedOrderByClause : trimmedOrderByClause.Remove(indexOfFirstSpace);
if (string.IsNullOrEmpty(propertyName))
{
continue;
}
if (!mappingDictionary.TryGetValue(propertyName, out List<MappedProperty> mappedProperties))
{
throw new ArgumentException($"Key mapping for {propertyName} is missing");
}
if (mappedProperties == null)
{
throw new ArgumentNullException(propertyName);
}
mappedProperties.Reverse();
foreach (var destinationProperty in mappedProperties)
{
if (destinationProperty.Revert)
{
orderDescending = !orderDescending;
}
source = source.OrderBy(destinationProperty.Name + (orderDescending ? " descending" : " ascending"));
}
} return source;
} public static IQueryable<object> ToDynamicQueryable<TSource>
(this IQueryable<TSource> source, string fields, Dictionary<string, List<MappedProperty>> mappingDictionary)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
} if (mappingDictionary == null)
{
throw new ArgumentNullException(nameof(mappingDictionary));
} if (string.IsNullOrWhiteSpace(fields))
{
return (IQueryable<object>)source;
} fields = fields.ToLower();
var fieldsAfterSplit = fields.Split(',').ToList();
if (!fieldsAfterSplit.Contains("id", StringComparer.InvariantCultureIgnoreCase))
{
fieldsAfterSplit.Add("id");
}
var selectClause = "new ("; foreach (var field in fieldsAfterSplit)
{
var propertyName = field.Trim();
if (string.IsNullOrEmpty(propertyName))
{
continue;
} var key = mappingDictionary.Keys.SingleOrDefault(k => String.CompareOrdinal(k.ToLower(), propertyName.ToLower()) == );
if (string.IsNullOrEmpty(key))
{
throw new ArgumentException($"Key mapping for {propertyName} is missing");
}
var mappedProperties = mappingDictionary[key];
if (mappedProperties == null)
{
throw new ArgumentNullException(key);
}
foreach (var destinationProperty in mappedProperties)
{
selectClause += $" {destinationProperty.Name},";
}
} selectClause = selectClause.Substring(, selectClause.Length - ) + ")";
return (IQueryable<object>)source.Select(selectClause);
} }
}

3、在ConfigureServices中注入

        public void ConfigureServices(IServiceCollection services)
{ //排序
var propertyMappingContainer = new PropertyMappingContainer();
propertyMappingContainer.Register<PostPropertyMapping>();
services.AddSingleton<IPropertyMappingContainer>(propertyMappingContainer); }

4、修改 PostRepository.cs 中的方法:

        public async Task<PaginatedList<Post>> GetPostsAsync(PostParameters parameters)
{ var Query = _myContext.Posts.AsQueryable();
if (!string.IsNullOrEmpty(parameters.Title))
{
var title = parameters.Title.ToLowerInvariant();
Query = Query.Where(x => x.Title.ToLowerInvariant()==title); }
Query = Query.ApplySort(parameters.OrderBy, _propertyMappingContainer.Resolve<PostDTO, Post>()); var count = await Query.CountAsync();
var data = await Query.Skip(parameters.PageIndex * parameters.PageSize).Take(parameters.PageSize).ToListAsync();
return new PaginatedList<Post>(parameters.PageIndex, parameters.PageSize,count,data);
}

ASP NET Core --- HTTP 翻页、过滤、排序的更多相关文章

  1. 解决 ASP.NET Core 自定义错误页面对 Middleware 异常无效的问题

    我们基于 Razor Class Library 实现了自定义错误页面的公用类库(详见之前的随笔),但是在实际使用时发现如果在 middleware 中发生了异常,则不能显示自定义错误页面,而是返回默 ...

  2. PHP.25-TP框架商城应用实例-后台2-商品列表页-搜索、翻页、排序

    商品列表页 1.翻页 控制器GoodsController.class.php添加方法lst(),显示列表页 在商品模型GoodsModel.class.php类中添加search方法 /** *实现 ...

  3. ASP.NET Core WebAPI帮助页--Swagger简单使用1.0

    1.什么是Swagger? Swagger是一个规范且完整的框架,提供描述.生产.消费和可视化RESTful API,它是为了解决Web API生成有用文档和帮助页的问题.   2.为啥选用swagg ...

  4. ASP.NET Core 2.1 Web API + Identity Server 4 + Angular 6 + Angular Material 实战小项目视频

    视频简介 ASP.NET Core Web API + Angular 6的教学视频 我是后端开发人员, 前端的Angular部分讲的比较差一些, 可以直接看代码!!!! 这是一个小项目的实战视频, ...

  5. 用ASP.NET Core 2.1 建立规范的 REST API -- 翻页/排序/过滤等

    本文所需的一些预备知识可以看这里: http://www.cnblogs.com/cgzl/p/9010978.html 和 http://www.cnblogs.com/cgzl/p/9019314 ...

  6. 在ASP.NET Core中通过EF Core实现一个简单的全局过滤查询

    前言 不知道大家是否和我有同样的问题: 一般在数据库的设计阶段,会制定一些默认的规则,其中有一条硬性规定就是一定不要对任何表中的数据执行delete硬删除操作,因为每条数据对我们来说都是有用的,并且是 ...

  7. ASP.NET Core Web API 索引 (更新Identity Server 4 视频教程)

    GraphQL 使用ASP.NET Core开发GraphQL服务器 -- 预备知识(上) 使用ASP.NET Core开发GraphQL服务器 -- 预备知识(下) [视频] 使用ASP.NET C ...

  8. ASP.NET Core Restful Web API 相关资源索引

    GraphQL 使用ASP.NET Core开发GraphQL服务器 -- 预备知识(上) 使用ASP.NET Core开发GraphQL服务器 -- 预备知识(下) [视频] 使用ASP.NET C ...

  9. 学习ASP.NET Core(08)-过滤搜索与分页排序

    上一篇我们介绍了AOP的基本概览,并使用动态代理的方式添加了服务日志:本章我们将介绍过滤与搜索.分页与排序并添加对应的功能 注:本章内容大多是基于solenovex的使用 ASP.NET Core 3 ...

随机推荐

  1. WebNotes(PHP、css、JavaScript等)

    1. 数据库编码格式 gb-2312仅支持简体中文,GBK支持简体.繁体中文,utf-8通用程度最高. 2. HTTP请求方法 get方法请求时,会将传输的数据跟在链接后“显式地”发送,受限于链接长度 ...

  2. 手写数字识别的k-近邻算法实现

    (本文为原创,请勿在未经允许的情况下转载) 前言 手写字符识别是机器学习的入门问题,k-近邻算法(kNN算法)是机器学习的入门算法.本文将介绍k-近邻算法的原理.手写字符识别问题分析.手写字符识别的k ...

  3. HDFS副本存放读取

    HDFS作为Hadoop中 的一个分布式文件系统,而且是专门为它的MapReduce设计,所以HDFS除了必须满足自己作为分布式文件系统的高可靠性外,还必须为 MapReduce提供高效的读写性能,那 ...

  4. Sublime Text Build 3065 License key

      Sublime Text Build 3065 License key 复制如下三个任意一个正版注册码即可 —– BEGIN LICENSE —– Andrew Weber Single User ...

  5. ASP.NET MVC中使用表单上传文件时的注意事项

    最近了好久没写ASP.NET 使用HTML的FORM来上传文件了,结果写了个文件上传发现ASP.NET MVC的Controller中老是读取不到上传的文件. MVC的View(Index.cshtm ...

  6. AngularJS 五 过滤器及验证

    AngularJS过滤: AngularJS过滤器允许我们格式化数据以在UI上显示而不改变原始格式. 格式: 一些比较重要的过滤器: Number               Filter       ...

  7. Go转json数组

    Go转json数组 最近因需要要调用gitlab的API,其中有一个是根据私有token获取Repositories列表 由于返回结果是一个json数组,单纯使用json.Unmarshal没法实现, ...

  8. SpringBoot非官方教程 | 第三篇:SpringBoot用JdbcTemplates访问Mysql

    转载请标明出处: 原文首发于https://www.fangzhipeng.com/springboot/2017/07/11/springboot3-JdbcTemplates-Mysql/ 本文出 ...

  9. [Windows]ping itsafe&环境变量

    (1)when you ping a computer from itsafe,the ping command should return the local IP address. (2)wind ...

  10. CentOS 7 安装oracle 11G

    一.安装Oracle前准备 首先要设置主机名,并在/etc/hosts下解析. 镜像没挂全,导致缺少包pdksh-5.2.14.compat-libstdc++-33-3.2.3 1.创建运行orac ...