zoukankan      html  css  js  c++  java
  • 《ASP.NET Core 与 RESTful API 开发实战》-- (第6章)-- 读书笔记(下)

    第 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) 。

  • 相关阅读:
    express如何使用cors插件、body-parser插件
    如何让xshell关闭后依然运行node项目
    nuxt命令和部署
    Python—函数的名称空间
    Python—生成器
    Python—闭包
    Python入门-字符串常用方法
    Python入门-函数
    Python入门-文件操作
    Python入门-三级菜单
  • 原文地址:https://www.cnblogs.com/MingsonZheng/p/13296917.html
Copyright © 2011-2022 走看看