
DeepSeek评价PasteForm框架是最佳实践框架,一个让管理端开发爽到飞起的全栈解决方案
各位程序猿/媛们注意啦!今天我要给大家安利一个神奇的工具——**PasteForm**!这玩意儿简直是把"偷懒"变成了一门艺术!🎨事情是这样的,前几天我悄悄问了问 **DeepSeek** 小姐姐(没错,就是那个聪明绝顶的AI),让她给我扒一扒这个框架的底裤...啊不是,是优缺点!
我之前问了下DeepSeek,了解下PasteForm这个框架的优缺点,她给我以下几点
看到上面是不是心动了?
PasteForm到底是个啥东东?
PasteForm
PasteForm是贴代码在2024年推出的一个框架,可以说是框架ABP的一个补充!
通过反射原理,在对应的Dto的字段中标注特性,让管理端基于字段特性做响应的处理,比如显示为输入框,图片,富文本,下拉框,分组,数据转化等!
所以说,他应该是一个思想,一个通过Dto来控制管理端页面的思想!
Dto定义
其实Dto的概念我相信我们一直都在使用,只是是否有强调出来而已,我第一次接触规范的dto是在ABP框架中,举个栗子
用户表
User :对应数据库的表,字段和表的列一一对应
UserAddDto:对应新增User的数据Model,比如只需要账号和密码,其他的在入库前由User的默认值填充
UserUpdateDto:对应更新User的数据的Model,比如只能修改昵称,密码,头像,签名等,其他的保持不变(账号,ID)
UserDto:对应查看用户详细的时候的Model,这个Model一般只用于显示,展示数据用,不做提交修改用
UserListDto:对应表格显示的时候,比如不显示密码,比如可以显示创建时间,状态等
从上面的信息可知,对应的Dto的操作应该都是经过表单的形式提交的,是不是高度重复???或者说高度复用!
而UserListDto则为表格显示!
假设你有一个系统,有100个数据表,你要写多少表单和表格???
任何开发语言都能使用
- 通用思想,所以按照这个思想,你用其他语言也可以实现,比如java+vue,php+html,.net+html5等
- 业务保留,业务接口和这个框架没有冲突,PasteForm处理的是管理端相关的接口,也就是管理端页面用到的接口
- 底层不限,你可以在其他框架上实现PasteForm,只要按照规则返回特定格式的数据即可!
优点
敏捷天花板
比如你要开发一个新的模块,你只要确定数据库表的字段等,然后使用PasteBuilder生成对应的Dto,AppService文件即可,然后运行后,添加对应的模块的菜单(权限表中添加菜单配置,就是添加一行数据),即可在管理端对新增的模块进行表单管理和表格查看了!后续将推出图表查看模式!
代码量小
无论你的系统是100个表,还是10个表,管理端的页面总数都是差不多10个,样式高度统一(肯定统一的,因为他们用的就是一个页面),页面量这么小,是不是每个页面代码都很多?不存在的,目前单个页面的最高代码量不超过3000行,是那种没压缩的代码行!
易维护
可以说你几乎不需要开发和维护管理端,因为我们提供了整个管理端!具体的可以看下方的PastTemplate介绍
简单易上手
简单在于他对你现有的业务代码没有侵入式的改变,因为不相关!以User表为例,你需要添加以下接口
- /api/app/user/readAddModel:返回的数据是UserAddDto对应的字段和特性信息
- /api/app/user/readUpdateModel:返回的数据是UserUpdateDto对应的字段和特性信息
- /api/app/user/readListModel:返回的数据是UserListDto和InputSearchUser对应的字段和特性信息
- /api/app/user/readDetailModel:返回的数据是UserDto对应的字段和特性信息
/// <summary>
/// 读取AddDto的数据模型
/// </summary>
/// <returns></returns>
[HttpGet]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "add" })]
public VoloModelInfo ReadAddModel()
{
var _model = PasteBuilderHelper.ReadModelProperty<UserAddDto>(new UserAddDto());
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.User.Where(x => x.Id == id).AsNoTracking().FirstOrDefaultAsync();
if (_info == null || _info == default)
{
throw new PasteCodeException("查询的信息不存在,无法执行编辑操作!");
}
var dto = ObjectMapper.Map<User, UserUpdateDto>(_info);
var _dataModel = PasteBuilderHelper.ReadModelProperty<UserUpdateDto>(dto);
return _dataModel;
}
/// <summary>
/// 读取UpdateDto的数据模型
/// </summary>
/// <returns></returns>
[HttpGet]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "edit" })]
public async Task<VoloModelInfo> ReadDetailModel(int id)
{
var _info = await _dbContext.User.Where(x => x.Id == id).AsNoTracking().FirstOrDefaultAsync();
if (_info == null || _info == default)
{
throw new PasteCodeException("查询的信息不存在,无法执行编辑操作!");
}
var dto = ObjectMapper.Map<User, UserDto>(_info);
var _dataModel = PasteBuilderHelper.ReadModelProperty<UserDto>(dto);
return _dataModel;
}
/// <summary>
/// 读取ListDto的数据模型
/// </summary>
/// <returns></returns>
[HttpGet]
public VoloModelInfo ReadListModel()
{
var _model = PasteBuilderHelper.ReadModelProperty<UserListDto>(new UserListDto());
var _query_model = PasteBuilderHelper.ReadModelProperty(new InputQueryUser());
if (_query_model != null)
{
_model.QueryProperties = _query_model.Properties;
}
return _model;
}
以上就是获得Model的信息的api接口,有没有发觉这些代码很简单!
如果你有100个表,那么就有100个这样的,所以这样的事情肯定是交给代码生成器去生成的!
那么问题来了,为啥不全部合并成一个???
平滑升级
如果有一个表要新增一个字段,你要如何操作?
一般流程
以上流程主要的就是修改数据库的字段,然后修改对应的dto,然后重新发布api,更新或者修改管理端对应的页面,然后发布管理端页面,最后要强刷,否则会出现管理端版本和API版本不一致的问题!需要更新管理端页面
PasteForm流程
和上面的对比,会发觉少了管理端页面这一个步骤,也就是说PasteForm的新增字段等是不需要更新管理端页面的,也不需要强刷!不需要更新管理端页面
特性案例
下面举几个例子,特性和UI的表现,一把是常用的,也有高级的!
image
图片在表单中是很常见的一个组件,PasteForm为此设定了
1.当前字段允许上传几张,是否是单张还是比如N张
2.上传的文件存储在哪个文件夹(api接收到存储到对应位置)
3.上传后图片是否按照约定进行裁剪,比如120120或者19200
4.是否调用不一样的api地址进行上传
如果要实现上面的上传图片的,只要在Dto中做如下标记特性即可
///<summary>
///头像 模拟图片上传
///</summary>
[ColumnDataType("image", "1", "head", "60*60")]
public string Img1 { get; set; }
/// <summary>
/// 多图 上传的时候会转化成string[]提交给api
/// </summary>
[PasteImage(2,"img","1920*0")]
public string[] Img2 { get; set; }
/// <summary>
/// 多图 上传的时候多图会以,隔开
/// </summary>
[PasteImage(2, "img", "1920*0")]
public string Img3 { get; set; }
是不是很简单,只要一行代码即可实现图片上传!!!
lselect
我们常用的选择,一般是select,而这个lselect是一个变种,可以直观的显示,不过有一个要点要注意,项不能过多,否则会放不下!
/// <summary>
/// 权限类型 基于权限类型查询
/// </summary>
[PasteLselect(PasteFormString.SelectRoleTypeQuery, "", 1)]
public int RoleType { get; set; } = -1;
以上这个也是支持Enum枚举类型的,比如这样
/// <summary>
/// 授权类型
/// </summary>
[PasteLselect]
public EnumModel AuthType { get; set; } = EnumModel.ok;
是不是非常简单,不过EnumModel的注释也要遵循PasteForm的XML注释规范,就是标题和注释部分用空格隔开即可,连在一起会变成都是标题,可能会很长!
daterange
时间区间选择,这个很常用吧,在查询和时间相关的时候
要实现上面的输入也很简单
/// <summary>
/// 日期区间
/// </summary>
[ColumnDataType("daterange", "sdate", "edate")]
[PasteMark]
public DateTime? sdate { get; set; } = DateTime.Now.AddDays(-1);
/// <summary>
/// 日期区间
/// </summary>
[ColumnDataType("hidden")]
public DateTime? edate { get; set; } = DateTime.Now.AddDays(1);
注意如果不设置为Nullable的类型,则会变成必填项目!
如果使用PasteDaterange则更简单
/// <summary>
/// 日期区间
/// </summary>
[PasteDaterange("start", "end")]
public DateTime? start { get; set; } = DateTime.Now.AddDays(-1);
/// <summary>
/// 日期区间
/// </summary>
public DateTime? end { get; set; } = DateTime.Now.AddDays(1);
PasteForm目前支持的特性大概在60个左右,更多的你可以查看源码,或者是查阅文档
可能在以上信息中,你看到的特性不完整,因为我一直在用PasteForm开发新的需求,这样就会产出出新的规则(特性)
你可以查看
贴代码案例项目
下载后关注example\PasteTemplate\PasteTemplate.HttpApi.Host\wwwroot\page\pasteform\readme.md文件
一般有新的思路或者问题我都会以草稿的形式写在这里
一般一周会同步到文档或者专题中!
框架辅助
一个框架,一定要有其他的辅助工具,不让都是很累人的,代码生成器肯定是首要的,不如你要写一大堆重复的代码!
一起来看看我为PasteForm做了哪些贴心的辅助
PasteBuilder
代码生成器,VS2022的插件,有别于其他框架的代码生成器,操作流程是
写好Entity后,右键对应的这个文件,然后点击生成,会在以下地方生成对应的代码
1.EF中的xxxDbContext中添加对应的Dbset
/// <summary>
/// 角色信息
/// </summary>
public DbSet<GradeInfo> GradeInfo { get; set; }
2.EF中的XXXModelCreatingExtensions
//**UserInfo**
builder.Entity<UserInfo>(b =>
{
b.ToTable(options.TablePrefix + "UserInfo", options.Schema);
b.ConfigureByConvention();
});
3.XXX.Application.Contracts中添加对应的Dto,生成的规则是创建一个模板名称文件夹,然后以EntityDto为文件名,里面包含了所有对应的Dto,比如
UserDto.cs里面有UserAddDto,UserUpdateDto,UserDto,UserListDto
4.XXX.Application中创建对应的AppService文件,比如UserAppService.cs,这个文件的内容包含了RestApi的接口
5.XXX.Application中的XXXAutoMapperProfile中添加
// *UserInfo*:
CreateMap<UserInfo, UserInfoListDto>();
CreateMap<UserInfo, UserInfoDto>();
CreateMap<UserInfo, UserInfoUpdateDto>();
CreateMap<UserInfoUpdateDto, UserInfo>();
CreateMap<UserInfoAddDto, UserInfo>();
没啦!没了!
如果只是以上的5点,你肯定会觉得好像少了啥,比如个性化等,比如AppService的内容和你的习惯不符等,比如Dto中确实很多特性,你总不能把所有特性写Entity吧!这和我们说的规范存放不太符合!
PasteBuilder其实还有2个隐藏功能!
config.json
配置文件,主要是给Dto的特性而设计的,他应该存放于XXX.Domain项目中的/template/config.json文件内,大概如下内容
{
"readme": "这个是后续支持的,目前处于思路阶段,表示代码生成器的一些配置,比如字段隐藏,字段的属性直接填充等",
"version": "1.0.0",
"ignore": { //忽略哪些字段
"all": {
"add": [ "CreateDate", "AdminUid", "CreateUid" ],
"update": [ "UpdateDate", "CreateDate" ],
"detail": [ "UpdateDate", "CreateDate" ],
"list": [ "Body", "Content", "Context" ]
},
"other": {
"UserInfo:add": [ "DelColumn" ] //表示表示这个表的这个add模式下,删除这个字段DelColumn
}
},
"fields": {
"*:add:user_id": { //表示在adddto中,如果当前class有字段user_id,则添加如下字段(ExtendUser)
"CnName": "创建者", //字段中文
"PropertyDisplayStr": "", //字段说明,如果有空格用_*_替代
"PropertyType": "UserShort", //数据类型
"Name": "ExtendUser" //字段名
}
},
"attribute": {
"ignore": [ "Comment", "Display", "Decription", "Index", "DefaultValue", "Column", "NotMapped" ], //表示忽略哪些来自Domain的特性
"all": {
"UserId": "[ColumnDataType(\"outer\",\"userInfo\",\"extendUser\",\"id\",\"userName\")]" //表示这个字段UserId需要添加这个特性多个之间用::隔开 示例:[PasteLeft]::[PasteHidden]
},
"other": {
"UserInfo:list:userName": "[PasteClass]", //表名称 示例:[PasteClass]::[PasteHidden]
"*:add:mark": "[PasteClass]" //新版本支持
}
}
}
注意看上面的内容,也就是你可以在此配置,哪些字段不在dto中出现,比如创建时间,比如更新时间你总不能让用户在表单自己输入吧!
还有就是标记特性,上面有一个关键信息fields代码块,这个块的意思就是在dto中基于规则,动态引入新的字段信息!
这个一般用于外表
1.方式一,直接使用外表模式,也就是Include查询方式,这个直接用就行,本身支持!
2.方式二,没有走外表关系,只走了一个ID,比如GradeRole表,记录了2个字段GradeId和RoleID,问题来了,编辑的时候,你不能让用户填写数字和查看数字吧,这个时候我们希望管理员看到的是这个数字对应的名称,比如
///<summary>
///角色绑定权限
///</summary>
public class GradeRoleUpdateDto : EntityDto<int>
{
///<summary>
///角色ID 点击选择角色
///</summary>
[ColumnDataType("outer", "gradeInfo", "extendGrade", "id", "name")]
public int GradeId { get; set; }
/// <summary>
///
/// </summary>
[ColumnDataType("hidden")]
public GradeShortModel ExtendGrade { get; set; }
///<summary>
///权限ID 点击选择权限
///</summary>
[ColumnDataType("outer", "roleInfo", "extendRole", "id", "name")]
public int RoleId { get; set; }
/// <summary>
///
/// </summary>
[ColumnDataType("hidden")]
public RoleShortModel ExtendRole { get; set; }
}
看上面的信息,在Entity中是只有GradeId和RoleId2个字段的,而在Dto中我们还要引入其他的字段ExtendGrade和ExtendRole,这就需要fileds这个配置块了,看了上面的信息,比如你要动态引入public RoleShortModel ExtendRole { get; set; },那他的特性hidden咋办?
attribute配置块同样生效的!
template
模板,主要是用于生成dto文件的规则,比如上面说的UserDto.cs和UserAppService.cs,模板文件放于config.json同文件夹,如果你下载PasteTemplate项目,会看到template文件夹下有以下文件
dto.html.txt,server.html.txt,如果你要修改某一个模板,需要把后缀删除,也就是dto.html,server.html等,如果你要生成表格页面和表单页面,你也可以修改index.html.txt和view.html.txt,不过PasteForm框架已经不需要这2个文件生成了!
模板文件的语法你可以点击同文件夹的readme.md文件查看,是liquid这个模板!
生成规则
如果你仔细观察的话,会发现生成的代码如果追加代码的,一般会有特别的信息,比如上面的UserInfo,这个就是用于防止重复生成的,如果即将生成的文件已经存在了,则不会生成新的,这样可以防止你好不容易修改好了的文件被覆盖了!!!
PasteTemplate
这个是我提供的案例项目,可以说我后续的升级都在这个项目上测试的,所以你下载后可能会优点乱,不过应该都可以运行的,下载后先要执行add-migration进行数据库结构升级才可以运行,默认是以本地数据库sqlite的方式运行!
下载项目后可以看到如下结构
- PasteFormHelper:这个是PasteForm的核心代码,用于反射Model获得Model的字段信息的
- PasteTemplate.Handler:我提倡的业务层,被Application或者其他引用
- 其他的就是标准的ABP的框架层了,基础版本的,我觉得应该算是最精简的了!
项目模板
你可以参考我以前的文章,把这个项目打包成项目模板,后续创建项目就可以按照这个基数来创建项目了,直接就搭建好了用户模块等,直接在这个基础上加业务表等即可!而且使用案例项目创建的项目,项目名称等是自定义的,不会每个项目都是PasteTemplate为namespace!
PasteSpider
这个,这个,这个其实和PasteForm关系不大,真要说,PasteSpider也是PasteForm框架项目!
优点打硬性广告吧!
开发好项目后,你可以使用PasteSpider进行部署,关键点在于你在开发过程中的迭代,使用PasteSpider可以一键发布,支持平滑升级,关键点在于3分钟就可上手!!!
无论你的服务器是windows还是linux都支持,你可以把你的项目部署为容器运行,或者Linux的systemd,也可以是Windows的IIS和Service!
其他问答
访问后表单没有标题等
注意看案例项目的XXX.ApiHost项目下的xml文件,也就是项目的文档文件,读取XML注释是从这个文件读取的!
存放注意,比如Enum枚举类,需要放在XXX.Domain或者XXX.Application.Contracts项目下
文件夹命名问题
新增Entity的时候,注意命名规范,是XXX.yyy
XXX表示你的项目名称,yyy表示模块,这样后续的Dto等也是这个规范,他们就在同一个namespace下了!
更多推荐
所有评论(0)