当前位置: 首页 > news >正文

Asp.Net Core学习随笔

学习自BLBL杨中科老师

环境:SQLServer,控制台(.net),Asp.net core,EF core

依赖注入(Dependency Injection)

依赖注入是实现控制反转(Inversion Of Control 即IOC)的一种方式(还有一种叫服务定位器的实现,但是不如依赖注入好用),软件开发中实现解耦常用的方式.

比如吃饭

​1. 传统写法(没有DI,紧耦合)

// 你(相当于程序中的类)自己做饭
class 我 {void 吃饭() {炒锅 锅 = new 炒锅();  // 自己造锅锅.炒菜("番茄炒蛋");    // 强依赖炒锅}
}

问题​​:

  • 如果想吃火锅,必须重写整个吃饭()方法
  • 锅坏了要自己修(修改代码)

2. 依赖注入写法(解耦)

// 定义「烹饪工具」接口
interface 烹饪工具 {void 做菜(string 菜名);
}// 实现不同工具
class 炒锅 : 烹饪工具 {public void 做菜(string 菜名) => Console.WriteLine($"用炒锅做{菜名}");
}class 火锅 : 烹饪工具 {public void 做菜(string 菜名) => Console.WriteLine($"用火锅煮{菜名}");
}// 你通过外部获得工具(依赖注入)
class 我 {private 烹饪工具 _工具;// 构造函数注入public 我(烹饪工具 工具) { _工具 = 工具;  // 工具由外部提供}void 吃饭() {_工具.做菜("番茄炒蛋"); // 不关心具体用什么工具}
}
// (外部决定给你什么工具)
var 我 = new 我(new 火锅());  // 想吃火锅
我.吃饭(); // 输出:用火锅煮番茄炒蛋// 换工具只需改一行代码
var 我 = new 我(new 炒锅());  // 改吃炒菜
我.吃饭(); // 输出:用炒锅做番茄炒蛋
编程概念生活化比喻好处
依赖注入(DI)外卖员递给你餐具想吃什么换什么工具
控制反转(IoC)不用自己买锅,外卖提供控制权交给外部
接口「能烹饪」的标准保证工具都能做饭

为什么这样做?​

