查看原文
其他

打造自己的.NET Core项目模板

DotNet 2019-08-03

(给DotNet加星标,提升.Net技能


转自:Catcher8

cnblogs.com/catcher1994/p/10061470.html


前言


每个人都有自己习惯的项目结构,有人的喜欢在项目里面建解决方案文件夹;有的人喜欢传统的三层命名;有的人喜欢单一,简单的项目一个csproj就搞定。


反正就是萝卜青菜,各有所爱。


可能不同的公司对这些会有特定的要求,也可能会随开发自己的想法去实践。


那么,问题就来了。如果有一个新项目,你会怎么去创建?


可能比较多的方式会是下面三种:


  • 简单粗暴型,打开VS就是右键添加,然后引入一堆包,每个项目添加引用。


  • 脚本型,基于dotnet cli,创建解决方案,创建项目,添加包,添加项目引用。


  • 高大上型,VS项目模板,直接集成到VS上面了。


以前我也是基于dotnet cli写好了sh或ps的脚本,然后用这些脚本来生成新项目。


但是呢,这三种方式,始终都有不尽人意的地方。


因为建好的都是空模板,还要做一堆复杂的操作才可以让项目“正常”的跑起来。比如,这个公共类要抄过来,那个公共类要抄过来。。。这不是明摆着浪费时间嘛。。。


下面介绍一个小办法来帮大家省点时间。


基于dotnet cli创建自己的项目模板,也就是大家常说的脚手架。


dotnet cli项目模板预热


开始正题之前,我们先看一下dotnet cli自带的一些模板。



可以看到种类还是很多的,由于工作大部分时间都是在写WebAPI,所以这里就用WebAPI来写个简单的模板。


下面我们就基于dotnet cli写一个自己的模板。


编写自己的模板


既然是模板,就肯定会有一个样例项目。


下面我们建一个样例项目,大致成这样,大家完全可以按照自己习惯来。



这其实就是一个普通的项目,里面添加了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。


  • shortName,简写,偷懒必备,好比能写 -h 就绝对不写 --help


  • 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/


总结


有一个自己的项目模板(脚手架),还是很方便的。


一建生成自己需要的东西,减少了不必要的代码复制,可以将更多精力放在业务实现上。


在平时还是要有一些积累,当积累足够丰富之后,我们的脚手架可能就会变得十分强大。


最后是文中的示例代码


Template:https://github.com/catcherwong/Demos/tree/master/src/Template


推荐阅读

(点击标题可跳转阅读)

超好用的C#控制台应用模板

尝鲜!.NET Core 2.2 新增部分功能体验

微软发布 .NET Core 2.2


看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能 

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存