最近呢,我有一个项目旧的想升级到PasteForm类型,为啥呢!!!
都知道PasteForm框架出名的是啥?
管理端啊,开发只要专注业务端和用户端,
管理端完全不用管,这里说得是管理端的页面
也就是我们说得系统后台,就是各种表单,各种表格,报表那个
你就说爽不爽!!!
另外一个很重要的特点是,几乎的限制等在Dto的特性中
比如限制最大长度
比如说这里是上传图片的
比如说这里是选择时间区间的
你都只要写一行特性即可!!!
这里说得不是运行速度哈,毕竟用了文档和反射,性能上是有点吃亏的,但是为啥还用呢
因为这个只对管理端起作用,也就是对于你得用户端,完全没影响啊!!!
快捷在于,你只要专注Dto的特性标记即可,
迅速是因为你可以快速迭代,可以说只要业务逻辑不是非常复杂的,我可以做到全部开发完成后再测试
也就是压根不需要频繁的测试发布测试等!!!
好了上面说了一堆,其实就是吹了一顿,那么开始干活
现在的情况是,我这个项目以前是用老方式写的
管理端的页面就有400多个啊!!!
还是各种VUE混合原生的,各种页面,我只能说,望洋兴叹啊!!!
也是因为这个问题,所以我才更加想升级到PasteForm,因为PasteForm的管理端页面不会多于10个!!!
而且要是没有特殊需求的话,压根不需要开发管理端啊!!!
既然要升级到PasteForm的,就需要按照PasteForm的规则办事,对吧!
举个例子
用户在新建XXX表单的时候,比如创建用户,创建商品,创建分类的时候
需要从API读取对应的表单内容,也就是表单的格式等信息
问题来了,我有大概150个表!!!
哎,灾难啊!!!虽然有代码生成器PasteBuilder
不过这个东西是给项目前期准备的,要是后期修改了字段,自定义了逻辑等就不太合适了!!
那有没有一个办法,通用,就是写一个通用的接口给所有的表用呢???
有特例的再进行特别写呢!!!
基于反射,理论上是可行的,因为你看几乎的新增表单数据是这个样子的
/// <summary>
/// 读取AddDto的数据模型
/// </summary>
/// <returns></returns>
[HttpGet]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "add" })]
public VoloModelInfo ReadAddModel()
{
var _model = PasteBuilderHelper.DynamicReadModelProperty<GradeInfoAddDto>(new GradeInfoAddDto());
return _model;
}
/// <summary>
/// 读取AddDto的数据模型
/// </summary>
/// <returns></returns>
[HttpGet]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "root", "root" })]
public VoloModelInfo ReadAddModel()
{
//当前的路由为 /api/app/userInfo/readAddModel
var _model = PasteBuilderHelper.ReadModelProperty<RoleInfoAddDto>(new RoleInfoAddDto());
return _model;
}
发觉没有,其实高度重复的!
对应的其他也是一样的,比如更新等
/// <summary>
/// 读取UpdateDto的数据模型
/// </summary>
/// <returns></returns>
[HttpGet]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "edit" })]
public async Task<VoloModelInfo> ReadUpdateModel(int id)
{
var _info = await _dbContext.GradeInfo.Where(x => x.Id == id).AsNoTracking().FirstOrDefaultAsync();
if (_info == null || _info == default)
{
throw new PasteCodeException("查询的信息不存在,无法执行编辑操作!");
}
var dto = ObjectMapper.Map<GradeInfo,GradeInfoUpdateDto>(_info);
var _dataModel = PasteBuilderHelper.DynamicReadModelProperty<GradeInfoUpdateDto>(dto);
return _dataModel;
}
按照上面的思路,我们只要使用路由中的表名(这个其实可以做一个映射哈,比如管理端用Abc,系统表示Tableb),直接上代码
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PasteForm.Application.Contracts;
using PasteForm.Handler;
using PasteForm.usermodels;
using PasteFormHelper;
using System;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
namespace PasteForm.Application
{
/// <summary>
/// 默认读取 这个很关键,实现了统一的新增 更新 详细 列表规范读取 相当于兜底的!
///</summary>
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "view" })]
[DisableAuditing]
public class DefaultAppService : PasteFormAppService
{
/// <summary>
/// 读取AddDto的数据模型 用于新增
/// </summary>
/// <returns></returns>
[HttpGet]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "view" })]
[Route("/api/app/{entityName}/readAddModel", Order = 10)]
public VoloModelInfo ReadAddModel([FromRoute] string entityName)
{
// 1. 根据实体名称查找对应的实体类型和 DTO 类型
var entityType = FindEntityType(entityName);
if (entityType == null)
{
throw new PasteCodeException($"找不到对应的实体类型: {entityName}");
}
var dtoType = FindDtoType(entityName, "AddDto");
if (dtoType == null)
{
throw new PasteCodeException($"找不到对应的 DTO 类型: {entityName}AddDto");
}
//new一个出来
var dto = Activator.CreateInstance(dtoType);
// 4. 动态读取 DTO 的属性信息
var dataModel = ReadModelProperties(dto, dtoType);
return dataModel;
}
/// <summary>
/// 用于更新表单
/// </summary>
/// <param name="entityName"></param>
/// <param name="id"></param>
/// <returns></returns>
/// <exception cref="PasteCodeException"></exception>
[HttpGet]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "view" })]
[Route("/api/app/{entityName}/readUpdateModel", Order = 10)]
public async Task<VoloModelInfo> ReadUpdateModel([FromRoute] string entityName, int id)
{
// 1. 根据实体名称查找对应的实体类型和 DTO 类型
var entityType = FindEntityType(entityName);
var dtoType = FindDtoType(entityName, "UpdateDto");
if (entityType == null || dtoType == null)
{
throw new PasteCodeException($"找不到对应的实体或 DTO 类型: {entityName}");
}
// 2. 动态查询实体
var entity = await FindEntityByIdAsync(entityType, id);
if (entity == null)
{
throw new PasteCodeException("查询的信息不存在,无法执行编辑操作!");
}
// 3. 动态映射实体到 DTO
var dto = MapEntityToDto(entity, entityType, dtoType);
//构建外表等 Include的如何搞定???
await BuildOuterQuery(dto, dtoType);
// 4. 动态读取 DTO 的属性信息
var dataModel = ReadModelProperties(dto, dtoType);
return dataModel;
}
/// <summary>
/// 用于查看详情
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("/api/app/{entityName}/readDetailModel", Order = 10)]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "view" })]
public async Task<VoloModelInfo> ReadDetailModel([FromRoute] string entityName, int id)
{
//var _info = await _dbContext.StoreCashWay.Where(x => x.Id == id).AsNoTracking().FirstOrDefaultAsync();
//if (_info == null || _info == default)
//{
// throw new PasteCodeException("查询的信息不存在,无法执行编辑操作!");
//}
//var dto = ObjectMapper.Map<StoreCashWay, StoreCashWayDto>(_info);
//var _dataModel = PasteBuilderHelper.ReadModelProperty<StoreCashWayDto>(dto);
//return _dataModel;
// 1. 根据实体名称查找对应的实体类型和 DTO 类型
var entityType = FindEntityType(entityName);
var dtoType = FindDtoType(entityName, "Dto");
if (entityType == null || dtoType == null)
{
throw new PasteCodeException($"找不到对应的实体或 DTO 类型: {entityName}");
}
// 2. 动态查询实体
var entity = await FindEntityByIdAsync(entityType, id);
if (entity == null)
{
throw new PasteCodeException("查询的信息不存在,无法执行编辑操作!");
}
// 3. 动态映射实体到 DTO
var dto = MapEntityToDto(entity, entityType, dtoType);
//构建外表等 Include的如何搞定???
await BuildOuterQuery(dto, dtoType);
// 4. 动态读取 DTO 的属性信息
var dataModel = ReadModelProperties(dto, dtoType);
return dataModel;
}
/// <summary>
/// 用户查看表格
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("/api/app/{entityName}/readListModel", Order = 10)]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "view" })]
public VoloModelInfo ReadListModel([FromRoute] string entityName, string searchName = "InputSearchBase")
{
// 动态查找实体对应的 ListDto 类型
var listDtoType = FindDtoType(entityName, "ListDto");
// 创建 ListDto 实例
var listDtoInstance = Activator.CreateInstance(listDtoType);
// 动态读取 ListDto 的属性信息
//var listModel = PasteBuilderHelper.ReadModelProperty(listDtoInstance, true);
var listModel = ReadModelProperties(listDtoInstance, listDtoType, true);
if (searchName != "InputSearchBase")
{
// 保持搜索模型的静态处理(假设 InputSearchBase 是固定类)
var searchModel = PasteBuilderHelper.ReadModelProperty(new InputSearchBase());
if (searchModel != null)
{
listModel.QueryProperties = searchModel.Properties;
}
}
else
{
var search_type = FindType(searchName);
if (search_type != null)
{
var search_model = Activator.CreateInstance(search_type);
//var searchModel = PasteBuilderHelper.ReadModelProperty(search_model);
var searchModel = ReadModelProperties(search_model, search_type, true);
if (searchModel != null)
{
listModel.QueryProperties = searchModel.Properties;
}
}
}
return listModel;
}
#region 以下是辅助函数
/// <summary>
///
/// </summary>
/// <param name="dto"></param>
/// <param name="dtoType"></param>
/// <returns></returns>
private async Task BuildOuterQuery(object dto, Type dtoType)
{
#region 看看是否要执行外表查询
foreach (var aty in dtoType.GetProperties())
{
//var _type = aty.PropertyType;
var attributes = aty.GetCustomAttributes<PasteShortAttribute>();
if (attributes?.Any() == true)
{
var short_attr = attributes.FirstOrDefault();
if (short_attr != null && short_attr != default)
{
var foreignKeyId = aty.GetValue(dto);
if (foreignKeyId == null || foreignKeyId.Equals(0))
{
continue;
}
var shortAttr = short_attr;
//var pro_val =//??? 当前字段的值
//short_attr.Args1 = "GradeInfo";//表示对应的外表的EntityName
//short_attr.Args2 = "ExtendGrade";//表示结果写入到这个字段
//short_attr.Args3 = "ToShortUser()";//表示要转换的类型 比如_dbContext.GradeInfo.Where(x=>x.Id==pro_val).Select(x=>x.ToShortUser()).FirstOrDefaultAsync();
//如何获取字段的当前值?
//await _dbContext.GradeInfo.Where(x => x.Id == 1).Select(y => y.ToShortGrade()).FirstOrDefaultAsync();
//ToShortGrade()是GradeInfo的扩展,返回的就是ExtendGrade的类型
// 获取外表的Entity类型和目标属性
var relatedEntityType = FindEntityType(shortAttr.Args1);
var targetProperty = dtoType.GetProperty(shortAttr.Args2);
if (relatedEntityType == null || targetProperty == null)
{
continue;
}
try
{
// 获取关联实体的DbSet
var dbSet = _dbContext.GetType()
.GetProperties()
.First(p => p.PropertyType.GenericTypeArguments.Contains(relatedEntityType))
.GetValue(_dbContext);
// 动态调用FindAsync方法获取关联实体
var findAsyncMethod = dbSet.GetType()
.GetMethod("FindAsync", new[] { typeof(object[]) });
// 执行查询
var valueTask = findAsyncMethod.Invoke(dbSet, new object[] { new object[] { foreignKeyId } });
// 获取ValueTask的结果
object relatedEntity;
// 检查是否是ValueTask
if (valueTask.GetType().Name.StartsWith("ValueTask"))
{
// 手动获取ValueTask的结果
var getAwaiterMethod = valueTask.GetType().GetMethod("GetAwaiter");
var awaiter = getAwaiterMethod.Invoke(valueTask, null);
var getResultMethod = awaiter.GetType().GetMethod("GetResult");
relatedEntity = getResultMethod.Invoke(awaiter, null);
}
else
{
// 普通Task,直接等待
await (Task)valueTask;
relatedEntity = ((Task<object>)valueTask).Result;
}
if (relatedEntity != null)
{
// 处理转换逻辑
object convertedResult = null;
// 检查是否需要特殊转换
if (!string.IsNullOrEmpty(shortAttr.Args3))
{
var extend = typeof(LinqExtend).GetMethod(shortAttr.Args3.Replace("()", ""), new[] { relatedEntityType });
// 处理特殊转换方法(例如:ToShortUser())
//convertedResult = ConvertEntity(relatedEntity, relatedEntityType, shortAttr.Args3);
convertedResult = extend.Invoke(null, new object[] { relatedEntity });
}
else
{
// 默认转换:直接使用目标属性类型
convertedResult = relatedEntity;
}
// 设置目标属性的值
targetProperty.SetValue(dto, convertedResult);
}
}
catch (Exception ex)
{
Logger.LogError(ex, $"加载关联数据失败: {shortAttr.Args1}");
// 可以选择记录错误但继续处理其他属性
}
//------------------------------------------------
}
}
}
#endregion
}
/// <summary>
///
/// </summary>
/// <param name="entityName"></param>
/// <returns></returns>
private Type FindEntityType(string entityName)
{
// 将前端传递的名称转换为 PascalCase (例如: storeCashWay -> StoreCashWay)
var pascalCaseName = ConvertToPascalCase(entityName);
var assembly = typeof(UserInfo).Assembly;//这里对不对呢... .. .
return assembly.GetTypes().FirstOrDefault(t => t.Name == entityName || t.Name == $"{pascalCaseName}");//这里可能有问题了
}
/// <summary>
///
/// </summary>
/// <param name="entityName"></param>
/// <param name="suffix"></param>
/// <returns></returns>
private Type FindDtoType(string entityName, string suffix)
{
var pascalCaseName = ConvertToPascalCase(entityName);
var dtoName = $"{pascalCaseName}{suffix}";
return typeof(InputSearchBase).Assembly.GetTypes().FirstOrDefault(t => t.Name == dtoName);
}
/// <summary>
///
/// </summary>
/// <param name="typeName"></param>
/// <returns></returns>
private Type FindType(string typeName)
{
var assembly = Assembly.GetExecutingAssembly();
//return assembly.GetTypes().FirstOrDefault(t => t.Name == typeName);
return typeof(InputSearchBase).Assembly.GetTypes().FirstOrDefault(t => t.Name == typeName);
}
/// <summary>
///
/// </summary>
/// <param name="entityType"></param>
/// <param name="id"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="PasteCodeException"></exception>
private async Task<object> FindEntityByIdAsync(Type entityType, int id, CancellationToken cancellationToken = default)
{
// 获取实体对应的 DbSet 属性
var dbSetProperty = _dbContext.GetType()
.GetProperties()
.FirstOrDefault(p =>
p.PropertyType.IsGenericType &&
p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>) &&
p.PropertyType.GetGenericArguments()[0] == entityType);
if (dbSetProperty == null)
{
throw new PasteCodeException($"找不到实体 {entityType.Name} 的 DbSet");
}
// 获取 DbSet 实例
var dbSet = dbSetProperty.GetValue(_dbContext);
// 获取 FindAsync 方法
var findAsyncMethod = dbSet.GetType()
.GetMethod("FindAsync", new[] { typeof(object[]), typeof(CancellationToken) });
// 调用方法
var valueTask = findAsyncMethod.Invoke(dbSet, new object[] { new object[] { id }, cancellationToken });
// 处理 ValueTask<T>
if (valueTask is Task task)
{
await task;
var resultProperty = task.GetType().GetProperty("Result");
return resultProperty.GetValue(task);
}
else
{
// 手动处理 ValueTask<T>
var getAwaiterMethod = valueTask.GetType().GetMethod("GetAwaiter");
var awaiter = getAwaiterMethod.Invoke(valueTask, null);
var getResultMethod = awaiter.GetType().GetMethod("GetResult");
return getResultMethod.Invoke(awaiter, null);
}
}
/// <summary>
///
/// </summary>
/// <param name="sourceType"></param>
/// <param name="destinationType"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
private MethodInfo GetMapMethod(Type sourceType, Type destinationType)
{
// 获取 Map<TSource, TDestination> 方法定义
var map = base.ObjectMapper;
var mapMethod = typeof(Volo.Abp.ObjectMapping.IObjectMapper)
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.FirstOrDefault(m =>
m.Name == "Map" &&
m.IsGenericMethodDefinition &&
m.GetGenericArguments().Length == 2 &&
m.GetParameters().Length == 1
);
if (mapMethod == null)
{
throw new InvalidOperationException("未找到 Map<TSource, TDestination> 方法");
}
// 构造具体的泛型方法
return mapMethod.MakeGenericMethod(sourceType, destinationType);
}
/// <summary>
///
/// </summary>
/// <param name="sourceInstance"></param>
/// <param name="sourceType"></param>
/// <param name="destinationType"></param>
/// <returns></returns>
public object MapEntityToDto(object sourceInstance, Type sourceType, Type destinationType)
{
// 获取 ObjectMapper 实例
var mapper = ObjectMapper;
// 获取映射方法
var mapMethod = GetMapMethod(sourceType, destinationType);
// 调用映射方法
return mapMethod.Invoke(ObjectMapper, new object[] { sourceInstance });
}
/// <summary>
/// 动态读取模型属性
/// </summary>
/// <param name="model"></param>
/// <param name="modelType"></param>
/// <param name="IsListModel">针对枚举的备注过滤</param>
/// <returns></returns>
private VoloModelInfo ReadModelProperties(object model, Type modelType, bool IsListModel = false)
{
// 获取目标泛型方法定义
var genericMethodDefinition = typeof(PasteBuilderHelper)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.First(m => m.Name == "DynamicReadModelProperty" && m.IsGenericMethodDefinition);
// 构建具体的泛型方法(传入实际类型参数)
var constructedGenericMethod = genericMethodDefinition
.MakeGenericMethod(modelType);
// 调用方法并转换结果
return (VoloModelInfo)constructedGenericMethod
.Invoke(null, new object[] { model, IsListModel });
}
/// <summary>
///
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
private string ConvertToPascalCase(string input)
{
if (string.IsNullOrEmpty(input))
{
return input;
}
if (char.IsUpper(input[0]))
{
return input;
}
// 将第一个单词全部转换为小写,后面部分保持不变
return input.Substring(0, 1).ToUpper() + input.Substring(1);
}
#endregion
}
}
上面中主要要注意的是程序集,还是我目前没有做啥缓存的操作
程序集呢,主要是我们程序一般不是单个的,也就是有dll的限制,所以有一个约定哈!
Entity的要放对象UserInfo同一个dll,这个要是你没有UserInfo你换一个有的就行,反正是为了锁定程序集
所有的Dto则锁定为InputSearchBase表示查询的基础对象
查询不用仓储,用的是DbContext
为啥不用仓储呢。。。
我感觉没必要,我看过很多资料,其实都是一个意思,都不知道仓储这个玩意干嘛用得!
有人说为了兼容不同数据库???
其实这个DbContext也是一样的
为了实现不一样的逻辑,或者说共性!
其实你继承某一个接口,一样可以实现,真的!!!
我之前有一个多层级排序的就是这么干的,我感觉其实和仓储一样!
上面的DefaultAPPService我测试了下,可以使用,还能处理外表类型的!爽歪歪!
其他问题等待你得反馈哈!
[Route("/api/app/{entityName}/readUpdateModel", Order = 10)]
兜底的干活,所以你要是要实现个性化,直接按照旧的方式写就行,优先级高于这个!