服务定位器
1 2 3 4 5
| using Microsoft.Extensions.DependencyInjection; ServiceCollection services = new(); services.AddScope<xxx>(); service.AddSingleton<xxx>(); service.AddTransient<xxx>();
|
不要在长生命周期的对象中引用比它生命周期短的对象。在ASP.NET中,这样做会抛出异常。
如果类无状态,建议为Singleton;如果类有状态,且有Scope控制,建议为Scope,因为通常这种Scope控制下的代码都是运行在同一线程中的,没有并发修改的问题;在使用Transient的时候要谨慎。
1 2 3 4 5 6 7
| using (ServiceProvider serviceProvider = services.BuildServiceProvider()) { using (IServiceScope scope = serviceProvider.CreateScope()) { TestServiceImpl t = scope.ServiceProvider.GetService<TestServiceImpl>(); } }
|
如果一个类实现了IDisposable接口,则离开Scope后会自动调用对象的Dispose方法。
DI的传染性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| using Microsoft.Extensions.DependencyInjection;
ServiceCollection services = new(); services.AddScoped<Controller>(); services.AddScoped<ILog, LogImpl>(); services.AddScoped<IConfig, ConfigImpl>(); services.AddScoped<IStorage, StorageImpl>();
using (ServiceProvider serviceProvider = services.BuildServiceProvider()) { serviceProvider.GetRequiredService<Controller>().Test(); }
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() { log.Log("开始上传"); storage.Save("----------", "1.txt"); log.Log("写入完毕"); } }
interface ILog { void Log(string message); }
class LogImpl : ILog { public void Log(string message) { Console.WriteLine(message); } }
interface IConfig { string GetValue(string key); }
class ConfigImpl : IConfig { public string GetValue(string key) { return $"{key}: hello"; } }
interface IStorage { 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($"文件{name}发送内容为{content}"); } }
|
DI综合案例
需求
有配置服务、日志服务,然后再开发一个邮件发送器服务。可以通过配置服务来从文件、环境变量、数据库等地方读取配置,可以通过日志服务来将程序运行过程中的日志信息写入文件、控制台、数据库等。
实现
创建四个类库项目,ConfigService
是配置服务的项目,LogService
是日志服务的项目,MailService
是邮件发送器的项目,然后再建一个.NET控制台项目MailSender
来调用MailService
。MailService
项目引用ConfigService
项目和LogService
项目,而MailSender
项目引用MailService
项目。
编写类库项目LogServices
,创建ILogProvider
接口。
1 2 3 4 5 6 7
| namespace LogService;
public interface ILoggerProvider { void LogError(string msg); void LogInformation(string msg); }
|
编写实现类ConsoleLogProvider
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| namespace LogService;
class ConsoleLoggerProvider : ILoggerProvider { public void LogError(string msg) { Console.WriteLine($"[ERROR] {msg}"); }
public void LogInformation(string msg) { Console.WriteLine($"[INFO] {msg}"); } }
|
编写一个ConsoleLogProviderExtensions
定义扩展方法AddConsoleLog
,命名空间为Microsoft.Extensions.DependencyInjection
。
1 2 3 4 5 6 7 8 9 10 11
| using LogService;
namespace Microsoft.Extensions.DependencyInjection;
public static class ConsoleLogExtension { public static void AddConsoleLog(this IServiceCollection services) { services.AddScoped<ILoggerProvider, ConsoleLoggerProvider>(); } }
|
编写配置服务的类库项目ConfigService
。接口IConfigService
。
1 2 3 4 5 6 7 8 9 10 11
| using LogService;
namespace Microsoft.Extensions.DependencyInjection;
public static class ConsoleLogExtension { public static void AddConsoleLog(this IServiceCollection services) { services.AddScoped<ILoggerProvider, ConsoleLoggerProvider>(); } }
|
环境变量读取配置类EnvVarConfigProvider。
1 2 3 4 5 6 7 8 9
| namespace ConfigService;
public class EnvVarConfigService : IConfigService { public string? GetValue(string key) { return Environment.GetEnvironmentVariable(key); } }
|
定义扩展方法。
1 2 3 4 5 6 7 8 9 10 11
| using ConfigService;
namespace Microsoft.Extensions.DependencyInjection;
public static class EnvVarConfigExtension { public static void AddEnvVarConfig(this IServiceCollection services) { services.AddScoped<IConfigService, EnvVarConfigService>(); } }
|
编写从ini文件中读取配置的类IniFileConfigServices以及对应扩展方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| namespace ConfigService;
public class IniFileConfigService : IConfigService { public string FilePath { get; set; }
public IniFileConfigService(string filePath) { FilePath = filePath; }
public string? GetValue(string key) { var pair=File.ReadAllLines(FilePath) .Select(s => s.Split('=')) .Select(s => new { Key = s[0], Value = s[1] }) .SingleOrDefault(p => p.Key == key);
return pair?.Value; } }
|
1 2 3 4 5 6 7 8 9 10 11
| using ConfigService;
namespace Microsoft.Extensions.DependencyInjection;
public static class LayeredConfigExtension { public static void AddLayeredConfig(this IServiceCollection services) { services.AddScoped<IConfigReader,LayeredConfigReader>(); } }
|
实现“可覆盖的配置读取器”,配置文件覆盖环境变量。定义一个从各个ConfigService
中读取项的IConfigReader
接口。编写实现类LayeredConfigReader
以及对应的扩展方法。
1 2 3 4 5 6
| namespace ConfigService;
public interface IConfigReader { string? GetValue(string key); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| namespace ConfigService;
internal class LayeredConfigReader : IConfigReader { private readonly IEnumerable<IConfigService> services;
public LayeredConfigReader(IEnumerable<IConfigService> services) { this.services = services; }
public string? GetValue(string key) { string? value = null; foreach (var service in services) { string? newValue = service.GetValue(key); if (newValue != null) { value = newValue; } } return value; } }
|
1 2 3 4 5 6 7 8 9 10 11
| using ConfigService;
namespace Microsoft.Extensions.DependencyInjection;
public static class LayeredConfigExtension { public static void AddLayeredConfig(this IServiceCollection services) { services.AddScoped<IConfigReader,LayeredConfigReader>(); } }
|
编写发送邮件的服务。IMailService
接口,实现类IMailSenderImpl
。注入IConfigReader
,ILoggerProvider
。不真的发邮件,想真的发邮件用MailKit
。
1 2 3 4 5 6
| namespace MailService;
public interface IMailService { void Send(string title, string to, string body); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| using ConfigService; using LogService;
namespace MailService;
public class MailServiceImpl : IMailService { private readonly ILoggerProvider logger; private readonly IConfigReader config;
public MailServiceImpl(ILoggerProvider logger, IConfigReader config) { this.logger = logger; this.config = config; }
public void Send(string title, string to, string body) { logger.LogInformation("准备发送邮件"); string? smtpServer = config.GetValue("SmtpServer"); string? userName = config.GetValue("UserName"); Console.WriteLine($"已发送\n【SMTP服务器】{smtpServer}\n【用户名】{userName}"); logger.LogInformation("邮件发送完成"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| using ConfigService; using MailService; using Microsoft.Extensions.DependencyInjection;
ServiceCollection services = new(); services.AddEnvVarConfig(); services.AddIniFileConfig("mail.ini"); services.AddScoped<IMailService, MailServiceImpl>(); services.AddConsoleLog(); services.AddLayeredConfig(); using (var sp = services.BuildServiceProvider()) { var mailService = sp.GetRequiredService<IMailService>(); mailService.Send("Hello", "joe@joe.com", "你好"); }
|