使用 Host 通用主机和直接创建手动创建 ServiceCollection 构建 DI 容器的区别
主要区别对比
| 特性 | ServiceCollection | Host |
|---|
| 功能完整性 | 基础 DI 容器 | 完整应用框架(DI + 配置 + 日志 + 生命周期) |
| 配置系统 | 需手动添加 | 默认集成(自动加载 appsettings.json) |
| 日志系统 | 需手动配置 | 默认集成(Console/Debug/EventLog 等) |
| 后台服务 | 不支持 | 支持 IHostedService/BackgroundService |
| 生命周期管理 | 手动管理作用域 | 自动管理应用生命周期 |
| 环境区分 | 需手动实现 | 内置开发/生产环境支持 |
| 扩展性 | 有限 | 通过 Configure* 方法高度可扩展 |
ServiceCollection 示例
var services = new ServiceCollection();
services.AddLogging(builder => builder.AddNLog("NLog.dev.config"));
ConfigurationBuilder builder = new();
builder.AddAppJsonFile("appsettings.json", optional: false, reloadOnChange: true);
builder.AddCommandLine(args);
IConfigurationRoot root = builder.Build();
services.AddOptions().Configure<AppSettings>(root.Bind);
services.AddOptions().Configure<DatabaseConfig>(root.GetSection("database").Bind);
services.AddSingleton<IMyService, MyService>();
services.AddTransient<Worker>();
using var provider = services.BuildServiceProvider();
using var scope = provider.CreateScope();
var worker = scope.ServiceProvider.GetRequiredService<Worker>();
worker.Execute();
Host 示例
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
services.Configure<AppSettings>(context.Configuration);
services.Configure<DatabaseConfig>(context.Configuration.GetRequiredSection("database"));
services.AddHostedService<WorkerService>();
services.AddScoped<MyTaskService>();
})
.UseNLog(new NLogProviderOptions() { ReplaceLoggerFactory = true })
.UseConsoleLifetime()
.Build();
await host.RunAsync();
NLog 实践细节
日志作用域的传递性
- logger 会自动继承上下文,调用位置的参数会传递到内部调用的对象
- 自动处理异步方法的传递,需要使用
await ,日志作用域通过 AsyncLocal<T> 会自动在异步调用链中流动
Parallel.For、new Thread()、Task.Run 无法自动传递作用域
- 使用作用域可以优化日志文件的管理和层次结构
- 示例
public class HandlerTaskService(PluginTaskService pluginTaskService, ILogger<HandlerTaskService> logger)
{
private readonly CancellationTokenSource cts = new();
public async Task Start(HandlerConfig handler, CancellationToken token)
{
using var scope = logger.BeginScope("Handler:{HandlerName}", handler.Name);
logger.LogInformation("正在启动 [{name}] 处理程序...", handler.Name);
var tasks = handler.Plugins.Select(plugin => pluginTaskService.Start(plugin, handler, cts.Token));
await Task.WhenAll(tasks);
}
}
public class PluginTaskService(IPluginServiceFactory factory, ILogger<PluginTaskService> logger)
{
public async Task Start(PluginConfig plugin, HandlerConfig handler, CancellationToken _token)
{
using var scope = logger.BeginScope("Plugin:{PluginName}", plugin.Name);
logger.LogInformation("启动插件 [{name}] ...");
await Task.Delay(1000, _token);
}
}
配置文件
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<variable name="category" value="${logger}" />
<variable name="handler" value="${scopeproperty:item=HandlerName:whenEmpty=Shared}" />
<variable name="dir" value="${basedir}/logs/${handler}" />
<variable name="layout" value="${longdate} ${uppercase:${level}:padding=-5}: [${category}] ${message}" />
<variable name="consoleLayout" value="${longdate} ${uppercase:${level}:padding=-5}: [${handler}][${category}] ${message}" />
<targets async="true">
<target name="console" xsi:type="ColoredConsole" layout="${consoleLayout}"/>
<target name="file" xsi:type="File" layout="${layout}"
fileName="${dir}/${shortdate}.log"
concurrentWrites="true"
keepFileOpen="true"
archiveFileName="${dir}/archives/${shortdate}_{#}.log"
archiveAboveSize="10485760"
maxArchiveFiles="10"
archiveNumbering="Rolling"
maxArchiveDays="30" />
</targets>
</nlog>
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<include file="NLog.config" />
<rules>
<logger name="*" minlevel="Debug" writeTo="console,file" />
</rules>
</nlog>