分页

举报
步步为营 发表于 2023/03/02 11:21:19 2023/03/02
【摘要】 1. program中注册`builder.Services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();` 2. 控制器中增加依赖

分页

分页后需要将分页数据返回到head文件中,如
在这里插入图片描述
实现过程

  1. program中注册builder.Services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();

  2. 控制器中增加依赖

private readonly IUrlHelper _urlHelper;
public TouristRoutesController( IActionContextAccessor actionContextAccessor)
{
 	_urlHelper = urlHelperFactory.GetUrlHelper(actionContextAccessor.ActionContext);
}
  1. 生成前一页和后一页的方法
public enum ResourceUriType
{
    PreviousPage,
    NextPage
}

private string? GenerateTouristRouteResourceURL(TouristRouteResourcecParamaters paramaters, PaginationResourceParamaters paramaters2, ResourceUriType type)
{
    return type switch
    {
        ResourceUriType.PreviousPage => _urlHelper.Link("GetTouristRoutes",
        new
        {
            keyword = paramaters.Keyword,
            rating = paramaters.Rating,
            pageNumber = paramaters2.PageNumber - 1,
            pageSize = paramaters2.PageSize
        }),
        ResourceUriType.NextPage => _urlHelper.Link("GetTouristRoutes",
        new
        {
            keyword = paramaters.Keyword,
            rating = paramaters.Rating,
            pageNumber = paramaters2.PageNumber + 1,
            pageSize = paramaters2.PageSize
        }),
        _ => _urlHelper.Link("GetTouristRoutes",
        new
        {
            keyword = paramaters.Keyword,
            rating = paramaters.Rating,
            pageNumber = paramaters2.PageNumber,
            pageSize = paramaters2.PageSize
        })
    };
}
  1. 控制器中的查询操作方法
[HttpGet(Name = "GetTouristRoutes")]
[HttpHead]
[Authorize(AuthenticationSchemes = "Bearer")]//identity多角色验证时必须指定
public async Task<IActionResult> GetTouristRoutes([FromQuery] TouristRouteResourcecParamaters paramaters, [FromQuery] PaginationResourceParamaters paramaters1)
{

    var routes = await _tourisTouteRepository.GetTourisRoutesAsync(paramaters.Keyword, paramaters.RatingOperator,paramaters.RatingValue, paramaters1.PageSize,paramaters1.PageNumber);
    if (routes == null || routes.Count() <= 0)
    {
        return NotFound("没有旅游路线");
    }
    var touristRouteDto = _mapper.Map<IEnumerable<TouristRouteDto>>(routes);
    //生成前页面链接
    var previousPageLink = routes.HasPrevious ? GenerateTouristRouteResourceURL(paramaters, paramaters1, ResourceUriType.PreviousPage) : null;
    //生成前页面链接
    var nextPageLink = routes.HasNext ? GenerateTouristRouteResourceURL(paramaters, paramaters1, ResourceUriType.NextPage) : null;

    //增加头部信息
    var paginationMetadata = new
    {
        previousPageLink,
        nextPageLink,
        totalCount = routes.TotalCount,
        pageSize = routes.PageSize,
        currentPage = routes.CurrentPage,
        totalPages = routes.TotalPages
    };
    Response.Headers.Add("x-pagination",Newtonsoft.Json.JsonConvert.SerializeObject(paginationMetadata));
    return Ok(touristRouteDto);
}

在这里插入图片描述

排序

思路为解析地址栏中用户想以哪个字段进行那种排序(升降),核心就是要引用using System.Linq.Dynamic.Core;可以通过字符串,而不是对象使用result.OrderBy(t => t.OriginalPrice);

  1. 映射值类
public class PropertyMappingValue
{
    //将要被映射的属性列表
    public IEnumerable<string> DestinationProperties { get; private set; }
    public PropertyMappingValue(IEnumerable<string> destinationProperties)
    {
        DestinationProperties = destinationProperties;
    }
}
  1. 映射类
