前言

每个人都有自己习惯的项目结构,有人的喜欢在项目里面建解决方案文件夹;有的人喜欢传统的三层命名;有的人喜欢单一,简单的项目一个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

示例如下:

  1. {
  2. "author": "Catcher Wong", //必须
  3. "classifications": [ "Web/WebAPI" ], //必须,这个对应模板的Tags
  4. "name": "TplDemo", //必须,这个对应模板的Templates
  5. "identity": "TplDemoTemplate", //可选,模板的唯一名称
  6. "shortName": "tpl", //必须,这个对应模板的Short Name
  7. "tags": {
  8. "language": "C#" ,
  9. "type":"project"
  10. },
  11. "sourceName": "TplDemo", // 可选,要替换的名字
  12. "preferNameDirectory": true // 可选,添加目录
  13. }

在这里,有几个比较重要的东西,一个是shortName,一个是sourceName

  • shortName,简写,偷懒必备,好比能写 -h 就绝对不写 --help
  • sourceName,这是个可选的字段,它的值会替换指定的项目名,正常是把项目名赋值在这里。如果不指定,创建的项目就和样例项目保持一致。

在写完template.json之后,还需要安装一下这个模板到我们的cli中。

使用 dotnet new -i进行模板的安装。

下面是安装示例。

  1. dotnet new -i ./content/TplDemo

这里要注意的是,与.template.config文件夹同级的目录,都会被打包进模板中。

在执行安装命令之后,就可以看到我们的模板已经安装好了。

这个时候已经迫不及待的想来试试这个模板了。

先来看看这个模板的帮助信息。

  1. dotnet new tpl -h

因为我们目前还没有设置参数,所以这里显示的是还没有参数。

下面来创建一个项目试试。

从创建一个项目,到运行起来,很简单,效果也是我们预期的。

下面来看看,新建的这个HelloTpl这个项目的目录结构和我们的模板是否一样。

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

是不是感觉又可以少复制粘贴好多代码了。

虽说,现在建项目,已经能把一个大的模板完整的copy出来了,但是始终不是很灵活!

可能有小伙伴会问,明明已经很方便了呀,为什么还会说它不灵活呢?

且听我慢慢道来。

如果说这个模板是个大而全的模板,包含了中间件A,中间件B,中间件C等N个中间件!

而在建新项目的时候,已经明确了只用中间件A,那么其他的中间件对我们来说,可能就没有太大的存在意义!

很多时候,不会想让这些多余的文件出现在代码中,有没有办法来控制呢?

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

文件过滤

模板项目中有一个RequestLogMiddleware,就用它来做例子。

我们只需要做下面几件事就可以了。

第一步,在template.json中添加过滤

加入一个名字为EnableRequestLog的symbol。同时指定源文件

  1. {
  2. "author": "Catcher Wong",
  3. //others...
  4. "symbols":{
  5. //是否启用RequestLog这个Middleware
  6. "EnableRequestLog": {
  7. "type": "parameter", //它是参数
  8. "dataType":"bool", //bool类型的参数
  9. "defaultValue": "false" //默认是不启用
  10. }
  11. },
  12. "sources": [
  13. {
  14. "modifiers": [
  15. {
  16. "condition": "(!EnableRequestLog)", //条件,由EnableRequestLog参数决定
  17. "exclude": [ //排除下面的文件
  18. "src/TplDemo/Middlewares/RequestLogMiddleware.cs",
  19. "src/TplDemo/Middlewares/RequestLogServiceCollectionExtensions.cs"
  20. ]
  21. }
  22. ]
  23. }
  24. ]
  25. }

第二步,在模板的代码中做一下处理

主要是Startup.cs,因为Middleware就是在这里启用的。

  1. using System;
  2. //other using...
  3. using TplDemo.Core;
  4. #if (EnableRequestLog)
  5. using TplDemo.Middlewares;
  6. #endif
  7. /// <summary>
  8. ///
  9. /// </summary>
  10. public class Startup
  11. {
  12. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  13. {
  14. //other code....
  15. #if (EnableRequestLog)
  16. //request Log
  17. app.UseRequestLog();
  18. #endif
  19. app.UseMvc(routes =>
  20. {
  21. routes.MapRoute(
  22. name: "default",
  23. template: "{controller=Home}/{action=Index}/{id?}");
  24. });
  25. }
  26. }

这样的话,只要EnableRequestLog是true,那么就可以包含这两段代码了。

下面更新一下已经安装的模板。

这个时候再去看它的帮助信息,已经可以看到我们加的参数了。

下面先建一个默认的(不启用RequestLog)

  1. dotnet new tpl -n NoLog

