.NET 依赖注入DI 注册服务

一、概念

1、控制反转:要什么给我就行了   
2、依赖注入简化模块的组装过程,降低模块之间的耦合度 
3、怎么创建xx对象 -> 我要xx对象
4、说白了就是定义了一套接口,我们写一个类去继承这个接口,那么这个类就需要实现这个接口的方法(功能和属性),这就是依赖注入。
5、注入体现的是一个IOC(控制反转的的思想)。在反转之前 ,我们先看看正转。
AccountController自己来实例化需要的依

控制反转

1、谁控制谁?

IoC/DI容器控制应用主程序。

2、控制什么?

IoC/DI容器控制对象本身的创建、实例化;控制对象之间的依赖关系。

3、何谓反转(对应于正向)?

因为现在应用程序不能主动去创建对象了,而是被动等待对象容器给它注入它所需要的资源,所以称之为反转。

4、哪些方面反转了?

    1.创建对象

    2.程序获取资源的方式反了

5、为何需要反转?

    1.引入IoC/DI容器过后,体系更为松散,而且管理和维护以及项目升级更有序;

    2.类之间真正实现了解耦

依赖

1、什么是依赖(按名称理解、按动词理解)?

依赖(按名称理解):依赖关系;

依赖(按动词理解):依赖的动作

2、谁依赖于谁?

应用程序依赖于IoC/DI容器

3、为什么需要依赖?

因为发生了反转,应用程序依赖的资源都是IoC/DI容器里面

4、依赖什么东西?

应用程序依赖于IoC/DI容器为它注入所需要的资源。(比如:依赖关系)

注入

1、谁注入于谁?

IoC/DI容器注入于应用程序。

2、注入什么东西?

注入应用程序需要的对象,比如依赖关系。

3、为何要注入?

因为程序要正常运行需要访问这些对象

IOC(控制反转Inversion of Control)

控制反转(Inversion of Control)就是使用对象容器反过来控制应用程序所需要的外部资源,这样的一种程序开发思想,调用者不再创建被调用者的实例,由IOC框架实现(容器创建)所以称为控制反转;

创建对象和对象非托管资源的释放都由外部容器去完成,实现项目层与层之间的解耦的一种设计思想。

DI(依赖注入)和DIP(依赖倒置原则)

相信很多人还分不清楚DI和DIP这两个词,甚至认为它们就是同一个词。

1、依赖倒置原则(Dependency Inversion Principle)为我们提供了降低模块间耦合度的一种思路,而依赖注入(Dependency Injection)是一种具体的实施方法

容器创建好实例后再注入调用者称为依赖注入,就是应用程序依赖IOC容器来注入所需要的外部资源,这样一种程序的开发思想。

2、能做什么(What)?

松散耦合对象,解耦项目架构层。

3、怎么做(How)?

使用Autofac/Unity/Spring等框架类库,里面有实现好了的IoC/DI容器。

4、用在什么地方(Where)?

凡是程序里面需要使用外部资源的情况,比如创建对象,都可以考虑使用IoC/DI容器。

DI和IOC是同一概念吗?

肯定不是同一概念啊,但它们两个描述的是同一件事件,从不同的角度来说:IOC是从对象容器的角度;DI是从应用程序的角度。

IOC是一种设计思想,而DI是这种设计思想的一个实现。

1、控制反转的描述:对象容器反过来控制应用程序,控制应用程序锁所需要的一些对象,比如DbContext。

2、依赖注入的描述:应用程序依赖对象容器,依赖它注入所需要的外部资源。

对IoC的理解:

1、应用程序无需主动new对象,而是描述对象应该如何被创建(构造方法、属性、方法参数等)。

2、应用程序不需要主动装配对象之间的依赖关系,而是描述需要哪个服务,IoC容器会帮你装配,被动接受装配。

3、主动变被动,是一种让服务消费者不直接依赖于服务提供者的组件设计方式,是一种减少类与类之间依赖的设计原则。

白话解释:

