.NET8 学习笔记(三)

378 阅读5分钟

.NET8 专题(三)

学习目标

  1. 服务注册与消费
  2. 依赖注入的多种方式
  3. Autofac的基本用法
  4. 控制器属性注入与模块注册
  5. 依赖注入扩展Scrutor

服务注册与消费

  1. 生命周期,决定了依赖注入系统采用什么样的方式,提供和释放服务实例

  2. 服务容器:可通过CreateScope,创建作用域(容器)

    • 根容器,创建子容器,
    • 对象引用层面,子容器不知道自己的父容器是谁,他们只知道根容器
    • 所有的子容器都是平级的
    • 作用域确定了容器的边界,.NET Core 每个http请求,都会创建一个作用域
    • image-20240703201921701.png
    • 根容器:应用容器,只有一个
    • 子容器:请求容器,根容器为每一个请求创建一个子容器
  3. 服务集合的类型:ServiceCollection

    • 服务集合中存放的是注册服务的类型信息,服务描述类 把类型信息,实现类型等等保存起来,在创建实例的时候,是根据类型信息注册的
    • 服务注册的本质就是创建服务描述类 ServiceDescriptor,再把这些类加进服务描述类集合中
    • 创建实例,通过构造函数创建,构造时,前提条件是,创建这个类所需的各种实例,已经存在于服务容器中了
    • 那么如果一个类有三个有参构造函数,并且有两个参数是已经注入进服务容器中的,那么会怎么创建实例呢?
      • 首先,构造的前提是,所需实例参数,已经存在于服务容器中了,所以三参数的构造,一定不会走进
      • 如果某个构造函数的参数类型的集合 能够成为 所有合法构造函数参数类型的 超集,这个构造函数就会被依赖注入系统所选择,所以两个参数的构造将被选择,创建出该实例
      • 如果多个构造参数个数相同,并且参数类型不同,并且参数类型已经存在于服务容器中,依赖注入会选择哪个呢?不可以这样,因为没有一个构造函数的参数类型是所有构造函数的超集,会抛出异常
  4. 依赖注入方法

    • 构造函数注入:原生只支持构造函数注入
    • 属性注入:Blazor支持属性注入
    • 方法注入:带有控制器,支持方法注入
    • 特性注入:Blazor支持特性注入
  5. 依赖注入与服务定位

    • 注入类型(服务),构造函数注入

    • 将容器注入,就是服务定位,服务定位器

    • 两种模式同样都有一个通过服务注册,创建的一个全局的容器,用来提供我们所需的服务实例

    • 差异体现

      • 服务容器或服务定位器被谁使用的角度:依赖注入系统中,只需要采用标准的注入定义服务类型就可以了,在应用启动之前完成所需实例的注册,服务容器的使用者是框架;服务定位器,是我们开发者在用,我们自己通过代码的方式去拿服务实例,不是控制反转了
      • 依赖注入系统,服务实例是被推过来的,服务定位器,服务实例是我们拉过来的
      • 服务之间的依赖关系应该是明确的,而不是模糊的
      • 服务定位器这种方式,并没有更好的利用框架的特点,与new对象没什么区别
      • 一个服务依赖另一个服务,基于类型的依,这种依赖是明确的
      • 服务定位器,是一个黑盒子,想要的服务好像都有,但是在拿服务的时候,我们不知道这个实例是否被创建过了,依赖注入被推迟了,绕过了构造函数的限制
    • public interface IMessageService { }
      
      public class EmailMessageService(IAccount account, ITool tool) : IMessageService
      {
          public IAccount Account { get; set; } = account;
          public ITool Tool { get; set; } = tool;
      
          public void Test()
          {
              Console.WriteLine(Account.GetHashCode());
              Console.WriteLine(Tool.GetHashCode());
          }
      }
      
      public class SmsMessageService(IServiceProvider serviceProvider) : IMessageService
      {
          public IServiceProvider ServiceProvider { get; set; } = serviceProvider;
      
          public void Test()
          {
              Console.WriteLine(ServiceProvider.GetRequiredService<IAccount>().GetHashCode());
              Console.WriteLine(ServiceProvider.GetRequiredService<ITool>().GetHashCode());
          }
      }
      

