【微软技术栈】C#.NET 中使用依赖注入
本文内容
- 先决条件
- 创建新的控制台应用程序
- 添加接口
- 添加默认实现
- 添加需要 DI 的服务
- 为 DI 注册服务
- 结束语
本文介绍如何在 .NET 中使用依赖注入 (DI)。 借助 Microsoft 扩展,可通过添加服务并在 IServiceCollection 中配置这些服务来管理 DI。 IHost 接口会公开 IServiceProvider 实例,它充当所有已注册的服务的容器。
本文介绍如何执行下列操作:
- 创建一个使用依赖注入的 .NET 控制台应用
- 生成和配置通用主机
- 编写多个接口及相应的实现
- 为 DI 使用服务生存期和范围设定
1、先决条件
- .NET Core 3.1 SDK 或更高版本。
- 熟悉如何创建新的 .NET 应用程序以及如何安装 NuGet 包。
2、创建新的控制台应用程序
通过 dotnet new 命令或 IDE 的“新建项目”向导,新建一个名为 ConsoleDI 的 .NET 控制台应用程序 Example 。 将 NuGet 包 Microsoft.Extensions.Hosting 添加到项目。
新的控制台应用项目文件应如下所示:
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net7.0</TargetFramework><Nullable>enable</Nullable><ImplicitUsings>true</ImplicitUsings><RootNamespace>ConsoleDI.Example</RootNamespace></PropertyGroup><ItemGroup><PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" /></ItemGroup></Project>
重要
在此示例中,需要 NuGet 包 Microsoft.Extensions.Hosting 来生成和运行应用。 某些元包可能包含 Microsoft.Extensions.Hosting
包,在这种情况下,不需要显式包引用。
3、添加接口
在此示例应用中,你将了解依赖项注入如何处理服务生存期。 你将创建多个表示不同服务生存期的接口。 将以下接口添加到项目根目录:
IReportServiceLifetime.cs
using Microsoft.Extensions.DependencyInjection;namespace ConsoleDI.Example;public interface IReportServiceLifetime
{Guid Id { get; }ServiceLifetime Lifetime { get; }
}
IReportServiceLifetime
接口定义了以下项:
- 表示服务的唯一标识符的
Guid Id
属性。 - 表示服务生存期的 ServiceLifetime 属性。
IExampleTransientService.cs
using Microsoft.Extensions.DependencyInjection;namespace ConsoleDI.Example;public interface IExampleTransientService : IReportServiceLifetime
{ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Transient;
}
IExampleScopedService.cs
using Microsoft.Extensions.DependencyInjection;namespace ConsoleDI.Example;public interface IExampleScopedService : IReportServiceLifetime
{ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Scoped;
}
IExampleSingletonService.cs
using Microsoft.Extensions.DependencyInjection;namespace ConsoleDI.Example;public interface IExampleSingletonService : IReportServiceLifetime
{ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Singleton;
}
IReportServiceLifetime
的所有子接口均使用默认值显式实现 IReportServiceLifetime.Lifetime
。 例如,IExampleTransientService
使用 ServiceLifetime.Transient
值显式实现 IReportServiceLifetime.Lifetime
。
4、添加默认实现
该示例实现使用 Guid.NewGuid() 的结果初始化其 Id
属性。 将各种服务的下列默认实现类添加到项目根目录:
ExampleTransientService.cs
namespace ConsoleDI.Example;internal sealed class ExampleTransientService : IExampleTransientService
{Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}
ExampleScopedService.cs
namespace ConsoleDI.Example;internal sealed class ExampleScopedService : IExampleScopedService
{Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}
ExampleSingletonService.cs
namespace ConsoleDI.Example;internal sealed class ExampleSingletonService : IExampleSingletonService
{Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}
每个实现都定义为 internal sealed
并实现其相应的接口。 例如,ExampleSingletonService
会实现 IExampleSingletonService
。
5、添加需要 DI 的服务
添加下列服务生存期报告器类,它作为服务添加到控制台应用:
ServiceLifetimeReporter.cs
namespace ConsoleDI.Example;internal sealed class ServiceLifetimeReporter
{private readonly IExampleTransientService _transientService;private readonly IExampleScopedService _scopedService;private readonly IExampleSingletonService _singletonService;public ServiceLifetimeReporter(IExampleTransientService transientService,IExampleScopedService scopedService,IExampleSingletonService singletonService) =>(_transientService, _scopedService, _singletonService) =(transientService, scopedService, singletonService);public void ReportServiceLifetimeDetails(string lifetimeDetails){Console.WriteLine(lifetimeDetails);LogService(_transientService, "Always different");LogService(_scopedService, "Changes only with lifetime");LogService(_singletonService, "Always the same");}private static void LogService<T>(T service, string message)where T : IReportServiceLifetime =>Console.WriteLine($" {typeof(T).Name}: {service.Id} ({message})");
}
ServiceLifetimeReporter
会定义一个构造函数,该函数需要上述每一个服务接口(即 IExampleTransientService
、IExampleScopedService
和 IExampleSingletonService
)。 对象会公开一个方法,使用者可通过该方法使用给定的 lifetimeDetails
参数报告服务。 被调用时,ReportServiceLifetimeDetails
方法会使用服务生存期消息记录每个服务的唯一标识符。 日志消息有助于直观呈现服务生存期。
6、为 DI 注册服务
使用以下代码更新 Program.cs:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ConsoleDI.Example;HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);builder.Services.AddTransient<IExampleTransientService, ExampleTransientService>();
builder.Services.AddScoped<IExampleScopedService, ExampleScopedService>();
builder.Services.AddSingleton<IExampleSingletonService, ExampleSingletonService>();
builder.Services.AddTransient<ServiceLifetimeReporter>();using IHost host = builder.Build();ExemplifyServiceLifetime(host.Services, "Lifetime 1");
ExemplifyServiceLifetime(host.Services, "Lifetime 2");await host.RunAsync();static void ExemplifyServiceLifetime(IServiceProvider hostProvider, string lifetime)
{using IServiceScope serviceScope = hostProvider.CreateScope();IServiceProvider provider = serviceScope.ServiceProvider;ServiceLifetimeReporter logger = provider.GetRequiredService<ServiceLifetimeReporter>();logger.ReportServiceLifetimeDetails($"{lifetime}: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()");Console.WriteLine("...");logger = provider.GetRequiredService<ServiceLifetimeReporter>();logger.ReportServiceLifetimeDetails($"{lifetime}: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()");Console.WriteLine();
}
每个 services.Add{LIFETIME}<{SERVICE}>
扩展方法添加(并可能配置)服务。 我们建议应用遵循此约定。 将扩展方法置于 Microsoft.Extensions.DependencyInjection 命名空间中以封装服务注册的组。 还包括用于 DI 扩展方法的命名空间部分 Microsoft.Extensions.DependencyInjection
:
- 允许在不添加其他
using
块的情况下在 IntelliSense 中显示它们。 - 在通常会调用这些扩展方法的
Program
或Startup
类中,避免出现过多的using
语句。
应用会执行以下操作:
- 使用默认活页夹设置创建一个 IHostBuilder 实例。
- 配置服务并对其添加相应的服务生存期。
- 调用 Build() 并分配 IHost 的实例。
- 调用
ExemplifyScoping
,传入 IHost.Services。
7、结束语
在此示例应用中,你创建了多个接口和相应的实现。 其中每个服务都唯一标识并与 ServiceLifetime 配对。 示例应用演示了如何针对接口注册服务实现,以及如何在没有支持接口的情况下注册纯类。 然后,示例应用演示了如何在运行时解析定义为构造函数参数的依赖项。
运行该应用时,它会显示如下所示的输出:
// Sample output:
// Lifetime 1: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
// IExampleTransientService: d08a27fa-87d2-4a06-98d7-2773af886125 (Always different)
// IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
// IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 1: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
// IExampleTransientService: b43d68fb-2c7b-4a9b-8f02-fc507c164326 (Always different)
// IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
// IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
//
// Lifetime 2: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
// IExampleTransientService: f3856b59-ab3f-4bbd-876f-7bab0013d392 (Always different)
// IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
// IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 2: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
// IExampleTransientService: a8015c6a-08cd-4799-9ec3-2f2af9cbbfd2 (Always different)
// IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
// IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
在应用输出中,可看到:
- Transient 服务总是不同的,每次检索服务时,都会创建一个新实例。
- Scoped 服务只会随着新范围而改变,但在一个范围中是相同的实例。
- Singleton 服务总是相同的,新实例仅被创建一次。
相关文章:
【微软技术栈】C#.NET 中使用依赖注入
本文内容 先决条件创建新的控制台应用程序添加接口添加默认实现添加需要 DI 的服务为 DI 注册服务结束语 本文介绍如何在 .NET 中使用依赖注入 (DI)。 借助 Microsoft 扩展,可通过添加服务并在 IServiceCollection 中配置这些服务来管理 DI。 IHost 接口会公开 IS…...

