打造自己的- NET Core 項目模板

前言

每個人都有自己習慣的項目結構,有人的喜歡在項目裏面建解決方案文件夾;有的人喜歡傳統的三層命名;有的人喜歡單一,簡單的項目一個 csproj 就搞定。。

反正就是蘿蔔青菜,各有所愛。

可能不同的公司對這些會有特定的要求,也可能會隨開發自己的想法去實踐。

那麼,問題就來了。如果有一個新項目,你會怎麼去創建?

可能比較多的方式會是下面三種:

以前我也是基於 dotnet cli 寫好了 sh 或 ps 的腳本,然後用這些腳本來生成新項目。

但是呢,這三種方式,始終都有不盡人意的地方。

因爲建好的都是空模板,還要做一堆複雜的操作纔可以讓項目 “正常” 的跑起來。比如,這個公共類要抄過來,那個公共類要抄過來。。。這不是明擺着浪費時間嘛。。。

下面介紹一個小辦法來幫大家省點時間。

基於 dotnet cli 創建自己的項目模板,也就是大家常說的腳手架。

dotnet cli 項目模板預熱

開始正題之前,我們先看一下 dotnet cli 自帶的一些模板。

可以看到種類還是很多的,由於工作大部分時間都是在寫 WebAPI,所以這裏就用 WebAPI 來寫個簡單的模板。

編寫自己的模板

既然是模板,就肯定會有一個樣例項目。

下面我們建一個樣例項目,大致成這樣,大家完全可以按照自己習慣來。

這其實就是一個普通的項目,裏面添加了 NLog,Swagger,Dapper 等組件,各個項目的引用關係是建好的。

該有的公共類,裏面也都包含了,好比宇內分享的那個 WebHostBuilderJexusExtensions。

下面是這個模板跑起來的效果。

就是一個簡單的 Swagger 頁面。

現在樣例已經有了,要怎麼把這個樣例變成一個模板呢?

答案就是template.json

在樣例的根目錄創建一個文件夾.template.config,同時在這個文件夾下面創建template.json

示例如下:

{
    "author": "Catcher Wong", //必須
    "classifications": [ "Web/WebAPI" ], //必須,這個對應模板的Tags
    "name": "TplDemo", //必須,這個對應模板的Templates
    "identity": "TplDemoTemplate", //可選,模板的唯一名稱
    "shortName": "tpl", //必須,這個對應模板的Short Name
    "tags": {
      "language": "C#" ,
      "type":"project"
    },
    "sourceName": "TplDemo",  // 可選,要替換的名字
    "preferNameDirectory": true  // 可選,添加目錄  
}

在這裏,有幾個比較重要的東西,一個是 shortName,一個是 sourceName

在寫完template.json之後,還需要安裝一下這個模板到我們的 cli 中。

使用 dotnet new -i進行模板的安裝。

下面是安裝示例。

dotnet new -i ./content/TplDemo

這裏要注意的是,與.template.config文件夾同級的目錄,都會被打包進模板中。

在執行安裝命令之後,就可以看到我們的模板已經安裝好了。

這個時候已經迫不及待的想來試試這個模板了。

先來看看這個模板的幫助信息。

dotnet new tpl -h

因爲我們目前還沒有設置參數,所以這裏顯示的是還沒有參數。

下面來創建一個項目試試。

從創建一個項目,到運行起來,很簡單,效果也是我們預期的。

下面來看看,新建的這個 HelloTpl 這個項目的目錄結構和我們的模板是否一樣。

可以看到,除了名字,其他的內容都是一樣的。

是不是感覺又可以少複製粘貼好多代碼了。

雖說,現在建項目,已經能把一個大的模板完整的 copy 出來了,但是始終不是很靈活!

可能有小夥伴會問,明明已經很方便了呀,爲什麼還會說它不靈活呢?

且聽我慢慢道來。

如果說這個模板是個大而全的模板,包含了中間件 A,中間件 B,中間件 C 等 N 箇中間件!

而在建新項目的時候,已經明確了只用中間件 A,那麼其他的中間件對我們來說,可能就沒有太大的存在意義!

很多時候,不會想讓這些多餘的文件出現在代碼中,有沒有辦法來控制呢?

答案是肯定的!可以把不需要的文件排除掉就可以了。

文件過濾

模板項目中有一個RequestLogMiddleware,就用它來做例子。

我們只需要做下面幾件事就可以了。

第一步,在template.json中添加過濾

加入一個名字爲EnableRequestLog的 symbol。同時指定源文件

{
    "author": "Catcher Wong",
    //others...
    "symbols":{
      //是否啓用RequestLog這個Middleware
      "EnableRequestLog": {
        "type": "parameter", //它是參數
        "dataType":"bool", //bool類型的參數
        "defaultValue": "false" //默認是不啓用
      }
    },
    "sources": [
      {
          "modifiers": [
              {
                  "condition": "(!EnableRequestLog)", //條件,由EnableRequestLog參數決定
                  "exclude": [ //排除下面的文件
                    "src/TplDemo/Middlewares/RequestLogMiddleware.cs",
                    "src/TplDemo/Middlewares/RequestLogServiceCollectionExtensions.cs" 
                  ]
              }
          ]
      }
    ]    
  }

第二步,在模板的代碼中做一下處理

主要是Startup.cs,因爲 Middleware 就是在這裏啓用的。

    using System;
    //other using...
    using TplDemo.Core;
#if (EnableRequestLog)    
    using TplDemo.Middlewares;
#endif

    /// <summary>
    /// 
    /// </summary>
    public class Startup
    {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            //other code....
#if (EnableRequestLog)
            //request Log
            app.UseRequestLog();
#endif            
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }

