给你们安利一个“敏捷开发框架天花板”的框架,DeepSeek评价的最佳开发框架,主要是针对表单,管理端的

我之前问了下DeepSeek,了解下PasteForm这个框架的优缺点,她给我以下几点 看到上面是不是心动了? PasteForm到底是个啥东东?

可以说PasteForm框架的核心是"All in Dto" 其实他可以适用于任何开发语言,也可以适用于其他管理端! 比如你可以把它作用于桌面端,或者是小程序上 后端也可以更换成rust/java/php等 他只是一个思想,并不是一个具体的! PasteForm框架可以说是"All in Dto"的一个具体表现,目前来说是最佳表象,一起来看看PasteForm是个啥玩意!

PasteForm

PasteForm是贴代码在2024年推出的一个框架,可以说是框架ABP的一个补充!

通过反射原理,在对应的Dto的字段中标注特性,让管理端基于字段特性做响应的处理,比如显示为输入框,图片,富文本,下拉框,分组,数据转化等!

所以说,他应该是一个思想,一个通过Dto来控制管理端页面的思想!

Dto定义

其实Dto的概念我相信我们一直都在使用,只是是否有强调出来而已,我第一次接触规范的dto是在ABP框架中,举个栗子

makefile 复制代码
用户表
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对应的字段和特性信息
csharp 复制代码
        /// <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个这样的,所以这样的事情肯定是交给代码生成器去生成的! 那么问题来了,为啥不全部合并成一个???

平滑升级

如果有一个表要新增一个字段,你要如何操作?

一般流程
graph LR A[新增字段] -- Entity --> B((Dto)) B --> C(重新生成CRUD的表单页面等) -- 覆盖 --> E[管理端] B -- 升级部署add-migration --> D{服务端} D --> F{更新字段} E -- 强刷 --> F

以上流程主要的就是修改数据库的字段,然后修改对应的dto,然后重新发布api,更新或者修改管理端对应的页面,然后发布管理端页面,最后要强刷,否则会出现管理端版本和API版本不一致的问题!需要更新管理端页面

PasteForm流程

graph LR A[新增字段] -- Entity --> B((Dto)) B -- 升级部署add-migration --> D{服务端} D -- 刷新 --> F{管理端}

和上面的对比,会发觉少了管理端页面这一个步骤,也就是说PasteForm的新增字段等是不需要更新管理端页面的,也不需要强刷!不需要更新管理端页面

特性案例

下面举几个例子,特性和UI的表现,一把是常用的,也有高级的!

image

图片在表单中是很常见的一个组件,PasteForm为此设定了 1.当前字段允许上传几张,是否是单张还是比如N张 2.上传的文件存储在哪个文件夹(api接收到存储到对应位置) 3.上传后图片是否按照约定进行裁剪,比如120120或者1920 0 4.是否调用不一样的api地址进行上传 如果要实现上面的上传图片的,只要在Dto中做如下标记特性即可

csharp 复制代码
        ///<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是一个变种,可以直观的显示,不过有一个要点要注意,项不能过多,否则会放不下!

csharp 复制代码
        /// <summary>
        /// 权限类型 基于权限类型查询
        /// </summary>
        [PasteLselect(PasteFormString.SelectRoleTypeQuery, "", 1)]
        public int RoleType { get; set; } = -1;

以上这个也是支持Enum枚举类型的,比如这样

csharp 复制代码
        /// <summary>
        /// 授权类型
        /// </summary>
        [PasteLselect]
        public EnumModel AuthType { get; set; } = EnumModel.ok;

是不是非常简单,不过EnumModel的注释也要遵循PasteForm的XML注释规范,就是标题和注释部分用空格隔开即可,连在一起会变成都是标题,可能会很长!

daterange

时间区间选择,这个很常用吧,在查询和时间相关的时候 要实现上面的输入也很简单

csharp 复制代码
        /// <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则更简单

csharp 复制代码
        /// <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文档 PasteForm专题

可能在以上信息中,你看到的特性不完整,因为我一直在用PasteForm开发新的需求,这样就会产出出新的规则(特性)

你可以查看 贴代码案例项目 下载后关注example\PasteTemplate\PasteTemplate.HttpApi.Host\wwwroot\page\pasteform\readme.md文件 一般有新的思路或者问题我都会以草稿的形式写在这里 一般一周会同步到文档或者专题中!

框架辅助

一个框架,一定要有其他的辅助工具,不让都是很累人的,代码生成器肯定是首要的,不如你要写一大堆重复的代码! 一起来看看我为PasteForm做了哪些贴心的辅助

PasteBuilder

代码生成器,VS2022的插件,有别于其他框架的代码生成器,操作流程是 写好Entity后,右键对应的这个文件,然后点击生成,会在以下地方生成对应的代码 1.EF中的xxxDbContext中添加对应的Dbset

csharp 复制代码
        /// <summary>
        /// 角色信息
        /// </summary>
        public DbSet<GradeInfo> GradeInfo { get; set; }

2.EF中的XXXModelCreatingExtensions

csharp 复制代码
            //**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中添加

csharp 复制代码
            // *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文件内,大概如下内容

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,问题来了,编辑的时候,你不能让用户填写数字和查看数字吧,这个时候我们希望管理员看到的是这个数字对应的名称,比如

csharp 复制代码
    ///<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的框架层了,基础版本的,我觉得应该算是最精简的了!
PasteDocument

这个是文档,在线文档,哪里不会点哪里的问题,专门针对特性mark而做的一个文档系统,开源的! 当你的用户在使用你的管理端的时候,他遇到某些表单项不知道啥意思的时候,直接点击表单项的?即可立马查看这个项的介绍!

PasteDocument在线文档

项目模板

你可以参考我以前的文章,把这个项目打包成项目模板,后续创建项目就可以按照这个基数来创建项目了,直接就搭建好了用户模块等,直接在这个基础上加业务表等即可!而且使用案例项目创建的项目,项目名称等是自定义的,不会每个项目都是PasteTemplate为namespace!

PasteSpider

这个,这个,这个其实和PasteForm关系不大,真要说,PasteSpider也是PasteForm框架项目! 优点打硬性广告吧! 开发好项目后,你可以使用PasteSpider进行部署,关键点在于你在开发过程中的迭代,使用PasteSpider可以一键发布,支持平滑升级,关键点在于3分钟就可上手!!! 无论你的服务器是windows还是linux都支持,你可以把你的项目部署为容器运行,或者Linux的systemd,也可以是Windows的IIS和Service!

PasteSpider开发者专属部署工具介绍

其他问答

访问后表单没有标题等

注意看案例项目的XXX.ApiHost项目下的xml文件,也就是项目的文档文件,读取XML注释是从这个文件读取的! 存放注意,比如Enum枚举类,需要放在XXX.Domain或者XXX.Application.Contracts项目下

文件夹命名问题

新增Entity的时候,注意命名规范,是XXX.yyy XXX表示你的项目名称,yyy表示模块,这样后续的Dto等也是这个规范,他们就在同一个namespace下了!

相关推荐
爱勇宝42 分钟前
第 1 章:别把“需求文档”当成真正的需求
前端·后端·程序员
IT_陈寒5 小时前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
易协同低代码6 小时前
通达OA核心类库TD类深度解析
后端
Gopher_HBo6 小时前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志7 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..7 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects7 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰7 小时前
一些基础和问题解决
后端
陈随易8 小时前
前端项目部署只要30秒
前端·后端·程序员
YIAN8 小时前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp