打造自己的.NET Core項目模板
1.前言
每個人都有自己習(xí)慣的項目結(jié)構(gòu),有人的喜歡在項目里面建解決方案文件夾;有的人喜歡傳統(tǒng)的三層命名;有的人喜歡單一,簡單的項目一個csproj就搞定。。
反正就是蘿卜青菜,各有所愛。
可能不同的公司對這些會有特定的要求,也可能會隨開發(fā)自己的想法去實踐。
那么,問題就來了。如果有一個新項目,你會怎么去創(chuàng)建?
可能比較多的方式會是下面三種:
簡單粗暴型,打開VS就是右鍵添加,然后引入一堆包,每個項目添加引用。 腳本型,基于dotnet cli,創(chuàng)建解決方案,創(chuàng)建項目,添加包,添加項目引用。 高大上型,VS項目模板,直接集成到VS上面了。以前我也是基于dotnet cli寫好了sh或ps的腳本,然后用這些腳本來生成新項目。
但是呢,這三種方式,始終都有不盡人意的地方。
因為建好的都是空模板,還要做一堆復(fù)雜的操作才可以讓項目“正常”的跑起來。比如,這個公共類要抄過來,那個公共類要抄過來。。。這不是明擺著浪費時間嘛。。。
下面介紹一個小辦法來幫大家省點時間。
基于dotnet cli創(chuàng)建自己的項目模板,也就是大家常說的腳手架。
2.dotnet cli項目模板預(yù)熱
開始正題之前,我們先看一下dotnet cli自帶的一些模板。
可以看到種類還是很多的,由于工作大部分時間都是在寫WebAPI,所以這里就用WebAPI來寫個簡單的模板。
下面我們就基于dotnet cli寫一個自己的模板。
3.編寫自己的模板
既然是模板,就肯定會有一個樣例項目。
下面我們建一個樣例項目,大致成這樣,大家完全可以按照自己習(xí)慣來。
這其實就是一個普通的項目,里面添加了NLog,Swagger,Dapper等組件,各個項目的引用關(guān)系是建好的。
該有的公共類,里面也都包含了,好比宇內(nèi)分享的那個WebHostBuilderJexusExtensions。

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

