ASP NET Core --- HTTP 翻页、过滤、排序
生活随笔
收集整理的這篇文章主要介紹了
ASP NET Core --- HTTP 翻页、过滤、排序
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
參照 草根專欄- 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 = 10;private const int DefaultMaxPageSize = 100;private int _pageIndex;public int PageIndex{get => _pageIndex;set => _pageIndex = value >= 0 ? value : 0;}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 <= 0){_maxPageSize = DefaultMaxPageSize;}if (_pageSize <= 0){_pageSize = DefaultPageSize;}_pageSize = _pageSize > _maxPageSize ? _maxPageSize : _pageSize;}} } View Code?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 >= 0 ? value : 0;}public int PageCount => TotalItemsCount / PageSize + (TotalItemsCount % PageSize > 0 ? 1 : 0);public bool HasPrevious => PageIndex > 0;public bool HasNext => PageIndex < PageCount - 1;public PaginatedList(int pageIndex, int pageSize, int totalItemsCount, IEnumerable<T> data){PageIndex = pageIndex;PageSize = pageSize;TotalItemsCount = totalItemsCount;AddRange(data);}} } View Code2、修改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 - 1,pageSize = parameters.PageSize,orderBy = parameters.OrderBy,fields = parameters.Fields};return _urlHelper.Link("GetPosts", previousParameters);case PaginationResourceUriType.NextPage:var nextParameters = new{pageIndex = parameters.PageIndex + 1,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);}} View Code [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> : IPropertyMappingwhere 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 == 1){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 == -1 ? trimmedField : trimmedField.Remove(indexOfFirstSpace);if (string.IsNullOrWhiteSpace(propertyName)){continue;}if (!propertyMapping.MappingDictionary.ContainsKey(propertyName)){return false;}}return true;}} } View Code 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 == -1 ?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()) == 0);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(0, selectClause.Length - 1) + ")";return (IQueryable<object>)source.Select(selectClause);}} } View Code?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);}轉載于:https://www.cnblogs.com/fuyouchen/p/9592498.html
總結
以上是生活随笔為你收集整理的ASP NET Core --- HTTP 翻页、过滤、排序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c/c++ base64编码后,通过ph
- 下一篇: 51Nod 1439 - 互质对(容斥+