//为了方便注入,先定义接口
public interface IPropertyMapping
{
}
public class PropertyMapping<TSource,TDestination>:IPropertyMapping
{
    //属性所匹配的字符串字典
    public Dictionary<string,PropertyMappingValue> _mappingDictionary { set; get; }
    public PropertyMapping(Dictionary<string, PropertyMappingValue> mappingDictionary)
    {
        _mappingDictionary = mappingDictionary;
    }
}
  1. 映射服务类
//为了方便注入,先定义接口
public interface IPropertyMappingService
{
    Dictionary<string, PropertyMappingValue> GetPropertyMapping<TSource, TDestination>();
    bool IsMappingExists<TSource, TDestingation>(string fields);
}
public class PropertyMappingService : IPropertyMappingService
{
    //设定字符串与PropertyMappingValue的对应关系
    private Dictionary<string, PropertyMappingValue> _touristRoutePropertyMapping =
       new Dictionary<string, PropertyMappingValue>(StringComparer.OrdinalIgnoreCase)
       {
           { "Id", new PropertyMappingValue(new List<string>(){ "Id" }) },
           { "Title", new PropertyMappingValue(new List<string>(){ "Title" })},
           { "Rating", new PropertyMappingValue(new List<string>(){ "Rating" })},
           { "OriginalPrice", new PropertyMappingValue(new List<string>(){ "OriginalPrice" })},
       };
	//定义一个映射对象,映射对象里面包含字符串和PropertyMappingValue对应的字典
    private IList<IPropertyMapping> _propertyMappings = new List<IPropertyMapping>();

    public PropertyMappingService()
    {	//将新建的对应列表加入到私有映射对象
        _propertyMappings.Add(
            new PropertyMapping<TouristRouteDto, TouristRoute>(
                _touristRoutePropertyMapping));
    }

    public Dictionary<string, PropertyMappingValue>
        GetPropertyMapping<TSource, TDestination>()
    {
        // 获得匹配的映射对象
        var matchingMapping =
            _propertyMappings.OfType<PropertyMapping<TSource, TDestination>>();

        if (matchingMapping.Count() == 1)
        {
            return matchingMapping.First()._mappingDictionary;
        }

        throw new Exception(
            $"Cannot find exact property mapping instance for <{typeof(TSource)},{typeof(TDestination)}");

    }

    public bool IsMappingExists<TSource, TDestination>(string fields)
    {
        var propertyMapping = GetPropertyMapping<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(" ");
            var propertyName = indexOfFirstSpace == -1 ?
                trimmedField : trimmedField.Remove(indexOfFirstSpace);

            if (!propertyMapping.ContainsKey(propertyName)) 
            {
                return false;
            }
        }
        return true;
    }
}

  1. nuget安装System.Linq.Dynamic.Core。IQueryable扩展类
public static class IQueryableExtensions
{
    public static IQueryable<T> ApplySort<T>(
        this IQueryable<T> source,
        string orderBy,
        Dictionary<string, PropertyMappingValue> mappingDictionary
    )
    {
        if (source == null)
        {
            throw new ArgumentNullException("source");
        }

        if (mappingDictionary == null)
        {
            throw new ArgumentNullException("mappingDictionary");
        }

        if (string.IsNullOrWhiteSpace(orderBy))
        {
            return source;
        }

        var orderByString = string.Empty;

        var orderByAfterSplit = orderBy.Split(',');

        foreach(var order in orderByAfterSplit)
        {
            var trimmedOrder = order.Trim();

            // 通过字符串“ desc”来判断升序还是降序
            var orderDescending = trimmedOrder.EndsWith(" desc");

            // 删除升序或降序字符串 " asc" or " desc"来获得属性的名称
            var indexOfFirstSpace = trimmedOrder.IndexOf(" ");
            var propertyName = indexOfFirstSpace == -1
                ? trimmedOrder
                : trimmedOrder.Remove(indexOfFirstSpace);

            if (!mappingDictionary.ContainsKey(propertyName))
            {
                throw new ArgumentException($"Key mapping for {propertyName} is missing");
            }
			
            PropertyMappingValue  propertyMappingValue = mappingDictionary[propertyName];
            if (propertyMappingValue == null)
            {
                throw new ArgumentNullException("propertyMappingValue");
            }

            foreach(var destinationProperty in 
                propertyMappingValue.DestinationProperties.Reverse())
            {
                // 给IQueryable 添加排序字符串
                orderByString = orderByString +
                    (string.IsNullOrWhiteSpace(orderByString) ? string.Empty : ", ")
                    + destinationProperty
                    + (orderDescending ? " descending" : " ascending");
            }
        }
        //核心
        //要引用using System.Linq.Dynamic.Core;可以通过字符串,而不是result.OrderBy(t => t.OriginalPrice);
        return source.OrderBy(orderByString);
    }
}
  1. program注册builder.Services.AddTransient<IPropertyMappingService, PropertyMappingService>();

  2. 在数据库服务仓库中