开启学历新征程,电大搜题助您轻松获取知识
作为一名电大学者,有肩负着传递真实信息、宣传正面价值的使命,而今天我要向您介绍的是一款非常实用的学习工具——电大搜题微信公众号。通过该平台,您可以获得更多关于浙江开放大学和广播电视大学的学习资源,助您在学习和工作上取…...

Redis 安装
前言 为什么需要学习如何安装Redis? 学习如何安装Redis对于软件开发人员来说是非常重要的,这是因为: 高效数据存储:Redis是一种高性能的键值存储系统,能够快速地存储和检索数据。学会安装Redis可以让开发人员和系统管…...
Windows GitBash解决Github添加密钥时提示Key is already in use的问题
通过添加多密钥实现 ssh-agent bashssh-keygen -t rsa -C ‘xx1’ -f ~/.ssh/id_rsa_xx1ssh-keygen -t rsa -C ‘xx2’ -f ~/.ssh/id_rsa_xx2ssh-add id_rsa_xx1ssh-add id_rsa_xx2 vim ~/.ssh/config Host github_xx1HostName github.comUser gitIdentityFile ~/.ssh/id_rs…...

第1关:简单查询
任务描述相关知识 检索数据表的内容编程要求测试说明 任务描述 本关任务: 用 SELECT 语句检索数据表中指定字段的数据; 用 SELECT 语句检索数据表中所有字段的数据。 相关知识 为了完成本关任务,你需要掌握:1.如何获取数据表…...
Android设计模式--Builder建造者模式
一,定义 Builder模式是一步一步创建一个复杂对象的创建型模式,它允许用户在不知道内部构建细节的情况下,可以更精细的控制对象的构造流程。 也就是将一个对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 二&…...

