系列导航及源代码

需求

在查询的场景中,还有一类需求不是很常见,就是在前端请求中指定返回的字段,所以关于搜索的最后一个主题我们就来演示一下关于数据塑形(Data Shaping)。

目标

实现数据塑形搜索请求。

原理与思路

对于数据塑形来说,我们需要定义一些接口和泛型类实现来完成通用的功能,然后修改对应的查询请求,实现具体的功能。

实现

定义通用接口和泛型类实现

  • IDataShaper.cs
using System.Dynamic;

namespace TodoList.Application.Common.Interfaces;

public interface IDataShaper<T>
{
IEnumerable<ExpandoObject> ShapeData(IEnumerable<T> entities, string fieldString);
ExpandoObject ShapeData(T entity, string fieldString);
}

并实现通用的功能:

  • DataShaper.cs
using System.Dynamic;
using System.Reflection;
using TodoList.Application.Common.Interfaces; namespace TodoList.Application.Common; public class DataShaper<T> : IDataShaper<T> where T : class
{
public PropertyInfo[] Properties { get; set; } public DataShaper()
{
Properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
} public IEnumerable<ExpandoObject> ShapeData(IEnumerable<T> entities, string? fieldString)
{
var requiredProperties = GetRequiredProperties(fieldString); return GetData(entities, requiredProperties);
} public ExpandoObject ShapeData(T entity, string? fieldString)
{
var requiredProperties = GetRequiredProperties(fieldString); return GetDataForEntity(entity, requiredProperties);
} private IEnumerable<PropertyInfo> GetRequiredProperties(string? fieldString)
{
var requiredProperties = new List<PropertyInfo>(); if (!string.IsNullOrEmpty(fieldString))
{
var fields = fieldString.Split(',', StringSplitOptions.RemoveEmptyEntries);
foreach (var field in fields)
{
var property = Properties.FirstOrDefault(pi => pi.Name.Equals(field.Trim(), StringComparison.InvariantCultureIgnoreCase));
if (property == null)
{
continue;
} requiredProperties.Add(property);
}
}
else
{
requiredProperties = Properties.ToList();
} return requiredProperties;
} private IEnumerable<ExpandoObject> GetData(IEnumerable<T> entities, IEnumerable<PropertyInfo> requiredProperties)
{
return entities.Select(entity => GetDataForEntity(entity, requiredProperties)).ToList();
} private ExpandoObject GetDataForEntity(T entity, IEnumerable<PropertyInfo> requiredProperties)
{
var shapedObject = new ExpandoObject();
foreach (var property in requiredProperties)
{
var objectPropertyValue = property.GetValue(entity);
shapedObject.TryAdd(property.Name, objectPropertyValue);
} return shapedObject;
}
}

定义扩展方法

为了使我们的Handle方法调用链能够直接应用,我们在Application/Extensions中新增一个DataShaperExtensions

  • DataShaperExtensions.cs
using System.Dynamic;
using TodoList.Application.Common.Interfaces; namespace TodoList.Application.Common.Extensions; public static class DataShaperExtensions
{
public static IEnumerable<ExpandoObject> ShapeData<T>(this IEnumerable<T> entities, IDataShaper<T> shaper, string? fieldString)
{
return shaper.ShapeData(entities, fieldString);
}
}

然后再对我们之前写的MappingExtensions静态类中添加一个方法:

  • MappingExtensions.cs
// 省略其他...
public static PaginatedList<TDestination> PaginatedListFromEnumerable<TDestination>(this IEnumerable<TDestination> entities, int pageNumber, int pageSize)
{
return PaginatedList<TDestination>.Create(entities, pageNumber, pageSize);
}

添加依赖注入

ApplicationDependencyInjection.cs中添加依赖注入:

  • DependencyInjection.cs
// 省略其他
services.AddScoped(typeof(IDataShaper<>), typeof(DataShaper<>));

修改查询请求和Controller接口

我们在上一篇文章实现排序的基础上增加一个字段用于指明数据塑形字段并对应修改Handle方法:

  • GetTodoItemsWithConditionQuery.cs
using System.Dynamic;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using MediatR;
using TodoList.Application.Common.Extensions;
using TodoList.Application.Common.Interfaces;
using TodoList.Application.Common.Mappings;
using TodoList.Application.Common.Models;
using TodoList.Application.TodoItems.Specs;
using TodoList.Domain.Entities;
using TodoList.Domain.Enums; namespace TodoList.Application.TodoItems.Queries.GetTodoItems; public class GetTodoItemsWithConditionQuery : IRequest<PaginatedList<ExpandoObject>>
{
public Guid ListId { get; set; }
public bool? Done { get; set; }
public string? Title { get; set; }
// 前端指明需要返回的字段
public string? Fields { get; set; }
public PriorityLevel? PriorityLevel { get; set; }
public string? SortOrder { get; set; } = "title_asc";
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
} public class GetTodoItemsWithConditionQueryHandler : IRequestHandler<GetTodoItemsWithConditionQuery, PaginatedList<ExpandoObject>>
{
private readonly IRepository<TodoItem> _repository;
private readonly IMapper _mapper;
private readonly IDataShaper<TodoItemDto> _shaper; public GetTodoItemsWithConditionQueryHandler(IRepository<TodoItem> repository, IMapper mapper, IDataShaper<TodoItemDto> shaper)
{
_repository = repository;
_mapper = mapper;
_shaper = shaper;
} public Task<PaginatedList<ExpandoObject>> Handle(GetTodoItemsWithConditionQuery request, CancellationToken cancellationToken)
{
var spec = new TodoItemSpec(request);
return Task.FromResult(
_repository
.GetAsQueryable(spec)
.ProjectTo<TodoItemDto>(_mapper.ConfigurationProvider)
.AsEnumerable()
// 进行数据塑形和分页返回
.ShapeData(_shaper, request.Fields)
.PaginatedListFromEnumerable(request.PageNumber, request.PageSize)
);
}
}

对应修改Controller:

  • TodoItemController.cs
[HttpGet]
public async Task<ApiResponse<PaginatedList<ExpandoObject>>> GetTodoItemsWithCondition([FromQuery] GetTodoItemsWithConditionQuery query)
{
return ApiResponse<PaginatedList<ExpandoObject>>.Success(await _mediator.Send(query));
}

验证

启动Api项目,执行查询TodoItem的请求:

  • 请求

  • 响应

我们再把之前讲到的过滤和搜索添加到请求里来:

  • 请求

  • 响应

总结

对于数据塑形的请求,关键步骤就是使用反射获取待返回对象的所有配置的可以返回的属性,再通过前端传入的属性名称进行过滤和值的重组进行返回。实现起来是比较简单的。但是在实际的使用过程中我不推荐这样用,除了某些非常适用的特殊场景。个人更偏向于向前端提供明确的接口定义。

