Loading

ASP.NET Core3基础:02. 依赖注入

简介

首先先介绍依赖注入相关的两个名词:

  • DI:依赖注入,inversion of control
  • IoC:控制反转,Dependency Injection

 

依赖注入简单来说就是把以前通过 new 创建对象,改成我们需要对象时,由框架给我们提供。这样的好处有很多,例如实现解耦、统一管理依赖(特别依赖链非常复杂时)、更方便进行单元测试等。

可以理解成,IoC是一种概念,而DI可以看成是实现IoC的一种方法。

 

下面看一个官网的例子:https://docs.microsoft.com/zh-cn/dotnet/architecture/modern-web-apps-azure/architectural-principles#dependency-inversion

 

应用程序中的依赖关系方向应该是抽象的方向,而不是实现详细信息的方向。 大部分应用程序都是这样编写的:编译时依赖关系顺着运行时执行的方向流动,从而生成一个直接依赖项关系图。 也就是说,如果模块 A 调用模块 B 中的函数,而模块 B 又调用模块 C 中的函数,则编译时 A 取决于 B,而 B 又取决于 C,如图中所示。

image

应用依赖关系反转原则后,A 可以调用 B 实现的抽象上的方法,让 A 可以在运行时调用 B,而 B 又在编译时依赖于 A 控制的接口(因此,典型的编译时依赖项发生反转)。 运行时,程序执行的流程保持不变,但接口引入意味着可以轻松插入这些接口的不同实现。

举一个生活中的例子:插座的插口有国标、欧标等,这就可以看成是接口(interface),一种协议,通过这个协议,不同生产电器的厂家的只要实现了这个协议,就可以接入任何品牌的插座接口,这样就实现了松耦合,即两者都依赖于抽象,不依赖具体实现。

image

依赖项反转是生成松散耦合应用程序的关键一环,因为可以将实现详细信息编写为依赖并实现更高级别的抽象,而不是相反。 因此,生成的应用程序的可测试性、模块化程度以及可维护性更高。 遵循依赖关系反转原则可实现依赖关系注入。

 

关于设计模式的6大原则,网上有很多资料,可以搜一下。

ASP.NET Core 3中的依赖注入

一、配置依赖注入

创建ASP.NET Core 3 API类型的项目时,已经默认就配置好了依赖注入框架,使用时只需要在 Startup.cs 文件的 ConfigureServices 方法中配置即可:

例如我们有个服务 IMyService,并且有个这个接口的实现类 MyService

public void ConfigureServices(IServiceCollection services) {
    services.AddScoped<IMyService, MyService>();
}

二、获取依赖注入

获取依赖注入有多种方法:

  • 在控制器的构造方法获取依赖注入:只需要把要注入的服务接口写到构造方法中即可
public class HomeController: Controller {
        private readonly IMyService _myService;
        public HomeController(IMyService myService) {
            _myService = myService;
    }
}
  • 在控制器的action获取注入:只要把参数标记为 [FromServices]
public class HomeController: Controller {
        public HomeController( ) { }
    public int Get([FromServices]IMyService myService) {
    }
}

依赖注入配置的各种姿势

上面的例子时最最简单的依赖注入方法,但是其实依赖注入还有很多需要注意的地方:

一、单例(Singleton)、范围(Scoped)、暂时(Transient)

在配置依赖注入时,有3中比较常用的注入类型,分别是:

  • Transient:暂时,即每次获取到的都是一个新的对象(可以通过 GetHashCode 方法进行验证)
  • Scoped:范围,即在一个范围内,多次获取到的是同一个对象,不同范围是不同的对象。比如说在API项目中,如果把服务配置为Scoped,则每次HTTP请求期间得到的都是同一个对象
  • Singleton:单例,即在Application的生命周期内,这个对象都是同一个。

在实际使用中用哪种方式需要根据具体的情况而定,没有哪一种好哪一种不好。

还是用上面那个IMyService的接口做例子,下面代码展示如何配置:

public void ConfigureServices(IServiceCollection services) {
    services.AddSingleton<IMyService, MyService>( );
    services.AddScoped<IMyService, MyService>( );
    services.AddTransient<IMyService, MyService>( );
}

二、各种配置的方法

注册的时候并不不止可以用泛型的方法进行注册,也可以通过其他方式注册:

// 1. 通过泛型注册,可以自动在容器释放时自动释放对象,不可传入参数
services.AddScoped<IMyService, MyService>( );
// 2. 通过工厂方法注册,可以自动在容器释放时自动释放对象,可以传入参数
services.AddScoped<IMyService>(sp => new MyService(123));
// 3. 注册自身,可以自动在容器释放时自动释放对象,不可传入参数
//    可以看成 services.AddScoped<MyService, MyService>( ); 的简写
services.AddScoped<MyService>( );
// 手动创建对象,不会自动释放对象,可以传入参数
services.AddScoped<IMyService>(new MyService(123));
services.AddScoped(new MyService(123));
// 注入泛型:注入的对象本身是个泛型,则需要使用typeof
// 并且如果泛型的参数是一个已经注入的服务,也会进行解析
services.AddScoped(typeof(IMyService<>), typeof(MyService<>));