  1. ​易扩展​​:新增空气炸锅只需实现烹饪工具接口,不用改的代码
  2. ​易测试​​:测试时可以传入「假锅」(Mock对象)
  3. ​解耦​​:你和具体锅具零耦合,只管吃饭不关心锅哪来的

可能你还是觉得自己做饭好(有时候也确实不使用这些复杂的概念比较好,但这往往是一些很小的项目),但软件开发(尤其是后端开发往往要长期维护,往往都是比较大的项目,需求改动较为频繁的项目)随着项目越来越复杂,这种依赖注入的好处就越来越明显(就比如你不可能所有的事都亲力亲为,但是如果约定好一个规范,那么别人的东西你也可以拿过来就用).如果暂时不理解也没关系!

服务定位器

先提一下服务定位器的实现,要引入

Microsoft.Extensions.DependencyInjection 这个包已经实现了服务定位器,我们直接拿来用

你可能感到奇怪:为什么不直接讲依赖注入?我的理解:先从服务器讲起逐渐过渡到依赖注入的这个过程,比较循序渐进,同时很多概念是相通的.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;namespace Asp.net_Study
{internal class Program{static void Main(string[] args){//想创建一个集合对象ServiceCollection serviceCollection = new ServiceCollection();//将你的服务注册到集合serviceCollection.AddTransient<TestServiceImpl>();//使用这个集合构建一个服务提供对象,别忘了使用using自动释放资源using (ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider()){//使用这个服务提供对象的GetService方法获取你刚才注册的服务var t = serviceProvider.GetService<TestServiceImpl>();//获取了服务对象,该干什么干什么t.Name = "Tom";t.SayHello();}}}public class TestServiceImpl{public string Name { get; set; }public void SayHello(){Console.WriteLine($"Hello,my name is {Name} ");}}
}
​对象生命周期(Lifetime)​
生命周期类型行为描述适用场景
​Transient​每次请求都创建​​新实例​轻量级、无状态服务(如工具类)
​Scoped​同一Web请求内共享实例(不同请求不同实例)数据库上下文(如EF Core的DbContext)
​Singleton​整个应用生命周期内​​只创建一个实例​​(所有请求共享)配置服务、缓存管理器

注册服务可以注册上述三种不同类型的服务

Transient

刚才的例子就是Transient,因为我使用了AddTransient

  static void Main(string[] args){//想创建一个集合对象ServiceCollection serviceCollection = new ServiceCollection();//将你的服务注册到集合serviceCollection.AddTransient<TestServiceImpl>();//使用这个集合构建一个服务提供对象,别忘了使用using自动释放资源using (ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider()){//使用这个服务提供对象的GetService方法获取你刚才注册的服务var t = serviceProvider.GetService<TestServiceImpl>();//获取了服务对象,该干什么干什么t.Name = "Tom";var t1 = serviceProvider.GetService<TestServiceImpl>();t1.Name = "Jack";t.SayHello();t1.SayHello();}}

可见确实给我们提供了两个实例,不然

t.SayHello();得到的结果也应该是Jack.
Singleton

单例没啥好说的

    static void Main(string[] args){//想创建一个集合对象ServiceCollection serviceCollection = new ServiceCollection();//将你的服务注册到集合serviceCollection.AddSingleton<TestServiceImpl>();//使用这个集合构建一个服务提供对象,别忘了使用using自动释放资源using (ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider()){//使用这个服务提供对象的GetService方法获取你刚才注册的服务var t = serviceProvider.GetService<TestServiceImpl>();//获取了服务对象,该干什么干什么t.Name = "Tom";var t1 = serviceProvider.GetService<TestServiceImpl>();t1.Name = "Jack";t.SayHello();t1.SayHello();}}

Scope

简单点说就是某个范围能确保获取的服务是一致的,出了范围就不是了

 static void Main(string[] args){//想创建一个集合对象ServiceCollection serviceCollection = new ServiceCollection();//将你的服务注册到集合serviceCollection.AddScoped<TestServiceImpl>();//使用这个集合构建一个服务提供对象,别忘了使用using自动释放资源using (ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider()){// using开辟的范围内能保证获取一致的对象using (IServiceScope scope = serviceProvider.CreateScope()){//t t1是一个对象var t = scope.ServiceProvider.GetService<TestServiceImpl>();var t1 = scope.ServiceProvider.GetService<TestServiceImpl>();t.Name = "Tom";t1.Name = "Jack";t.SayHello();t1.SayHello();}using (IServiceScope scope1 = serviceProvider.CreateScope()){var t = scope1.ServiceProvider.GetService<TestServiceImpl>();var t1 = scope1.ServiceProvider.GetService<TestServiceImpl>();t.Name = "张三";t1.Name = "李四";t.SayHello();t1.SayHello();}}}

使用接口替代实际的类型

可以看到现在我们的代码一下子就非常灵活了,因为我们现在除了注册哪里使用了实际的类型,其他的地方我们都是使用的接口,要是需求有变动,我们只需要重新实现该接口,替换实现类型就可以.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;namespace Asp.net_Study
{internal class Program{static void Main(string[] args){//想创建一个集合对象ServiceCollection serviceCollection = new ServiceCollection();//实现服务类型和实现类型分离serviceCollection.AddScoped<ITestServiceImpl, TestServiceImpl>();//使用这个集合构建一个服务提供对象,别忘了使用using自动释放资源using (ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider()){using (IServiceScope scope = serviceProvider.CreateScope()){ITestServiceImpl t = scope.ServiceProvider.GetService<ITestServiceImpl>();t.Name = "Tom";t.SayHello();}}}}public class TestServiceImpl : ITestServiceImpl{public string Name { get; set; }public void SayHello(){Console.WriteLine($"Hello,my name is {Name} ");}}public interface ITestServiceImpl{string Name { get; set; }void SayHello();}
}

注册的方法多种多样,也可以同一个服务注册多个实现,那么理所当然的获取服务的方法也五花八门.这里不多说,用到那个查一下就好.

依赖注入的传染性

一个服务如果依赖另一个服务,直接声明然后构造函数传入你需要的服务就可以,而无需考虑它从哪来.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;namespace Asp.net_Study
{internal class Program{static void Main(string[] args){//想创建一个集合对象ServiceCollection serviceCollection = new ServiceCollection();//实现服务类型和实现类型分离serviceCollection.AddScoped<ILog, Log>();serviceCollection.AddScoped<ISave, Save>();//使用这个集合构建一个服务提供对象,别忘了使用using自动释放资源using (ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider()){ISave save = serviceProvider.GetService<ISave>();save.SaveInfo();}}}public interface ILog{void LogInfo(string message);}public class Log : ILog{public void LogInfo(string message){Console.WriteLine(message);}}public interface ISave{void SaveInfo();}public class Save : ISave{private readonly ILog _log;public Save(ILog log){this._log = log;}public void SaveInfo(){_log.LogInfo("Log something");Console.WriteLine("SaveInfo");}}
}

依赖注入核心概念​

​1. 服务(Service)​
  • ​定义​​:任何可以被注入使用的类或接口(如数据库访问类、日志工具、业务逻辑类)
public interface ILoggerService {void Log(string message);
}public class ConsoleLogger : ILoggerService {public void Log(string message) => Console.WriteLine(message);
}
​2. 注册服务(Service Registration)​
  • ​作用​​:告诉DI容器​​如何创建服务实例​
  • ​ASP.NET Core 注册方式​​:
// Program.cs
builder.Services.AddScoped<ILoggerService, ConsoleLogger>(); // 注册接口和实现类
3. 查询服务(Service Resolution)​
  • ​方式​​:通过构造函数、IServiceProvider 或 [FromServices] 获取服务实例
public class MyController : ControllerBase {private readonly ILoggerService _logger;// 构造函数注入(最常用)public MyController(ILoggerService logger) {_logger = logger;}[HttpGet]public IActionResult Get([FromServices] ILoggerService logger) {logger.Log("使用FromServices注入");return Ok();}
}
​4. 对象生命周期(Lifetime)​
生命周期类型行为描述适用场景
​Transient​每次请求都创建​​新实例​轻量级、无状态服务(如工具类)
​Scoped​同一Web请求内共享实例(不同请求不同实例)数据库上下文(如EF Core的DbContext)
​Singleton​整个应用生命周期内​​只创建一个实例​​(所有请求共享)配置服务、缓存管理器


http://www.mrgr.cn/news/98549.html

相关文章:

  • 【版本控制】git命令使用大全
  • netty-socketio + springboot 消息推送服务
  • 爬虫框架 - Coocan
  • JWT令牌:实现安全会话跟踪与登录认证的利器
  • 解决PIP 安装出错ERROR: cp310-cp310-manylinux_2_28_x86_64.whl is not a supported wheel
  • webpack vite
  • 半导体设备通信标准—secsgem v0.3.0版本使用说明文档(1)之概述、如何安装及使用方法
  • 【Bluedroid】A2DP Sink播放流程源码分析(二)
  • VGA显示
  • vue3环境搭建、nodejs22.x安装、yarn 1全局安装、npm切换yarn 1、yarn 1 切换npm
  • Jenkins插件下载慢解决办法
  • RAGFlow本地部署教程 :多模态检索+动态生成,用AI重构企业知识生产力
  • 第3篇:深入 Framer Motion Variants:掌握组件动画编排的艺术
  • Android掉帧分析
  • 利用pnpm patch命令实现依赖包热更新:精准打补丁指南
  • hadoop执行sqoop任务找不到jar
  • HTTP HTTPS RSA
  • idea如何使用git
  • MCP服务,阿里云百炼,Cline,mysql-mcp-server,MCP通信原理
  • 【HD-RK3576-PI】定制用户升级固件