第三方的依赖注入框架

  1. Autofac:高级生命周期管理,AOP动态代理,模块化配置,条件绑定

  2. 内置的依赖注入,需要sdk.web,Autofac可适用于任何类型.NET中使用

  3. 中小型web应用,内置依赖注入基本够用的,有一些特殊情况,内置依赖注入不够用时,才需要Autofac

  4. Autofac的基本使用

    • 安装Autofac

    • public interface IAccount;
      
      public interface IMessage;
      
      public interface ITool;
      
      public class Account: IAccount{}
      
      public class Message: IMessage{}
      
      public class Tool: ITool{}
      
      public class Program
      {
          private static IContainer? Container { get; set; }
      
          static void Main(string[] args)
          {
              var builder = new ContainerBuilder();
      
              builder.RegisterType<Account>().As<IAccount>();
              builder.RegisterType<Message>().As<IMessage>();
              builder.RegisterType<Tool>().As<ITool>();
      
              Container = builder.Build();
              
              Debug.Assert(Container.Resolve<IAccount>() is Account);
          }
      }
      
  5. Autofac与内置依赖注入整合使用

    • 除安装Autofac外,还需要Autofac.Extension.DependencyInjection

    • public interface IAccount{ }
      public interface IMessage{ }
      public interface ITool{ }
      
      public interface ITest
      {
          public IMessage Message { get; set; }
      }
      
      public class Base
      {
          public Base()
          {
              Console.WriteLine($"Created:{GetType().Name}");
          }
      
      }
      
      public class Account:Base, IAccount{}
      public class Message:Base, IMessage{}
      
      public class Tool : Base, ITool { }
      
      public class Test: ITest
      {
          public IMessage Message { get; set; }
      
          public Test(IAccount account, ITool tool, IMessage message)
          {
              Message = message;
              Console.WriteLine($"Ctor:Test(IAccount, ITool, IMessage)");
          }
      }
      
      public class Program
      {
          static void Main(string[] args)
          {
              var serviceCollection = new ServiceCollection()
                  .AddTransient<ITool, Tool>();
      
              var containerBuilder = new ContainerBuilder();
              
      		//populate 把内置的依赖注入的信息,整合进Autofac的container中
              containerBuilder.Populate(serviceCollection);
              
              //注册test服务,并启用了属性注入的功能
              containerBuilder.RegisterType<Test>().As<ITest>().PropertiesAutowired();
              //注册当前程序集中,所有基类型为Base类型的,并且这些类型都将被注册为他们实现的第一个接口,并且注册为作用域生命周期的实例
              containerBuilder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
                  .Where(t => t.BaseType == typeof(Base))
                  .As(t => t.GetInterfaces()[0])
                  .InstancePerLifetimeScope();
               
               //Populate后整合的话,如果注册了两个相同的类型,内置依赖注入会覆盖掉Autofac注册的服务
               containerBuilder.Populate(serviceCollection);
      
              
      
              var container = containerBuilder.Build();
              var provider = new AutofacServiceProvider(container);
      
              var test = provider.GetRequiredService<ITest>();
              Debug.Assert(test.Message is Message);
          }
      }
      
  6. .NET Web应用使用Autofac

    • var builder = WebApplication.CreateBuilder(args);
      
      builder.Host
      	//把创建ServiceProvider的工厂替换为Autofac的ServiceProvider工厂,只要实现了IServiceProviderFactory接口,就可以替换,这样创建出来的ServiceProvider就是Autofac的container了
          .UseServiceProviderFactory(new AutofacServiceProviderFactory())
          .ConfigureContainer<ContainerBuilder>(container =>
          {
              container.RegisterModule<ControllerModule>();
              container.RegisterModule(new ServicesModule("Services"));
              
              //获取当前程序集,过滤,只注册当前程序集下,文件名带有Services的命名空间下的所有类型,之后把他们注册为他们实现的接口,之后统一设置生命周期为作用域类型
              //var assembly = Assembly.GetExecutingAssembly();
              //builder.RegisterAssemblyTypes(assembly)
              //    .Where(t => t.Namespace == $"{assembly.GetName().Name}.{name}")
              //    .AsImplementedInterfaces()
              //    .InstancePerLifetimeScope();
              
              //var containerBaseType = typeof(ControllerBase);
              //builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
              //    .Where(t => containerBaseType.IsAssignableFrom(t) && t != containerBaseType)
              //    .PropertiesAutowired();
          });
      
      // Add services to the container.
      // builder.Services.AddTransient<IMessage, Message>();
      builder.Services.AddControllers().AddControllersAsServices();
      // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
      builder.Services.AddEndpointsApiExplorer();
      builder.Services.AddSwaggerGen();
      
      var app = builder.Build();
      
      // Configure the HTTP request pipeline.
      if (app.Environment.IsDevelopment())
      {
          app.UseSwagger();
          app.UseSwaggerUI();
      }
      
      app.UseAuthorization();
      
      app.MapControllers();
      
      app.Run();
      
      
  7. 默认情况下,.NET Core 不会通过容器创建控制器实例,但是会通过容器获取控制器构造函数所需要的实例,控制器实例不在容器中,但创建控制器实例所需要的实例会通过IOC容器来获得,这就是为什么控制器默认情况下,只支持构造函数的注入,而不支持属性注入

  8. 想要实现属性注入,需要改变控制器的默认行为,将控制器以服务的形式,添加进IOC容器中

    • builder.Services.AddControllers().AddControllersAsServices();

    • 改变创建控制器实例的行为,让ASP.NET Core能够从IOC容器中获取控制器实例

    • 让.NET Core 能够发现控制器实例的类型,作为服务,注册进服务集合中

    • container =>
          {
              container.RegisterModule<ControllerModule>();
              container.RegisterModule(new ServicesModule("Services"));
              
              //获取当前程序集,过滤,只注册当前程序集下,文件名带有Services的命名空间下的所有类型,之后把他们注册为他们实现的接口,之后统一设置生命周期为作用域类型
              //var assembly = Assembly.GetExecutingAssembly();
              //builder.RegisterAssemblyTypes(assembly)
              //    .Where(t => t.Namespace == $"{assembly.GetName().Name}.{name}")
              //    .AsImplementedInterfaces()
              //    .InstancePerLifetimeScope();
              //container.RegisterType<MessageController>().PropertiesAutowired();
              
              //var containerBaseType = typeof(ControllerBase);
      
              //builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
              //    .Where(t => containerBaseType.IsAssignableFrom(t) && t != containerBaseType)
              //    .PropertiesAutowired();
          });
      
    • 这个委托虽然写在前面,但是 是在这句话之后执行的,因为Populate方法是先覆盖的,内置的容器默认没有开启属性注入,因此,需要在Autofac中再注册一遍,开启属性注入

    • builder.Services.AddControllers().AddControllersAsServices();