我们在 ASP.NET Core 中,想使用某个类型的时候可以不用自己去 new,可以由容器通过构造方法来注入具体的实现类型,而我们一般在构造方法上定义的依赖类型都是接口,而不是去依赖具体的实现,这里就体现了 SOLID 原则中的依赖倒置原则(DIP)。这也是IOC(Inversion of Control),即控制反转,不直接依赖具体实现,将依赖交给容器去控制。上述几者是具有一定的关联关系的,DIP 是一种软件设计原则,IOC 是 DIP 的具体实现方式,DI 是 IOC 的一种实现方式。



为啥使用依赖注入

private ILoginService<ApplicationUser> _loginService;
public AccountController()
{
    //new
  _loginService = new EFLoginService()
}
大师说,这样不好。你不应该自己创建它,而是应该由你的调用者给你。于是你通过构造函数让外界把这两个依传给你。
public AccountController(ILoginService<ApplicationUser> loginService)
{
  _loginService = loginService;
}
把依赖的创建丢给其它人,自己只负责使用,其它人丢给你依赖的这个过程理解为注入。


服务(service):对象;(数据库对象)
注册服务;
服务容器:负责管理注册的服务;
查询服务:创建对象及关联对象;

对象生命周期: IServiceScope

  1. AddTransient(瞬态):每次调用都创建一个新的对象,即使同一个请求获取多次也会是不同的实例
  2. AddScoped(范围); 一个范围(using)内拿到同一个对象   类有状态且有Scope控制,每次请求,都获取一个新的实例。同一个请求获取多次会得到相同的实例
  3. AddSingleton(单例) : 同一个对象      类无状态(没有属性,没有成员变量)  第一个请求上创建一个新实例,并且在应用程序的剩余生命周期中,将相同的实例提供给所有使用者类。
推荐做法:
  1. 范围服务通常应由单个Web请求/线程使用。因此,不应该在线程之间共享服务范围。
  2. 配置为单例的服务可能会导致应用程序中的内存泄漏。
  3. 内存泄漏通常是由单例服务引起的。这是因为创建的实例不会被丢弃,它将保留在内存中直到应用程序结束。因此,一旦不使用它们,最好将它们释放。
  4. 将服务注册为临时服务会缩短其使用寿命,通常可能不太在乎多线程和内存泄漏。
  5. 不要在单例服务中依赖瞬态或作用域服务。因为瞬时服务在单例服务注入时成为一个单例实例,并且如果瞬态服务不旨在支持这种情况,则可能导致问题。在这种情况下,ASP.NET Core的默认DI容器已经引发异常。


二、使用DI

根据类型来获取和注册服务
服务类型     实现类型
可以是类 也可以是接口(建议面向接口编程,更灵活)  

