我之前问了下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个这样的,所以这样的事情肯定是交给代码生成器去生成的!
那么问题来了,为啥不全部合并成一个???

平滑升级

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

一般流程
Entity
覆盖
升级部署add-migration
强刷
新增字段
Dto
重新生成CRUD的表单页面等
管理端
服务端
更新字段

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

PasteForm流程

Entity
升级部署add-migration
刷新
新增字段
Dto
服务端
管理端

和上面的对比,会发觉少了管理端页面这一个步骤,也就是说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文档
PasteForm专题

可能在以上信息中,你看到的特性不完整,因为我一直在用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下了!

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