使用.NET 6开发TodoList应用(17)——实现数据塑形的更多相关文章

  1. 使用.NET 6开发TodoList应用(4)——引入数据存储

    需求 作为后端CRUD程序员(bushi,数据存储是开发后端服务一个非常重要的组件.对我们的TodoList项目来说,自然也需要配置数据存储.目前的需求很简单: 需要能持久化TodoList对象并对其 ...

  2. 使用.NET 6开发TodoList应用(3)——引入第三方日志库

    需求 在我们项目开发的过程中,使用.NET 6自带的日志系统有时是不能满足实际需求的,比如有的时候我们需要将日志输出到第三方平台上,最典型的应用就是在各种云平台上,为了集中管理日志和查询日志,通常会选 ...

  3. 使用.NET 6开发TodoList应用(1)——系列背景

    前言 想到要写这样一个系列博客,初衷有两个:一是希望通过一个实践项目,将.NET 6 WebAPI开发的基础知识串联起来,帮助那些想要入门.NET 6服务端开发的朋友们快速上手,对使用.NET 6开发 ...

  4. 使用.NET 6开发TodoList应用(2)——项目结构搭建

    为了不影响阅读的体验,我把系列导航放到文章最后了,有需要的小伙伴可以直接通过导航跳转到对应的文章 : P TodoList需求简介 首先明确一下我们即将开发的这个TodoList应用都需要完成什么功能 ...

  5. 使用.NET 6开发TodoList应用(5)——领域实体创建

    需求 上一篇文章中我们完成了数据存储服务的接入,从这一篇开始将正式进入业务逻辑部分的开发. 首先要定义和解决的问题是,根据TodoList项目的需求,我们应该设计怎样的数据实体,如何去进行操作? 长文 ...

  6. 使用.NET 6开发TodoList应用(5.1)——实现Repository模式

    需求 经常写CRUD程序的小伙伴们可能都经历过定义很多Repository接口,分别做对应的实现,依赖注入并使用的场景.有的时候会发现,很多分散的XXXXRepository的逻辑都是基本一致的,于是 ...

  7. 使用.NET 6开发TodoList应用(6)——使用MediatR实现POST请求

    需求 需求很简单:如何创建新的TodoList和TodoItem并持久化. 初学者按照教程去实现的话,应该分成以下几步:创建Controller并实现POST方法:实用传入的请求参数new一个数据库实 ...

  8. 使用.NET 6开发TodoList应用文章索引

    系列导航 使用.NET 6开发TodoList应用(1)--系列背景 使用.NET 6开发TodoList应用(2)--项目结构搭建 使用.NET 6开发TodoList应用(3)--引入第三方日志 ...

  9. 使用.NET 6开发TodoList应用(16)——实现查询排序

    系列导航及源代码 使用.NET 6开发TodoList应用文章索引 需求 关于查询的另一个需求是要根据前端请求的排序字段进行对结果相应的排序. 目标 实现根据排序要求返回排序后的结果 原理与思路 要实 ...

随机推荐

  1. oracle(创建数据库对象)

    1 --创建数据库 2 --1.SYSDBA系统权限 3 startup:--启动数据库. 4 shutdown:--关闭数据库. 5 alter database[mount]|[open]|[ba ...

  2. Linux上Zookeeper集群搭建

    一.官网 https://zookeeper.apache.org/ 二.下载安装 (1)下载 复制链接地址  http://mirror.bit.edu.cn/apache/zookeeper/zo ...

  3. 商城项目的购物车模块的实现------通过session实现

    1.新建购物车的实体类Cart public class Cart implements java.io.Serializable{ private Shangpin shangpin;//存放商品实 ...

  4. Java变量和常量

    变量 变量要素包括:变量名,变量类型,作用域. 变量作用域:类变量(static),实例变量(没有static),局部变量(写在方法中) //类中可以定义属性(变量) static double sa ...

  5. Mybatis-plus报Invalid bound statement (not found)错误

    错误信息 org.springframework.security.authentication.InternalAuthenticationServiceException: Invalid bou ...

  6. 【JAVA今法修真】 第四章 redis特性 击穿雪崩!

    感谢这段时间大家的支持,关注我的微信号:南橘ryc ,回复云小霄,就可以获取到最新的福利靓照一张,还等什么,赶快来加入我们吧~ "明日便是决赛了,咋只会用法器没练过法术呢.". 选 ...

  7. 一文详解面试常考的TopK问题

    首发公众号:bigsai ,转载请附上本文链接 前言 hello,大家好,我是bigsai哥哥,好久不见,甚是想念哇! 今天给大家分享一个TOPK问题,不过我这里不考虑特别大分布式的解决方案,普通的一 ...

  8. noVNC实现浏览器远程访问Windows桌面

    一.简介 1.VNC介绍 VNC (Virtual Network Console)是虚拟网络控制台的缩写.它 是一款优秀的远程控制工具软件.VNC 是在基于 UNIX 和 Linux 操作系统的免费 ...

  9. [BUUCTF]REVERSE——[GUET-CTF2019]re

    [GUET-CTF2019]re 附件 步骤: 查壳儿,upx壳,64位程序 upx脱壳儿,然后扔进64位ida,通过检索字符串,找到有关flag的信息定位到关键函数 让我们输入flag,然后满足su ...

  10. LuoguP7080 [NWRRC2013]Ballot Analyzing Device 题解

    Content 有 \(n\) 名选手参加一个比赛,有 \(m\) 个人为他们投票.第 \(i\) 个人的投票情况用一个长度为 \(n\),并且仅包含 . 和 X 两个字符的字符串,其中,如果第 \( ...