第 6 章 高级查询和日志

6.3 排序

RESTful API 在实现排序时应支持对集合资源的一个或多个属性进行排序

示例对 authors 资源按照其属性 Age 升序排序,再按 BirthPlace 属性降序排序:https://localhost:5000/api/authors? orderby=age,birthplace desc

在 ASP.NET Core 中实现排序,与过滤和查询一样,通过对查询字符串中的排序项进行解析,然后在分页操作之前,将它们指定的排序方式进行排序,并最终返回结果

首先在 AuthorResourceParameters 中添加属性

public string SortBy { get; set; } = "Name";

接下来,在 AuthorRepository 的 GetAllAsync 方法中,使用 OrderBy 子句来实现查询

if (parameters.SortBy == "Name")
{
queryableAuthors = queryableAuthors.OrderBy(author => author.Name);
}

由于 LINQ 的 OrderBy 扩展方法不支持直接使用字符串,当资源支持多个排序字段时,一一判断比较繁琐,而且在进行后续排序时,还应该使用 ThenBy 子句,使得判断更加复杂,幸运的是可以借助第三方库 System.Linq.Dynamic.Core 实现动态 LINQ 查询

System.Linq.Dynamic.Core 除了支持直接使用属性名排序之外,还支持多属性排序,多个属性之间使用逗号隔开,每个属性默认以升序排序,若要使用降序排序,则应在属性名后添加 desc 或 descending,并以空格隔开

nuget 安装该库

Install-Package Microsoft.EntityFrameworkCore.DynamicLinq

安装成功后修改 AuthorRepository 的 GetAllAsync 方法

var orderedAuthors = queryableAuthors.OrderBy(parameters.SortBy);

return PagedList<Author>.CreateAsync(orderedAuthors, parameters.PageNumber, parameters.PageSize);

排序选项 SortBy 同样作为分页数据的一部分,应返回给客户端,在 AuthorController 的 GetAuthorsAsync 方法生成分页数据时,添加代码

previousePageLink = pagedList.HasPrevious
? Url.Link(nameof(GetAuthorsAsync), new
{
pageNumber = pagedList.CurrentPage - 1,
pageSize = pagedList.PageSize,
birthPlace = parameters.BirthPlace,
searchQuery = parameters.SearchQuery,
sortBy = parameters.SortBy
})
: null,
nextPageLink = pagedList.HasNext
? Url.Link(nameof(GetAuthorsAsync), new
{
pageNumber = pagedList.CurrentPage + 1,
pageSize = pagedList.PageSize,
birthPlace = parameters.BirthPlace,
searchQuery = parameters.SearchQuery,
sortBy = parameters.SortBy
})
: null

为了解决 DTO 与实体属性名不同时的映射问题,可以在程序中添加一个字典,来存储需要进行映射的属性及其对应的属性名

然而对于 AuthorDto 中的 Age 属性和 Author 中的 BirthDate 属性,其排序规则正好相反,即年龄越小,出生日期越靠后,这种情况下,除了要考虑映射外,还应考虑方向

namespace Library.API.Helpers
{
public class PropertyMapping
{
public bool IsRevert { get; private set; }
public string TargetProperty { get; private set; } public PropertyMapping(string targetProperty, bool isRevert = false)
{
IsRevert = isRevert;
TargetProperty = targetProperty;
}
}
}

接着,可以在 AuthorRepository 中定义一个字典

private Dictionary<string, PropertyMapping> mappingDict = null;

public AuthorRepository(DbContext dbContext) : base(dbContext)
{
mappingDict = new Dictionary<string, PropertyMapping>(StringComparer.OrdinalIgnoreCase);
mappingDict.Add("Name", new PropertyMapping("Name"));
mappingDict.Add("Age", new PropertyMapping("BirthDate", true));
mappingDict.Add("BirthPlace", new PropertyMapping("BirthPlace"));
}

为了使这一分析逻辑的通用性和 GetAllAsync 的方法简洁,可以将它放到一个扩展方法中

namespace Library.API.Extentions
{
public static class IQueryableExtention
{
private const string OrderSequence_Asc = "asc";
private const string OrderSequence_Desc = "desc"; public static IQueryable<T> Sort<T>(this IQueryable<T> source, string orderBy,
Dictionary<string, PropertyMapping> mapping) where T : class
{
var allQueryParts = orderBy.Split(',');
List<string> sortParts = new List<string>();
foreach (var item in allQueryParts)
{
string property = string.Empty;
bool isDescending = false;
if (item.ToLower().EndsWith(OrderSequence_Desc))
{
property = item.Substring(0, item.Length - OrderSequence_Desc.Length).Trim();
isDescending = true;
}
else
{
property = item.Trim();
} if (mapping.ContainsKey(property))
{
if (mapping[property].IsRevert)
{
isDescending = !isDescending;
} if (isDescending)
{
sortParts.Add($"{mapping[property].TargetProperty} {OrderSequence_Desc}");
}
else
{
sortParts.Add($"{mapping[property].TargetProperty} {OrderSequence_Asc}");
}
}
} string finalExpression = string.Join(',', sortParts);
source = source.OrderBy(finalExpression);
return source;
}
}
}

