.Net DI(Dependency Injection)依赖注入机制

发布时间 2023-03-22 19:14:09作者: wskxy

1、简介

  DI:Dependency Injection,即依赖注入,他是IOC的具体实现。

  在DI中,底层服务对象不再负责依赖关系的创建,而是交由顶端调用进行管理注入

  好处:降低组件之间的耦合度,使代码更加灵活

2、实例

  我们举个例子,有个User Login的功能,Login需要通过DB验证,DB需要读取Config和进行Log记录

  依赖关系如图

   

  DI的概念,就是把DB的依赖(Config&Log)提到User层,该怎么实现呢?

  接着往下走...

3、代码结构

  通过代码我们来看一下原理。

  框架说明:

    1)Service类库:Standard2.1,类库推荐都使用Standard,这样可以在Framework、core、net之间通用

    2)UserSite:Net5 控制台程序

  

 

  编码内容:

  ConfigService,包含两种实现方式

namespace ConfigService
{
    public interface IConfig
    {
        public string GetValue(string key);//获取name的值
    }
}


using System;
namespace ConfigService
{
    public class EnvironmentConfig : IConfig //从环境变量里面读取配置信息,自行添加到电脑User里面
    {
        public string GetValue(string key)
        {
            return Environment.GetEnvironmentVariable(key, EnvironmentVariableTarget.User);
        }
    }
}


using System.IO;
using System.Linq;
namespace ConfigService
{
    public class IniFileConfig : IConfig //从ini文件读取配置信息,UserSite如果注入此服务,需要创建ini文件:key=value   就行
    {
        string _filePath;
        public IniFileConfig(string filePath)
        {
            this._filePath = filePath;
        }
        public string GetValue(string key)
        {
            string str = "";
            var kv = File.ReadAllLines(_filePath)
                .Select(x => x.Split("="))
                .Select(x => new { key = x[0], value = x[1] })
                .SingleOrDefault(x => x.key == key);
            return kv?.value;
        }
    }
}

  LogService,简单实现

namespace LogService
{
    public interface ILog
    {
        public void LogInfo(string msg);
        public void LogError(string msg);
    }
}


using System;
namespace LogService
{
    public class ConsoleLog : ILog
    {
        public void LogInfo(string msg)
        {
            Console.WriteLine($"Info:{msg}");
        }
        public void LogError(string msg)
        {
            Console.WriteLine($"Error:{msg}");
        }
    }
}

  DBService,只是做思路演示,这边只要读取到dblink就算访问数据库成功

namespace DBService
{
    public interface IDBHelper
    {
        public bool CheckUser(string acc, string pwd);
    }
}

using LogService;
using ConfigService;
namespace DBService
{
    public class SqlServerHelper : IDBHelper
    {
        private IConfig _config;
        private ILog _log;
        public SqlServerHelper(IConfig config,ILog log) //Net默认从构造函数进行以来注入
        {
            this._config = config;
            this._log = log;
        }
        public bool CheckUser(string acc, string pwd)
        {
            var dblink = this._config.GetValue("dblink");
            this._log.LogInfo($"获取数据库链接={dblink}");
            if (string.IsNullOrWhiteSpace(dblink))
            {
                this._log.LogError($"登录失败");
                return false;
            }
            this._log.LogInfo($"登录成功-{acc}-{pwd}");
            return true;
        }
    }
}

  主题来了,UserSite Program代码如下:

  我们需要注入SqlServerHelper服务,就需要将其依赖项一同注入,这样就将具体实现类提到了顶端注入,从而进行解耦

  比如:读取配置文件,我希望从哪读取,就注入哪一个(注意,都注入的话,IConfig会以后注入的为准)

using System;
using ConfigService;
using LogService;
using DBService;
using Microsoft.Extensions.DependencyInjection;

namespace UserSite
{
    class Program
    {
        static void Main(string[] args)
        {
            ServiceCollection services = new ServiceCollection();
            services.AddScoped<ILog, ConsoleLog>();
            services.AddScoped<IConfig, EnvironmentConfig>(); //1、我希望从环境变量读取配置
            services.AddScoped(typeof(IConfig), x => new IniFileConfig("db.ini")); //2、我希望从ini读取配置
            services.AddScoped<IDBHelper, SqlServerHelper>();
            using (var sp = services.BuildServiceProvider())
            {
                var service = sp.GetRequiredService<IDBHelper>();
                service.CheckUser("kxy", "123");
            };
            Console.ReadLine();
        }
    }
}

  DI的简单原理就是这样。。

4、Net Core Api DI如何实现

  依赖注入一般是在StartUp实现,Net6以后取消了StartUp,将builder迁移至Program

builder.Services.AddScoped<IDBHelper, SqlServerHelper>();

  正常是在构造函数注入

using DIDemo.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DIDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class HomeController : ControllerBase
    {
        IDBHelper _dBHelper;
        public HomeController(IDBHelper dBHelper) { 
            this._dBHelper = dBHelper;
        }
        [HttpGet]
        public bool Get(string acc,string pwd)
        {
            return _dBHelper.CheckUser(acc, pwd);
        }
    }
}

  当然,如果一些服务构造起来非常耗时,在一个控制器下面又只是一两个api需要使用到,

  如果使用构造函数注入,所有api的访问都会进行依赖注入,这是十分不友好的,即使首次注入后服务器会缓存资源

  可更换为函数注入

using DIDemo.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DIDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class HomeController : ControllerBase
    {
        [HttpGet]
        public bool Get([FromServices] IDBHelper _dBHelper, string acc,string pwd)
        {
            return _dBHelper.CheckUser(acc, pwd);
        }
    }
}