.Net6开发winform程序使用依赖注入
作者:harrychinese
.net Blazor webassembly 和 webAPI 内建支持依赖注入, Winform 和 Console 应用虽然不带有依赖注入功能, 但增加依赖注入也很简单.
本文将示例如何为 WinForm 程序增加依赖注入特性, 实现通过DI容器获取Cofiguration 实例, 并读取appsettings.json文件.
安装依赖库, 有点多
- Microsoft.Extensions.DependencyInjection 库, 依赖注入的类库
- Microsoft.Extensions.Configuration 库, 包含IConfiguration接口 和 Configuration类
- Microsoft.Extensions.Configuration.Json 库, 为 IConfiguration 增加了读取 Json 文件功能,
- Microsoft.Extensions.Hosting 库, 提供 Host 静态类, 有能力从 appsettings.{env.EnvironmentName}.json 加载相应 env 的设定值, 并将设定值用于IConfiguration/ILoggerFactory中, 同时增加 Console/EventSourceLogger 等 logger.
仅适用于 Asp.Net core 和 Console 类应用
- Microsoft.Extensions.Logging 库, 包含 ILogger 和 ILoggerFactory 接口
- Serilog.Extensions.Logging 库, 为DI 容器提供 AddSerilog() 方法.
- Serilog.Sinks.File 库, 提供 Serilog rolling logger
- Serilog.Sinks.Console 库, 增加 serilog console logger
- Serilog.Settings.Configuration 库, 允许在 appsetting.json 配置 Serilog, 顶层节点要求是 Serilog.
- Serilog.Enrichers.Thread 和 Serilog.Enrichers.Environment 库, 为输出日志文本增加 Thread和 env 信息
补充库:
- Microsoft.Extensions.Options.ConfigurationExtensions 库, 为DI容器增加了从配置文件中实例化对象的能力, 即 serviceCollection.Configure<TOptions>(IConfiguration)
- Microsoft.Extensions.Options 库, 提供以强类型的方式读取configuration文件, 这是.Net中首选的读取configuration文件方式.
appsettings.json 配置文件
配置一个 ConnectionString, 另外配 serilog
{ "ConnectionStrings": { "oeeDb": "Server=localhost\\SQLEXPRESS01;Database=Oee;Trusted_Connection=True;" }, "Serilog": { "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], "MinimumLevel": "Debug", "WriteTo": [ { "Name": "Console" }, { "Name": "File", "Args": { "path": "Logs/serilog.txt" } } ], "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ] } }
Program.cs , 增加DI容器
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Serilog; namespace Collector { internal static class Program { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { ApplicationConfiguration.Initialize(); //未使用依赖注入的写法 //Application.Run(new FormMain()); //生成 DI 容器 ServiceCollection services = new ServiceCollection(); ConfigureServices(services); //注册各种服务类 //先用DI容器生成 serviceProvider, 然后通过 serviceProvider 获取Main Form的注册实例 var serviceProvider =services.BuildServiceProvider(); var formMain = serviceProvider.GetRequiredService<FormMain>(); //主动从容器中获取FormMain实例, 这是简洁写法 // var formMain = (FormMain)serviceProvider.GetService(typeof(FormMain)); //更繁琐的写法 Application.Run(formMain); } /// <summary> /// 在DI容器中注册所有的服务类型 /// </summary> /// <param name="services"></param> private static void ConfigureServices(ServiceCollection services) { //注册 FormMain 类 services.AddScoped<FormMain>(); //register configuration IConfigurationBuilder cfgBuilder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")}.json", optional: true, reloadOnChange: false) ; IConfiguration configuration=cfgBuilder.Build(); services.AddSingleton<IConfiguration>(configuration); //Create logger instance var serilogLogger = new LoggerConfiguration() .ReadFrom.Configuration(configuration) .Enrich.FromLogContext() .CreateLogger(); //register logger services.AddLogging(builder => { object p = builder.AddSerilog(logger: serilogLogger, dispose: true); }); } } }
FormMain.cs , 验证依赖注入的效果
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Collector { public partial class FormMain : Form { private readonly IConfiguration _configuration; private readonly ILogger _logger; /// <summary> /// 为 FormMain 构造子增加两个形参, 构造子参数将由于DI容器自动注入 /// </summary> /// <param name="configuration"></param> /// <param name="logger">形参必须是 ILogger泛型类型, 不能是 ILogger 类型</param> public FormMain(IConfiguration configuration, ILogger<FormMain> logger) { _configuration = configuration; _logger = logger; InitializeComponent(); var connectionString = _configuration.GetConnectionString("oeeDb"); //从配置文件中读取oeeDb connectionString _logger.LogInformation(connectionString); //将connection String 写入到日志文件中 } } }
DI容器管理配置文件Section
上面示例, 我们通过 _configuration.GetConnectionString("oeeDb") 可以拿到connectionString, 非常方便, 这主要是得益于.Net 已经类库已经考虑到在配置文件中存储 connectionString 是一个普遍的做法, 所以类库内置支持了.
如果在 appsettings.json 中存一些自定义的信息, 如何方便读取呢? 微软推荐的 Options 模式, 下面详细介绍.
首先安装库:
- Microsoft.Extensions.Options.ConfigurationExtensions 库, 为DI容器增加了从配置文件中实例化对象的能力, 即 serviceCollection.Configure<TOptions>(IConfiguration)
- Microsoft.Extensions.Options 库, 提供以强类型的方式读取configuration文件, 这是.Net中首选的读取configuration文件方式.
假设 appsettings.json 中要存放appKey和appSecret信息, 具体配置如下:
"AppServiceOptions": { "appKey": "appkey1", "appSecret": "appSecret1" }
定义对应的 Poco Class, 推荐后缀为 Options,
public class AppServiceOptions { public string AppKey { get; set; } = ""; public string AppSecret { get; set; } = ""; }
注册函数 ConfigureServices()中, 注册 AppServiceOptions 类, 告知DI容器, 要基于配置文件AppServiceOptions section来实例化
private static void ConfigureServices(ServiceCollection services) { //注册 FormMain 类 services.AddScoped<FormMain>(); //register configuration IConfigurationBuilder cfgBuilder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")}.json", optional: true, reloadOnChange: false) ; IConfiguration configuration=cfgBuilder.Build(); services.AddSingleton<IConfiguration>(configuration); //Create logger instance var serilogLogger = new LoggerConfiguration() .ReadFrom.Configuration(configuration) .Enrich.FromLogContext() .CreateLogger(); //register logger services.AddLogging(builder => { object p = builder.AddSerilog(logger: serilogLogger, dispose: true); }); //注册 AppServiceOptions 类, 告知DI容器, 要基于配置文件AppServiceOptions section来实例化 services.AddOptions(); services.Configure<AppServiceOptions>(configuration.GetSection("AppServiceOptions")); }
主动从DI容器中获取 AppServiceOptions 配置信息代码如下, 注意GetRequiredService函数的的泛型参数要使用 IOptions<> 包一下.
var appServiceOptionsWrapper=serviceProvider.GetRequiredService<IOptions<AppServiceOptions>>(); AppServiceOptions appServiceOptions= appServiceOptionsWrapper.Value;
将 AppServiceOptions 注入到 FormMain 的代码, 和主动从DI容器中获取 AppServiceOptions 实例一样, 都需要使用 IOptions<> 接口包一下构造子形参.
public partial class FormMain : Form { private readonly IConfiguration _configuration; private readonly ILogger _logger; private AppServiceOptions _appServiceOptions; /// <summary> /// 为 FormMain 构造子增加三个形参, 构造子参数将由于DI容器自动注入 /// </summary> /// <param name="configuration">形参必须是接口 IConfigurations</param> /// <param name="logger">形参必须是 ILogger泛型类型, 不能是 ILogger 类型</param> /// <param name="appServiceOptionsWrapper">形参必须是 IOptions 泛型接口 </param> public FormMain(IConfiguration configuration, ILogger<FormMain> logger, IOptions<AppServiceOptions> appServiceOptionsWrapper) { _configuration = configuration; _logger = logger; _appServiceOptions = appServiceOptionsWrapper.Value; InitializeComponent(); var connectionString = _configuration.GetConnectionString("oeeDb"); //从配置文件中读取oeeDb connectionString _logger.LogInformation(connectionString); //将connection String 写入到日志文件中 } private void button1_Click(object sender, EventArgs e) { this.Text = _appServiceOptions.AppKey; } }
.net core 复杂 configuration Section 的读取
appsettings文件定义一个复杂的设置项, 顶层是一个json 数组, 里面又嵌套了另一个数组
"PlcDevices": [ { "PlcDeviceId": "Plc1", "IpAddress": "127.0.0.1", "Port": 1234, "SlaveId": 1, "DataPoints": [ { "ModbusAddress": 0, "EqpId": "eqp1" }, { "ModbusAddress": 0, "EqpId": "eqp2" } ] }, { "PlcDeviceId": "Plc2", "IpAddress": "127.0.0.2", "Port": 1234, "SlaveId": "2", "DataPoints": [ { "ModbusAddress": 0, "EqpId": "eqp3" }, { "ModbusAddress": 0, "EqpId": "eqp4" } ] } ]
对应poco对象为:
public class PlcDevice { public string IpAddress { get; set; } = ""; public int Port { get; set; } = 0; public string PlcDeviceId { get; set; } = ""; public int SlaveId { get; set; } public List<DataPoint> DataPoints { get; set; } } public class DataPoint { public int ModbusAddress { get; set; } public string EqpId { get; set; } = ""; }
读取 json 的C# 代码:
services.AddOptions(); //实例化一个对应 PlcDevices json 数组对象, 使用了 IConfiguration.Get<T>() var PlcDeviceSettings= configuration.GetSection("PlcDevices").Get<List<PlcDevice>>(); //或直接通过 service.Configure<T>() 将appsettings 指定 section 放入DI 容器, 这里的T 为 List<PlcDevice> services.Configure<List<PlcDevice>>(configuration.GetSection("PlcDevices"));
到此这篇关于.Net6开发winform程序使用依赖注入的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持脚本之家。