在 Sort 逻辑内部中,通过解析得到最终的排序表达式,并使用 System.Linq.Dynamic.Core 库中的 OrderBy 对 IQueryable 对象排序,并返回排序后的结果

接着,修改 AuthorRepository 的 GetAuthorsAsync 方法中的返回结果语句

//var orderedAuthors = queryableAuthors.OrderBy(parameters.SortBy);
var orderedAuthors = queryableAuthors.Sort(parameters.SortBy, mappingDict);

运行程序,请求 URL:https://localhost:5001/api/authors?pageSize=3&sortby=birthplace,age

6.4 日志与异常

ASP.NET Core 内部集成了日志的功能,但是并不支持向文件输出日志,因此我们通过 NLog 实现

安装nuget

Install-Package NLog.Extensions.Logging

NLog 通过 XML 形式的文件来配置它的使用方式,添加一个 nlog.config

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <targets>
<target name="asyncFile" xsi:type="AsyncWrapper">
<target name="log_file" xsi:type="File"
fileName="${basedir}/Logs/${shortdate}/${shortdate}.txt"
layout="${longdate} | ${message} ${onexception:${exception:format=message} ${newline} ${stacktrace} ${newline}"
archiveFileName="${basedir}/archives/${shortdate}-{#####}.txt"
archiveAboveSize="102400"
archiveNumbering="Sequence"
concurrentWrites="true"
keepFileOpen="false" />
</target>
<target name="console" xsi:type="ColoredConsole" layout="[${date:format=HH\:mm\:ss}]:${message} ${exception:format=message}" />
</targets> <rules>
<logger name="*" minlevel="Error" writeTo="asyncFile" />
<logger name="*" minlevel="Debug" writeTo="console" />
</rules>
</nlog>

