NG筆記3-使用TypeScript 一文曾提及「另建程式碼產生器專案,將ViewModel規格轉成JavaScript(或TypeScript)、C#類別」的做法,方便Client及Server端共享一致的強型別ViewModel,規格如有更動,重跑程式產生器就能同步更新。

日前網友Ark詢問,有無上述做法的實際範例可供參考。先前其他專案也曾有類似程式產生器需求,手邊有運行多時的實例,但綁死過多專屬邏輯,複雜度過高難以抽離重用,算一算手邊的確缺少一組能獨立運行的淺顯範例,索性另起一套簡單的概念驗證(Proof Of Concept)範例,方便有興趣研究的同事網友參考。

範例程式碼我已放上Github,有需要的朋友請自取。以下則為簡單說明:

  1. 解決方案有三個專案:
    • CodeGen:程式產生器,其中的Models.xml為View Model定義,只有Player、Game兩組資料物件定義。
    • ViewModels:為共用程式庫(Class Library),CodeGen產生ViewModels.cs及額外ViewModel邏輯放在這裡。
    • Web:ASP.NET MVC應範例,內含CodeGen產生的ViewModel.ts(TypeScript)以及呼叫MVC方法由Server端取回ViewModel的範例
  2. Models.xml是模型定義,實務上我更愛用Excel,編輯修改方便,甚至可交給非IT人員維護。POC力求精簡,用XML意思到了就好。 
  3. 在CodeGen專案,Program.cs負責解析XML轉成結構化的屬性名稱、屬性型別資料。
    這部分沒什麼既定規則標準,全憑開發者自行定義欄位名稱、型別、註解該怎麼標示,只要確保XML文件與轉換邏輯匹配即可。支援的型別愈多,解析程式就愈複雜,例如要涵蓋陣列、泛型、自訂型別,需定義標示方式並加入對應的解析邏輯。
    我的範例只處理最基本的int、float、double、decimal、DateTime、bool、string,再加上Nullable。想要更彈性支援更多型別,請大家自由發揮。 CodeGen專案的ModelInfo及PropInfo型別用來將XML定義內容轉為.NET端的資料結構,方便後續應用。
  4. 將ModelInfo及PropInfo轉成C#及TypeScript型別的重任由執行期間T4範本一肩扛起,以下分別為產生C#型別及TypeScript型別的T4範本檔:


  5. 兩個T4範本的模型定義都來自this.Models(List<MethodInfo>),Models由Program.cs解析XML而得,要怎麼傳給T4範本呢?我習慣的做法是宣告與T4範本同名的Partial Class,新増接受List<ModelInfo>參數的建構式以及Models供T4範本使用,如此在T4中就可大大方方用this.Models取得Program.cs傳入的模型定義:
  6. Program.cs依以下方式將XML解析轉成List<ModelInfo>交給T4範本轉成BaseModels.cs及BaseModels.ts,產生的檔案分別寫入ViewModels專案及Web專案下的對應位置:
            static void Main(string[] args)
            {
                var models = ReadModels();
                var t4csvm = new T4.CsViewModels(models);
                File.WriteAllText(@"..\..\..\ViewModels\BaseModels.cs", 
                    t4csvm.TransformText(), Encoding.UTF8);
                var t4tsvm = new T4.TsViewModels(models);
                File.WriteAllText(@"..\..\..\Web\Scripts\BaseModels.ts", 
                    t4tsvm.TransformText(), Encoding.UTF8);
            }
  7. 產生的BaseModels.cs長這樣:

    BaseModels.ts長這樣:
  8. 由於轉換所得的ViewModel型別只有屬性,如需入額外邏輯,在C#端可借用Partial Class的特性,為已有的Player型別增加建構式、屬性、方法。 


    注意:請避免直接修改BaseModels.cs,因為修改內容每次重跑CodeGen就會被覆寫。如果想調整BaseModels.cs,請直接修改T4範本,或使用Partial Class技巧實現。
    至於TypeScript型別,雖然沒有Partial Class的概念,但可以用繼承的做法加上自訂邏輯,要客製化不是問題。
  9. 如此,在C#端我們就有強型別的Player可用:

    在TypeScript端也有強型別的Player可用,Intellisense還能提供註解說明

未來若View Model要修改,調完XML重跑CodeGen,BaseModels.cs與BaseModels.ts就自動更新,程式端馬上有修正後的新版View Model可用,很酷吧?

希望程式範例跟說明內容夠清楚,如有不清楚之處歡迎回饋給我,我再盡力補充。


Comments

# by Ark

感謝黑暗大提供這麼酷的POC, 小弟大概了解您的作法, 用同一份Model定義產生兩份強型別CLASS, 分別給C#與TS使用, 那不知黑暗大如何處理與資料庫ORM這塊呢?是透過EF來處理嗎?還是用SQLHELPER之類取出DataTable再填入強型別物件中呢?

# by Jeffrey

to Ark, 利用相同原理可用T4產生Create Table的SQL Script建立資料表,.NET 2.0時代我曾自己產生ORM Entity,但LINQ to SQL/EF問市後,多半改用現成的輪子。 至於資料資存取,我傾向新增修改用EF,刪除查詢用Dapper,自己掌握SQL指令以確保效能。

# by Ark

看了黑暗大介紹的Dapper之後, 實作了一下果然相見恨晚啊>"<, 不過想再請教的是為什麼黑暗大新增修改用EF而刪除查詢用Dapper? Dapper也是可以處理新增修改, 小弟對於EF還停留4.0的印象, 每次DB加欄位 要重gen就覺得很麻煩, 不知6.0之後是否方便許多?

# by Jeffrey

to Ark, 要用Dapper處理新増,得自己寫出"insert into my_table (col1,col2,col3...) values (@p1,@p2,@p3,...)" SQL語法,想像資料表有五十個欄位的情境,就能體會為什麼用EF比較方便。(參考:http://blog.darkthread.net/blogs/darkthreadtw/archive/2009/07/14/linq-to-sql-research1.aspx ) EntityFramework 5/6已支援在edmx設計畫面透過右鍵選單「Update Model from Database...」更新Schema,不需要重建,用起來還算方便。

Post a comment