在日常开发中, 有时会遇到一些相似的代码, 甚至是只要 CV 一次, 改几个名称, 就可以实现功能了, 而且总归起来, 都可以由一些公用的页面更改而来, 因此, 结合我日常开发中使用到的页面, 封装一个适合自己的代码生成器, 仅处于入门阶段, 包括生成的代码结构都仅是把框架展示出来, 内部详细暂时没得, 针对于应用服务中的接口和实现, 相关 Dto,MVC 中的控制器, 视图及视图模型进行了模板制作及生成相关的文件.
一, 设计思路
方案一: 开始想到的是, 搞个控制台, 然后给一个. cs 文件, 然后控制台去解析其中的命名空间, 类名, 属性, 再用配置好的 razor 模板去替换, 再生成相关的一些文件出来, 但是发现, 万事开头难, 第一步去解析 cs 文件就不好搞, 找了网上的资料, 不太好弄, 干脆想了下, 放弃这种方案, 因为想到了另一种常用的方案.
方案二: 直接在控制台中, 配置控制台去访问数据库, 然后给定指定表名, 去读取数据库中的表和字段, 再反过来去生成相关文件, 但是这里会遇到一个这样的问题, 比如我使用的是 MySQL,MySQL 本身有个配置表名大小写忽视的, 这样一来, 获取到的表名都将是小写打头, 尽管可能配置了是区分大小写, 但是, 我设计表时, 采用 Pre_table, 形式区分业务表, 比如是 CRM 模块需要用到的 CRM_Client, 那将用 CRM 打头, 后面这部分 Client 才是实际代码中的类名, 种种问题都有可能, 但是作为没有那么多可能性下, 比如没得前缀, 不区分大小写, 形式简单, 那么可以考虑使用. 此时, 想到了 abp 中的 Migrator 控制台并想到了方案三.
方案三: 如果说直接搞一个控制台在代码中, 模仿 Abp 自带的 Migrator 一样, 启动后, 给定类名, 通过反射去取得该类的属性名, 岂不是美滋滋, 需要哪个类的相关文件, 只需启动, 然后输入类名, 即可得到相关的文件. 这几种方案的前提都是在 Dto 文件中会展示所有实体字段, 如果需要选择性的使用字段, 则还需借助人工智能, 以人力去完成更改生成的文件.
二, Razor 引擎的使用
我选择了方案二作为入手去实现, 并且采用 Razor 引擎 https://github.com/Antaris/RazorEngine 作为模板解析的工具. Nuget 引入 RazorEngine.NetCore 包, 开始实现依靠模板生成代码.
1, 先尝试下 Razor 引擎, 控制台中 CV 下 Razor 引擎提供的 Demo, 引入相关命名空间, 学习下如何去使用.
- string template = "Hello @Model.Name, welcome to RazorEngine!";
- var result = Engine.Razor.RunCompile(template, "templateKey", null, new {
- Name = "World"
- });
- Console.WriteLine(result);
运行完毕, 可以获取到运行结果, 需要注意的是, 如果是在 Linux 或是 Mac 跑会得到错误, 该问题是 Razor 引擎本身的问题, 暂时只能在 Windows 下跑.
2, 熟悉了下 Razor 的使用方式后, 开始使用简单文件形式填充一些数据模拟生成过程.
首先, 一个文件作为填充模板, 一个文件内存储 JSON 数据作为数据源, 程序启动时加载两个文件.
- var templatePage = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SimpleCOders\\Templates", "templatePage.txt");
- TemplatePage = File.ReadAllText(templatePage);
- var templatePageJson = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SimpleCOders\\Templates", "templatePageJson.json");
- TemplatePageJson = File.ReadAllText(templatePageJson);
其次, 数据源整理成相应类结构, 得到批量待解析数据.
- var templatePageJsonList = JsonConvert.DeserializeObject<List<PageDataModel>>(TemplatePageJson);
- foreach (var templatePageJson in templatePageJsonList)
- {
- RazorParse(
- templatePageJson.Index ?? 1,
- templatePageJson.Date,
- templatePageJson.Index - 1,
- templatePageJson.Index + 1,
- templatePageJson.Content
- );
- }
最后, 设计一下解析器, 将读取到的数据源, 进行解析成相关的类, 然后依次按照模板生成文件
- var entityResult = Engine.Razor.RunCompile(TemplatePage, "templatePageKey", null, new
- {
- PostData = (date ?? DateTime.Now).ToString("yyyy-MM-dd"),
- PrevIndex = prev.Value,
- NextIndex = next.Value,
- Contenthtml = content
- });
按照一条数据便是一个模板文件去生成可以得到批量生成文件.
三, 适合自己的简单代码生成器
开始着手适合自己的简单代码生成器, 思路一致, 只是增加了需要读取数据库这一环节.
1, 模板制作, 以应用服务接口为例, 常用的增删改查进行封装, 利用 Razor 语法进行填充处理, 此处对于主键类型, 没有进行处理, 只能支持诸如 int,long 之类的, 后期在继续优化.
- using Abp.Application.Services;
- using Abp.Application.Services.Dto;
- using System.Collections.Generic;
- using System.Threading.Tasks;
- using @Model.ProjectNameSpace.@Model.ProjectModule.@(Model.EntityName)s.Dto;
- namespace @Model.ProjectNameSpace.@Model.ProjectModule.@(Model.EntityName)s
- {
- /// <summary>
- /// @(Model.EntityDescription)应用服务接口
- /// </summary>
- public interface I@(Model.EntityName)AppService : IApplicationService
- {
- /// <summary>
- /// 获取 @(Model.EntityDescription)数据集合
- /// </summary>
- /// <param name="input"></param>
- /// <returns></returns>
- Task<PagedResultDto<@(Model.EntityName)ListDto>> GetPaged@(Model.EntityName)(GetPaged@(Model.EntityName)Input input);
- /// <summary>
- /// 获取 @(Model.EntityDescription)编辑信息
- /// </summary>
- /// <param name="input"></param>
- /// <returns></returns>
- Task<Get@(Model.EntityName)ForEditOutput> Get@(Model.EntityName)ForEdit(NullableIdDto<@Model.EntityKeyType> input);
- /// <summary>
- /// 创建或修改 @(Model.EntityDescription)信息
- /// </summary>
- /// <param name="input"></param>
- /// <returns></returns>
- Task CreateOrUpdate@(Model.EntityName)(CreateOrUpdate@(Model.EntityName)Input input);
- /// <summary>
- /// 删除 @(Model.EntityDescription)
- /// </summary>
- /// <param name="input"></param>
- /// <returns></returns>
- Task Delete@(Model.EntityName)(List<EntityDto<@Model.EntityKeyType>> inputs);
- }
- }
2, 设置相应的解析器, 与之前的尝试不同, 这次使用了具体的类型, 这是 Razor 中的另一种方式, 解析完毕后将文件按照指定路径保存, 尽量符合项目的路径存储.
- var iRazorAppService = Engine.Razor.RunCompile(IRazorAppService, nameof(IRazorAppService), typeof(TemplateParseModel), templateParseModel);
- UtilHelper.Save(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, applicationPath, $"I{templateParseModel.EntityName}AppService.cs"), iRazorAppService);
- builder.Append(iRazorAppService);
3, 数据库连接读取表结构, 控制台下, 采用直接读取的形式, 不走 DbContext 方式, Nuget 中引入 MySQL.Data 包(我本地用的 MySQL), 增加 Appsettings.JSON 文件并配置好连接字符串, 用 sql 语句形式直接读取数据库中的信息, 此处封装了一个 DbHelper 类及将读取到的信息封装到指定类中.
- using (var SqlConnection = new MySqlConnection(connectionStr))
- {
- SqlConnection.Open();
- var columsInfo = string.Format(@"select table_name,column_name,ordinal_position,is_nullable,data_type,character_maximum_length,column_key,column_comment
- from information_schema.COLUMNS
- where table_schema = '{0}' and table_name = '{1}'", dbschema, tablename);
- MySqlCommand mySqlCommand = new MySqlCommand(columsInfo, SqlConnection);
- MySqlDataReader dataReader = mySqlCommand.ExecuteReader();
- List<ColumnInfo> sqlDatasList = new List<ColumnInfo>();
- while (dataReader.Read())
- {
- var columnInfo = new ColumnInfo()
- {
- TableName = dataReader[dataReader.GetName(0)].ToString(),
- Name = dataReader[dataReader.GetName(1)].ToString(),
- OrdinalPosition = StringExtension.GetValueOrNull<int>(dataReader[dataReader.GetName(2)].ToString()),
- IsNullable = dataReader[dataReader.GetName(3)].ToString(),
- DataType = dataReader[dataReader.GetName(4)].ToString(),
- CharacterMaximumLength = StringExtension.GetValueOrNull<int>(dataReader[dataReader.GetName(2)].ToString()),
- ColumnKey = dataReader[dataReader.GetName(6)].ToString(),
- ColumnComment = dataReader[dataReader.GetName(7)].ToString(),
- };
- sqlDatasList.Add(columnInfo);
- }
- dataReader.Close();
- SqlConnection.Close();
- return sqlDatasList;
4, 启动后输入表名, 实体名, 实体描述(并未保存到数据库中), 再通过手动将其加入到项目中, 诸如命名空间及模块名称都加入到了配置文件中, 方便配置, 至少相对手动去一个个添加来讲, 减少了部分工作量, 也达到了辅助的效果, 但是要达到全面辅助, 还得在进行继续优化, 针对其中的类等等, 暂时没有加入属性, 只放置了 Id,Name 等等, 之后得考虑把数据库中字段也循环输出到模板文件中.
至此, 依靠 Razor 引擎制作一个简单的 (算是减少了工作量) 代码生成器初步完成了, 年后继续完善, 加入丰富的功能, 并移入到框架中作为提高生产力的手段. 新年快乐~
仓库地址:
2020-01-01, 望技术有成后能回来看见自己的脚步
来源: https://www.cnblogs.com/CKExp/p/11996382.html