...
if (!string.IsNullOrWhiteSpace(orderBy))
{
    var touristRouteMappingDictonary = _propertyMappingService.GetPropertyMapping<TouristRouteDto, TouristRoute>();
    result= result.ApplySort(orderBy, touristRouteMappingDictonary);
}
return await PaginationList<TouristRoute>.CreateAsync(pageNumber,pageSize,result);

  1. 控制器中的操作方法
//判断输入中是否有匹配的相应类型
if (!_propertyMappingService.IsMappingExists<TouristRouteDto,TouristRoute>(paramaters.OrderBy))
{
    return BadRequest("输入正确排序参数");
}

数据塑形

在某些条件下,用户只想得到商品id和名称,如果返回商品的所有信息则造成了带宽的浪费。

  1. 对象或者集合的扩展方法,以集合为例
public static class IEnumberableExtensions
{
    public static IEnumerable<ExpandoObject> ShapeData<TSource>(this IEnumerable<TSource> source,string fields)
    {
        if (source == null)
        {
            throw new ArgumentNullException(nameof(source));
        }
        var expandoObjectList = new List<ExpandoObject>();//保存动态对象

        //避免列表中遍历数据,创建一个属性信息列表
        var propertyInfoList = new List<PropertyInfo>();

        if (string.IsNullOrWhiteSpace(fields))
        {
            //获得TSource所有的属性和实例
            var propertyInfos = typeof(TSource)
                .GetProperties(BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
            propertyInfoList.AddRange(propertyInfos);
        }
        else
        {
            var fieldAfterSplit = fields.Split(',');
            foreach (var field in fieldAfterSplit)
            {
                var propertyName = field.Trim();
                var propertyInfo = typeof(TSource)
                    .GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
                if (propertyInfo == null )
                {
                    throw new ArgumentNullException("无相关属性");
                }
                propertyInfoList.Add(propertyInfo);
            }
        }
        foreach (TSource sourceObject in source )
        {
            //创建动态对象
            var dataShapedObject = new ExpandoObject();
            foreach (var propertyInfo in propertyInfoList)
            {
                //获得对应属性的真实数据
                var propertyValue = propertyInfo.GetValue(sourceObject);
                ((IDictionary<string,object>)dataShapedObject).Add(propertyInfo.Name, propertyValue);
            }
            expandoObjectList.Add(dataShapedObject);
        }
        return expandoObjectList;
    }
}

