使用 .NET Core 3.1 构建 Windows 工作服务以删除文件夹中的旧文件






4.89/5 (17投票s)
一个 Windows 服务,用于从一组可配置的文件夹中删除比特定日期旧的文件
免责声明
我们在此创建的服务用于从独立配置文件中指定的文件夹列表中删除早于特定日期的文件。此应用程序已在我的环境中进行了测试,据我所知是有效的。我不能保证它在您的环境中也能完美运行。在使用此服务于生产环境之前,我强烈建议您进行一些额外的测试。
引言
在本文中,我将介绍如何使用 Visual Studio 中的 Worker Service 模板创建简单的 Windows Service。
在 .NET Core 3.0 引入 Worker Service 之前,您可能会使用 .NET Windows Service 模板来创建 Windows 服务。我一直认为使用该模板的体验从来都不够直观。您可能遇到的第一个问题是,如果不实现一些代码技巧,您就无法在 Visual Studio 环境中运行该服务。此外,该模板也没有为您提供太多关于使服务按预期运行所需内容的指导。幸运的是,随着 .NET 3.0 和 Worker Service 的出现,这一切都变得容易多了。
解决方案
要跟随本文,您需要在您的环境中安装 Visual Studio 2019 和 .NET Core 3.0。
首先,您需要创建一个新项目并选择 Worker Process 模板,如下图所示
添加配置设置
在 `appsettings.json` 文件中,我们需要添加一些配置。一个用于设置文件被删除前允许的最大年龄的阈值。我们称此设置为 `NumberOfDaysBeforeDelete`。我将其值设置为 90 天。除此之外,我们还需要指向包含要监视的文件夹列表的独立配置文件。我称此设置为 `ConfigurationFilePath`,并将其指向我 `c:\temp` 文件夹中的一个位置。最后一个设置用于配置此服务执行自定义代码的间隔。我称此设置为 `RunIntervallInHours`,并将其值设置为 12 小时。这意味着该服务将每天检查配置文件中每个文件夹的内容两次,并删除所有上次写入日期超过 90 天的文件。这三个配置设置被分组在 `appsettings.json` 文件的一个节点中,我称之为“`App.Configurations`”,如下所示
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"App.Configurations": {
"ConfigurationFilePath": "C:\\Temp\\CleanUpFolders\\CleanUpConfigurations.config",
"NumberOfDaysBeforeDelete": "10",
"RunIntervallInHours": "12"
}
}
请注意,您的项目中既有 `appsetttings.json` 文件,也有 `appsettings.development.json` 文件。运行时使用哪个 `appsettings.json` 文件取决于您的环境设置。当从 Visual Studio 运行此服务时,它将默认使用 `Properties/launchSettings.json` 文件中配置的开发设置。
添加包含文件夹列表的配置文件
我在 `c:\Temp\CleauUpFoders\` 中创建了一个名为 `CleanUpConfigurations.config` 的文件,并在其中放入了以下两行
C:\Temp\CleanUpFolders\Folder1
C:\Temp\CleanUpFolders\Folder2
我还在同一位置创建了“`Folder1`”和“`Folder2`”文件夹。
我们的代码
在创建的新解决方案中,有两个文件对我们最初很重要:`program.cs` 用于启动应用程序,`worker.cs` 将包含我们此解决方案的自定义代码。在 `program.cs` 文件中,您会找到设置主机环境的代码。为了方便将此应用程序作为 Windows 服务运行,您需要添加以下 NuGet 包。
Install-Package Microsoft.Extensions.Hosting.WindowsServices
安装该包后,您可以在 `Host.CreateDefaultBuilder(args)` 之后添加 `.UseWindowsService()` 行,以便在 `program.cs` 文件中得到如下所示的内容。
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<worker>();
});
}
在 `worker.cs` 文件中,我们首先将 `IServiceScopeFactory` 的一个实例注入到构造函数中。这将用于访问 `appsettings` 配置文件。
public Worker(ILogger<worker> logger, IServiceScopeFactory serviceScopeFactory)
{
_logger = logger;
_serviceScopeFactory = serviceScopeFactory;
}
为了确保每次服务启动时都能读取配置,我重写了 `StartAsync` 方法并在其中检索我的应用程序配置
public override Task StartAsync(CancellationToken cancellationToken)
{
_configuration = _serviceScopeFactory.CreateScope().
ServiceProvider.GetRequiredService<iconfiguration>();
_numberOfDaysBeforeDelete = int.Parse(_configuration
["App.Configurations:NumberOfDaysBeforeDelete"]);
_runIntervallInHours = int.Parse(_configuration
["App.Configurations:RunIntervallInHours"]);
_folderPaths = File.ReadAllLines(_configuration
["App.Configurations:ConfigurationFilePath"]).Select(x => x.Trim()).ToList();
return base.StartAsync(cancellationToken);
}
请注意,我正在读取 `ConfigurationFilePath` 设置中指定的文件中的所有行,并将它们放入 `list _folderPaths` 中。
以下变量在 `Worker` 类中声明
private IList<string> _folderPaths;
private int _numberOfDaysBeforeDelete;
private int _runIntervallInHours;
我们从配置文件中检索文件夹并删除所有早于 90 天之久的文件,这段代码将放在 `ExecuteAsync` 方法中。`ExecuteAsync` 方法以 `CancellationToken` 作为参数。`CancellationToken` 非常重要,因为它将用于识别服务何时停止。发生这种情况时,`IsCancellationRequested` 属性将设置为 `true`。因此,我们 `ExecuteAsync` 中的第一行代码是
while (!stoppingToken.IsCancellationRequested)
我将在此方法中进一步向下进行第二次 `IsCancellationRequested` 检查,以确保在收到取消请求时尽快停止执行。获取早于 x 天的文件并删除它们的代码写在两行中
var files = Directory.GetFiles(path).Select(file => new FileInfo(file)).Where
(file => file.LastWriteTime < DateTime.Now.AddDays(-1* _numberOfDaysBeforeDelete)).ToList();
// Delete found files
files.ForEach(file => file.Delete());
在关闭 `while` 循环之前,我设置了一个延迟。由于我们的 `setting _runIntervallInHours` 设置为 12 小时,因此此代码将使服务休眠 12 小时,然后继续执行。
await Task.Delay(TimeSpan.FromHours(_runIntervallInHours), stoppingToken);
注意此处使用的取消令牌,当 Windows 服务停止时,这将导致等待过程被取消。
`ExecuteAsync` 方法的完整代码将如下所示
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
foreach (var path in _folderPaths)
{
if (!stoppingToken.IsCancellationRequested)
{
// Get old files
var files = Directory.GetFiles(path).Select
(file => new FileInfo(file)).Where
(file => file.LastWriteTime < DateTime.Now.AddDays
(-1* _numberOfDaysBeforeDelete)).ToList();
// Delete found files
files.ForEach(file => file.Delete());
}
}
await Task.Delay(TimeSpan.FromHours(_runIntervallInHours), stoppingToken);
}
}
这就是我们所需的所有代码。您可以直接按 F5 从 Visual Studio 运行应用程序,看看它是否有效。
将应用程序转换为 Windows 服务
现在继续并发布应用程序。
发布到文件夹
以管理员身份打开 PowerShell 终端并运行以下命令
sc.exe create FolderCleanUpService binpath= "C:\Apps\FolderCleanUpService.exe" start= auto
这将把服务安装到您的 Windows 计算机上。
您可以将您的服务放在任何您想要的地方,我在这里选择将其放在我 `c` 驱动器的 `Apps` 文件夹中。我使用 `start=auto` 标志来确保服务在 Windows 启动时自动启动。要为服务设置描述,您需要执行第二个命令
sc.exe description "FolderCleanUpService"
"This service monitors and deletes files older than 90 days"
按 (Window + R) 并运行命令 `services.msc`,然后您应该会看到所有服务的列表,在该列表中,您应该能看到 `FolderCleanUpService` 服务。您需要手动启动它第一次,但由于启动类型设置为自动,因此它会在重启后自行启动。
您可以使用此命令删除该服务
sc.exe delete "FolderCleanUpService"
历史
- 2020 年 4 月 3 日:初始版本