.NET8 专题(三)
学习目标
- 服务注册与消费
- 依赖注入的多种方式
- Autofac的基本用法
- 控制器属性注入与模块注册
- 依赖注入扩展Scrutor
服务注册与消费
-
生命周期,决定了依赖注入系统采用什么样的方式,提供和释放服务实例
-
服务容器:可通过CreateScope,创建作用域(容器)
- 根容器,创建子容器,
- 对象引用层面,子容器不知道自己的父容器是谁,他们只知道根容器
- 所有的子容器都是平级的
- 作用域确定了容器的边界,.NET Core 每个http请求,都会创建一个作用域
- 根容器:应用容器,只有一个
- 子容器:请求容器,根容器为每一个请求创建一个子容器
-
服务集合的类型:ServiceCollection
- 服务集合中存放的是注册服务的类型信息,服务描述类 把类型信息,实现类型等等保存起来,在创建实例的时候,是根据类型信息注册的
- 服务注册的本质就是创建服务描述类 ServiceDescriptor,再把这些类加进服务描述类集合中
- 创建实例,通过构造函数创建,构造时,前提条件是,创建这个类所需的各种实例,已经存在于服务容器中了
- 那么如果一个类有三个有参构造函数,并且有两个参数是已经注入进服务容器中的,那么会怎么创建实例呢?
- 首先,构造的前提是,所需实例参数,已经存在于服务容器中了,所以三参数的构造,一定不会走进
- 如果某个构造函数的参数类型的集合 能够成为 所有合法构造函数参数类型的 超集,这个构造函数就会被依赖注入系统所选择,所以两个参数的构造将被选择,创建出该实例
- 如果多个构造参数个数相同,并且参数类型不同,并且参数类型已经存在于服务容器中,依赖注入会选择哪个呢?不可以这样,因为没有一个构造函数的参数类型是所有构造函数的超集,会抛出异常
-
依赖注入方法
- 构造函数注入:原生只支持构造函数注入
- 属性注入:Blazor支持属性注入
- 方法注入:带有控制器,支持方法注入
- 特性注入:Blazor支持特性注入
-
依赖注入与服务定位
-
注入类型(服务),构造函数注入
-
将容器注入,就是服务定位,服务定位器
-
两种模式同样都有一个通过服务注册,创建的一个全局的容器,用来提供我们所需的服务实例
-
差异体现
- 服务容器或服务定位器被谁使用的角度:依赖注入系统中,只需要采用标准的注入定义服务类型就可以了,在应用启动之前完成所需实例的注册,服务容器的使用者是框架;服务定位器,是我们开发者在用,我们自己通过代码的方式去拿服务实例,不是控制反转了
- 依赖注入系统,服务实例是被推过来的,服务定位器,服务实例是我们拉过来的
- 服务之间的依赖关系应该是明确的,而不是模糊的
- 服务定位器这种方式,并没有更好的利用框架的特点,与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()); } }
-
第三方的依赖注入框架
-
Autofac:高级生命周期管理,AOP动态代理,模块化配置,条件绑定
-
内置的依赖注入,需要sdk.web,Autofac可适用于任何类型.NET中使用
-
中小型web应用,内置依赖注入基本够用的,有一些特殊情况,内置依赖注入不够用时,才需要Autofac
-
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); } }
-
-
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); } }
-
-
.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();
-
-
默认情况下,.NET Core 不会通过容器创建控制器实例,但是会通过容器获取控制器构造函数所需要的实例,控制器实例不在容器中,但创建控制器实例所需要的实例会通过IOC容器来获得,这就是为什么控制器默认情况下,只支持构造函数的注入,而不支持属性注入
-
想要实现属性注入,需要改变控制器的默认行为,将控制器以服务的形式,添加进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();
-