当前位置: 首页 > news >正文

(二)RestAPI 毛子(Tags——子实体/异常处理/验证/Search/Sort功能)


文章目录

  • 项目地址
  • 一、给Habit添加Tags
    • 1.1 创建Tags
      • 1. 创建一个新的HabitTags实体
      • 2. 设置Habit和Tags的关系
      • 3. 设置HabitTag表
      • 4. 在HabitConfiguration里配置
      • 5. 将表添加到EFCore里
      • 6. 迁移数据
    • 1.2 给Habit增加/修改标签
      • 1. 创建UpsertHabitTagsDto
      • 2. 创建查询HabitWithTagsDto
      • 3. 创建HabitTagsController
    • 1.3 使用Fluent API验证Create tag逻辑
      • 1. 安装所需要的包
      • 2. 注册服务
      • 3. 添加CreateTagValidator
      • 4. 使用validator
    • 1.4 添加异常处理中间件
      • 1. 创建全局异常处理中间件
      • 2. 使用validator异常处理中间件
      • 3. 注册服务以及中间件
      • 4. 修改原来的validator
  • 二、添加searching/filter功能
    • 2.1 从GetHabit的Controller添加search功能
    • 2.2 从GetHabit添加Filter
    • 2.3 优化参数
  • 三、动态排序
    • 3.1 配置服务
    • 3.2 创建sort服务
      • 1. ISortMappingDefinition
      • 2.SortMappingDefinition
      • 3.SortMapping
      • 4. SortMappingProvider
      • 5. QueryableExtensions
    • 3.3 在HabitMaping里添加具体需要排序的字段
    • 3.4 在Program里注册服务
    • 3.5 在Controller 使用服务GetHabits
    • 3.6 发送请求


项目地址

  • 教程作者:
  • 教程地址:
  • 代码仓库地址:
  • 所用到的框架和插件:
dbt 
airflow

一、给Habit添加Tags

1.1 创建Tags

1. 创建一个新的HabitTags实体

  • 该实体需要创建一个表,关联Habits和Tags
namespace DevHabit.Api.Entities;public sealed class HabitTag
{public string HabitId { get; set; }public string TagId { get; set; }public DateTime CreatedAtUtc { get; set; }
}

2. 设置Habit和Tags的关系

  • 在Habit 的是实体里创建关系

在这里插入图片描述

3. 设置HabitTag表

  • 添加HabitTagConfiguration.cs 配置文件

在这里插入图片描述
注意:这里Tag的WithMany()是空的原因是,暂时我们业务上不考虑通过Tag来找所有的Habits; 但是,Habit里面,我们建立了多对多关系,需要通过habit来找到所有的Tag

4. 在HabitConfiguration里配置

  • 修改了Habit的实体,我们需要在HabitConfiguration里配置关系

在这里插入图片描述

5. 将表添加到EFCore里

  • ApplicationDbContext.cs 里,添加新的表

public sealed class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : DbContext(options)
{public DbSet<Habit> Habits { get; set; }public DbSet<Tag> Tags { get; set; }public DbSet<HabitTag> HabitTags { get; set; }protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.HasDefaultSchema(Schemas.Application);modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);}
}

6. 迁移数据

  1. 在Api的Console里先输入add-migration add HabitTag
  2. 换到docker环境,运行程序,自动执行

1.2 给Habit增加/修改标签

1. 创建UpsertHabitTagsDto

  • 创建UpsertHabitTagsDto,用于处理前端传来的api数据
namespace DevHabit.Api.DTOs.HabitTags;public sealed record UpsertHabitTagsDto
{public required List<string> TagIds { get; init; }
}

2. 创建查询HabitWithTagsDto

  • 用于查询单个habit后,显示该habit里的Tags

public sealed record HabitWithTagsDto
{public required string Id { get; init; }public required string Name { get; init; }public string? Description { get; init; }public required HabitType Type { get; init; }public required FrequencyDto Frequency { get; init; }public required TargetDto Target { get; init; }public required HabitStatus Status { get; init; }public required bool IsArchived { get; init; }public DateOnly? EndDate { get; init; }public MilestoneDto? Milestone { get; init; }public required DateTime CreatedAtUtc { get; init; }public DateTime? UpdatedAtUtc { get; init; }public DateTime? LastCompletedAtUtc { get; init; }public required string[] Tags { get; init; }
}