css实现鼠标悬停时元素的显示与隐藏
css实现鼠标悬停时元素的显示与隐藏 跟着B站黑马学习小兔鲜项目,有个点记录一下 就是当鼠标悬浮在商品列表上时,列表中的商品会显示出来,离开时,商品隐藏,如下: 感觉这个功能经常会遇到,但一直…...

天气越来越寒冷,一定要注意保暖
你们那里下雪了吗?听说西安已经下了今年的第一场雪,我们这里虽然隔了几百公里,但是只下雨没有下雪,不过气温是特别的冷,尤其是对我们这些上班族和上学的人而言,不管多冷,不管刮风下雨࿰…...

03 # 类型基础:动态类型与静态类型
通俗定义 静态类型语言:在编译阶段确定所有变量的类型 编译阶段确定属性偏移量用偏移量访问代替属性名访问偏移量信息共享 动态类型语言:在执行阶段确定所有变量的类型 在程序运行时,动态计算属性偏移量需要额外的空间存储属性名所有对象的…...

Python编程——模块、包和__init__.py
1. 模块 Python中的一个文件即为一个模块(Module),一个模块引用另外一个模块的变量、函数或类时,使用import来导入。模块名即文件名。 如fibo.py 文件下有如下代码: def fib(n): # write Fibonacci series up to na, b 0, 1while a <…...
220kV110kV10kV变电站初步设计
摘要 由于国内人民生活水平的提高,科技不断地进步,控制不断地完善,从而促使变电站设计技术在电气系统领域占据主导权,也使得220kV/110kV/10kV变电站被广泛应用。在变电站系统设计领域中,220kV/110kV/10kV变电站成为目…...

