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(); services.AddScoped (); //1、我希望从环境变量读取配置 services.AddScoped(typeof(IConfig), x => new IniFileConfig("db.ini")); //2、我希望从ini读取配置 services.AddScoped (); using (var sp = services.BuildServiceProvider()) { var service = sp.GetRequiredService (); service.CheckUser("kxy", "123"); }; Console.ReadLine(); } } }
DI的简单原理就是这样。。
4、Net Core Api DI如何实现
依赖注入一般是在StartUp实现,Net6以后取消了StartUp,将builder迁移至Program
builder.Services.AddScoped();
正常是在构造函数注入
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); } } }