三、尝试注入、删除注入、替换注入、多注入

如果不确定之前是否已经注入过这个类型的,并且希望已经注入的话不注入,没有注入的话才进行注入时,可以使用尝试注入:

// 之前没有注入过 IMyService 类型的对象,这次注入成功
services.TryAddScoped<IMyService, MyService>( );
// 这里再尝试注入 MyService2 到 IMyService 时,并不会注入,容器中还是 MyService
services.TryAddScoped<IMyService, MyService2>( );

如果希望删除已经注入的服务,则可以调用:

services.RemoveAll<IMyService>();

如果喜欢替换被注入的服务,则可以调用:

// 注册Service1
services.AddSingleton<IMyService, MyService1>( );
// 替换注册,替换上面的IMyService注册的MyService1,改为MyService2
services.Replace(ServiceDescriptor.Singleton<IMyService, MyService2>( ));

如果希望注册多个实现类到同一个服务:

// 注册多个接口实现
public void ConfigureServices(IServiceCollection services) {
    services.AddSingleton<IMyService, MyService1>( );
    services.AddSingleton<IMyService, MyService2>( );
}
// 使用时可以使用IEnumerable进行接受多个
public int GetServiceList([FromServices]IEnumerable<IMyServices> services) {
}

示例项目

紧接着上一篇的API框架,继续完善,最后会演示一个学生信息登记的页面:

  • 首先新建一个Services的文件夹(真是项目这个会分离出来当作独立的项目),并创建一个 IStudentService 的接口和 StudentService 的类,并且继承 IStudentService 接口。这个接口暂时只有一个获取全部学生信息的方法:GetAllStudent
  • 再新建一个文件夹Model(同样真实的项目会分离出来),并且在这个文件夹创建一个学生的实体类 Student,这个类包含 Name 和 Age 两个属性。
// 抽象接口
public interface IStudentService {
    List<Student> GetAllStudent( );
}
// 具体的实现类
public class StudentService : IStudentService {
    public List<Student> GetAllStudent( ) {
        // 这里为了方便,直接写死
        return new List<Student>( ) {
            new Student(){ID = 1, Name = "张三", Age = 10 },
            new Student(){ID = 2, Name = "李四", Age = 12 },
            new Student(){ID = 3, Name = "王五", Age = 14 }
        };
    }
}
// 学生的实体类
public class Student {
    public int ID { get; set; }
    /// <summary>
    /// 姓名
    /// </summary>
    public string Name { get; set; }
    /// <summary>
    /// 年龄
    /// </summary>
    public int Age { get; set; }
}
  • 然后在Ccontroller文件夹下面新建一个 StudentController 的控制器,这个控制器调用 IStudentService 的 GetAllStudent 方法,把数据返回到前端
public class StudentController : Controller {
    [Route("/student/getall")]
    public List<Student> GetAllStudent([FromServices] IStudentService student) {
        // 通过容器注入 IStudentService 服务,并且调用 GetAllStudent 方法
        return student.GetAllStudent( );
    }
}
  • 最后,我们需要把 IStudentService 配置到容器中,让 StudentController 在获取的时候能获取到服务的实例:
public class Startup {
    public Startup(IConfiguration configuration) {
        Configuration = configuration;
    }
    public IConfiguration Configuration { get; }
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services) {
        services.AddControllers( );
        // 配置服务 IStudentService 到 StudentService
        services.AddScoped<IStudentService, StudentService>( );
    }
    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
        if (env.IsDevelopment( )) {
            app.UseDeveloperExceptionPage( );
        }
        app.UseRouting( );
        app.UseAuthorization( );
        app.UseEndpoints(endpoints => {
            endpoints.MapControllers( );
        });
    }
}

运行项目,并且导航到 http://localhost:5000/student/getall

结果如下

image.png

最后的项目结构

image.png

总结

这篇大概把mvc默认框架下的依赖注入部分常用的方法列举出来了,并且通过一个例子演示了如何使用依赖注入服务,如果需要更详细了解依赖注入可以查看官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1#register-groups-of-services-with-extension-methods

当然依赖注入还可以进行注入配置文件,让程序可以运动在不同的环境下,这个将在后面会有一篇单独的说明配置文件如何注入、热更新等。

下一篇会是使用actofac增强注入,autofac可以实现更多的注入方式,例如注入到属性的对象等。

 

 

posted @ 2020-09-07 10:06  削着苹果走路  阅读(313)  评论(0编辑  收藏  举报