.Net Core——用程式碼寫程式碼?

Max發表於2021-05-21

想要用程式碼寫程式碼,肯定是繞不開反射的。反射的概念相比都不陌生,只是應用多少就因人而異,今天分享一個程式碼生成器的思路,僅供參考,不要過分依賴哦。

思路分析

眾所周知,利用反射可以在程式執行時獲取到任一物件的型別、屬性、引數、方法等,並加以呼叫,利用這些獲取到的可以在程式執行時追加各種自定義的功能。以CRUD為例,我們可以利用反射獲取到所有的Model並編寫程式碼模板,最終達成用程式碼生成程式碼的結果。思路既然能走通,開搞開搞~

編碼階段

首先要確定生成哪些程式碼。CRUD每天都在做,但大部分都一樣,這就導致每天有些時間都在板磚,毫無意義。所以我們需要自動生成全庫的CRUD程式碼。

首先要寫一個CRUD的模板程式碼,考慮到ORM框架太多,這裡就以SqlSugar為例:

     [HttpGet]
        public async Task<IActionResult> GetList(int index = 1, int size = 15)
        {
            RefAsync<int> count = 0;

            return PageMsg(await _sqlHelper.DB
                .Queryable<Models.AD>()
                .OrderBy(x => x.Weight)
                .ToPageListAsync(index, size, count), count.Value);
        }

        [HttpPost, Role("新增廣告")]
        public async Task<IActionResult> Add([FromForm] Models.AD ad)
        {
            Models.AD add = await _sqlHelper.DB
                .Insertable(ad)
                .RemoveDataCache()
                .ExecuteReturnEntityAsync();

            return Ok(add);
        }

        [HttpPut, Role("修改廣告")]
        public async Task<IActionResult> Cag([FromForm] Models.AD ad)
        {
       
int result = await _sqlHelper.DB .Updateable(ad) .RemoveDataCache() .ExecuteCommandAsync();
       
       return
YesOrNo(result > 0); } [HttpDelete] public async Task<IActionResult> Delete([FromForm] int id) {
       
int result = await _sqlHelper.DB .Deleteable<Models.AD>() .Where(x => x.ID == id) .ExecuteCommandAsync();
       return YesOrNo(result > 0); }

