千家信息网

关于ASP.NET Core依赖注入的详细介绍

发表于:2024-09-22 作者:千家信息网编辑
千家信息网最后更新 2024年09月22日,这篇文章主要讲解了"关于ASP.NET Core依赖注入的详细介绍",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"关于ASP.NET Core依赖注入的
千家信息网最后更新 2024年09月22日关于ASP.NET Core依赖注入的详细介绍

这篇文章主要讲解了"关于ASP.NET Core依赖注入的详细介绍",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"关于ASP.NET Core依赖注入的详细介绍"吧!

目录
  • 依赖注入

    • 什么是依赖注入

    • 依赖注入有什么好处

  • ASP.NET Core内置的依赖注入

    • 服务生存周期

    • 服务释放

    • TryAdd{Lifetime}扩展方法

    • 解析同一服务的多个不同实现

    • Replace && Remove 扩展方法

  • Autofac

    • 服务解析和注入

      • 构造函数注入

      • 方法注入

      • 属性注入

    • 一些注意事项

      • 框架默认提供的服务

        依赖注入

        什么是依赖注入

        简单说,就是将对象的创建和销毁工作交给DI容器来进行,调用方只需要接收注入的对象实例即可。

        • 微软官方文档-DI

        依赖注入有什么好处

        依赖注入在.NET中,可谓是"一等公民",处处都离不开它,那么它有什么好处呢?

        假设有一个日志类 FileLogger,用于将日志记录到本地文件。

        public class FileLogger{    public void LogInfo(string message)    {    }}

        日志很常用,几乎所有服务都需要记录日志。如果不使用依赖注入,那么我们就必须在每个服务中手动 new FileLogger 来创建一个 FileLogger 实例。

        public class MyService{    private readonly FileLogger _logger = new FileLogger();    public void Get()    {        _logger.LogInfo("MyService.Get");    }}

        如果某一天,想要替换掉 FileLogger,而是使用 ElkLogger,通过ELK来处理日志,那么我们就需要将所有服务中的代码都要改成 new ElkLogger。

        public class MyService{    private readonly ElkLogger _logger = new ElkLogger();    public void Get()    {        _logger.LogInfo("MyService.Get");    }}
        • 在一个大型项目中,这样的代码分散在项目各处,涉及到的服务均需要进行修改,显然一个一个去修改不现实,且违反了"开闭原则"。

        • 如果Logger中还需要其他一些依赖项,那么用到Logger的服务也要为其提供依赖,如果依赖项修改了,其他服务也必须要进行更改,更加增大了维护难度。

        • 很难进行单元测试,因为它无法进行 mock

        正因如此,所以依赖注入解决了这些棘手的问题:

        • 通过接口或基类(包含抽象方法或虚方法等)将依赖关系进行抽象化

        • 将依赖关系存放到服务容器中

        • 由框架负责创建和释放依赖关系的实例,并将实例注入到构造函数、属性或方法中

        ASP.NET Core内置的依赖注入

        服务生存周期

        Transient
        瞬时,即每次获取,都是一个全新的服务实例

        Scoped
        范围(或称为作用域),即在某个范围(或作用域内)内,获取的始终是同一个服务实例,而不同范围(或作用域)间获取的是不同的服务实例。对于Web应用,每个请求为一个范围(或作用域)。

        Singleton
        单例,即在单个应用中,获取的始终是同一个服务实例。另外,为了保证程序正常运行,要求单例服务必须是线程安全的。

        服务释放

        若服务实现了IDisposable接口,并且该服务是由DI容器创建的,那么你不应该去Dispose,DI容器会对服务自动进行释放。

        如,有Service1、Service2、Service3、Service4四个服务,并且都实现了IDisposable接口,如:

        public class Service1 : IDisposable{    public void Dispose()    {        Console.WriteLine("Service1.Dispose");    }}public class Service2 : IDisposable{    public void Dispose()    {        Console.WriteLine("Service2.Dispose");    }}public class Service3 : IDisposable{    public void Dispose()    {        Console.WriteLine("Service3.Dispose");    }}public class Service4 : IDisposable{    public void Dispose()    {        Console.WriteLine("Service4.Dispose");    }}

        并注册为:

        public void ConfigureServices(IServiceCollection services){    // 每次使用完(请求结束时)即释放    services.AddTransient();    // 超出范围(请求结束时)则释放    services.AddScoped();    // 程序停止时释放    services.AddSingleton();    // 程序停止时释放    services.AddSingleton(sp => new Service4());}

        构造函数注入一下

        public ValuesController(    Service1 service1,     Service2 service2,     Service3 service3,     Service4 service4){ }

        请求一下,获取输出:

        Service2.Dispose
        Service1.Dispose

        这些服务实例都是由DI容器创建的,所以DI容器也会负责服务实例的释放和销毁。注意,单例此时还没到释放的时候。

        但如果注册为:

        public void ConfigureServices(IServiceCollection services){    // 注意与上面的区别,这个是直接 new 的,而上面是通过 sp => new 的    services.AddSingleton(new Service1());    services.AddSingleton(new Service2());    services.AddSingleton(new Service3());    services.AddSingleton(new Service4());}

        此时,实例都是咱们自己创建的,DI容器就不会负责去释放和销毁了,这些工作都需要我们开发人员自己去做。

        更多注册方式,请参考官方文档-Service registration methods

        TryAdd{Lifetime}扩展方法

        当你将同样的服务注册了多次时,如:

        services.AddSingleton();services.AddSingleton();

        那么当使用IEnumerable<{Service}>(下面会讲到)解析服务时,就会产生多个MyService实例的副本。

        为此,框架提供了TryAdd{Lifetime}扩展方法,位于命名空间Microsoft.Extensions.DependencyInjection.Extensions下。当DI容器中已存在指定类型的服务时,则不进行任何操作;反之,则将该服务注入到DI容器中。

        services.AddTransient();// 由于上面已经注册了服务类型 IMyService,所以下面的代码不不会执行任何操作(与生命周期无关)services.TryAddTransient();services.TryAddTransient();
        • TryAdd:通过参数ServiceDescriptor将服务类型、实现类型、生命周期等信息传入进去

        • TryAddTransient:对应AddTransient

        • TryAddScoped:对应AddScoped

        • TryAddSingleton:对应AddSingleton

        • TryAddEnumerable:这个和TryAdd的区别是,TryAdd仅根据服务类型来判断是否要进行注册,而TryAddEnumerable则是根据服务类型和实现类型一同进行判断是否要进行注册,常常用于注册同一服务类型的多个不同实现。举个例子吧:

        // 注册了 IMyService - MyService1services.TryAddEnumerable(ServiceDescriptor.Singleton());// 注册了 IMyService - MyService2services.TryAddEnumerable(ServiceDescriptor.Singleton());// 未进行任何操作,因为 IMyService - MyService1 在上面已经注册了services.TryAddEnumerable(ServiceDescriptor.Singleton());

        解析同一服务的多个不同实现

        默认情况下,如果注入了同一个服务的多个不同实现,那么当进行服务解析时,会以最后一个注入的为准。

        如果想要解析出同一服务类型的所有服务实例,那么可以通过IEnumerable<{Service}>来解析(顺序同注册顺序一致):

        public interface IAnimalService { }public class DogService : IAnimalService { }public class PigService : IAnimalService { }public class CatService : IAnimalService { }public void ConfigureServices(IServiceCollection services){    // 生命周期没有限制    services.AddTransient();    services.AddScoped();    services.AddSingleton();}public ValuesController(    // CatService    IAnimalService animalService,       // DogService、PigService、CatService    IEnumerable animalServices){}

        Replace && Remove 扩展方法

        上面我们所提到的,都是注册新的服务到DI容器中,但是有时我们想要替换或是移除某些服务,这时就需要使用ReplaceRemove

        // 将 IMyService 的实现替换为 MyService1services.Replace(ServiceDescriptor.Singleton());// 移除 IMyService 注册的实现 MyServiceservices.Remove(ServiceDescriptor.Singleton());// 移除 IMyService 的所有注册services.RemoveAll();// 清除所有服务注册services.Clear();

        Autofac

        Autofac 是一个老牌DI组件了,接下来我们使用Autofac替换ASP.NET Core自带的DI容器。

        1.安装nuget包:

        Install-Package AutofacInstall-Package Autofac.Extensions.DependencyInjection

        2.替换服务提供器工厂

        public static IHostBuilder CreateHostBuilder(string[] args) =>    Host.CreateDefaultBuilder(args)        .ConfigureWebHostDefaults(webBuilder =>        {            webBuilder.UseStartup();        })        // 通过此处将默认服务提供器工厂替换为 autofac        .UseServiceProviderFactory(new AutofacServiceProviderFactory());

        3.在 Startup 类中添加 ConfigureContainer 方法

        public class Startup{    public Startup(IConfiguration configuration)    {        Configuration = configuration;    }    public IConfiguration Configuration { get; }    public ILifetimeScope AutofacContainer { get; private set; }    public void ConfigureServices(IServiceCollection services)    {        // 1. 不要 build 或返回任何 IServiceProvider,否则会导致 ConfigureContainer 方法不被调用。        // 2. 不要创建 ContainerBuilder,也不要调用 builder.Populate(),AutofacServiceProviderFactory 已经做了这些工作了        // 3. 你仍然可以在此处通过微软默认的方式进行服务注册                services.AddOptions();        services.AddControllers();        services.AddSwaggerGen(c =>        {            c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication.Ex", Version = "v1" });        });    }    // 1. ConfigureContainer 用于使用 Autofac 进行服务注册    // 2. 该方法在 ConfigureServices 之后运行,所以这里的注册会覆盖之前的注册    // 3. 不要 build 容器,不要调用 builder.Populate(),AutofacServiceProviderFactory 已经做了这些工作了    public void ConfigureContainer(ContainerBuilder builder)    {        // 将服务注册划分为模块,进行注册        builder.RegisterModule(new AutofacModule());    }        public class AutofacModule : Autofac.Module    {        protected override void Load(ContainerBuilder builder)        {            // 在此处进行服务注册            builder.RegisterType().As();        }    }    public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)    {        // 通过此方法获取 autofac 的 DI容器        AutofacContainer = app.ApplicationServices.GetAutofacRoot();    }}

        服务解析和注入

        上面我们主要讲了服务的注入方式,接下来看看服务的解析方式。解析方式有两种:

        1.IServiceProvider

        2.ActivatorUtilities

        • 用于创建未在DI容器中注册的服务实例

        • 用于某些框架级别的功能

        构造函数注入

        上面我们举得很多例子都是使用了构造函数注入--通过构造函数接收参数。构造函数注入是非常常见的服务注入方式,也是首选方式,这要求:

        • 构造函数可以接收非依赖注入的参数,但必须提供默认值

        • 当服务通过IServiceProvider解析时,要求构造函数必须是public

        • 当服务通过ActivatorUtilities解析时,要求构造函数必须是public,虽然支持构造函数重载,但必须只能有一个是有效的,即参数能够全部通过依赖注入得到值

        方法注入

        顾名思义,方法注入就是通过方法参数来接收服务实例。

        [HttpGet]public string Get([FromServices]IMyService myService){    return "Ok";}

        属性注入

        ASP.NET Core内置的依赖注入是不支持属性注入的。但是Autofac支持,用法如下:

        老规矩,先定义服务和实现

        public interface IUserService {    string Get();}public class UserService : IUserService{    public string Get()    {        return "User";    }}

        然后注册服务

        • 默认情况下,控制器的构造函数参数由DI容器来管理吗,而控制器实例本身却是由ASP.NET Core框架来管理,所以这样"属性注入"是无法生效的

        • 通过AddControllersAsServices方法,将控制器交给 autofac 容器来处理,这样就可以使"属性注入"生效了

        public void ConfigureServices(IServiceCollection services){    services.AddControllers().AddControllersAsServices();}public void ConfigureContainer(ContainerBuilder builder){    builder.RegisterModule();}public class AutofacModule : Autofac.Module{    protected override void Load(ContainerBuilder builder)    {        builder.RegisterType().As();        var controllerTypes = Assembly.GetExecutingAssembly().GetExportedTypes()            .Where(type => typeof(ControllerBase).IsAssignableFrom(type))            .ToArray();        // 配置所有控制器均支持属性注入        builder.RegisterTypes(controllerTypes).PropertiesAutowired();    }}

        最后,我们在控制器中通过属性来接收服务实例

        public class ValuesController : ControllerBase{    public IUserService UserService { get; set; }    [HttpGet]    public string Get()    {        return UserService.Get();    }}

        通过调用Get接口,我们就可以得到IUserService的实例,从而得到响应

        User

        一些注意事项

        • 避免使用服务定位模式。尽量避免使用GetService来获取服务实例,而应该使用DI。

        using Microsoft.Extensions.DependencyInjection;public class ValuesController : ControllerBase{    private readonly IServiceProvider _serviceProvider;    // 应通过依赖注入的方式获取服务实例    public ValuesController(IServiceProvider serviceProvider)    {        _serviceProvider = serviceProvider;    }    [HttpGet]    public string Get()    {        // 尽量避免通过 GetService 方法获取服务实例        var myService = _serviceProvider.GetService();        return "Ok";    }}
        • 避免在ConfigureServices中调用BuildServiceProvider。因为这会导致创建第二个DI容器的副本,从而导致注册的单例服务出现多个副本。

        public void ConfigureServices(IServiceCollection services){    // 不要在该方法中调用该方法    var serviceProvider = services.BuildServiceProvider();}
        • 一定要注意服务解析范围,不要在 Singleton 中解析 Transient 或 Scoped 服务,这可能导致服务状态错误(如导致服务实例生命周期提升为单例)。允许的方式有:

        1.在 Scoped 或 Transient 服务中解析 Singleton 服务

        2.在 Scoped 或 Transient 服务中解析 Scoped 服务(不能和前面的Scoped服务相同)

        • 当在Development环境中运行、并通过 CreateDefaultBuilder生成主机时,默认的服务提供程序会进行如下检查:

        1.不能在根服务提供程序解析 Scoped 服务,这会导致 Scoped 服务的生命周期提升为 Singleton,因为根容器在应用关闭时才会释放。

        2.不能将 Scoped 服务注入到 Singleton 服务中

        • 随着业务增长,需要依赖注入的服务也越来越多,建议使用扩展方法,封装服务注入,命名为Add{Group_Name},如将所有 AppService 的服务注册封装起来

        namespace Microsoft.Extensions.DependencyInjection{    public static class ApplicationServiceCollectionExtensions    {        public static IServiceCollection AddApplicationService(this IServiceCollection services)        {            services.AddTransient();            services.AddScoped();            services.AddSingleton();            services.AddSingleton(sp => new Service4());            return services;        }    }}

        然后在ConfigureServices中调用即可

        public void ConfigureServices(IServiceCollection services){    services.AddApplicationService();}

        框架默认提供的服务

        以下列出一些常用的框架已经默认注册的服务:

        服务类型生命周期
        Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactoryTransient
        IHostApplicationLifetimeSingleton
        IHostLifetimeSingleton
        IWebHostEnvironmentSingleton
        IHostEnvironmentSingleton
        Microsoft.AspNetCore.Hosting.IStartupSingleton
        Microsoft.AspNetCore.Hosting.IStartupFilterTransient
        Microsoft.AspNetCore.Hosting.Server.IServerSingleton
        Microsoft.AspNetCore.Http.IHttpContextFactoryTransient
        Microsoft.Extensions.Logging.ILoggerSingleton
        Microsoft.Extensions.Logging.ILoggerFactorySingleton
        Microsoft.Extensions.ObjectPool.ObjectPoolProviderSingleton
        Microsoft.Extensions.Options.IConfigureOptionsTransient
        Microsoft.Extensions.Options.IOptionsSingleton
        System.Diagnostics.DiagnosticSourceSingleton
        System.Diagnostics.DiagnosticListenerSingleton

        感谢各位的阅读,以上就是"关于ASP.NET Core依赖注入的详细介绍"的内容了,经过本文的学习后,相信大家对关于ASP.NET Core依赖注入的详细介绍这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

        服务 实例 方法 容器 函数 类型 方式 周期 属性 框架 不同 参数 多个 生命 范围 控制器 日志 程序 控制 作用 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 乐至软件开发工资一般多少钱 sql数据库的递归循环 fivem服务器提供商 uci数据库打不开 网络安全游戏标语 戴尔服务器连接显示器有几种接口 云服务器的5兆是什么意思 西安ios软件开发培训 pdrr网络安全模型包括 版权局 计算机软件开发能力 软件开发项目工时管理系统 几种常用的应用服务器教案 北大的图书馆数据库能下载书吗 尼日利亚和美国合作网络安全 网络安全观和网络强国思想 运维单位的网络安全规定 网络安全教育微视频剧本 淘宝助理未正确连接到服务器 斯威变频器服务器怎么查故障 软件开发接项目赚钱吗 黑龙江整机销售软件开发公司 coc兵种升级数据库 蒂森尚途默纳克服务器 青少年的网络安全的重要性 网络安全性类型802.x 郑州人工智能软件开发服务 嵌入式软件开发即征即退 近十年创业指数数据库 软件开发投融资创业计划书 太一互联网科技
        0