.NET控制反转组件取名为   Dependencylnjection
1、安装  lnstall-Package     Microsoft.Extensions.DependencyInjection
2、using    Microsoft.Extensions.DependencyInjection
3、调用 ServiceCollection 的BuildServiceProvider()  创建    IServiceProvider
           //注册
            ServiceCollection services = new ServiceCollection();
            //services.AddTransient<TestServiceImp1>();//瞬态
            //services.AddSingleton<TestServiceImp1>();//单例
            services.AddScoped<TestServiceImp1>();//范围


            //建立  ServiceProvider 服务定位器
            using (ServiceProvider sp = services.BuildServiceProvider())
            {
                ////使用  要个一个对象
                //TestServiceImp1 t = sp.GetService<TestServiceImp1>();
                //t.Name = "lily";
                //t.say();

                //TestServiceImp1 t1 = sp.GetService<TestServiceImp1>();
                //Console.WriteLine(object.ReferenceEquals(t, t1));
                //t1.Name = "tom";
                //t1.say();

                //t.say();//1、瞬态  lily   新的对象  2、单例  tom  同一对象

                TestServiceImp1 tt1;

                //using 定义了Scoped 范围
                using (IServiceScope scope1 = sp.CreateScope())
                {
                    //在scope中获取Scope相关的对象,scope1.ServiceProvider而不是sp
                    TestServiceImp1 t = scope1.ServiceProvider.GetService<TestServiceImp1>();
                    t.Name = "lily";
                    t.say();

                    TestServiceImp1 t1 = scope1.ServiceProvider.GetService<TestServiceImp1>();
                    Console.WriteLine(object.ReferenceEquals(t, t1));//ture
                    tt1 = t;
                }

                using (IServiceScope scope2 = sp.CreateScope())
                {
                    //在scope中获取Scope相关的对象,scope1.ServiceProvider而不是sp
                    TestServiceImp1 t = scope2.ServiceProvider.GetService<TestServiceImp1>();
                    t.Name = "lily";
                    t.say();

                    TestServiceImp1 t1 = scope2.ServiceProvider.GetService<TestServiceImp1>();
                    Console.WriteLine(object.ReferenceEquals(t, t1));//ture

                    Console.WriteLine(object.ReferenceEquals(tt1, t1));//flase 一个范围(using)内拿到同一个对象
                }

            }



            //ITestService t = new TestServiceImp1();
            //t.Name = "xw";
            //t.say();
            Console.Read();
 ServiceCollection services = new ServiceCollection();
            //ITestService 服务类型(接口) ,TestServiceImp1 实现类型
            services.AddScoped<ITestService, TestServiceImp1>();
            //services.AddScoped(typeof(ITestService), typeof(TestServiceImp1));
            using (ServiceProvider sp = services.BuildServiceProvider())
            {
                //获得服务  注册什么类型拿什么类型
                //GetService 如果找不到服务,就返回null
                //ITestService ts1 = sp.GetService<ITestService>();
                //ITestService ts1= (ITestService)sp.GetService(typeof(ITestService));//非泛型方法
                //ts1.Name = "tom";
                //ts1.say();
                //Console.WriteLine(ts1.GetType());

                // GetServices  s获取全部服务
                var tests = sp.GetServices<ITestService>();
                foreach (var t in tests)
                {
                    Console.WriteLine(t.GetType());
                }
            }
            Console.ReadLine();

.NET 的DI 默认是 构造函数 注入。

补:AddTransient<>  <>的含义
 services.AddTransient<MQ.ISOService, MQ.SOService>();//这个<>其实就是,当代码读取到MQ.ISOService的接口的时候,会返回MQ.SOService这个对象。
 
自己理解:
我们想在A类中使用B类,并实例化,就必须new B,这样就会产生问题 
  1. 如果要修改B 或者替换B 就必须修改A 
  2. .。。。
所以使用依赖注入
在Program.cs、IServiceProvider  
先接口  
public interface IMyDependency
{
    void WriteMessage(string message);
}
再实现  
public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}
再去注册 
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

//注册
builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();
最后去调用使用
public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

如果这时候 需要更改B 
public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}
再注入 ,!!将原来MyDependency的替换MyDependency2   就可以了  不用对A 有影响
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency2>();

var app = builder.Build();
好处:
  • 不使用具体类型 MyDependency,仅使用它实现的 IMyDependency 接口。 这样可以轻松地更改实现,而无需修改控制器或 Razor 页面。
  • 不创建 MyDependency 的实例,这由 DI 容器创建。

后期补:

1、MVC 控制器中可以 
FromServicesAttribute 允许将服务直接注入到操作方法,而无需使用构造函数注入
        public IActionResult About([FromServices] IDateTime dateTime)
        {
            return Content($"Current server time: {dateTime.Now}");
        }
2、配置文件注入
创建表示选项的类
public class SampleWebSettings
{
    public string Title { get; set; }
    public int Updates { get; set; }
}
将配置类添加到服务集合中:
public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IDateTime, SystemDateTime>();
    services.Configure<SampleWebSettings>(Configuration);

    services.AddControllersWithViews();
}
将应用配置为从 JSON 格式化的文件读取设置:  需要创建samplewebsettings.json
public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddJsonFile("samplewebsettings.json",
                    optional: false,
                    reloadOnChange: true);
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

{
  "Title": "Title11",
  "Updates": 11
}

以下代码从服务容器请求 IOptions<SampleWebSettings> 设置,并通过 Index 方法使用它们:

