X-Admin&ABP框架開發-代碼生成器


  在日常開發中,有時會遇到一些相似的代碼,甚至是只要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,望技術有成后能回來看見自己的腳步


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM