服务定位器

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来调用MailServiceMailService项目引用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。注入IConfigReaderILoggerProvider 。不真的发邮件,想真的发邮件用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())
{
// 第一个对象只能用ServiceLocator
var mailService = sp.GetRequiredService<IMailService>();
mailService.Send("Hello", "joe@joe.com", "你好");
}