3. 创建HabitTagsController

  • 该Controller用于给Habit 处理标签
    [HttpPut]public async Task<ActionResult> UpsertHabitTags(string habitId, UpsertHabitTagsDto upsertHabitTagsDto){Habit? habit = await dbContext.Habits.Include(h => h.HabitTags).FirstOrDefaultAsync(h => h.Id == habitId);if (habit is null){return NotFound();}var currentTagIds = habit.HabitTags.Select(ht => ht.TagId).ToHashSet();if (currentTagIds.SetEquals(upsertHabitTagsDto.TagIds)){return NoContent();}List<string> existingTagIds = await dbContext.Tags.Where(t => upsertHabitTagsDto.TagIds.Contains(t.Id)).Select(t => t.Id).ToListAsync();if (existingTagIds.Count != upsertHabitTagsDto.TagIds.Count){return BadRequest("One or more tag IDs is invalid");}habit.HabitTags.RemoveAll(ht => !upsertHabitTagsDto.TagIds.Contains(ht.TagId));string[] tagIdsToAdd = upsertHabitTagsDto.TagIds.Except(currentTagIds).ToArray();habit.HabitTags.AddRange(tagIdsToAdd.Select(tagId => new HabitTag{HabitId = habitId,TagId = tagId,CreatedAtUtc = DateTime.UtcNow}));await dbContext.SaveChangesAsync();return NoContent();}

代码解释:

  1. 发起http://localhost:5000/habits/h_0195e900-3c05-7ee6-8b26-7ec6b367d66a/tags请求
  2. 接收一个habitId以及 UpsertHabitTagsDto
    在这里插入图片描述
  3. 根据Id从数据库查询该habit的数据
    在这里插入图片描述
  4. 查看是否有新增或者取消:通过数据库tags所有Id的hashSet和前端传来的进行对比,如果没有变化,则返回204

在这里插入图片描述
5. 排除非法habitId:查询Tags表的tagId和前端传来的Id进行对比,如果有不一样的,则报错

在这里插入图片描述
6. 删除Dto里取出的Tag,保存新增的Tags 完成upsert

在这里插入图片描述
7. 发送请求更改Tag
在这里插入图片描述

1.3 使用Fluent API验证Create tag逻辑

1. 安装所需要的包

2. 注册服务

3. 添加CreateTagValidator

4. 使用validator

1.4 添加异常处理中间件

在这里插入图片描述

1. 创建全局异常处理中间件

  • 该中间件的作用是,捕获所有未处理的异常,返回标准化错误。
    GlobalExceptionHandler.cs
namespace DevHabit.Api.Middleware;public sealed class GlobalExceptionHandler(IProblemDetailsService problemDetailsService) : IExceptionHandler
{public ValueTask<bool> TryHandleAsync(HttpContext httpContext,Exception exception,CancellationToken cancellationToken){return problemDetailsService.TryWriteAsync(new ProblemDetailsContext{HttpContext = httpContext,Exception = exception,ProblemDetails = new ProblemDetails{Title = "Internal Server Error",Detail = "An error occurred while processing your request. Please try again"}});}
}

2. 使用validator异常处理中间件

  • 不使用全局的validator,每个错误我们都需要if判断,以及每次需要传递错误信息,导致错误不统一以及大量重复代码
    在这里插入图片描述
  • 创建中间件,统一所有的validator返回格式,以及状态码
    ValidationExceptionHandler.cs
public sealed class ValidationExceptionHandler(IProblemDetailsService problemDetailsService) : IExceptionHandler
{public async ValueTask<bool> TryHandleAsync(HttpContext httpContext,Exception exception,CancellationToken cancellationToken){if (exception is not ValidationException validationException){return false;}httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;var context = new ProblemDetailsContext{HttpContext = httpContext,Exception = exception,ProblemDetails = new ProblemDetails{Detail = "One or more validation errors occurred",Status = StatusCodes.Status400BadRequest}};var errors = validationException.Errors.GroupBy(e => e.PropertyName).ToDictionary(g => g.Key.ToLowerInvariant(),g => g.Select(e => e.ErrorMessage).ToArray());context.ProblemDetails.Extensions.Add("errors", errors);return await problemDetailsService.TryWriteAsync(context);}
}

