02020212 .NET Core重难点知识12-服务定位器、.NET依赖注入示例
1. 服务定位器(视频Part2-27)
1.1 IServiceProvider的服务定位器方法
T GetService<T>() → 如果获取不到对象,则返回null。
object GetService(Type serviceType)
T GetRequiredService<T>() → 如果获取不到对象,则抛异常。
object GetRequiredService(Type serviceType)
IEnumerable<T> GetServices<T>() → 适用于可能有很多满足条件的服务。
IEnumerable<object> GetService(Type serviceType)
1.2 其它注册方法
- 服务类型和实现类型不一致的注册。
- 简单看看其它Add方法。
1.3 GetService()注册方法
using Microsoft.Extensions.DependencyInjection;
using System;namespace Demo02
{public interface ITestService{public string Name { get; set; }public void SayHi();}public class TestServiceImp1 : ITestService, IDisposable{public string Name { get; set; }public void Dispose(){Console.WriteLine("Dispose...");}public void SayHi(){Console.WriteLine($"Hi! I'm {Name}");}}class Program{static void Main(string[] args){ServiceCollection services = new ServiceCollection();services.AddScoped<ITestService, TestServiceImp1>(); // @1 // services.AddScoped<typeof(ITestService), typeof(TestServiceImp1)>(); // @2// services.AddSingleton<ITestService, TestServiceImp1>(); // @3// services.AddSingleton<typeof(ITestService), new TestServiceImp1()>; // @4 TestServiceImp1构造函数可以添加参数using (ServiceProvider sp = services.BuildServiceProvider()){ITestService ts1 = sp.GetService<ITestService>(); // @5 返回的是实现类的对象// TestServiceImp1 ts1 = sp.GetService<TestServiceImp1>(); // @6 返回的是实现类的对象ts1.Name = "Qinway";ts1.SayHi();Console.WriteLine(ts1);}Console.ReadLine();}}
}控制台输出:
Hi! I'm Qinway
Demo02.TestServiceImp1
Dispose...说明:
1. 在@1处两个泛型:ITestService表示服务类型(接口),实现类型TestServiceImp1(实现类)。此时不管实现类是什么,我只要接口的服务。
2. 随着项目变得复杂,写注册服务的人(ITestService)和写实现服务的人(TestServiceImp1),可能不是同一个人来做的。
3. 在@1处和@2处两种写法等价。
4. 在@4处,直接将实现类new好了,这样适用于对象需要传递某个参数的时候使用。可以在构造函数添加参数,由开发者自己控制创建对象的条件。
5. 如果采用@4处的注册形式,那么不用用@5处的获取形式,而是要采用@6处的获取形式。注册类型是实现类,那么拿的时候也是实现类。
6. 在@5和@6处,如果GetService找不到服务,就返回null。
7. GetService方法还有非泛型的方法重载,一般不用。
1.3 GetRequiredService()注册方法
using Microsoft.Extensions.DependencyInjection;
using System;namespace Demo02
{public interface ITestService{public string Name { get; set; }public void SayHi();}public class TestServiceImp1 : ITestService, IDisposable{public string Name { get; set; }public void Dispose(){Console.WriteLine("Dispose...");}public void SayHi(){Console.WriteLine($"Hi! I'm {Name}");}}class Program{static void Main(string[] args){ServiceCollection services = new ServiceCollection();services.AddScoped<ITestService, TestServiceImp1>();using (ServiceProvider sp = services.BuildServiceProvider()){TestServiceImp1 ts1 = sp.GetRequiredService<TestServiceImp1>(); // 抛出异常ts1.Name = "Qinway";ts1.SayHi();Console.WriteLine(ts1);}Console.ReadLine();}}
}异常信息:System.InvalidOperationException:“No service for type 'Demo02.TestServiceImp1' has been registered.”说明:GetRequiredService方法不会返回null,这个方法本身就会抛出异常。对比:GetService方法如果找不到,返回null;GetRequiredService如果找不到,抛出异常。这里和C#基础知识显式类型转换(如果不能转换抛异常)和as转换(如果不能转换返回null)类似。
1.4 GetServices()注册方法
- 该方法适用于实现一个接口有多个服务的情况,如果实现一个接口有多个服务,那么将服务都返回。
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;namespace Demo02
{public interface ITestService{public string Name { get; set; }public void SayHi();}public class TestServiceImp1 : ITestService, IDisposable{public string Name { get; set; }public void Dispose(){Console.WriteLine("Dispose...");}public void SayHi(){Console.WriteLine($"Hi! I'm {Name}");}}public class TestServiceImp2 : ITestService{public string Name { get; set; }public void SayHi(){Console.WriteLine($"你好,我是{Name}");}}class Program{static void Main(string[] args){ServiceCollection services = new ServiceCollection();services.AddScoped<ITestService, TestServiceImp1>(); // 注册第一个services.AddScoped<ITestService, TestServiceImp2>(); // 注册第二个using (ServiceProvider sp = services.BuildServiceProvider()){IEnumerable<ITestService> ts1 = sp.GetServices<ITestService>();foreach (ITestService it in ts1){Console.WriteLine(it);}}Console.ReadLine();}}
}控制台输出:
Demo02.TestServiceImp1
Demo02.TestServiceImp2
Dispose...
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;namespace Demo02
{public interface ITestService{public string Name { get; set; }public void SayHi();}public class TestServiceImp1 : ITestService, IDisposable{public string Name { get; set; }public void Dispose(){Console.WriteLine("Dispose...");}public void SayHi(){Console.WriteLine($"Hi! I'm {Name}");}}public class TestServiceImp2 : ITestService{public string Name { get; set; }public void SayHi(){Console.WriteLine($"你好,我是{Name}");}}class Program{static void Main(string[] args){ServiceCollection services = new ServiceCollection();services.AddScoped<ITestService, TestServiceImp1>(); // 注册第一个services.AddScoped<ITestService, TestServiceImp2>(); // 注册第二个using (ServiceProvider sp = services.BuildServiceProvider()){var t1 = sp.GetService<ITestService>(); // 如果注册了多个,用Service方法那么获取最后一个。var t2 = sp.GetRequiredService<ITestService>(); // 如果注册了多个,用GetRequiredService方法那么获取的也是最后一个。Console.WriteLine(t1);Console.WriteLine(t2);}Console.ReadLine();}}
}控制台输出:
Demo02.TestServiceImp2
Demo02.TestServiceImp2
2. .NET依赖注入(视频Part-2-28)
- 在这节课之前,我们讲的GetService等方法都是服务定位器的用法。从这节课开始,是真正的依赖注入。
2.1 依赖注入魅力渐显
- 依赖注入是有传染性的,如果一个类的对象是通过DI创建的,那么这个类的构造函数中声明的所有服务类型的参数都会被DI赋值。
- 但是如果一个对象是程序员手动创建的,那么这个对象就和DI没有关系,它的构造函数声明的服务类型参数就不会被自动赋值。
- .NET的DI默认是构造函数注入。
- 例如:编写一个类,连接数据库做插入操作,并且记录日志(模拟的输出),把Dao、日志都放入单独的服务类。
- connstr见备注。
2.2 依赖注入示例
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;namespace Demo02
{class Controller{private readonly ILog log;private readonly IStorage storage;public Controller(ILog log, IStorage storage){this.log = log;this.storage = storage;}public void Test(){this.log.Log("开始上传");this.storage.Save("ABC", "1.txt");this.log.Log("上传完毕");}}interface ILog{public void Log(string msg);}class LogImpl : ILog{public void Log(string msg){Console.WriteLine($"日志:{msg}");}}interface IConfig{public string GetValue(string name);}class ConfigImpl : IConfig{public string GetValue(string name){return "Hello";}}interface IStorage{public void Save(string content, string name);}class StorageImpl : IStorage{private readonly IConfig config;public StorageImpl(IConfig config){this.config = config;}public void Save(string content, string name){string server = config.GetValue("server");Console.WriteLine($"向服务器{server}的文件名为{name}上传{content}");}}class Program{static void Main(string[] args){ServiceCollection services = new ServiceCollection();services.AddScoped<Controller>();services.AddScoped<ILog, LogImpl>();services.AddScoped<IStorage, StorageImpl>();services.AddScoped<IConfig, ConfigImpl>();using (var sp = services.BuildServiceProvider()){var c = sp.GetRequiredService<Controller>();c.Test();}Console.ReadLine();}}
}控制台输出:
日志:开始上传
向服务器Hello的文件名为1.txt上传ABC
日志:上传完毕说明:
1. 这样做的好处就是业务代码不用动,只需要配置新的服务即可。
2. DI可以降低模块之间的耦合。
结尾
书籍:ASP.NET Core技术内幕与项目实战
视频:https://www.bilibili.com/video/BV1pK41137He
著:杨中科
ISBN:978-7-115-58657-5
版次:第1版
发行:人民邮电出版社
※敬请购买正版书籍,侵删请联系85863947@qq.com※
※本文章为看书或查阅资料而总结的笔记,仅供参考,如有错误请留言指正,谢谢!※