在 Configure 添加如下代码

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IMapper mapper, ILoggerFactory loggerFactory)
{
loggerFactory.AddNLog();
loggerFactory.ConfigureNLog("nlog.config");
。。。

在 Controller 注入使用

public IMapper Mapper { get; set; }
public IRepositoryWrapper RepositoryWrapper { get; set; }
public ILogger<AuthorController> Logger { get; set; } public AuthorController(IRepositoryWrapper repositoryWrapper, IMapper mapper, ILogger<AuthorController> logger)
{
RepositoryWrapper = repositoryWrapper;
Mapper = mapper;
Logger = logger;
}

在 MVC 应用程序中,可以通过异常过滤器 IExceptionFilter 处理异常

首先定义 ApiError

namespace Library.API.Helpers
{
public class ApiError
{
public string Message { get; set; }
public string Detail { get; set; }
}
}

接着,添加一个过滤器

namespace Library.API.Filters
{
public class JsonExceptionFilter : IExceptionFilter
{
public IHostEnvironment Environment { get; }
public ILogger Logger { get; } public JsonExceptionFilter(IHostEnvironment environment, ILogger<Program> logger)
{
Environment = environment;
Logger = logger;
} public void OnException(ExceptionContext context)
{
var error = new ApiError();
if (Environment.IsDevelopment())
{
error.Message = context.Exception.Message;
error.Detail = context.Exception.ToString();
}
else
{
error.Message = "服务器出错";
error.Detail = context.Exception.Message;
} context.Result = new ObjectResult(error)
{
StatusCode = StatusCodes.Status500InternalServerError
}; StringBuilder sb = new StringBuilder();
sb.AppendLine($"服务器发生异常:{context.Exception.Message}");
sb.AppendLine(context.Exception.ToString());
Logger.LogCritical(sb.ToString());
}
}
}

最后将它添加到 MVC 配置中,即可生效

services.AddMvc(configure =>
{
configure.Filters.Add<JsonExceptionFilter>();
。。。

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。

《ASP.NET Core 与 RESTful API 开发实战》-- (第6章)-- 读书笔记(下)的更多相关文章

  1. 使用ASP.NET Core构建RESTful API的技术指南

    译者荐语:利用周末的时间,本人拜读了长沙.NET技术社区翻译的技术标准<微软RESTFul API指南>,打算按照步骤写一个完整的教程,后来无意中看到了这篇文章,与我要写的主题有不少相似之 ...

  2. 4类Storage方案(AS开发实战第四章学习笔记)

    4.1 共享参数SharedPreferences SharedPreferences按照key-value对的方式把数据保存在配置文件中,该配置文件符合XML规范,文件路径是/data/data/应 ...

  3. 菜单Menu(AS开发实战第四章学习笔记)

    4.5 菜单Menu Android的菜单主要分两种,一种是选项菜单OptionMenu,通过按菜单键或点击事件触发,另一种是上下文菜单ContextMenu,通过长按事件触发.页面的布局文件放在re ...

  4. [Android]《Android艺术开发探索》第一章读书笔记

    1. 典型情况下生命周期分析 (1)一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart方法就会被调用. (2)当用户打开新的Activity或者切换到桌面的时候,回调如下 ...

  5. 温故知新,使用ASP.NET Core创建Web API,永远第一次

    ASP.NET Core简介 ASP.NET Core是一个跨平台的高性能开源框架,用于生成启用云且连接Internet的新式应用. 使用ASP.NET Core,您可以: 生成Web应用和服务.物联 ...

  6. 快读《ASP.NET Core技术内幕与项目实战》WebApi3.1:WebApi最佳实践

    本节内容,涉及到6.1-6.6(P155-182),以WebApi说明为主.主要NuGet包:无 一.创建WebApi的最佳实践,综合了RPC和Restful两种风格的特点 1 //定义Person类 ...

  7. 零基础ASP.NET Core WebAPI团队协作开发

    零基础ASP.NET Core WebAPI团队协作开发 相信大家对“前后端分离”和“微服务”这两个词应该是耳熟能详了.网上也有很多介绍这方面的文章,写的都很好.我这里提这个是因为接下来我要分享的内容 ...

  8. ASP.NET Core WebApi构建API接口服务实战演练

    一.ASP.NET Core WebApi课程介绍 人生苦短,我用.NET Core!提到Api接口,一般会想到以前用到的WebService和WCF服务,这三个技术都是用来创建服务接口,只不过Web ...

  9. 从 0 使用 SpringBoot MyBatis MySQL Redis Elasticsearch打造企业级 RESTful API 项目实战

    大家好!这是一门付费视频课程.新课优惠价 699 元,折合每小时 9 元左右,需要朋友的联系爱学啊客服 QQ:3469271680:我们每课程是明码标价的,因为如果售价为现在的 2 倍,然后打 5 折 ...

  10. Asp.Net Core 5 REST API - Step by Step

    翻译自 Mohamad Lawand 2021年1月19日的文章 <Asp.Net Core 5 Rest API Step by Step> [1] 在本文中,我们将创建一个简单的 As ...

随机推荐

  1. Feign 进行rpc 调用时使用ribbon负载均衡源码解析

    转载请注明出处: Feign客户端接口的动态代理生成是基于JDK的动态代理来实现的,那么在所有的方法调用的时候最终都会走InvocationHandler接口的实现,默认就是ReflectiveFei ...

  2. 超全面总结Vue面试知识点,助力金三银四

    前言 本文会对Vue中一些常见的重要知识点以及框架原理进行整理汇总,意在帮助作者以及读者自测Vue的熟练度以及方便查询与复习.金三银四的到来,想必vue会是很多面试官的重点考核内容,希望小伙伴们读完本 ...

  3. ABP微服务系列学习-微服务模板结构

    开源版本ABP CLI里面的模板是不包含微服务模板的,而商业版里面有一个微服务模板.这个模板据说是微服务的最佳实践,eShopOnAbp这个仓库的结构基本也和商业版的微服务模板一致.那就开始学习一下. ...

  4. AHB to Sram设计

    规格说明 现在要对addr1进行操作(原addr1中存储的数据为data),现在需要写入data1,下一拍对addr1进行读操作,需要读出data1(读出最新的数据data1,而不是data),这时候 ...

  5. Linux 中常用的基础命令

    by emanjusaka from https://www.emanjusaka.top/2024/01/linux-base-command 彼岸花开可奈何 本文欢迎分享与聚合,全文转载请留下原文 ...

  6. [转帖]内存管理参数zone_reclaim_mode分析

    zone_reclaim_mode 官方解释 调整方法 调整的影响 官方解释 最近在性能优化,看到了zone_reclaim_mode参数,记录备用 zone_reclaim_mode: Zone_r ...

  7. [转帖]一次ORA-3136的处理

    https://oracleblog.org/working-case/deal-with-ora3136/ 最近收到一个告警,用户说数据库无法连接,但是从监控上看,oracle的后台进程已经侦听进程 ...

  8. [转帖]Linux中查看各文件夹大小命令du -h --max-depth=1

    https://www.cnblogs.com/the-tops/p/8798678.html 最近排查服务器异常的时候,常会遇到磁盘慢的情况,这个时候,查找那个文件夹占用的内存的时候常用到这个命令: ...

  9. [转帖]总结:shell中的if条件判断

    一.if 的基本语法 if [ command ];then xxx elif [ command ];then xxx else xxx fi 二.常见的一些写法案例 1.if [ "x$ ...

  10. [转帖]【存储测试】vdbench存储性能测试工具

    一.前言 1.介绍  vdbench是一个I/O工作负载生成器,通常用于验证数据完整性和度量直接附加(或网络连接)存储性能.它可以运行在windows.linux环境,可用于测试文件系统或块设备基准性 ...