参照 草根专栏- 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. xml或其他附件下载到客户端

    //xml Document document=DocumentHelper.createDocument(); Element root=document.addElement("root ...

  2. linux 学习(三) php相关

    五 php相关 配置文件位置 /etc/apache2/apache2.conf 1禁止列举目录 sudo vi /etc/apache2/sites-enabled/000-default 删除Op ...

  3. 64 位系统(win7/win8) 下使用C# 程序问题

    1  C# 程序是控制台类,使用的组件如果是32位,建议在编译的时候,platform (X86,AnyCPU,X64)选择X86 .使用X86 模式编译,才能调用32位程序的API. 2  ASP. ...

  4. html 固定长度 超出长度 显示省略号

    a{         width: 80px;/* 要显示文字的宽度 */         float: left;/* 左对齐,不设置的话只在IE下好用 */         overflow: h ...

  5. 配置web项目session永不超时

    众所周知,当用户登录网站后较长一段时间没有与服务器进行交互,将会导致服务器上的用户会话数据(即session)被销毁.此时,当用户再次操作网页时,如果服务器进行了session校验,那么浏览器将会提醒 ...

  6. svg了解一下

    工作需求,要用svg动态生成思维导图.我的天,这是我的短板. 但是没办法,需求在这,硬着头皮上吧. 本来想偷懒,看看网上有没有现成的可以copy的,逛了一圈发现没有. 这个过程种发现了D3.Three ...

  7. Python 学习笔记(七)Python字符串(二)

    索引和切片 索引  是从0开始计数:当索引值为负数时,表示从最后一个元素(从右到左)开始计数 切片 用于截取某个范围内的元素,通过:来指定起始区间(左闭右开区间,包含左侧索引值对应的元素,但不包含右测 ...

  8. rest_framework --- APIView

    一.什么是rest_framework 它是基于Django的,帮助我们快速开发符合RESTful规范的接口框架. 安装方式有很多种,可以通过pip,或者在pycharm中安装也可以 二.APIVie ...

  9. django-初始配置(纯手写)

    我们通过django-admin startproject zhuyu命令创建好项目后,在pycharm中打开 我们需要在在该项目中,配置一些相关操作. 1.template(存放模板的文件夹) 如果 ...

  10. 数论(一)LOJ1282

    1.题目来源LOJ1282 You are given two integers: n and k, your task is to find the most significant three d ...