我正在为后台服务编写代码,旨在通过OPC UA协议收集数据。在我决定重构以符合现代模式 (DI) 之前,服务按预期工作。
在重构服务的主要部分(while 循环例程)之前,我尝试重构 FileLogger 以将其用作服务,以便将其构造函数注入到我的托管服务中。
当我尝试在容器中注册服务时出现问题。首先,当我将日志目录的路径传递给构造函数时,我将其硬编码为日志目录的路径(worker 的第一次迭代使用了
@$"{System.AppContext.BaseDirectory}\logs"
路径),但它无法启动。
我将一段代码放入
StartAsync
方法中:
using (StreamWriter writer = new("path to file", true)) {
await writer.WriteLineAsync(message);
}
没有创建文件,我的服务已退出游戏。看起来
StartAsync
根本没有启动,并且在创建主机时抛出了未处理的异常。
文件记录服务的结构:
using System.Globalization;
using System.Text.RegularExpressions;
namespace OpcService.Services;
internal interface IOpcLogger
{
Task<bool> AppendMessage(string message, DateTime timestamp);
Task<bool> AppendMessageList(List<string> messages, DateTime timestamp);
bool DeleteMessagesBeforeToday(byte daysBeforeToday);
}
internal class OpcFileLogger : IOpcLogger
{
private const string TIMESTAMP_FORMAT = "HH:mm:ss";
public string MessageDirectory { get; }
public OpcFileLogger(string messageDirectory)
=> MessageDirectory = (string.IsNullOrWhiteSpace(messageDirectory))
? messageDirectory
: throw new ArgumentException($"{nameof(messageDirectory)} cannot be null/empty/whitespace.");
public async Task<bool> AppendMessage(string message, DateTime timestamp)
{
try {
if (string.IsNullOrWhiteSpace(message)) {
throw new ArgumentException($"{nameof(message)} cannot be null/empty/whitespace.", nameof(message));
}
if (!Directory.Exists(MessageDirectory)) {
Directory.CreateDirectory(MessageDirectory);
}
message = $"[{timestamp.ToString(TIMESTAMP_FORMAT)}] {message}";
using (StreamWriter writer = new(@$"{MessageDirectory}\{timestamp.ToString("ddMMyyyy")}.txt", true)) {
await writer.WriteLineAsync(message);
}
return true;
} catch {
return false;
}
}
// other methods
}
依赖类:
internal class OpcUaWorker : BackgroundService
{
private WorkerSettings? ServiceSettings { get; set; }
private readonly IOpcLogger _opcLogger;
private string WorkerFilePath { get; } = @$"{System.AppContext.BaseDirectory}\worker.json";
private string DBConfigFilePath { get; } = @$"{System.AppContext.BaseDirectory}\db.config";
// private string LogsDirectoryPath { get; } = @$"{System.AppContext.BaseDirectory}\logs";
public OpcUaWorker(IOpcLogger opcLogger)
=> _opcLogger = opcLogger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// opc session
}
public override async Task StartAsync(CancellationToken stoppingToken)
{
await _opcLogger.AppendMessage("Module is starting...\n", DateTime.Now);
await base.StartAsync(stoppingToken);
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
await _opcLogger.AppendMessage("Module is stopping...\n", DateTime.Now);
await base.StopAsync(stoppingToken);
}
}
注射器程序:
using OpcService;
using OpcService.Services;
IHost host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices(services => {
services.AddSingleton<IOpcLogger, OpcFileLogger>(provider => new OpcFileLogger(@"d:\Projects\OpcUaWorker\logs"));
services.AddHostedService<OpcUaWorker>();
})
.Build();
await host.RunAsync();
仅当使用
services.AddSingleton<IOpcLogger, OpcFileLogger>();
并且不使用参数化构造函数 (public string MessageDirectory { get; } = @"d:\Projects\OpcUaWorker\logs";
) 时,它才有效。
按照评论中的建议,我在事件视图中发现以下错误记录:
Application: OpcUaWorker.exe
CoreCLR Version: 6.0.1122.52304
.NET Version: 6.0.11
Description: The process was terminated due to an unhandled exception.
Exception Info: System.InvalidOperationException: Unable to resolve service for type 'OpcService.Services.IOpcLogger' while attempting to activate 'OpcService.OpcUaWorker'.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(ServiceIdentifier serviceIdentifier, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
我通过查看事件查看器在评论部分的推荐的帮助下制定了解决方案。在我尝试使用不带
await host.RunAsync();
的 VSCode 调试代码(来自同一用户的另一个提示)后,我意识到我的对象没有使用正确的参数初始化,因此查看 OpcUaWorker
构造函数使我发现:
public OpcFileLogger(string messageDirectory)
=> MessageDirectory = (string.IsNullOrWhiteSpace(messageDirectory))
? throw new ArgumentException($"{nameof(messageDirectory)} cannot be null/empty/whitespace.")
: messageDirectory;