public class SettingsController : Controller
{
    private readonly SampleWebSettings _settings;

    public SettingsController(IOptions<SampleWebSettings> settingsOptions)
    {
        _settings = settingsOptions.Value;
    }

    public IActionResult Index()
    {
        ViewData["Title"] = _settings.Title;
        ViewData["Updates"] = _settings.Updates;
        return View();
    }
}
<h1>@ViewData["Title"] --Title</h1>

<h1>@ViewData["Updates"] --Updates</h1>

三、原理和源码分析

        // This method gets called by the runtime. Use this method to add services to the container.
        // 配置服务
        // 以依赖注入的方式将服务添加到服务(IOC)容器
        public void ConfigureServices(IServiceCollection services)
        {
            // 主机(IOC容器主机里)构造器里面,他就已经把容器创建好了
            // 整个应用都在主机里,整个应用都是可以用这个容器的,获取已注册的任何类型的实例
            // 正是因为我们用到了依赖注入的手段,所有才产生控制反转这个结果
            // 工厂模式,你可以把IOC容器看做是工厂模式的生华
            // 类型的注册、实例(生命周期)的解析
            // IOC 容器本身也是一个对象,这个对象里面存储的都是 已注册类型
            // ASP.NET CORE 依赖注入是最基础,都得注入进来,你才可以用
            // 实现单例模式,管理单例对象
            // 主机创建完以后,容器里就已经为我们默认注册了一些服务(主机环境变量,整个应用配置)
            //ASP.NET CORE 内置的服务组件
            //添加对控制器和API相关功能的支持,但是不支持视图和页面(WEB API 的模板)
            services.AddControllers();
            //mvc 默认模板
            services.AddControllersWithViews();
            // 这个mvc他是3.0之前的版本,2.x版本使用的
            services.AddMvc();
            //WEB  应用程序(Razor)
            services.AddRazorPages();
            //配置跨域
            //services.AddCors(options => options.AddPolicy());
            //第三方的服务,支持.net core 提供一个服务扩展方法
            //服务都是生命周期
            //请求一个实例(是有生命周期,生存期)
            //内置 和第三方 的都会把生存期设置好,不需要设置
            // 自定义服务类
            //第一种
            //services.AddTransient<IMessageService, EmailService>();
            //services.AddTransient<IMessageService, SmsService>();
            
            //第二种 封装服务注册的方法(服务构建器) 可配置选择
            services.AddMessage(options=>options.UseEmail());
            //内置的不支持属性注入,就要第三方的IOC
        }

扩展方法-服务构建器  (动态,可配置切换服务)
ConfigureServices中
            // 自定义服务类
            //第一种
            //services.AddTransient<IMessageService, EmailService>();
            //services.AddTransient<IMessageService, SmsService>();
            
            //第二种 封装服务注册的方法(服务构建器) 可配置选择
            services.AddMessage(options=>options.UseEmail());
    //扩展类 扩展Startup里面 ConfigureServices 的 IServiceCollection
    public static class MessageServericeExtensions
    {
        public static void AddMessage(this IServiceCollection services)
        {
            services.AddSingleton<IMessageService, EmailService>();
        }

        public static void AddMessage(this IServiceCollection services,Action<MessageServiceBuider> options)
        {
            //创建构建器对象
            var builder =new MessageServiceBuider(services);
            // 调用委托,对象传过来
            options(builder);
        }
    }

    //消息服务构建器
    //配置类 
    public class MessageServiceBuider
    {
        public IServiceCollection _services { get; set; }

        public MessageServiceBuider(IServiceCollection services)
        {
            this._services = services;
        }

        public void UseEmail()
        {
            _services.AddSingleton<IMessageService, EmailService>();
        }

        public void UseSms()
        {
            _services.AddSingleton<IMessageService, SmsService>();
        }
    }

IServiceCollection注册的本质就是在构建ServiceDescriptor集合

四、如何批量注册服务  待学习


五、生命法则




posted @ 2023-08-28 10:46  不争丶  阅读(78)  评论(0编辑  收藏  举报