• 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 Code

    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 - 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> : 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 == 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);
            }
  • 相关阅读:
    Django
    闭包&装饰器
    Python学习 Day 062
    Python学习 Day 059
    Python学习 Day 058
    Python生成器
    第一类对象(函数),闭包及迭代器
    进阶函数的学习
    对函数的初步了解
    python文件操作
  • 原文地址:https://www.cnblogs.com/fuyouchen/p/9592498.html
Copyright © 2020-2023  润新知