• 當前位置:首頁 > IT技術 > Windows編程 > 正文

    《ASP.NET Core 與 RESTful API 開發實戰》-- (第6章)-- 讀書筆記(下)
    2021-10-22 16:52:58


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


    《ASP.NET Core 與 RESTful API 開發實戰》-- (第6章)-- 讀書筆記(下)_xml


    本文摘自 :https://blog.51cto.com/u

    開通會員,享受整站包年服務
    国产呦精品一区二区三区网站|久久www免费人咸|精品无码人妻一区二区|久99久热只有精品国产15|中文字幕亚洲无线码