使用.NET 6开发TodoList应用(17)——实现数据塑形
系列导航及源代码
需求
在查询的场景中,还有一类需求不是很常见,就是在前端请求中指定返回的字段,所以关于搜索的最后一个主题我们就来演示一下关于数据塑形(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);
}
添加依赖注入
在Application的DependencyInjection.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)——实现数据塑形的更多相关文章
- 使用.NET 6开发TodoList应用(4)——引入数据存储
需求 作为后端CRUD程序员(bushi,数据存储是开发后端服务一个非常重要的组件.对我们的TodoList项目来说,自然也需要配置数据存储.目前的需求很简单: 需要能持久化TodoList对象并对其 ...
- 使用.NET 6开发TodoList应用(3)——引入第三方日志库
需求 在我们项目开发的过程中,使用.NET 6自带的日志系统有时是不能满足实际需求的,比如有的时候我们需要将日志输出到第三方平台上,最典型的应用就是在各种云平台上,为了集中管理日志和查询日志,通常会选 ...
- 使用.NET 6开发TodoList应用(1)——系列背景
前言 想到要写这样一个系列博客,初衷有两个:一是希望通过一个实践项目,将.NET 6 WebAPI开发的基础知识串联起来,帮助那些想要入门.NET 6服务端开发的朋友们快速上手,对使用.NET 6开发 ...
- 使用.NET 6开发TodoList应用(2)——项目结构搭建
为了不影响阅读的体验,我把系列导航放到文章最后了,有需要的小伙伴可以直接通过导航跳转到对应的文章 : P TodoList需求简介 首先明确一下我们即将开发的这个TodoList应用都需要完成什么功能 ...
- 使用.NET 6开发TodoList应用(5)——领域实体创建
需求 上一篇文章中我们完成了数据存储服务的接入,从这一篇开始将正式进入业务逻辑部分的开发. 首先要定义和解决的问题是,根据TodoList项目的需求,我们应该设计怎样的数据实体,如何去进行操作? 长文 ...
- 使用.NET 6开发TodoList应用(5.1)——实现Repository模式
需求 经常写CRUD程序的小伙伴们可能都经历过定义很多Repository接口,分别做对应的实现,依赖注入并使用的场景.有的时候会发现,很多分散的XXXXRepository的逻辑都是基本一致的,于是 ...
- 使用.NET 6开发TodoList应用(6)——使用MediatR实现POST请求
需求 需求很简单:如何创建新的TodoList和TodoItem并持久化. 初学者按照教程去实现的话,应该分成以下几步:创建Controller并实现POST方法:实用传入的请求参数new一个数据库实 ...
- 使用.NET 6开发TodoList应用文章索引
系列导航 使用.NET 6开发TodoList应用(1)--系列背景 使用.NET 6开发TodoList应用(2)--项目结构搭建 使用.NET 6开发TodoList应用(3)--引入第三方日志 ...
- 使用.NET 6开发TodoList应用(16)——实现查询排序
系列导航及源代码 使用.NET 6开发TodoList应用文章索引 需求 关于查询的另一个需求是要根据前端请求的排序字段进行对结果相应的排序. 目标 实现根据排序要求返回排序后的结果 原理与思路 要实 ...
随机推荐
- Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null objec
遇到这个一场折腾了1个小时, 这是系统在解析XML的时候出错, 最后费了好大的劲才发现 XML文件中,<View> 写成小写的 <view> 了. 崩溃啊.......... ...
- Can static functions be virtual in C++?
In C++, a static member function of a class cannot be virtual. For example, below program gives comp ...
- Spring MVC入门(二)—— URI Builder模式
URI Builder Spring MVC作为一个web层框架,避免不了处理URI.URL等和HTTP协议相关的元素,因此它提供了非常好用.功能强大的URI Builder模式来完成,这就是本文重点 ...
- 【Linux】【Services】【Docker】应用
1. Docker应用: 镜像:包含了启动Docker容器所需要的文件系统层级及其内容:基于UnionFS采用分层结构实现: bootfs,rootfs registry:保存docker镜像及镜像层 ...
- 深入.NET框架与面向对象的回顾
.NET DOTNET DNET 点NET(.NET框架支持跨语言开发.如C#,VB .NET ,C++.NET,F# ,lronRuby,Others) 任何人,在任何地方,使用任何终端设备,都能访 ...
- 【kafka学习笔记】合理安排broker、partition、consumer数量
broker的数量最好大于等于partition数量 一个partition最好对应一个硬盘,这样能最大限度发挥顺序写的优势. broker如果免得是多个partition,需要随机分发,顺序IO会退 ...
- [BUUCTF]REVERSE——[V&N2020 公开赛]CSRe
[V&N2020 公开赛]CSRe 附件 步骤: 例行检查,无壳儿,但是有NET混淆,使用de4dot工具进行处理 之后用dnSpy打开,从入口点开始看程序 找到有关flag的信息 flag由 ...
- PowerDotNet平台化软件架构设计与实现系列(08):缓存平台
几乎所有后端应用都会或多或少用到缓存,尤其是分布式缓存服务,以及和本地缓存构造的二级缓存.根据我们一贯的节约代码的风格,为了复用的目标,抽象出缓存平台,进行缓存管理. 考虑到很多公司都会自己造或者直接 ...
- UVA294 约数 Divisors 题解
Content 给定 \(n\) 个区间 \([l,r]\),求出每个区间内约数个数最大的数. 数据范围:\(1\leqslant l<r\leqslant 10^{10}\),\(r-l\le ...
- CF1427A Avoiding Zero 题解
Content 请将一个长度为 \(n\) 的数列 \(A\) 重新排序,使得这个数列所有的前缀和 \(\neq 0\),或者证明没有这样的方案. 数据范围:\(t\) 组数据,\(1\leqslan ...