Git企业开发级讲解(一)
📘北尘_:个人主页 🌎个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上,不忘来时的初心 文章目录 一、Git初识1、提出问题2、如何解决--版本控制器3、注意事项 二、Git 安装1、Linux-centos2、…...
【微信支付通知】对resource解密 AEAD_AES_256_GCM算法工具类
微信支付JSPIA支付-支付通知中,对resource解密 import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.Key; import java.util.Base64;public class AEADDecryption {public sta…...

JVM虚拟机:垃圾回收之三色标记
本文重点 在前面的课程中我们已经学习了垃圾回收器CMS和G1,其中CMS和G1中的mixedGC都存在四个过程,这四个过程中有一个过程叫做并发标记,也就是说程序一边运行,一边标记垃圾。这个过程最困难的是:如果在标记垃圾的时候,如果对象的引用关系发生了改变,此时应该如何处理?…...
唯坚持而已
写在前面 假如有一天我失业了: 大葱一毛二一斤,卖一三轮车三百斤还不到40块钱,我会回乡种大葱么? 小麦、玉米块儿八毛的一斤,亩产1000斤,五亩地,一年一茬小麦一茬玉米,才万把块钱&a…...

【大语言模型】Docker部署清华大学ChatGLM3教程
官方地址:https://github.com/THUDM/ChatGLM3 1 将代码保存至本地 方法1: git clone https://github.com/THUDM/ChatGLM3 方法2: https://github.com/THUDM/ChatGLM3/archive/refs/heads/main.zip 2 创建Docker文件 注:请先…...

详解 KEIL C51 软件的使用·设置工程·编绎与连接程序
详解 KEIL C51 软件的使用建立工程-CSDN博客 2. 设置工程 (1)在图 2-15 的画面中点击 会弹出如图 2-16 的对话框.其中有 10 个选择页.选择“Target” 项,也就是图 2-16 的画面. 图 2-16 在图 2-16 中,箭头所指的是晶振的频率值,默认是所选单片机最高的可用频率值.该设置值与单…...

小程序实现语音识别功能
不废话,直接上代码 <template><view><u-popupround"16" :show"recordShow" :close-on-click-overlay"false":safe-area-inset-bottom"false"close"close"open"open"><view clas…...
判断两层对象中是否有空的value值
1、方法 hasEmptyValue(obj) {for (var key in obj) {if (obj.hasOwnProperty(key)) {var value obj[key];// 检查第一层属性值是否为空if (value null || value undefined || value ) {return true;}// 检查第二层属性值是否为空if (typeof value object) {for (var inn…...

【SQLite】环境安装
SQLite - C/C SQLite简介 SQLite 是一种轻量级的嵌入式数据库引擎,它在程序中直接访问数据库文件而不需要独立的数据库服务器。以下是一些关于 SQLite 的简介信息: 嵌入式数据库引擎: SQLite 是一种嵌入式数据库引擎,这意味着它…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...

jdbc查询mysql数据库时,出现id顺序错误的情况
我在repository中的查询语句如下所示,即传入一个List<intager>的数据,返回这些id的问题列表。但是由于数据库查询时ID列表的顺序与预期不一致,会导致返回的id是从小到大排列的,但我不希望这样。 Query("SELECT NEW com…...
用递归算法解锁「子集」问题 —— LeetCode 78题解析
文章目录 一、题目介绍二、递归思路详解:从决策树开始理解三、解法一:二叉决策树 DFS四、解法二:组合式回溯写法(推荐)五、解法对比 递归算法是编程中一种非常强大且常见的思想,它能够优雅地解决很多复杂的…...