這樣的話,只要EnableRequestLog是 true,那麼就可以包含這兩段代碼了。

下面更新一下已經安裝的模板。

這個時候再去看它的幫助信息,已經可以看到我們加的參數了。

下面先建一個默認的 (不啓用 RequestLog)

dotnet new tpl -n NoLog

這個命令等價於

dotnet new tpl -n WithLog -E false

下面是建好之後的目錄結構和 Startup.cs

可以看到 RequestLog 相關的東西都已經不見了。

再建一個啓用 RequestLog 的,看看是不是真的起作用了。

dotnet new tpl -n WithLog -E true

可以看到,效果已經出來了。

下面在介紹一個比較有用的特性。動態切換,這個其實和上面介紹的內容相似。

直接舉個例子來說明吧。

假設我們的模板支持 MSSQL, MySQL, PgSQL 和 SQLite 四種數據庫操作

在新建一個項目的時候,只需要其中一種,好比說要建一個 PgSQL 的,肯定就不想看到其他三種。

這裏不想看到,有兩個地方,一個是 nuget 包的引用,一個是代碼。

上一小節是對某個具體的功能進行了開關的操作,這裏有了 4 個,我們要怎麼處理呢?

我們可以用類型是choice的參數來完成這個操作。

修改template.json,加入下面的內容

{
  "author": "Catcher Wong",
  //others
  "symbols":{
    "sqlType": {
      "type": "parameter",
      "datatype": "choice",
      "choices": [
        {
          "choice": "MsSQL",
          "description": "MS SQL Server"
        },
        {
          "choice": "MySQL",
          "description": "MySQL"
        },
        {
          "choice": "PgSQL",
          "description": "PostgreSQL"
        },
        {
          "choice": "SQLite",
          "description": "SQLite"
        }
      ],
      "defaultValue": "MsSQL",
      "description": "The type of SQL to use"
    },  
    "MsSQL": {
      "type": "computed",
      "value": "(sqlType == \"MsSQL\")"
    },
    "MySQL": {
      "type": "computed",
      "value": "(sqlType == \"MySQL\")"
    },
    "PgSQL": {
      "type": "computed",
      "value": "(sqlType == \"PgSQL\")"
    },
    "SQLite": {
      "type": "computed",
      "value": "(sqlType == \"SQLite\")"
    }
  }
}

看了上面的 JSON 內容之後,相信大家也知道個所以然了。有一個名爲 sqlType 的參數,它有幾中數據庫選擇,默認是 MsSQL。

還另外定義了幾個計算型的參數,它的取值是和 sqlType 的值息息相關的。

MsSQL,MySQL,PgSQL 和 SQLite 這 4 個參數也是我們在代碼裏要用到的!!

修改 csproj 文件,讓它可以根據 sqlType 來動態引用 nuget 包,我們加入下面的內容

<ItemGroup Condition="'$(MySQL)' == 'True' ">  
    <PackageReference Include="MySqlConnector" Version="0.47.1" />
</ItemGroup>

<ItemGroup Condition="'$(PgSQL)' == 'True' ">  
    <PackageReference Include="Npgsql" Version="4.0.3" />
</ItemGroup>

<ItemGroup Condition="'$(SQLite)' == 'True' ">  
    <PackageReference Include="Microsoft.Data.Sqlite" Version="2.1.0" />
</ItemGroup>

同樣的,代碼也要做相應的處理

#if (MsSQL)
    using System.Data.SqlClient;
#elif (MySQL)
    using MySql.Data.MySqlClient;
#elif (PgSQL)
    using Npgsql;
#else 
    using Microsoft.Data.Sqlite;
#endif

    protected DbConnection GetDbConnection()
    {
#if (MsSQL)            
        return new SqlConnection(_connStr);
#elif (MySQL)            
        return new MySqlConnection(_connStr);
#elif (PgSQL)             
        return new NpgsqlConnection(_connStr);
#else              
        return new SqliteConnection(_connStr);
#endif              
    }

修改好之後,同樣要去重新安裝這個模板,安裝好之後,就可以看到 sqlType 這個參數了。

下面分別創建一個 MsSQL 和 PgSQL 的項目,用來對比和驗證。

先後執行

dotnet new tpl -n MsSQLTest -s MsSQL 
dotnet new tpl -n PgSQLTest -s PgSQL

然後打開對應的 csproj

可以看到,PgSQL 的,添加多了NPgsql這個包。而 MsSQL 的卻沒有。

同樣的,DapperRepositoryBase 也是一樣的效果。在創建 Connection 對象的時候,都根據模板來生成了。

當然這個是在我們自己本地安裝的模板,其他人是沒有辦法使用的。

如果想公開,可以發佈到 nuget 上面去。如果是在公司內部共享,可以搭建一個內部的 nuget 服務,將模板上傳到內部服務器裏面去。

下面是一些可以開箱即用的模板:

https://dotnetnew.azurewebsites.net/

總結

有一個自己的項目模板 (腳手架),還是很方便的。

一建生成自己需要的東西,減少了不必要的代碼複製,可以將更多精力放在業務實現上。

在平時還是要有一些積累,當積累足夠豐富之後,我們的腳手架可能就會變得十分強大。

參考文檔

dotnet new 下面默認的模板 https://github.com/aspnet/Templating

templating 的源碼 https://github.com/dotnet/templating

template.json 的說明 https://github.com/dotnet/templating/wiki/Reference-for-template.json

dotnet cli 的文檔 https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet?tabs=netcore21

最後是文中的示例代碼

Template

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://www.cnblogs.com/catcher1994/p/10061470.html