提问人:steb 提问时间:11/7/2023 最后编辑:Stevensteb 更新时间:11/7/2023 访问量:31
自定义 ConsoleFormatter 中的 DI/HttpContext
DI/HttpContext in custom ConsoleFormatter
问:
在我的自定义中,我需要从添加到 DI 的服务中获取值。此值保存在 SCOPED 服务中,因为它为每个新请求生成一个新值,但应通过请求保留该值。ConsoleFormatter
问题是我不知道如何在 中访问 DI 服务,我在下面尝试的是将服务容器放入 options 参数中。我的想法是,.NET 服务是一个单一实例,这样我就可以在日志记录时请求我的服务并获取我的范围服务。ConsoleFormatter
public static class ConsoleLoggerExtensions
{
public static ILoggingBuilder AddCustomConsoleFormatter(
this ILoggingBuilder builder, Action<CustomConsoleFormatterOptions> options)
{
return builder.AddConsole(
options => options.FormatterName = nameof(CustomLoggingFormatter))
.AddConsoleFormatter<
CustomLoggingFormatter, CustomConsoleFormatterOptions>(
options);
}
}
public sealed class CustomConsoleFormatterOptions : ConsoleFormatterOptions
{
public IServiceProvider serviceProvider { get; set; }
}
public sealed class CustomLoggingFormatter : ConsoleFormatter, IDisposable
{
static long RowIndex = 1;
readonly IDisposable _optionsReloadToken;
CustomConsoleFormatterOptions _formatterOptions;
public CustomLoggingFormatter(
IOptionsMonitor<CustomConsoleFormatterOptions> options)
: base(nameof(SebLoggingFormatter))
{
(_optionsReloadToken, _formatterOptions) =
(options.OnChange(ReloadLoggerOptions), options.CurrentValue);
}
public override void Write<TState>(in LogEntry<TState> logEntry,
IExternalScopeProvider scopeProvider, TextWriter textWriter)
{
string message =
logEntry.Formatter?.Invoke(logEntry.State, logEntry.Exception);
if (message is null)
return;
var idService =
_formatterOptions.serviceProvider.GetService<IIdService>();
var requestId = idService.RequestId;
}
void ReloadLoggerOptions(CustomConsoleFormatterOptions options) =>
_formatterOptions = options;
public void Dispose() =>
_optionsReloadToken?.Dispose();
}
在我的启动中,我添加了一个 Task 来获取 CustomFormnaytterOptions
services.AddLogging(opt => opt.AddCustomConsoleFormatter(options =>
{
var serviceProvider = services.BuildServiceProvider();
options.serviceProvider = serviceProvider;
}));
但是当我从服务请求 IdService 时,我会得到一个值为空的服务,对我来说这可能是因为:
- 没有 http-context(IdService 正在通过当前请求中的标头选取或创建 Id)
- 我在这里使用的 .net 服务是启动时的旧服务(=不是单例),因此与当前请求无关
我不明白的一件事是(ConsoleFormatterOptions 使用)在 .net 中的这种选项编码模式似乎是一个内置功能。我是否应该以某种方式尝试使选项失效,以便它从启动重新运行任务?
答:
1赞
Steven
11/7/2023
#1
您的解决方案存在以下几个问题,导致其无法正常工作:
- 将该字段设置为 将导致格式化程序获取所有 DI 注册的副本。这不会授予您访问请求的权限,也不会授予您访问活动范围或 HTTP 请求的权限。
CustomConsoleFormatterOptions.serviceProvider
services.BuildServiceProvider()
IIdService
- 多次调用是一种有问题的做法,这就是为什么 .NET Core 中有一个代码分析器会警告你这一点。
BuildServiceProvider
- 记录器及其格式化程序始终被框架假定为单例。尽管格式化程序可以注入依赖关系,但这些依赖关系本身已经、应该或将要成为单例。如果注入 ,它将是“根”服务提供程序,并且不会授予你访问作用域内服务的权限。
IServiceProvider
解决方案是注入类,因为它允许访问当前:IHttpContextAccessor
HttpContext
public sealed class CustomLoggingFormatter : ConsoleFormatter
{
private readonly IHttpContextAccessor accessor;
public CustomLoggingFormatter(IHttpContextAccessor accessor)
: base(nameof(SebLoggingFormatter))
{
this.accessor = accessor;
}
public override void Write<TState>(in LogEntry<TState> logEntry,
IExternalScopeProvider scopeProvider, TextWriter textWriter)
{
string message =
logEntry.Formatter?.Invoke(logEntry.State, logEntry.Exception);
if (message is null)
return;
// Pull scoped service from request
var idService =
this.accessor.HttpContext.RequestServices.GetService<IIdService>();
var requestId = idService.RequestId;
}
}
这可以按如下方式连接:
builder.AddHttpAccessor();
builder.AddConsole(
options => options.FormatterName = nameof(CustomLoggingFormatter))
.AddConsoleFormatter<
CustomLoggingFormatter, CustomConsoleFormatterOptions>();
评论
0赞
steb
11/7/2023
太棒了,哈哈,太简单了,只需注入 IHttpContextAccessor :-)我确实设法通过做同样的事情让它工作,首先使用我的服务并请求 HttpContextAccessor,然后从该 DiContainer 请求 IdService。但当然,你的方式是唯一正确的方式。非常感谢您的帮助!
评论