打造自己的.NET Core項(xiàng)目模板
前言
每個(gè)人都有自己習(xí)慣的項(xiàng)目結(jié)構(gòu),有人的喜歡在項(xiàng)目里面建解決方案文件夾;有的人喜歡傳統(tǒng)的三層命名;有的人喜歡單一,簡(jiǎn)單的項(xiàng)目一個(gè)csproj就搞定。。
反正就是蘿卜青菜,各有所愛(ài)。
可能不同的公司對(duì)這些會(huì)有特定的要求,也可能會(huì)隨開(kāi)發(fā)自己的想法去實(shí)踐。
那么,問(wèn)題就來(lái)了。如果有一個(gè)新項(xiàng)目,你會(huì)怎么去創(chuàng)建?
可能比較多的方式會(huì)是下面三種:
- 簡(jiǎn)單粗暴型,打開(kāi)VS就是右鍵添加,然后引入一堆包,每個(gè)項(xiàng)目添加引用。
- 腳本型,基于dotnet cli,創(chuàng)建解決方案,創(chuàng)建項(xiàng)目,添加包,添加項(xiàng)目引用。
- 高大上型,VS項(xiàng)目模板,直接集成到VS上面了。
以前我也是基于dotnet cli寫(xiě)好了sh或ps的腳本,然后用這些腳本來(lái)生成新項(xiàng)目。
但是呢,這三種方式,始終都有不盡人意的地方。
因?yàn)榻ê玫亩际强漳0?,還要做一堆復(fù)雜的操作才可以讓項(xiàng)目“正?!钡呐芷饋?lái)。比如,這個(gè)公共類要抄過(guò)來(lái),那個(gè)公共類要抄過(guò)來(lái)。。。這不是明擺著浪費(fèi)時(shí)間嘛。。。
下面介紹一個(gè)小辦法來(lái)幫大家省點(diǎn)時(shí)間。
基于dotnet cli創(chuàng)建自己的項(xiàng)目模板,也就是大家常說(shuō)的腳手架。
dotnet cli項(xiàng)目模板預(yù)熱
開(kāi)始正題之前,我們先看一下dotnet cli自帶的一些模板。
可以看到種類還是很多的,由于工作大部分時(shí)間都是在寫(xiě)WebAPI,所以這里就用WebAPI來(lái)寫(xiě)個(gè)簡(jiǎn)單的模板。
下面我們就基于dotnet cli寫(xiě)一個(gè)自己的模板。
編寫(xiě)自己的模板
既然是模板,就肯定會(huì)有一個(gè)樣例項(xiàng)目。
下面我們建一個(gè)樣例項(xiàng)目,大致成這樣,大家完全可以按照自己習(xí)慣來(lái)。
這其實(shí)就是一個(gè)普通的項(xiàng)目,里面添加了NLog,Swagger,Dapper等組件,各個(gè)項(xiàng)目的引用關(guān)系是建好的。
該有的公共類,里面也都包含了,好比宇內(nèi)分享的那個(gè)WebHostBuilderJexusExtensions。
下面是這個(gè)模板跑起來(lái)的效果。
就是一個(gè)簡(jiǎn)單的Swagger頁(yè)面。
現(xiàn)在樣例已經(jīng)有了,要怎么把這個(gè)樣例變成一個(gè)模板呢?
答案就是template.json
!
在樣例的根目錄創(chuàng)建一個(gè)文件夾.template.config
,同時(shí)在這個(gè)文件夾下面創(chuàng)建template.json
。
示例如下:
{ "author": "Catcher Wong", //必須 "classifications": [ "Web/WebAPI" ], //必須,這個(gè)對(duì)應(yīng)模板的Tags "name": "TplDemo", //必須,這個(gè)對(duì)應(yīng)模板的Templates "identity": "TplDemoTemplate", //可選,模板的唯一名稱 "shortName": "tpl", //必須,這個(gè)對(duì)應(yīng)模板的Short Name "tags": { "language": "C#" , "type":"project" }, "sourceName": "TplDemo", // 可選,要替換的名字 "preferNameDirectory": true // 可選,添加目錄 }
在這里,有幾個(gè)比較重要的東西,一個(gè)是shortName,一個(gè)是sourceName。
- shortName,簡(jiǎn)寫(xiě),偷懶必備,好比能寫(xiě)
-h
就絕對(duì)不寫(xiě)--help
- sourceName,這是個(gè)可選的字段,它的值會(huì)替換指定的項(xiàng)目名,正常是把項(xiàng)目名賦值在這里。如果不指定,創(chuàng)建的項(xiàng)目就和樣例項(xiàng)目保持一致。
在寫(xiě)完template.json
之后,還需要安裝一下這個(gè)模板到我們的cli中。
使用 dotnet new -i
進(jìn)行模板的安裝。
下面是安裝示例。
dotnet new -i ./content/TplDemo
這里要注意的是,與.template.config
文件夾同級(jí)的目錄,都會(huì)被打包進(jìn)模板中。
在執(zhí)行安裝命令之后,就可以看到我們的模板已經(jīng)安裝好了。
這個(gè)時(shí)候已經(jīng)迫不及待的想來(lái)試試這個(gè)模板了。
先來(lái)看看這個(gè)模板的幫助信息。
dotnet new tpl -h
因?yàn)槲覀兡壳斑€沒(méi)有設(shè)置參數(shù),所以這里顯示的是還沒(méi)有參數(shù)。
下面來(lái)創(chuàng)建一個(gè)項(xiàng)目試試。
從創(chuàng)建一個(gè)項(xiàng)目,到運(yùn)行起來(lái),很簡(jiǎn)單,效果也是我們預(yù)期的。
下面來(lái)看看,新建的這個(gè)HelloTpl這個(gè)項(xiàng)目的目錄結(jié)構(gòu)和我們的模板是否一樣。
可以看到,除了名字,其他的內(nèi)容都是一樣的。
是不是感覺(jué)又可以少?gòu)?fù)制粘貼好多代碼了。
雖說(shuō),現(xiàn)在建項(xiàng)目,已經(jīng)能把一個(gè)大的模板完整的copy出來(lái)了,但是始終不是很靈活!
可能有小伙伴會(huì)問(wèn),明明已經(jīng)很方便了呀,為什么還會(huì)說(shuō)它不靈活呢?
且聽(tīng)我慢慢道來(lái)。
如果說(shuō)這個(gè)模板是個(gè)大而全的模板,包含了中間件A,中間件B,中間件C等N個(gè)中間件!
而在建新項(xiàng)目的時(shí)候,已經(jīng)明確了只用中間件A,那么其他的中間件對(duì)我們來(lái)說(shuō),可能就沒(méi)有太大的存在意義!
很多時(shí)候,不會(huì)想讓這些多余的文件出現(xiàn)在代碼中,有沒(méi)有辦法來(lái)控制呢?
答案是肯定的!可以把不需要的文件排除掉就可以了。
文件過(guò)濾
模板項(xiàng)目中有一個(gè)RequestLogMiddleware
,就用它來(lái)做例子。
我們只需要做下面幾件事就可以了。
第一步,在template.json
中添加過(guò)濾
加入一個(gè)名字為EnableRequestLog
的symbol。同時(shí)指定源文件
{ "author": "Catcher Wong", //others... "symbols":{ //是否啟用RequestLog這個(gè)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
,因?yàn)镸iddleware就是在這里啟用的。
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,那么就可以包含這兩段代碼了。
下面更新一下已經(jīng)安裝的模板。
這個(gè)時(shí)候再去看它的幫助信息,已經(jīng)可以看到我們加的參數(shù)了。
下面先建一個(gè)默認(rèn)的(不啟用RequestLog)
dotnet new tpl -n NoLog
這個(gè)命令等價(jià)于
dotnet new tpl -n WithLog -E false
下面是建好之后的目錄結(jié)構(gòu)和Startup.cs
可以看到RequestLog相關(guān)的東西都已經(jīng)不見(jiàn)了。
再建一個(gè)啟用RequestLog的,看看是不是真的起作用了。
dotnet new tpl -n WithLog -E true
可以看到,效果已經(jīng)出來(lái)了。
下面在介紹一個(gè)比較有用的特性。動(dòng)態(tài)切換,這個(gè)其實(shí)和上面介紹的內(nèi)容相似。
動(dòng)態(tài)切換
直接舉個(gè)例子來(lái)說(shuō)明吧。
假設(shè)我們的模板支持MSSQL, MySQL, PgSQL和SQLite四種數(shù)據(jù)庫(kù)操作
在新建一個(gè)項(xiàng)目的時(shí)候,只需要其中一種,好比說(shuō)要建一個(gè)PgSQL的,肯定就不想看到其他三種。
這里不想看到,有兩個(gè)地方,一個(gè)是nuget包的引用,一個(gè)是代碼。
上一小節(jié)是對(duì)某個(gè)具體的功能進(jìn)行了開(kāi)關(guān)的操作,這里有了4個(gè),我們要怎么處理呢?
我們可以用類型是choice
的參數(shù)來(lái)完成這個(gè)操作。
修改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)容之后,相信大家也知道個(gè)所以然了。有一個(gè)名為sqlType的參數(shù),它有幾中數(shù)據(jù)庫(kù)選擇,默認(rèn)是MsSQL。
還另外定義了幾個(gè)計(jì)算型的參數(shù),它的取值是和sqlType的值息息相關(guān)的。
MsSQL,MySQL,PgSQL和SQLite這4個(gè)參數(shù)也是我們?cè)诖a里要用到的!!
修改csproj文件,讓它可以根據(jù)sqlType來(lái)動(dòng)態(tài)引用nuget包,我們加入下面的內(nèi)容
<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>
同樣的,代碼也要做相應(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 }
修改好之后,同樣要去重新安裝這個(gè)模板,安裝好之后,就可以看到sqlType這個(gè)參數(shù)了。
下面分別創(chuàng)建一個(gè)MsSQL和PgSQL的項(xiàng)目,用來(lái)對(duì)比和驗(yàn)證。
先后執(zhí)行
dotnet new tpl -n MsSQLTest -s MsSQL dotnet new tpl -n PgSQLTest -s PgSQL
然后打開(kāi)對(duì)應(yīng)的csproj
可以看到,PgSQL的,添加多了NPgsql
這個(gè)包。而MsSQL的卻沒(méi)有。
同樣的,DapperRepositoryBase也是一樣的效果。在創(chuàng)建Connection對(duì)象的時(shí)候,都根據(jù)模板來(lái)生成了。
當(dāng)然這個(gè)是在我們自己本地安裝的模板,其他人是沒(méi)有辦法使用的。
如果想公開(kāi),可以發(fā)布到nuget上面去。如果是在公司內(nèi)部共享,可以搭建一個(gè)內(nèi)部的nuget服務(wù),將模板上傳到內(nèi)部服務(wù)器里面去。
下面是一些可以開(kāi)箱即用的模板:https://dotnetnew.azurewebsites.net/
總結(jié)
有一個(gè)自己的項(xiàng)目模板(腳手架),還是很方便的。
一建生成自己需要的東西,減少了不必要的代碼復(fù)制,可以將更多精力放在業(yè)務(wù)實(shí)現(xiàn)上。
在平時(shí)還是要有一些積累,當(dāng)積累足夠豐富之后,我們的腳手架可能就會(huì)變得十分強(qiáng)大。
參考文檔
dotnet new下面默認(rèn)的模板 https://github.com/aspnet/Templating
templating的源碼 https://github.com/dotnet/templating
template.json的說(shuō)明 https://github.com/dotnet/templating/wiki/Reference-for-template.json
dotnet cli的文檔 https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet?tabs=netcore21
最后是文中的示例代碼
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
.net調(diào)用JScript腳本及JS url加密解密
.net調(diào)用JScript腳本及JS url加密解密,需要的朋友可以參考一下2013-03-03.NET 中Worker Service的使用入門(mén)
隨著 .NET Core 3.0 的發(fā)布,ASP.NET 團(tuán)隊(duì)引入了一個(gè)新的 Worker Service 項(xiàng)目模板,該模板作為 .NET SDK 的一部分發(fā)布。在本文中,我將向您介紹這個(gè)新模板,以及使用它開(kāi)發(fā)的一些實(shí)際的服務(wù)示例。2021-05-05小心!ASP.NET網(wǎng)站發(fā)布時(shí)的那些坑
ASP.NET網(wǎng)站發(fā)布時(shí)的那些坑,要小心了,為什么網(wǎng)站發(fā)布后,每個(gè)頁(yè)面第一次打開(kāi)都很卡?ASP.NET session 頻繁丟失如何解決?具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04asp.net 多數(shù)據(jù)庫(kù)支持的思考
最近一直在思考如何做一個(gè)支持多種數(shù)據(jù)庫(kù)的程序,打印了很多的資料,在.NET 2.0中,新增加了DbProviderFactory抽象工廠類,讓數(shù)據(jù)層基類可以實(shí)現(xiàn)多種數(shù)據(jù)庫(kù),但在數(shù)據(jù)訪問(wèn)層中的參數(shù)部分我覺(jué)得是個(gè)麻煩。2009-07-07開(kāi)源跨平臺(tái)運(yùn)行服務(wù)插件TaskCore.MainForm
這篇文章主要為大家詳細(xì)介紹了開(kāi)源跨平臺(tái)運(yùn)行服務(wù)插件TaskCore.MainForm的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06輕松解決asp.net用戶ASPNET登錄失敗問(wèn)題的方法分享
這篇文章介紹了asp.net用戶ASPNET登錄失敗問(wèn)題的方法,有需要的朋友可以參考一下2013-11-11