  1. 只需要在操作方法的返回值中使用return Ok(touristRouteDto.ShapeData(Fields));访问地址为https://localhost:7243/api/touristroutes?fields=id,title

多媒体HATEOAS

即在返回数据的同时,返回相关的自发现链接
在这里插入图片描述

private IEnumerable<LinkDto> CreateLinkForTouristRoute(Guid touristRouteId, string? fields)
{
    var links = new List<LinkDto>();
    links.Add(
        new LinkDto(
                Url.Link("GetTouristRouteById",new { touristRouteId , fields }),
                "self",
                "Get"
            )
        
        );
    return links;
}

[HttpGet("{touristRouteId:Guid}", Name = "GetTouristRouteById")]
public async Task<IActionResult> GetTouristRouteById(Guid touristRouteId,string? fields, [FromHeader(Name ="Accept")] string mediaType)
{
    var routes = await _tourisTouteRepository.GetTourisRouteAsync(touristRouteId);

    if (routes == null)
    {
        return NotFound($"旅游路线{touristRouteId}找不到");
    }
    var touristRouteDto = _mapper.Map<TouristRouteDto>(routes);
    var linkDtos = CreateLinkForTouristRoute(touristRouteId, fields);
    var result = routes.ShapeData(fields) as IDictionary<string, object>; //数据塑形
	result.Add("links", linkDtos);
    return Ok(result);
}

某些情况,会不想返回自发现链接,这时候就需要head中的内容协商来解决,如头文件中Accept:"application/vnd.qs.hateoas+json"
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

全局设定媒体类型

  1. program
builder.Services.Configure<MvcOptions>(config => {
    var outputFormatter = config.OutputFormatters.OfType<NewtonsoftJsonOutputFormatter>()?.FirstOrDefault();
    if (outputFormatter !=null)
    {
        outputFormatter.SupportedMediaTypes.Add("application/vnd.qs.hateoas+json");
    }
});
  1. 操作方法
using Microsoft.Net.Http.Headers;
[HttpGet("{touristRouteId:Guid}", Name = "GetTouristRouteById")]
public async Task<IActionResult> GetTouristRouteById(Guid touristRouteId,string? fields, [FromHeader(Name ="Accept")] string mediaType)
{
 //一般为TryParse,因为可以能会设置多个
    if (!MediaTypeHeaderValue.TryParse(mediaType, out MediaTypeHeaderValue parasedMediatype))
    {
        return BadRequest();
    }
    

    var routes = await _tourisTouteRepository.GetTourisRouteAsync(touristRouteId);

    if (routes == null)
    {
        return NotFound($"旅游路线{touristRouteId}找不到");
    }
    var touristRouteDto = _mapper.Map<TouristRouteDto>(routes);
    var linkDtos = CreateLinkForTouristRoute(touristRouteId, fields);
    var result = routes.ShapeData(fields) as IDictionary<string, object>;
   
    if (parasedMediatype.MediaType == "application/vnd.qs.hateoas+json")
    {
        result.Add("links", linkDtos);

    }
    return Ok(result);
    
}
//只有头文件中的Accept中设置"application/vnd.qs.hateoas+json"才能获得link

局部设定媒体类型

//筛选器
[Produces(
    "application/json",
    "application/vnd.aleks.hateoas+json"
    )]
[HttpGet("{touristRouteId:Guid}", Name = "GetTouristRouteById")]
public async Task<IActionResult> GetTouristRouteById(Guid touristRouteId,string? fields, [FromHeader(Name ="Accept")] string mediaType)
{
    if (!MediaTypeHeaderValue.TryParse(mediaType, out MediaTypeHeaderValue parasedMediatype))
    {
        return BadRequest();
    }
    

    var routes = await _tourisTouteRepository.GetTourisRouteAsync(touristRouteId);

    if (routes == null)
    {
        return NotFound($"旅游路线{touristRouteId}找不到");
    }
    var touristRouteDto = _mapper.Map<TouristRouteDto>(routes);
    var linkDtos = CreateLinkForTouristRoute(touristRouteId, fields);
    var result = routes.ShapeData(fields) as IDictionary<string, object>;
	//SubTypeWithoutSuffix会把后面的+json去掉
    bool isHateoas = parasedMediatype.SubTypeWithoutSuffix.EndsWith("hateoas", StringComparison.InvariantCultureIgnoreCase);
    


    if (isHateoas)
    {
        result.Add("links", linkDtos);

    }
    return Ok(result);

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。