开发一个现代化的.NetCore控制台程序,包含依赖注入/配置/日志等要素
前言
最近需要开发小工具的场景有点多,上次我用 go 语言开发了一个 hive 导出工具,体验还不错,只是 go 语言的语法实在是喜欢不起来,这次继续试试用 C# 来开发小工具。
这次小工具的功能很简单,数据库数据迁移,不过这不重要,主要是记录一下更适合 .Net Core 宝宝体质的控制台小工具开发过程
本文中,我为「现代化的控制台应用的开发体验」做了个定义:能像 Web 应用那样很优雅地整合各种组件,恰好 .NetCore 提供的工具可以实现。我使用了 Microsoft.Extensions.*
系列的组件,包括依赖注入、配置、日志,再补充一下环境变量读取、调试等功能的第三方组件。
本文的小工具非常简单,面向非专业用户,不需要会命令行知识,所以所有功能采用配置文件的方式来控制,如果要开发传统的 CLI 工具,可以使用 System.CommandLine 这个库。
依赖
本项目使用到的依赖如下
<ItemGroup>
<PackageReference Include="dotenv.net" Version="3.1.3" />
<PackageReference Include="Dumpify" Version="0.6.0" />
<PackageReference Include="FreeSql" Version="3.2.802" />
<PackageReference Include="FreeSql.Provider.Dameng" Version="3.2.802" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
<PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Serilog.Extensions.Logging.File" Version="3.0.0" />
</ItemGroup>
虽然是个控制台小工具,但为了更丝滑的开发体验,我搭建了一个简单的项目骨架。
配置
我一开始想要使用的是 dotenv
在写 python 和 go 的时候大量使用 dotenv ,感觉很方便
dotenv
C# 里使用也很简单,安装 dotenv.net
这个库
执行 DotEnv.Load();
就可以把 .env
文件里的配置读取到环境变量里面
之后就是直接从环境变量中加载就行,比如 Environment.GetEnvironmentVariable()
方法
Microsoft.Extensions.Configuration
用过 AspNetCore
的同学对这个组件应该不陌生
本来我是打算使用 dotenv 来做配置,不过最后还是使用 json 文件搭配这个配置组件,原因无他,就是这个组件方便好用。
安装了相关的依赖之后,执行以下代码初始化
var configBuilder = new ConfigurationBuilder();
configBuilder.AddEnvironmentVariables();
configBuilder.SetBasePath(Environment.CurrentDirectory);
configBuilder.AddJsonFile("appsettings.json", optional: false, reloadOnChange: false);
var config = configBuilder.Build();
这样就得到了 IConfigurationRoot
对象
编写配置文件
熟悉的 appsettings.json
,对于写 AspNetCore 的人来说:DNA,动了!
{
"Logging": {
"LogLevel": {
"Default": "Debug"
}
},
"ConnectionStrings": {
"Default": "server=host;port=1234;user=user;password=pwd;database=db;poolsize=5"
},
"DmTableMigration": {
"Schema": "schema",
"DbLink": "link_test",
"Fake": true,
"ExcludeTables": ["table1", "table2"]
}
}
定义强类型配置实体
为了更好的开发体验,我们使用强类型配置
新建 AppSettings.cs
public class AppSettings {
public string Schema { get; set; }
public string DbLink { get; set; }
public bool Fake { get; set; }
public List<string> ExcludeTables { get; set; } = new();
}
注册 Options
这里使用了 Microsoft.Extensions.Configuration.Binder
库实现了配置绑定,搭配使用 IOptionsMonitor<T>
或者 IOptionsSnapshot<T>
进行配置注入的时候,可以实现配置热更新。
services.AddOptions().Configure<AppSettings>(e => config.GetSection("DmTableMigration").Bind(e));
在上面的初始化配置时 configBuilder.AddJsonFile("appsettings.json", optional: false, reloadOnChange: false);
,可以把 reloadOnChange
设置为 true
,即可实现配置文件修改时自动加载。
如果不需要热更新的话,可以简化注册方式
services.AddOptions<AppSettings>("DmTableMigration");
这样就是程序启动的时候读取配置,后面配置修改也不会生效,注入的时候只能使用 IOptions<T>
注入配置
注入的时候这样写
private readonly AppSettings _settings = options.Value;
ctor(IOptions<AppSettings> options) {
_settings = options.Value;
}
ctor 代表构造方法
日志
日志是程序必不可少的一部分
我使用了 Microsoft.Extensions.Logging
日志框架,这个框架官方的 Provider 没有可以写入文件的,所以我又搭配 Serilog
来记录日志到文件。其实也可以自己实现一个写入文件的 Provider ,等有时间我来搞一下。
PS:.NetCore 平台推荐的日志组件有 NLog 和 Serilog,我觉得 Serilog 更方便,NLog 非要写什么 xml 配置,让我想起了在 spring 里被 xml 支配的恐惧,拒绝 ×
Serilog配置
直接在程序里配置就行了
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.File("logs/migration-logs.log")
.CreateLogger();
Logging配置
同时输出日志到控制台和 Serilog
Serilog 又配置了日志写入文件
services.AddLogging(builder => {
builder.AddConfiguration(config.GetSection("Logging"));
builder.AddConsole();
builder.AddSerilog(dispose: true);
});
依赖注入
使用 Microsoft.Extensions.DependencyInjection
实现依赖注入
AutoFac 也是一种选择,据说功能更多,我还没用过,接下来找时间体验一下。
注册服务
var services = new ServiceCollection();
services.AddLogging(builder => {
builder.AddConfiguration(config.GetSection("Logging"));
builder.AddConsole();
builder.AddSerilog(dispose: true);
});
services.AddSingleton(fsql);
services.AddOptions().Configure<AppSettings>(e => config.GetSection("DmTableMigration").Bind(e));
services.AddScoped<MigrationService>();
使用服务
在 IoC 容器里注册的服务可以拿出来使用,参考以下代码。
await using (var sp = services.BuildServiceProvider()) {
var migrationService = sp.GetRequiredService<MigrationService>();
migrationService.Run();
}
服务有不同的生命周期,比如 scope 类型的服务,可以使用以下代码创建一个 scope ,在里面进行注入。
await using (var sp = services.BuildServiceProvider()) {
using (var scope = sp.CreateScope()) {
var spScope = scope.ServiceProvider;
var service = spScope.GetRequiredService<MigrationService>();
}
}
其他关于依赖注入的使用方法可以参考官方文档。
调试小工具
这里还要推荐 Dumpify
这个调试小工具
使用非常方便,安装 nuget 包之后,在任何对象后面加个 .Dump()
就可以输出它的结构了。
这个小工具我目前用着觉得很不错~
编译 & 发布
对于这种简单的小工具我习惯把发布配置写在项目配置里
对于这个小工具,我的发布方案是:包含运行时的 SingleFile + partial Trimmed
实测打包出来是 22MB 左右,再使用 zip 压缩,最终大小是 9MB ,尺寸控制还算不错了。
编辑 .csproj
文件,配置如下
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
<PublishRelease>true</PublishRelease>
</PropertyGroup>
在 Trim 的时候我也遇到了一点小问题,默认的 TrimMode 是 full ,最大程度缩减发布的程序尺寸,这个时候编译出来大概是 17MB 的样子,不过 JSON 序列化的时候遇到了问题,所以我切换到了 partial 模式,之后程序运行良好。
关于 AOT
至于最近很火的 .Net8 AOT 方案,我也有试过,但并不理想,首先这个小工具是基于依赖注入框架构建的,AOT天生就对依赖注入这种基于反射的技术不太友好,所以在试用 AOT 的时候我就发现了第一步的配置加载就不太行了。
接着解决了配置加载的问题之后,我又遇到了 JSON 序列化问题,这个也是基于反射实现的,也不好搞。
我不太想在小工具的开发上花太多时间,所以没有深入研究,不过接下来 AOT 似乎是一个小的热门趋势,也许我会找时间探索一下。
对了,如果要发布 AOT 的话,只需要做以下配置
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
</PropertyGroup>
杂项
获取达梦数据库一个 Schema 下的所有表
从 all_objects
这个视图(表?)里获取。
PS:达梦这种国产数据库,坑挺多的。当然 Oracle 也一样
logger.LogInformation("获取Table列表");
var list = fsql.Ado.Query<Dictionary<string, object>>(
$"SELECT OBJECT_NAME FROM all_objects WHERE owner='{_settings.Schema}' AND object_type='TABLE'");
var tableList = list.Select(e => e["OBJECT_NAME"].ToString() ?? "")
.Where(e => !string.IsNullOrWhiteSpace(e))
.Where(e => !_settings.ExcludeTables.Contains(e))
.ToList();
logger.LogInformation("Table列表:{List}", string.Join(",", tableList));
C# 新语法 Primary Ctor
应该是这个名字吧?Primary Constructor
当 class 只有一个带参数的构造方法时,可以使用以简化代码。
原代码
public class MigrationService {
AppSettings _settings;
IFreeSql _fsql;
ILogger<MigrationService> _logger;
MigrationService(IFreeSql fsql, IOptions<AppSettings> options, ILogger<MigrationService> logger) {
_settings = options.Value;
_fsql = fsql;
_logger = logger;
}
}
新语法
public class MigrationService(IFreeSql fsql, IOptions<AppSettings> options, ILogger<MigrationService> logger) {
private readonly AppSettings _settings = options.Value;
}
小结
时间和篇幅关系,本文只能简略介绍「现代化控制台应用」的开发思路,在接下来的探索过程中可能随时会有补充,我会继续在博客里的本文进行补充,如果你是在除了博客园或者StarBlog之外的其他平台看到本文,可以「查看原文」看看本文的最新版。
开发一个现代化的.NetCore控制台程序,包含依赖注入/配置/日志等要素的更多相关文章
- 大比速:remoting、WCF(http)、WCF(tcp)、WCF(RESTful)、asp.net core(RESTful) .net core 控制台程序使用依赖注入(Autofac)
大比速:remoting.WCF(http).WCF(tcp).WCF(RESTful).asp.net core(RESTful) 近来在考虑一个服务选型,dotnet提供了众多的远程服务形式.在只 ...
- .net core 控制台程序使用依赖注入(Autofac)
1.Autofac IOC 容器 ,便于在其他类获取注入的对象 using System; using System.Collections.Generic; using System.Linq; u ...
- VS开发】如何给console控制台程序更换应用程序图标
[VS开发]如何给console控制台程序更换应用程序图标 标签:[VS开发] 实际上非常简单,就是增加一个图标资源,在资源视图里,然后修改其ID为IDC_MAINFRAME,然后编译生成即可! 20 ...
- Android(java)学习笔记219:开发一个多界面的应用程序之两种意图
1.两种意图: (1)显式意图: 在代码里面用intent设置要开启Activity的字节码.class文件: (2)隐式意图: Android(java)学习笔记218:开发一个多界面的应用程序之人 ...
- Android(java)学习笔记162:开发一个多界面的应用程序之两种意图
1.两种意图: (1)显式意图: 在代码里面用intent设置要开启Activity的字节码.class文件: (2)隐式意图: Android(java)学习笔记218:开发一个多界面的应用程序之人 ...
- 【半小时大话.net依赖注入】(一)理论基础+实战控制台程序实现AutoFac注入
系列目录 第一章|理论基础+实战控制台程序实现AutoFac注入 第二章|AutoFac的常见使用套路 第三章|实战Asp.Net Framework Web程序实现AutoFac注入 第四章|实战A ...
- 理论基础+实战控制台程序实现AutoFac注入
[半小时大话.net依赖注入](一)理论基础+实战控制台程序实现AutoFac注入 系列目录# 第一章|理论基础+实战控制台程序实现AutoFac注入 第二章|AutoFac的常见使用套路 第三章 ...
- Mac OS X上用CoreCLR运行一个真正的.NET控制台程序
这个真正的控制台程序来自corefxlab,名叫CoreClrHelloWorld,是一个跨平台的.NET控制台演示程序,可以显示微软.Linux.苹果的logo. CoreClrHelloWorld ...
- VS2013开发一个简单的asmx接口程序
一.开发和调试 1:创建一个ASP.NET web应用程序 2:选择空的模板 3:系统生成项目目录 4:右键项目-添加项-新建项 5:选择Web 服务(ASMX) 6:选择之后项目中会有一个Test ...
- windows下建立netcore控制台程序,然后传送到centos7下的docker容器里运行
1.首先,在window下用vs2017开发netcore控制台项目. 2.把建立好的项目传送到centos7下面的容器里. docker cp sharefoldersforwindows/ 359 ...
随机推荐
- 文心一言 VS 讯飞星火 VS chatgpt (68)-- 算法导论6.5 7题
文心一言 VS 讯飞星火 VS chatgpt (68)-- 算法导论6.5 7题 七.试说明如何使用优先队列来实现一个先进先出队列,以及如何使用优先队列来实现栈(队列和栈的定义见 10.1 节.) ...
- Redis从入门到放弃(5):事务
1.事务的定义 Redis的事务提供了一种"将多个命令打包, 然后一次性.按顺序地执行"的机制. redis事务的主要作用就是串联多个命令防止别的命令插队. 但是,事务并不具有传统 ...
- NOIP2022 题解
终于有机会补NOIP的题了 T1 考虑枚举 C 与 F 的纵列 考虑预处理出每个点最左边和最下边可以延伸到哪 之后枚举列,然后对行做类似于扫描线的操作,统计有多少可行的 "第一横行" ...
- mysql 命令安装
1. mysql 下载安装好压缩文件,下面我们进入正题,少废话. 09:39:112023-08-05 先到 mysql 官方网站下载:https://dev.mysql.com/downloa ...
- .NET5从零基础到精通:全面掌握.NET5开发技能
C#版本新语法-官网: C#7:https://docs.microsoft.com/zh-cn/dotnet/csharp/whats-new/csharp-7 C#8:https://docs.m ...
- 产品代码都给你看了,可别再说不会DDD(一):DDD入门
这是一个讲解DDD落地的文章系列,作者是<实现领域驱动设计>的译者滕云.本文章系列以一个真实的并已成功上线的软件项目--码如云(https://www.mryqr.com)为例,系统性地讲 ...
- 使用Java来开发物联网应用
这是Hello, Lithosphere Tutorials系列教程中的其中一篇. 感觉介绍用C/C++,用Python来开发物联网应用的文章比较多,用Java来做物联网的文章比较少. 这篇文章,介绍 ...
- Jmeter关联之正则表达式提取器
正则表达式简介 摘自网上的说法,正则表达式是对字符串(包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为"元字符"))操作的一种 逻辑公式,就是用事先定义好的一些特定字符 ...
- QA|linux指令awk '{print $(NF-1)}'为啥用单引号而不是双引号?|linux
linux指令awk '{print $(NF-1)}'为啥用单引号而不是双引号? 我的理解: 因为单引号不对会内容进行转义,而双引号会,举个栗子 1 a=1 2 echo "$a" ...
- EXE一机一码打包加密大师(EXE加密, 一机一码, 添加授权,添加静态密码,支持设置试用时间)
EXE一机一码打包加密大师可以打包加密保护EXE文件,同时给EXE文件添加上一机一码认证,或者静态密码,不同的电脑打开加密后的文件需要输入不同的激活码才能正常使用,保护文件安全,方便向用户收费. 下载 ...