分享一个 .NET Core Console 项目使用依赖注入的详细例子

前言

依赖注入(Dependency Injection,简称DI)是一种软件设计模式,主要用于管理和组织一个软件系统中不同模块之间的依赖关系。

在依赖注入中,依赖项(也称为组件或服务)不是在代码内部创建或查找的,而是由外部系统提供给组件。

具体来说,当某个角色(如一个 C# 实例,调用者)需要另一个角色(如另一个 C# 实例,被调用者)的协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。

但在依赖注入中,创建被调用者的工作不再由调用者来完成,而是由外部容器来完成,并注入给调用者。

依赖注入可以提高代码的可维护性、可测试性、可替换性和可扩展性,降低组件之间的耦合度,使得代码更加清晰和灵活,以前我们写过在 Asp.NET Core Web API 项目中如何通过内置的依赖注入容器使用依赖注入的文章《一个简单的 ASP.NET Core 依赖注入例子,提高代码的可维护性和可扩展性》,今天继续分享一个在 .NET Core Console 项目使用依赖注入的详细例子,大家可以比较两者之间的不同。

Step By Step 步骤

  1. 创建一个 .NET Core Console 项目

  2. 从 Nuget 安装以下包

    Microsoft.Extensions.DependencyInjection

    System.Data.SqlClient

  3. 创建 User 实体类:

    c# 复制代码
    record User(long Id, string UserName, string Password);
  4. 创建接口 IUserBiz 和 IUserDAO 及其实现类(留意注释

    c# 复制代码
    interface IUserBiz
    {
        /// <summary>
        /// 检查用户名、密码是否匹配
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        public bool CheckLogin(string userName, string password);
    }
    
    interface IUserDAO
    {
        /// <summary>
        /// 查询用户名为userName的用户信息
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        public User? GetByUserName(string userName);
    }
    
    class UserBiz : IUserBiz
    {
        private readonly IUserDAO userDAO;
    
    	// 通过构造方法要求注入 IUserDAO 服务
        public UserBiz(IUserDAO userDAO)
        {
            this.userDAO = userDAO;
        }
    
        public bool CheckLogin(string userName, string password)
        {
            var user = userDAO.GetByUserName(userName);
            if (user == null)
            {
                return false;
            }
            else
            {
                return user.Password == password;
            }
        }
    }
    
    class UserDAO : IUserDAO
    {
        private readonly IDbConnection conn;
    
    	// 通过构造方法要求依赖注入容器为其注入一个 IDbConnection 对象
        public UserDAO(IDbConnection conn)
        {
            this.conn = conn;
        }
    
        public User? GetByUserName(string userName)
        {
            using var dt = SqlHelper.ExecuteQuery(conn, $"select * from T_Users where UserName={userName}");
            if (dt.Rows.Count <= 0)
            {
                return null;
            }
            DataRow row = dt.Rows[0];
            int id = (int)row["Id"];
            string uname = (string)row["UserName"];
            string password = (string)row["Password"];
            return new User(id, uname, password);
        }
    }	
  5. 创建数据库操作帮助类 SqlHelper

    c# 复制代码
    using System.Data;
    
    static class SqlHelper
    {
    	// 执行查询语句并返回 DataTable
        public static DataTable ExecuteQuery(this IDbConnection conn, FormattableString formattable)
        {
            using IDbCommand cmd = CreateCommand(conn, formattable);
            DataTable dt = new DataTable();
            using var reader = cmd.ExecuteReader();
            dt.Load(reader);
            return dt;
        }
    
    	// 执行查询语句并返回一个值
        public static object? ExecuteScalar(this IDbConnection conn, FormattableString formattable)
        {
            using IDbCommand cmd = CreateCommand(conn, formattable);
            return cmd.ExecuteScalar();
        }
    
    	// 执行 DML 语句
        public static int ExecuteNonQuery(this IDbConnection conn, FormattableString formattable)
        {
            using IDbCommand cmd = CreateCommand(conn, formattable);
            int result = cmd.ExecuteNonQuery();
            return result;
        }
    
    	// 创建 Command
        private static IDbCommand CreateCommand(IDbConnection conn, FormattableString formattable)
        {
            var cmd = conn.CreateCommand();
            string sql = formattable.Format;
            for (int i = 0; i < formattable.ArgumentCount; i++)
            {
                sql = sql.Replace("{" + i + "}", "@p" + i);
                var parameter = cmd.CreateParameter();
                parameter.ParameterName = "@p" + i;
                parameter.Value = formattable.GetArgument(i);
                cmd.Parameters.Add(parameter);
            }
            cmd.CommandText = sql;
            return cmd;
        }
    }
  6. 打开 Program.cs,引入依赖注入命名空间

    c# 复制代码
    using Microsoft.Extensions.DependencyInjection;
  7. 创建依赖注入容器

    c# 复制代码
    // 2. 创建用于注册服务的容器
    ServiceCollection services = new ServiceCollection();
    ``
  8. 注入服务(留意注释

    c# 复制代码
    // 3.1 注入 IDbConnection 服务(范围)
    services.AddScoped<IDbConnection>(sp => {
    	string connStr = "Server=(localdb)\\mssqllocaldb;Database=TestDB;Trusted_Connection=True;MultipleActiveResultSets=true";
    	var conn = new SqlConnection(connStr);
    	conn.Open();
    	return conn;
    });
    
    // 3.2 注入 IUserDAO 和 IUserBiz 服务(范围)
    // ----把UserDAO注册为IUserDAO服务的实现类
    // ----实现类的参数也会一起注入
    services.AddScoped<IUserDAO, UserDAO>();
    services.AddScoped<IUserBiz, UserBiz>();	
  9. 使用(留意注释

    c# 复制代码
    // 调用 IServiceCollection 的 BuildServiceProvider 方法创建一个 ServiceProvider 对象
    using (ServiceProvider sp = services.BuildServiceProvider())
    {
    	// 调用 GetRequiredService 方法获取服务
    	var userBiz = sp.GetRequiredService<IUserBiz>();
    	bool b = userBiz.CheckLogin("jacky", "123456");
    	Console.WriteLine(b);
    }

结语

依赖注入的实现方式有多种,如构造注入、属性注入和接口注入等。

通过依赖注入,可以更容易地管理和维护系统的各个组件,轻松地将模拟依赖注入到单元测试中,更灵活地添加新功能或替换现有组件。

虽然依赖注入在软件开发中有很多优点,但在使用时也需要谨慎,以确保正确地管理和配置依赖关系,避免潜在的问题,比如违背单一职责原则等设计原则,导致代码结构混乱,维护成本增加等。

附录:完整的 Program.cs 代码(留意注释

c# 复制代码
using DI魅力渐显_依赖注入;

// 1. 引用依赖注入命名空间
using Microsoft.Extensions.DependencyInjection;
using System.Data;
using System.Data.SqlClient;

// 2. 创建用于注册服务的容器
ServiceCollection services = new ServiceCollection();

// 3.1 注入 IDbConnection 服务(范围)
services.AddScoped<IDbConnection>(sp => {
    string connStr = "Server=(localdb)\\mssqllocaldb;Database=TestDB;Trusted_Connection=True;MultipleActiveResultSets=true";
    var conn = new SqlConnection(connStr);
    conn.Open();
    return conn;
});

// 3.2 注入 IUserDAO 和 IUserBiz 服务(范围)
// ----把UserDAO注册为 IUserDAO 服务的实现类
// ----实现类的参数也会一起注入
services.AddScoped<IUserDAO, UserDAO>();
services.AddScoped<IUserBiz, UserBiz>();

// 4. 使用
// 调用 IServiceCollection 的 BuildServiceProvider 方法创建一个 ServiceProvider 对象
using (ServiceProvider sp = services.BuildServiceProvider())
{
    // 调用 GetRequiredService 方法获取服务
    var userBiz = sp.GetRequiredService<IUserBiz>();
    bool b = userBiz.CheckLogin("jacky", "123456");
    Console.WriteLine(b);
}

我是老杨,一个奋斗在一线的资深研发老鸟,让我们一起聊聊技术,聊聊人生。

都看到这了,求个点赞、关注、在看三连呗,感谢支持。

相关推荐
_oP_i11 分钟前
.NET Core 项目配置到 Jenkins
运维·jenkins·.netcore
军训猫猫头2 小时前
20.抽卡只有金,带保底(WPF) C#
ui·c#·wpf
向宇it12 小时前
【从零开始入门unity游戏开发之——C#篇25】C#面向对象动态多态——virtual、override 和 base 关键字、抽象类和抽象方法
java·开发语言·unity·c#·游戏引擎
向宇it13 小时前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
坐井观老天18 小时前
在C#中使用资源保存图像和文本和其他数据并在运行时加载
开发语言·c#
pchmi20 小时前
C# OpenCV机器视觉:模板匹配
opencv·c#·机器视觉
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭1 天前
C#都可以找哪些工作?
开发语言·c#
boligongzhu1 天前
Dalsa线阵CCD相机使用开发手册
c#
向宇it1 天前
【从零开始入门unity游戏开发之——C#篇23】C#面向对象继承——`as`类型转化和`is`类型检查、向上转型和向下转型、里氏替换原则(LSP)
java·开发语言·unity·c#·游戏引擎·里氏替换原则