1. 程式人生 > 其它 >dotnet templating 定製自己的專案模板

dotnet templating 定製自己的專案模板

由於工作需要,研究了一下VS 專案模板生成的相關內容,本文做一下記錄藉助.NET Core Template Engine建立一個加單的專案模板。

建立專案程式碼和配置檔案

首先建立一個Minimal 版本的Web Api專案,基於這個專案定義自己的專案模板。Template Engine 脫離$safeprojectname$這種專案模板引數的方式,好處是模板專案本身始終是一個可以正常編譯執行的專案,可以及時發現製作模板過程中的問題。

.template.config介紹

在專案根目錄下建立.template.config資料夾,存在專案相關配置檔案。資料夾內建立三個json檔案

  • template.json 必須的模板配置檔案,定義一些模板配置資訊以及模板生成的邏輯等
  • ide.host.json 非必須的VS介面配置檔案,定義模板圖示以及配置展示引數等
  • dotnetcli.host.json 非必須的Cli配置檔案,定義命令中引數的短名稱

官方文件中給了template.json檔案必須引數的說明:

除了這三個檔案還可以增加多語言的支援,具體用法可以參考aspnetcore的專案模板

修改.template.config下的檔案

template.json

對symbols欄位做一些說明,其他必須引數上面的圖片中有說明。這裡我定義了四個引數:Framework,UseAuthorize,SeparateRouteHandler,IsLast。Framework和IsLast只作演示,UseAuthorize,SeparateRouteHandler兩個引數是定製模板時需要用到的引數。

Framework引數的datatype為choice型別,vs中會展示一個下拉列表。replaces的指定當前我們選擇的值替換專案中的哪個值,類似sourceName。其他欄位均為字面意思。

UseAuthorize,SeparateRouteHandler兩個型別為bool型別,UseAuthorize是否引入認證鑑權,演示程式碼中的判斷。SeparateRouteHandler演示模板的檔案排除邏輯,生成專案時根據該值判斷排除多餘檔案。

IsLast引數則是簡單的演示計算引數。

sources引數用來自定義輸出源,定義modifiers通過condition指定一個條件包含或者排除哪些檔案,本文使用的是exclude。

primaryOutputs引數自定義輸出,這裡只定義了專案名。

postActions則是定義模板生成後的動作,支援的Action參考文件

更多介紹(如生成Guid,隨機數等)參考Wiki

示例檔案內容:

{
  "$schema": "http://json.schemastore.org/template",
  "author": "Stacking",
  "classifications": ["web", "api"],
  "identity": "stacking.web.tmpl",
  "name": "Stacking Web Api",
  "shortName": "stack.tmpl",
  "tags": {
    "language": "C#",
    "type": "project"
  },
  "sourceName": "Stacking.Web",
  "preferNameDirectory": true,
  "sources": [
    {
      "modifiers": [
        {
          "condition": "(!SeparateRouteHandler)",
          "exclude": ["DemoHandler.cs"]
        }
      ]
    }
  ],
  "symbols": {
    "Framework": {
      "type": "parameter",
      "description": "The target .net framework for the project.",
      "datatype": "choice",
      "choices": [
        {
          "choice": "net6.0",
          "description": "Target net6.0"
        },
        {
          "choice": "net5.0",
          "description": "Target net5.0"
        }
      ],
      "replaces": "net6.0",
      "defaultValue": "net6.0"
    },
    "UseAuthorize": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "false",
      "description": "this application add authorize."
    },
    "SeparateRouteHandler": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "false",
      "description": "route handler use a separate file."
    },
    "IsLast": {
      "type": "computed",
      "value": "(Framework == \"net6.0\")"
    }
  },
  "primaryOutputs": [
    {
      "path": "Stacking.Web.csproj"
    }
  ],
  "postActions": [
    {
      "description": "Restore NuGet packages required by this project.",
      "manualInstructions": [
        {
          "text": "Run 'dotnet restore'"
        }
      ],
      "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
      "continueOnError": true
    }
  ]
}

exclude指定路徑陣列支援Dic/** 以及Dic/*.cs的方式

ide.host.json

該檔案只有兩個簡單的定義,通過icon指定模板圖示(相對路徑),symbolInfo定義引數資訊,如vs上顯示哪些引數以及引數說明和預設值等。

示例檔案內容:

{
  "$schema": "http://json.schemastore.org/vs-2017.3.host",
  "icon": "WebAPI.png",
  "symbolInfo": [
    {
      "id": "Framework",
      "name": {
        "text": ".net framework"
      },
      "description": {
        "text": ".net framework for the project"
      },
      "isVisible": true,
      "defaultValue": "net6.0"
    },
    {
      "id": "UseAuthorize",
      "name": {
        "text": "the project use authorize"
      },
      "isVisible": true
    },
    {
      "id": "SeparateRouteHandler",
      "name": {
        "text": "separate route handler file"
      },
      "invertBoolean": false,
      "isVisible": true
    }
  ]
}

invertBoolean引數的值為true時會將bool型別引數的值反轉,如SeparateRouteHandler的值在指定為true時,模板的判斷邏輯程式碼中這個值會變為false。

dotnetcli.host.json

cli的json檔案中只定義了一個symbolInfo引數,定義命令列方式建立模板時引數的短名稱。

示例檔案內容:

{
  "$schema": "http://json.schemastore.org/dotnetcli.host",
  "symbolInfo": {
    "Framework": {
      "longName": "framework"
    },
    "UseAuthorize": {
      "longName": "use-authorize",
      "shortName": "au"
    },
    "SeparateRouteHandler": {
      "longName": "separate-route-handler",
      "shortName": "separate"
    }
  }
}

自定義模板

自定義CS檔案

cs檔案自定義使用類似預編譯指令的方式 ,使用#if,#endif等命令自定義輸出邏輯

#if (SeparateRouteHandler)
using Stacking.Web;
#endif
#if(UseAuthorize)
using Microsoft.AspNetCore.Authentication.JwtBearer;
#endif

如果判斷流程複雜且模板檔案變動較大可以參考aspnetcore官網模板例項,定義兩個獨立檔案再通過rename和exclude控制模板輸出內容。

{
     "condition": "(UseMinimalAPIs && (IndividualAuth || OrganizationalAuth))",
     "rename": {
       "Program.MinimalAPIs.OrgOrIndividualB2CAuth.cs": "Program.cs"
     },
     "exclude": [
       "Program.MinimalAPIs.WindowsOrNoAuth.cs"
     ]
}

自定義csproj檔案

csproj檔案使用註釋的方式,判斷引數控制邏輯

<!--#if(UseAuthorize)-->
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
<!--#endif-->

自定義json檔案

json檔案同樣使用註釋的方式

//#if(EnableOpenAPI)
"launchUrl": "swagger",
//#else
"launchUrl": "weatherforecast",
//#endif

安裝、解除安裝模板

在專案根目錄下執行dotnet new -i ./命令安裝自定義的模板。

解除安裝模板命令:dotnet new -u ./ 如果不是模板目錄則需要指定模板全路徑

模板釋出到nuget不再說明,寫不動了。參考官網模板定義一個templates.nuspec檔案,執行nuget.exe pack命令即可。

最終效果

示例程式碼