能看出這是一組操作AD類的CRUD,接下來就是需要把上面的模板封裝起來,AD這種表的名稱作為變數輸入即可,如果想控制程式碼檔案的路徑,也可以作為引數傳入。完整版:

     public void Make(string tableName, string path)
        {
            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);

            using FileStream fs = new FileStream($"{path}/{tableName}Controller.cs", FileMode.Append);

            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            sb.Append("using System.Threading.Tasks;");
            sb.Append("\n");
            sb.Append("using XXXX.Services.DB;");
            sb.Append("\n");
            sb.Append("using Microsoft.AspNetCore.Authorization;");
            sb.Append("\n");
            sb.Append("using Microsoft.AspNetCore.Mvc;");
            sb.Append("\n");
            sb.Append("using SqlSugar;");
            sb.Append("\n");
            sb.Append("namespace XXXX.Controllers.v1");
            sb.Append("\n");
            sb.Append("{");
            sb.Append("\n");
            sb.Append("     [ApiController, Route(\"v1/[controller]/[action]\"), Authorize]");
            sb.Append("\n");
            sb.Append($"     public class {tableName}Controller : BaseController");
            sb.Append("\n");
            sb.Append("     {");
            sb.Append("\n");
            sb.Append("         private readonly SqlHelper _sqlHelper;");
            sb.Append("\n");
            sb.Append($"        public {tableName}Controller(SqlHelper sqlHelper)");
            sb.Append("\n");
            sb.Append("         {");
            sb.Append("\n");
            sb.Append("             _sqlHelper = sqlHelper;");
            sb.Append("\n");
            sb.Append("         }");
            sb.Append("\n");
            sb.Append("         [HttpGet]");
            sb.Append("\n");
            sb.Append($"         public async Task<IActionResult> GetList(int index = 1, int size = 15)");
            sb.Append("\n");
            sb.Append("         {");
            sb.Append("\n");
            sb.Append($"            RefAsync<int> count = 0;");
            sb.Append("\n");
            sb.Append("             return Ok(new{");
            sb.Append("\n");
            sb.Append($"               rows = await _sqlHelper.DB.Queryable<Models.{tableName}>().ToPageListAsync(index, size, count),");
            sb.Append("\n");
            sb.Append("                total = count.Value");
            sb.Append("\n");
            sb.Append("             });");
            sb.Append("\n");
            sb.Append("         }");
            sb.Append("\n");
            sb.Append("         [HttpPost]");
            sb.Append("\n");
            sb.Append($"         public async Task<IActionResult> Add([FromForm] Models.{tableName} {tableName.ToLower()})");
            sb.Append("\n");
            sb.Append("         {");
            sb.Append("\n");
            sb.Append($"             Models.{tableName} add = await _sqlHelper.DB.Insertable({tableName.ToLower()}).RemoveDataCache().ExecuteReturnEntityAsync();");
            sb.Append("\n");
            sb.Append("              return Ok(add);");
            sb.Append("\n");
            sb.Append("         }");
            sb.Append("\n");
            sb.Append("         [HttpPut]");
            sb.Append("\n");
            sb.Append($"         public async Task<IActionResult> Cag([FromForm] Models.{tableName} {tableName.ToLower()})");
            sb.Append("\n");
            sb.Append("         {");
            sb.Append("\n");
            sb.Append($"             int result = await _sqlHelper.DB.Updateable({tableName.ToLower()}).RemoveDataCache().ExecuteCommandAsync();");
            sb.Append("\n");
            sb.Append("              return YesOrNo(result>0);");
            sb.Append("\n");
            sb.Append("         }");
            sb.Append("\n");
            sb.Append("         [HttpDelete]");
            sb.Append("\n");
            sb.Append($"         public async Task<IActionResult> Cag([FromForm] int id)");
            sb.Append("\n");
            sb.Append("         {");
            sb.Append("\n");
            sb.Append($"             int result = await _sqlHelper.DB.Deleteable<Models.{tableName}>().Where(x => x.ID == id).ExecuteCommandAsync();");
            sb.Append("\n");
            sb.Append("              return YesOrNo(result>0);");
            sb.Append("\n");
            sb.Append("         }");
            sb.Append("\n");
            sb.Append("     }");
            sb.Append("\n");
            sb.Append("}");

            using StreamWriter sw = new StreamWriter(fs);

            sw.WriteLine(sb.ToString());
        }

為了方便理解,刻意美化了一下。

OK,程式碼模板搞好了,該從哪裡拿到所有的Model呢?思路是先載入程式集,然後找到存放Model的名稱空間,然後找到名稱空間下面所有符合條件的Class,然後就可以拿到具體的名稱,從而呼叫程式碼模板進行檔案生成,像這樣:

       var assembly = Assembly.Load("程式集名稱");

            var types = assembly.ExportedTypes.Where(a => a.FullName.Contains("Model所處的名稱空間")).ToList();

       //指定
string path = $"{Directory.GetCurrentDirectory()}/autoCode"; foreach (var item in types) { string tableName = item.FullName.Split('.')[^1]; Make(tableName, path); }

接下來直接執行,走完以後的結果是這樣的:

 

 

 隨便開啟一個檔案,是這樣的:

 

 

 效果還是不錯的。

如果需要更加精細的控制,可以預先在Model設定特性,然後通過判斷特性是否存在來決定如何生成程式碼,舉個例子:

       var assembly = Assembly.Load("程式集名稱");

            var types = assembly.ExportedTypes.Where(a => a.FullName.Contains("Model所處的名稱空間")).ToList();

       //指定
            string path = $"{Directory.GetCurrentDirectory()}/autoCode";

            foreach (var item in types)
            {
          //獲取所有公開屬性 foreach (var prop in item.GetProperties())
          {
   MyAttribute attr = method.GetCustomAttribute(typeof(MyAttribute), true) as MyAttribute;
            if(attr is null)
            {
              //進入這裡代表該屬性沒有附加 MyAttribute
            }
} }

當然,還可以獲取MyAttribute的屬性拿過來進行判斷等等。而實際上很多ORM框架也是用類似手段實現Code First、DB First,不過複雜程度比較高。

這樣批量生成程式碼有點像活字印刷,雖然可以解決大量機械重複的工作,但靈活度還是不夠高,人腦還是無法替代的。

奇技淫巧,見笑了。

相關文章