X-Admin&ABP框架开发-代码生成器
微笑刺客D 人气:0在日常开发中,有时会遇到一些相似的代码,甚至是只要CV一次,改几个名称,就可以实现功能了,而且总归起来,都可以由一些公用的页面更改而来,因此,结合我日常开发中使用到的页面,封装一个适合自己的代码生成器,仅处于入门阶段,包括生成的代码结构都仅是把框架展示出来,内部详细暂时没得,针对于应用服务中的接口和实现,相关Dto,MVC中的控制器、视图及视图模型进行了模板制作及生成相关的文件。
一、设计思路
方案一:开始想到的是,搞个控制台,然后给一个.cs文件,然后控制台去解析其中的命名空间,类名,属性,再用配置好的razor模板去替换,再生成相关的一些文件出来,但是发现,万事开头难,第一步去解析cs文件就不好搞,找了网上的资料,不太好弄,干脆想了下,放弃这种方案,因为想到了另一种常用的方案。
方案二:直接在控制台中,配置控制台去访问数据库,然后给定指定表名,去读取数据库中的表和字段,再反过来去生成相关文件,但是这里会遇到一个这样的问题,比如我使用的是mysql,mysql本身有个配置表名大小写忽视的,这样一来,获取到的表名都将是小写打头,尽管可能配置了是区分大小写,但是,我设计表时,采用Pre_table,形式区分业务表,比如是CRM模块需要用到的CRM_Client,那将用CRM打头,后面这部分Client才是实际代码中的类名,种种问题都有可能,但是作为没有那么多可能性下,比如没得前缀,不区分大小写,形式简单,那么可以考虑使用。此时,想到了abp中的Migrator控制台并想到了方案三。
方案三:如果说直接搞一个控制台在代码中,模仿Abp自带的Migrator一样,启动后,给定类名,通过反射去取得该类的属性名,岂不是美滋滋,需要哪个类的相关文件,只需启动,然后输入类名,即可得到相关的文件。这几种方案的前提都是在Dto文件中会展示所有实体字段,如果需要选择性的使用字段,则还需借助人工智能,以人力去完成更改生成的文件。
二、Razor引擎的使用
我选择了方案二作为入手去实现,并且采用Razor引擎作为模板解析的工具。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引擎本身的问题,暂时只能在window下跑。
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引擎制作一个简单的(算是减少了工作量)代码生成器初步完成了,年后继续完善,加入丰富的功能,并移入到框架中作为提高生产力的手段。新年快乐~
仓库地址:https://gitee.com/530521314/Partner.TreasureChest.git
2020-01-01,望技术有成后能回来看见自己的脚步
加载全部内容