.Net6 winform 程序使用依赖注入

本文演示如何在WinForm应用中实现依赖注入,利用.NET的IServiceCollection和IConfiguration接口,通过appsettings.json配置文件读取数据库连接字符串,并使用Serilog进行日志记录。通过依赖注入,可以更方便地管理FormMain实例和日志服务。

.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 信息

========================================

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容器如何实例化一个带参数的类

========================================

上面实例中 FormMain 的构造子, 仅仅含有DI容器中已有的对象, 所以在DI实例化对象时, 我们不需要关注太多就可以.  但如果 FormMain 还有其他参数, FormMain 类将如何被DI 容器管理呢?

安装 Microsoft.Extensions.Options 库,  可参考: https://csharp.christiannagel.com/2016/07/27/diwithoptions/

WinForm 应用中实现依赖注入(Dependency Injection,DI)以解耦组件,可以通过以下方式来完成: 在 WinForm 中,由于窗体通常通过 `new` 实例化,而非通过容器解析,因此需要一种机制来确保窗体能够从 DI 容器获取其所需的依赖项。通常的做法是将窗体的创建过程交由容器管理,或通过构造函数注入依赖项。 ### 使用构造函数注入实现模块解耦 构造函数注入是一种常见的依赖注入方式,它允许窗体在实例化时接收其所需的依赖项。例如,在一个用户窗体中注入 `IUserService` 服务: ```csharp public partial class UserForm : Form { private readonly IUserService _userService; public UserForm(IUserService userService) { InitializeComponent(); _userService = userService; } private void btnSave_Click(object sender, EventArgs e) { string name = txtName.Text; int age = int.Parse(txtAge.Text); _userService.SaveUser(name, age); } } ``` 这种方式确保了窗体与具体的服务实现解耦,只需接口定义,而无需关心具体实现细节[^3]。 ### 使用 DI 容器管理依赖 在 .NET Core 3.1 中,微软提供了内置的依赖注入容器,开发者可以将服务注册到容器中,并在窗体中通过构造函数获取这些服务。例如,在 `Program.cs` 中配置服务: ```csharp var host = Host.CreateDefaultBuilder() .ConfigureServices((context, services) => { services.AddTransient<IUserService, UserService>(); services.AddSingleton<MainForm>(); }) .Build(); Application.Run(host.Services.GetRequiredService<MainForm>()); ``` 这样,`MainForm` 可以在其构造函数中接收 `IUserService` 实例: ```csharp public partial class MainForm : Form { private readonly IUserService _userService; public MainForm(IUserService userService) { InitializeComponent(); _userService = userService; } } ``` 此方法确保了窗体及其依赖项之间的松耦合关系,并且可以轻松扩展到多个服务和模块[^1]。 ### 自动解析模块依赖 如果应用使用了模块化架构,例如 SailingEase Winform Framework,模块在加载时会自动从容器中解析所需服务。开发者只需在模块的构造函数中声明所需的服务参数,框架会自动完成注入: ```csharp public class MyModule { public MyModule(IServiceA serviceA, IServiceB serviceB) { // 使用 serviceA 和 serviceB } } ``` 这种机制简化了模块的初始化过程,并确保了模块与服务之间的解耦[^2]。 ### 使用第三方 DI 容器(如 Autofac、Ninject) 虽然 .NET Core 提供了原生的 DI 容器,但在某些场景下,可能需要更高级的功能,例如属性注入、方法注入等。这时可以使用第三方容器如 Autofac 或 Ninject,它们提供了更灵活的依赖管理能力。例如使用 Autofac 的方式如下: ```csharp var builder = new ContainerBuilder(); builder.RegisterType<UserService>().As<IUserService>(); var container = builder.Build(); var form = container.Resolve<UserForm>(); Application.Run(form); ``` 在 `UserForm` 中,构造函数接收 `IUserService` 实例,容器会自动完成注入: ```csharp public partial class UserForm : Form { private readonly IUserService _userService; public UserForm(IUserService userService) { InitializeComponent(); _userService = userService; } } ``` 这种方式提供了更高的灵活性和可扩展性,适合复杂的企业级应用[^1]。 ### 总结 通过构造函数注入、DI 容器配置、模块化框架支持以及第三方容器的使用WinForm 应用可以有效地实现组件解耦,提高代码的可维护性和可测试性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值