3. 注册服务以及中间件

program.cs
在这里插入图片描述

4. 修改原来的validator

  • 我们只需要验证即可,不需要在添加各种判断

在这里插入图片描述

二、添加searching/filter功能

2.1 从GetHabit的Controller添加search功能

  • 对Name和Descripion进行匹配
    在这里插入图片描述
  • 发起请求
    在这里插入图片描述

2.2 从GetHabit添加Filter

  • 添加传入type类型和status
    在这里插入图片描述
  • 发起一个参数的请求:http://localhost:5000/habits?type=1
  • 发起多个参数的请求:http://localhost:5000/habits?type=1&status=1

2.3 优化参数

  • 上面的所有参数,我们都写在了controller里,如果参数多,会导致代码不好阅读
  • 创建HabitsQueryParameters用来管理所有的Query
    HabitsQueryParameters.cs
public sealed record HabitsQueryParameters
{[FromQuery(Name = "q")]public string? Search { get; set; }public HabitType? Type { get; init; }public HabitStatus? Status { get; init; }
}
  • 修改Controller

在这里插入图片描述

三、动态排序

  • 使用功能:传递habits?sort=name desc,description自动进行排序

3.1 配置服务

  1. 安装所需要的包System.Linq.Dynamic.Core
  2. 在使用这个包的地方,添加该包引用
using System.Dynamic;
  1. 在 HabitsQueryParameters类里,添加sort字段
    在这里插入图片描述

3.2 创建sort服务

在这里插入图片描述

1. ISortMappingDefinition

  • 提供Sort服务的接口,用于注册
namespace DevHabit.Api.Services.Sorting;public interface ISortMappingDefinition;

2.SortMappingDefinition

  • 接口实现:
public sealed class SortMappingDefinition<TSource, TDestination> : ISortMappingDefinition
{public required SortMapping[] Mappings { get; init; }
}
  • HabitMapping.cs里实现:前端Dto转为实体
    public static readonly SortMappingDefinition<HabitDto, Habit> SortMapping = new(){Mappings =[new SortMapping(nameof(HabitDto.Name), nameof(Habit.Name)),new SortMapping(nameof(HabitDto.Description), nameof(Habit.Description)),new SortMapping(nameof(HabitDto.Type), nameof(Habit.Type)),new SortMapping($"{nameof(HabitDto.Frequency)}.{nameof(FrequencyDto.Type)}",$"{nameof(Habit.Frequency)}.{nameof(Frequency.Type)}"),new SortMapping($"{nameof(HabitDto.Frequency)}.{nameof(FrequencyDto.TimesPerPeriod)}",$"{nameof(Habit.Frequency)}.{nameof(Frequency.TimesPerPeriod)}"),new SortMapping($"{nameof(HabitDto.Target)}.{nameof(TargetDto.Value)}",$"{nameof(Habit.Target)}.{nameof(Target.Value)}"),new SortMapping($"{nameof(HabitDto.Target)}.{nameof(TargetDto.Unit)}",$"{nameof(Habit.Target)}.{nameof(Target.Unit)}"),new SortMapping(nameof(HabitDto.Status), nameof(Habit.Status)),new SortMapping(nameof(HabitDto.EndDate), nameof(Habit.EndDate)),new SortMapping(nameof(HabitDto.CreatedAtUtc), nameof(Habit.CreatedAtUtc)),new SortMapping(nameof(HabitDto.UpdatedAtUtc), nameof(Habit.UpdatedAtUtc)),new SortMapping(nameof(HabitDto.LastCompletedAtUtc), nameof(Habit.LastCompletedAtUtc))]};

3.SortMapping

  • 服务方法
public sealed record SortMapping(string SortField, string PropertyName, bool Reverse = false);

4. SortMappingProvider

  • 该方法主要有两个功能:
    1. 从SortMappingDefinition里找到字段映射
    2. 传入一个排序字符串,比如 “name asc, price desc”,会提取出排序字段名(name, price),然后检查这些字段是否在 GetMappings 返回的字段映射中存在。