这个命令等价于

  1. dotnet new tpl -n WithLog -E false

下面是建好之后的目录结构和Startup.cs

可以看到RequestLog相关的东西都已经不见了。

再建一个启用RequestLog的,看看是不是真的起作用了。

  1. dotnet new tpl -n WithLog -E true

可以看到,效果已经出来了。

下面在介绍一个比较有用的特性。动态切换,这个其实和上面介绍的内容相似。

动态切换

直接举个例子来说明吧。

假设我们的模板支持MSSQL, MySQL, PgSQL和SQLite四种数据库操作

在新建一个项目的时候,只需要其中一种,好比说要建一个PgSQL的,肯定就不想看到其他三种。

这里不想看到,有两个地方,一个是nuget包的引用,一个是代码。

上一小节是对某个具体的功能进行了开关的操作,这里有了4个,我们要怎么处理呢?

我们可以用类型是choice的参数来完成这个操作。

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

  1. {
  2. "author": "Catcher Wong",
  3. //others
  4. "symbols":{
  5. "sqlType": {
  6. "type": "parameter",
  7. "datatype": "choice",
  8. "choices": [
  9. {
  10. "choice": "MsSQL",
  11. "description": "MS SQL Server"
  12. },
  13. {
  14. "choice": "MySQL",
  15. "description": "MySQL"
  16. },
  17. {
  18. "choice": "PgSQL",
  19. "description": "PostgreSQL"
  20. },
  21. {
  22. "choice": "SQLite",
  23. "description": "SQLite"
  24. }
  25. ],
  26. "defaultValue": "MsSQL",
  27. "description": "The type of SQL to use"
  28. },
  29. "MsSQL": {
  30. "type": "computed",
  31. "value": "(sqlType == \"MsSQL\")"
  32. },
  33. "MySQL": {
  34. "type": "computed",
  35. "value": "(sqlType == \"MySQL\")"
  36. },
  37. "PgSQL": {
  38. "type": "computed",
  39. "value": "(sqlType == \"PgSQL\")"
  40. },
  41. "SQLite": {
  42. "type": "computed",
  43. "value": "(sqlType == \"SQLite\")"
  44. }
  45. }
  46. }

看了上面的JSON内容之后,相信大家也知道个所以然了。有一个名为sqlType的参数,它有几中数据库选择,默认是MsSQL。

还另外定义了几个计算型的参数,它的取值是和sqlType的值息息相关的。

MsSQL,MySQL,PgSQL和SQLite这4个参数也是我们在代码里要用到的!!

修改csproj文件,让它可以根据sqlType来动态引用nuget包,我们加入下面的内容

  1. <ItemGroup Condition="'$(MySQL)' == 'True' ">
  2. <PackageReference Include="MySqlConnector" Version="0.47.1" />
  3. </ItemGroup>
  4. <ItemGroup Condition="'$(PgSQL)' == 'True' ">
  5. <PackageReference Include="Npgsql" Version="4.0.3" />
  6. </ItemGroup>
  7. <ItemGroup Condition="'$(SQLite)' == 'True' ">
  8. <PackageReference Include="Microsoft.Data.Sqlite" Version="2.1.0" />
  9. </ItemGroup>

同样的,代码也要做相应的处理

  1. #if (MsSQL)
  2. using System.Data.SqlClient;
  3. #elif (MySQL)
  4. using MySql.Data.MySqlClient;
  5. #elif (PgSQL)
  6. using Npgsql;
  7. #else
  8. using Microsoft.Data.Sqlite;
  9. #endif
  10. protected DbConnection GetDbConnection()
  11. {
  12. #if (MsSQL)
  13. return new SqlConnection(_connStr);
  14. #elif (MySQL)
  15. return new MySqlConnection(_connStr);
  16. #elif (PgSQL)
  17. return new NpgsqlConnection(_connStr);
  18. #else
  19. return new SqliteConnection(_connStr);
  20. #endif
  21. }

修改好之后,同样要去重新安装这个模板,安装好之后,就可以看到sqlType这个参数了。

下面分别创建一个MsSQL和PgSQL的项目,用来对比和验证。

先后执行

  1. dotnet new tpl -n MsSQLTest -s MsSQL
  2. 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