就是一個簡單的Swagger頁面。
現(xiàn)在樣例已經(jīng)有了,要怎么把這個樣例變成一個模板呢?
答案就是template.json!
在樣例的根目錄創(chuàng)建一個文件夾.template.config,同時在這個文件夾下面創(chuàng)建template.json。
示例如下:
{
????"author":?"Catcher?Wong",?//必須
????"classifications":?[?"Web/WebAPI"?],?//必須,這個對應(yīng)模板的Tags
????"name":?"TplDemo",?//必須,這個對應(yīng)模板的Templates
????"identity":?"TplDemoTemplate",?//可選,模板的唯一名稱
????"shortName":?"tpl",?//必須,這個對應(yīng)模板的Short?Name
????"tags":?{
??????"language":?"C#"?,
??????"type":"project"
????},
????"sourceName":?"TplDemo",??//?可選,要替換的名字
????"preferNameDirectory":?true??//?可選,添加目錄??
}
在這里,有幾個比較重要的東西,一個是shortName,一個是sourceName。
shortName,簡寫,偷懶必備,好比能寫 -h就絕對不寫 ``--help`sourceName,這是個可選的字段,它的值會替換指定的項目名,正常是把項目名賦值在這里。如果不指定,創(chuàng)建的項目就和樣例項目保持一致。在寫完 template.json之后,還需要安裝一下這個模板到我們的cli中。
使用 dotnet new -i進(jìn)行模板的安裝。
下面是安裝示例:
dotnet?new?-i?./content/TplDemo
這里要注意的是,與.template.config文件夾同級的目錄,都會被打包進(jìn)模板中。在執(zhí)行安裝命令之后,就可以看到我們的模板已經(jīng)安裝好了。

這個時候已經(jīng)迫不及待的想來試試這個模板了。
先來看看這個模板的幫助信息。
dotnet?new?tpl?-h

因為我們目前還沒有設(shè)置參數(shù),所以這里顯示的是還沒有參數(shù)。
下面來創(chuàng)建一個項目試試。
從創(chuàng)建一個項目,到運行起來,很簡單,效果也是我們預(yù)期的。
下面來看看,新建的這個HelloTpl這個項目的目錄結(jié)構(gòu)和我們的模板是否一樣。
可以看到,除了名字,其他的內(nèi)容都是一樣的。
是不是感覺又可以少復(fù)制粘貼好多代碼了。
雖說,現(xiàn)在建項目,已經(jīng)能把一個大的模板完整的copy出來了,但是始終不是很靈活!
可能有小伙伴會問,明明已經(jīng)很方便了呀,為什么還會說它不靈活呢?
且聽我慢慢道來。
如果說這個模板是個大而全的模板,包含了中間件A,中間件B,中間件C等N個中間件!
而在建新項目的時候,已經(jīng)明確了只用中間件A,那么其他的中間件對我們來說,可能就沒有太大的存在意義!
很多時候,不會想讓這些多余的文件出現(xiàn)在代碼中,有沒有辦法來控制呢?
答案是肯定的!可以把不需要的文件排除掉就可以了。
4.文件過濾
模板項目中有一個RequestLogMiddleware,就用它來做例子。

我們只需要做下面幾件事就可以了。
第一步,在template.json中添加過濾
加入一個名字為EnableRequestLog的symbol。同時指定源文件
{
????"author":?"Catcher?Wong",
????//others...
????"symbols":{
??????//是否啟用RequestLog這個Middleware
??????"EnableRequestLog":?{
????????"type":?"parameter",?//它是參數(shù)
????????"dataType":"bool",?//bool類型的參數(shù)
????????"defaultValue":?"false"?//默認(rèn)是不啟用
??????}
????},
????"sources":?[
??????{
??????????"modifiers":?[
??????????????{
??????????????????"condition":?"(!EnableRequestLog)",?//條件,由EnableRequestLog參數(shù)決定
??????????????????"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
????///?
????///?
????///?
????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,那么就可以包含這兩段代碼了。
下面更新一下已經(jīng)安裝的模板。
這個時候再去看它的幫助信息,已經(jīng)可以看到我們加的參數(shù)了。
下面先建一個默認(rèn)的(不啟用RequestLog)
dotnet?new?tpl?-n?NoLog
這個命令等價于
dotnet?new?tpl?-n?WithLog?-E?false
下面是建好之后的目錄結(jié)構(gòu)和Startup.cs
可以看到RequestLog相關(guān)的東西都已經(jīng)不見了。
再建一個啟用RequestLog的,看看是不是真的起作用了。
dotnet?new?tpl?-n?WithLog?-E?true

可以看到,效果已經(jīng)出來了。
下面在介紹一個比較有用的特性。動態(tài)切換,這個其實和上面介紹的內(nèi)容相似。
5.動態(tài)切換
直接舉個例子來說明吧。
假設(shè)我們的模板支持MSSQL, MySQL, PgSQL和SQLite四種數(shù)據(jù)庫操作
在新建一個項目的時候,只需要其中一種,好比說要建一個PgSQL的,肯定就不想看到其他三種。
這里不想看到,有兩個地方,一個是nuget包的引用,一個是代碼。
上一小節(jié)是對某個具體的功能進(jìn)行了開關(guān)的操作,這里有了4個,我們要怎么處理呢?
我們可以用類型是choice的參數(shù)來完成這個操作。
修改template.json,加入下面的內(nèi)容
{
??"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內(nèi)容之后,相信大家也知道個所以然了。有一個名為sqlType的參數(shù),它有幾中數(shù)據(jù)庫選擇,默認(rèn)是MsSQL。
還另外定義了幾個計算型的參數(shù),它的取值是和sqlType的值息息相關(guān)的。
MsSQL,MySQL,PgSQL和SQLite這4個參數(shù)也是我們在代碼里要用到的!!
修改csproj文件,讓它可以根據(jù)sqlType來動態(tài)引用nuget包,我們加入下面的內(nèi)容
"'$(MySQL)'?==?'True'?">??
????"MySqlConnector"?Version="0.47.1"?/>
"'$(PgSQL)'?==?'True'?">??
????"Npgsql"?Version="4.0.3"?/>
"'$(SQLite)'?==?'True'?">??
????"Microsoft.Data.Sqlite"?Version="2.1.0"?/>
同樣的,代碼也要做相應(yīng)的處理
#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這個參數(shù)了。

下面分別創(chuàng)建一個MsSQL和PgSQL的項目,用來對比和驗證。
先后執(zhí)行
dotnet?new?tpl?-n?MsSQLTest?-s?MsSQL?
dotnet?new?tpl?-n?PgSQLTest?-s?PgSQL
然后打開對應(yīng)的csproj

可以看到,PgSQL的,添加多了NPgsql這個包。而MsSQL的卻沒有。
同樣的,DapperRepositoryBase也是一樣的效果。在創(chuàng)建Connection對象的時候,都根據(jù)模板來生成了。
當(dāng)然這個是在我們自己本地安裝的模板,其他人是沒有辦法使用的。
如果想公開,可以發(fā)布到nuget上面去。如果是在公司內(nèi)部共享,可以搭建一個內(nèi)部的nuget服務(wù),將模板上傳到內(nèi)部服務(wù)器里面去。
下面是一些可以開箱即用的模板:
https://dotnetnew.azurewebsites.net/
6. 總結(jié)
有一個自己的項目模板(腳手架),還是很方便的。
一建生成自己需要的東西,減少了不必要的代碼復(fù)制,可以將更多精力放在業(yè)務(wù)實現(xiàn)上。
在平時還是要有一些積累,當(dāng)積累足夠豐富之后,我們的腳手架可能就會變得十分強(qiáng)大。
參考文檔:
dotnet new下面默認(rèn)的模板 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
最后是文中的示例代碼:https://github.com/catcherwong/Demos/tree/master/src/Template
原文:https://www.cnblogs.com/catcher1994/p/10061470.html


副業(yè)剛需,沒有人能拒絕這個網(wǎng)站!

終于GitHub App 已支持簡體中文!