public sealed class SortMappingProvider(IEnumerable<ISortMappingDefinition> sortMappingDefinitions)
{public SortMapping[] GetMappings<TSource, TDestination>(){SortMappingDefinition<TSource, TDestination>? sortMappingDefinition = sortMappingDefinitions.OfType<SortMappingDefinition<TSource, TDestination>>().FirstOrDefault();if (sortMappingDefinition is null){throw new InvalidOperationException($"The mapping from '{typeof(TSource).Name}' into'{typeof(TDestination).Name} isn't defined");}return sortMappingDefinition.Mappings;}public bool ValidateMappings<TSource, TDestination>(string? sort){if (string.IsNullOrWhiteSpace(sort)){return true;}var sortFields = sort.Split(',').Select(f => f.Trim().Split(' ')[0]).Where(f => !string.IsNullOrWhiteSpace(f)).ToList();SortMapping[] mapping = GetMappings<TSource, TDestination>();return sortFields.All(f => mapping.Any(m => m.SortField.Equals(f, StringComparison.OrdinalIgnoreCase)));}
}

5. QueryableExtensions

  • 使用扩展方法,可以让任何 Queryable<T> 调用 ApplySort(...),并根据前端传来的排序字符串,结合后端配置的字段映射,对查询数据进行动态排序
using System.Linq.Dynamic.Core;namespace DevHabit.Api.Services.Sorting;internal static class QueryableExtensions
{public static IQueryable<T> ApplySort<T>(this IQueryable<T> query,string? sort,SortMapping[] mappings,string defaultOrderBy = "Id"){if (string.IsNullOrWhiteSpace(sort)){return query.OrderBy(defaultOrderBy);}string[] sortFields = sort.Split(',').Select(s => s.Trim()).Where(s => !string.IsNullOrWhiteSpace(s)).ToArray();var orderByParts = new List<string>();foreach (string field in sortFields){(string sortField, bool isDescending) = ParseSortField(field);SortMapping mapping = mappings.First(m =>m.SortField.Equals(sortField, StringComparison.OrdinalIgnoreCase));string direction = (isDescending, mapping.Reverse) switch{(false, false) => "ASC",(false, true) => "DESC",(true, false) => "DESC",(true, true) => "ASC"};orderByParts.Add($"{mapping.PropertyName} {direction}");}string orderBy = string.Join(",", orderByParts);return query.OrderBy(orderBy);}private static (string SortField, bool IsDescending) ParseSortField(string field){string[] parts = field.Split(' ');string sortField = parts[0];bool isDescending = parts.Length > 1 &&parts[1].Equals("desc", StringComparison.OrdinalIgnoreCase);return (sortField, isDescending);}
}

3.3 在HabitMaping里添加具体需要排序的字段

3.4 在Program里注册服务

3.5 在Controller 使用服务GetHabits

3.6 发送请求

habits?sort=name desc,description


http://www.mrgr.cn/news/97265.html

相关文章:

  • ROS Master多设备连接
  • 【计算机网络】Linux配置SNAT策略
  • Kubernetes 集群搭建(一):k8s 从环境准备到 Calico 网络插件部署(1.16版本)
  • T113s3远程部署Qt应用(dropbear)
  • 通过枚举、AOP、注解、反射填充公共字段
  • 在线记事本——支持Markdown
  • JavaScript基础--01-JS简介
  • 数据蒸馏:Dataset Distillation by Matching Training Trajectories 论文翻译和理解
  • 【Python基础】循环语句(2215字)
  • DDPM 做了什么
  • Go语言-初学者日记(七):用 Go 写一个 RESTful API 服务!
  • JavaScript基础--09-流程控制语句:选择结构(if和switch)
  • Windows 安装和使用 ElasticSearch
  • Python实现ssh自动连接
  • 【双维畅聊】网页版聊天室测试报告
  • 服务器磁盘io性能监控和优化
  • 道路裂缝数据集CrackForest-156-labelme
  • 3D图像重建中Bundle Adjustment的推导与实现
  • 【Python爬虫高级技巧】BeautifulSoup高级教程:数据抓取、性能调优、反爬策略,全方位提升爬虫技能!
  • 001 vue