打造自己的.NET Core项目模板的更多相关文章

  1. NET Core项目模板

    打造自己的.NET Core项目模板 https://www.cnblogs.com/catcher1994/p/10061470.html 前言 每个人都有自己习惯的项目结构,有人的喜欢在项目里面建 ...

  2. 使用 DotNet CLI 创建自定义的 WPF 项目模板

    描述 当我们安装完 DotNetCore 3.0 版本的 SDK 后,我们就可以创建基于 DotNetCore 的 WPF 项目模板,通过如下 CLI 可以方便快捷的创建并运行我们的项目: dotne ...

  3. 在Centos7中创建.net core 项目,并用Apache做代理服务器部署.net core项目

    这一篇实例记录一次用Centos7创建并部署.net core项目的过程,希望能帮到用到的小伙伴. Kestrel 是 ASP.NET Core 项目模板中包括的默认 Web 服务器,Kestrel可 ...

  4. Net core学习系列(四)——Net Core项目执行流程

    "跨平台"后的ASP.Net Core是如何接收并处理请求的呢? 它的运行和处理机制和之前有什么不同?本章从"宏观"到"微观"地看一下它的结 ...

  5. 从零开始实现ASP.NET Core MVC的插件式开发(二) - 如何创建项目模板

    标题:从零开始实现ASP.NET Core MVC的插件式开发(二) - 如何创建项目模板 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/11155 ...

  6. 使用 .NET CORE 创建 项目模板,模板项目,Template

    场景:日常工作中,你可能会碰到需要新建一个全新的解决方案的情况(如公司新起了一个新项目,需要有全新配套的后台程序),如果公司内部基础框架较多.解决方案需要DDD模式等,那么从新起项目到各种依赖引用到能 ...

  7. .NET Core - 自定义项目模板

    前言: 前面介绍 自定义项目模板 中介绍了一种简单的方式--通过创建项目导出为项目模板方式实现.本次将采用dotenet cil(手脚架)来创建项目模板. 那么,我们首先看下当前dotnet 支持的项 ...

  8. 如何使用 vue-cli 3 的 preset 打造基于 git repo 的前端项目模板

    vue-cli 之 Preset vue-cli 插件开发指南 TLDR 背景介绍 vue-cli 3 完全推翻了 vue-cli 2 的整体架构设计,所以当你需要给组里定制一份基于 vue-cli ...

  9. asp.net core web 解决方案多项目模板制作打包总结

    一.文件夹\项目结构 1.1.文件夹 net6.0:针对.net 6.0 项目模板 net6.0pack:针对net6.0打包 1.2.项目结构 Web\WebApi多项目.各层项目.单元测试项目 目 ...

随机推荐

  1. [git]checkout&branch

    git branch 和 git checkout经常在一起使用,所以在此将它们合在一起 1.Git branch 一般用于分支的操作,比如创建分支,查看分支等等, 1.1 git branch 不带 ...

  2. VB.NET或C#报错:You must hava a license to use this ActiveX control.

    VB.NET或者C# winform开发时,如果使用了Microsoft Visual Basic 6.0 ActiveX,并动态创建该控件实例,那么程序移植到没有安装Visual Basic 6.0 ...

  3. 如何查找MySQL中查询慢的SQL语句(转载)

    转载自https://www.cnblogs.com/qmfsun/p/4844472.html 如何在mysql查找效率慢的SQL语句呢?这可能是困然很多人的一个问题,MySQL通过慢查询日志定位那 ...

  4. 用Group by分组后,取每组的前3条记录,怎么取?

    使用子查询进行查询 SELECT * FROM home_content a WHERE ( SELECT count(id) FROM home_content WHERE class_link = ...

  5. Flutter 获取服务器数据

    文档 文档版本有些老 使用 dio 来获取数据 demo import 'dart:io'; import 'dart:convert'; import 'package:flutter/materi ...

  6. koa 写简单服务

    这两天用koa写了点服务,这里面和express还是有部分区别的 1.静态服务:  koa 中,是有中间件, koa-static, const static_f = require('koa-sta ...

  7. 文件访问时间简记(Modify time 和 Change time)

    [root@77-29-68-bx-core]# stat hql.out File: 'hql.out' Size: 13750 Blocks: 32 IO Block: 4096 regular ...

  8. Java线程中的同步

    1.对象与锁 每一个Object类及其子类的实例都拥有一个锁.其中,标量类型int,float等不是对象类型,但是标量类型可以通过其包装类来作为锁.单独的成员变量是不能被标明为同步的.锁只能用在使用了 ...

  9. IDEA使用Maven搭建SSM框架

    搭建环境:Intellij IDEA 2017 JDK 1.8 Tomcat 8.5 MySQL 5.7 Spring 4.x Mybatis 3.x 这个过程确实太麻烦了,我用了两个小时 所以建议用 ...

  10. [Swift]LeetCode254.因子组合 $ Factor Combinations

    Numbers can be regarded as product of its factors. For example, 8 = 2 x 2 x 2; = 2 x 4. Write a func ...