我们熟悉软件工程的都知道IoC(Inverse of Control 控制反转)的概念,而DI(依赖注入)是IoC模式的实现方式之一,例如Java中的Spring框架,整体就是基于DI来设计的;微软从.Net Core开始也提供了官方DI实现框架Microsoft.Extensions.DependencyInjection
,DI也是ASP.Net Core的核心。
关于什么是DI以及它的优点这里就不赘述了,具体可以参考软件工程相关章节。
如果在控制台项目中单独使用DI,我们需要先使用NuGet进行安装。
Install-Package Microsoft.Extensions.DependencyInjection
DI的使用方法其实在各种语言、框架中都是类似的,.Net Core中的DI设计精良,使用非常直观,我们这里直接看一个完整的例子。
首先我们有一个学生服务类,其中包含了一个根据主键查询学生对象的方法,根据面向对象的原则我们这里还将其划分为了接口和实现类。
public interface StudentService
{
public Student GetStudentById(int id);
}
public class StudentServiceImpl : StudentService
{
public Student GetStudentById(int id)
{
// 此处省略具体查询数据源等业务逻辑
}
}
此外我们还有一个学生控制器,你可以理解为它是某个MVC架构代码中的C,其中包含了一个GetStudentDetailPage()
方法,它通过查询前面编写的StudentService
,向页面返回学生信息。
public class StudentController
{
private StudentService studentService;
public StudentController(StudentService studentService)
{
this.studentService = studentService;
}
public Student GetStudentDetailPage()
{
Student student = studentService.GetStudentById(1);
return student;
}
}
代码中比较特别的是我们没有new
任何的StudentService
对象,服务类的实例是由IoC容器通过构造方法依赖注入到StudentController
对象的。
public class Program
{
static void Main()
{
// IServiceCollection用于向IoC容器中注册服务
IServiceCollection services = new ServiceCollection();
// 注册一个瞬时态的服务
services.AddTransient<StudentController>();
// 注册一个单例的服务
services.AddSingleton<StudentService, StudentServiceImpl>();
// ServiceProvider用于获取服务实例
using (ServiceProvider serviceProvider = services.BuildServiceProvider())
{
// 获取服务
StudentController controller = serviceProvider.GetRequiredService<StudentController>();
// 调用服务中的方法
Student student = controller.GetStudentDetailPage();
Console.WriteLine("{0} {1}", student.Id, student.Name);
}
}
}
在Main
方法中,我们首先创建了DI框架提供的IServiceCollection
对象,它用于向IoC容器注册服务,这里我们将前面创建的StudentService
和StudentController
注册进去。
随后我们获取了ServiceProvider
对象,注意它实现了IDisposable
接口,因此我们使用了using
语句块。
最后,我们获取了StudentController
并调用了其中的方法。获取服务时我们使用了GetRequiredService()
方法,该方法通过泛型参数指定获取的服务类型,注意该方法永远不会返回null
,如果指定的服务从未注册过,此处将抛出异常。
上面DI中我们注册服务时,使用过类似services.AddSingleton<StudentService, StudentServiceImpl>()
的写法,这里我们其实指定的是对象的生命周期。可用的生命周期有三种:
生命周期 | 说明 |
---|---|
Transient | 瞬态,每次获取服务时都会创建新的对象 |
Singleton | 单例,全局只会创建一个对象,每次获取的对象都是同一个 |
Scoped | 作用域内单例,例如一次HTTP请求,在处理本次请求的过程中单例 |
有关Scoped
这里还要单独说明一下,我们看如下例子代码。
IServiceCollection services = new ServiceCollection();
services.AddScoped<StudentService, StudentServiceImpl>();
using (ServiceProvider serviceProvider = services.BuildServiceProvider())
{
// 创建作用域
using (IServiceScope serviceScope = serviceProvider.CreateScope())
{
// 获取ServiceProvider
IServiceProvider scopedServiceProvider = serviceScope.ServiceProvider;
// 获取作用域内服务
StudentService studentService = scopedServiceProvider.GetRequiredService<StudentService>();
// ...
}
}
在代码中,我们可以使用CreateScope()
方法创建作用域,该方法返回IServiceScope
实例,在同一个作用域实例上获取的服务都被认为是同一个作用域内,此时Scoped
生命周期就会生效,获取同一个服务对象。
而在ASP.Net Core中,IServiceScope
实例会由框架维护,每一次请求会生成对应的作用域。
如果熟悉Spring应该不难理解这3种生命周期,这里就不多介绍了。