using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace Domain.Processing { public class FolderQueueSettings { public string QueueFolderPath { get; set; } public string ProcessedFolderPath { get; set; } public string FailedFolderPath { get; set; } public int MaxRetryAttempts { get; set; } = 3; public int RetryDelayMs { get; set; } = 2000; public void Validate() { if (string.IsNullOrWhiteSpace(QueueFolderPath)) throw new ArgumentException("QueueFolderPath must be specified."); Directory.CreateDirectory(QueueFolderPath); if (!string.IsNullOrWhiteSpace(ProcessedFolderPath)) Directory.CreateDirectory(ProcessedFolderPath); if (!string.IsNullOrWhiteSpace(FailedFolderPath)) Directory.CreateDirectory(FailedFolderPath); } } public interface ISerializer<T> { string Serialize(T item); T Deserialize(string serialized); } /// <summary> /// Generic queue based on folder storage. /// Files are created in QueueFolderPath and processed sequentially. /// </summary> public class FolderQueue<T> { private readonly FolderQueueSettings _settings; private readonly ISerializer<T> _serializer; private readonly ILogger<FolderQueue<T>> _logger; public FolderQueue(FolderQueueSettings settings, ISerializer<T> serializer, ILogger<FolderQueue<T>> logger = null) { _settings = settings ?? throw new ArgumentNullException(nameof(settings)); _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); _logger = logger; _settings.Validate(); } public async Task AddToQueueAsync(T item) { if (item == null) throw new ArgumentNullException(nameof(item)); var serialized = _serializer.Serialize(item); var fileName = Path.Combine(_settings.QueueFolderPath, $"{DateTime.UtcNow:yyyyMMdd_HHmmss}_{Guid.NewGuid()}.json"); await File.WriteAllTextAsync(fileName, serialized, Encoding.UTF8); _logger?.LogInformation("Item added to queue: {FileName}", fileName); } public async Task ProcessQueueAsync(Func<T, Task> processor) { if (processor == null) throw new ArgumentNullException(nameof(processor)); var files = Directory.GetFiles(_settings.QueueFolderPath, "*.json"); _logger?.LogInformation("Processing {Count} items from queue", files.Length); foreach (var file in files) { await ProcessFileAsync(file, processor); } } private async Task ProcessFileAsync(string filePath, Func<T, Task> processor) { var retryCount = 0; while (retryCount <= _settings.MaxRetryAttempts) { try { var content = await File.ReadAllTextAsync(filePath, Encoding.UTF8); var item = _serializer.Deserialize(content); await processor(item); await MoveToProcessedFolderAsync(filePath); _logger?.LogInformation("Successfully processed item: {FileName}", Path.GetFileName(filePath)); return; } catch (Exception ex) { retryCount++; _logger?.LogWarning(ex, "Failed to process file {FileName} (attempt {Attempt}/{Max})", Path.GetFileName(filePath), retryCount, _settings.MaxRetryAttempts + 1); if (retryCount > _settings.MaxRetryAttempts) { await MoveToFailedFolderAsync(filePath); _logger?.LogError(ex, "File moved to failed folder after {Retries} attempts: {FileName}", retryCount, Path.GetFileName(filePath)); return; } if (_settings.RetryDelayMs > 0) await Task.Delay(_settings.RetryDelayMs); } } } private async Task MoveToProcessedFolderAsync(string filePath) { if (!string.IsNullOrWhiteSpace(_settings.ProcessedFolderPath)) { var fileName = Path.GetFileName(filePath); var dest = Path.Combine(_settings.ProcessedFolderPath, fileName); if (File.Exists(dest)) File.Delete(dest); File.Move(filePath, dest); } else { File.Delete(filePath); } } private async Task MoveToFailedFolderAsync(string filePath) { if (!string.IsNullOrWhiteSpace(_settings.FailedFolderPath)) { var fileName = Path.GetFileName(filePath); var dest = Path.Combine(_settings.FailedFolderPath, fileName); if (File.Exists(dest)) File.Delete(dest); File.Move(filePath, dest); } } } }
Example mail queue
using System.Net.Mail; using Microsoft.Extensions.Logging; namespace Domain.Mail { public class MailQueueService : IMailQueueService { private readonly FolderQueue<MailMessage> _queue; private readonly ILogger<MailQueueService> _logger; public MailQueueService(MailQueueSettings settings, IMailSerializer mailSerializer, ILogger<MailQueueService> logger = null) { var folderSettings = new FolderQueueSettings { QueueFolderPath = settings.QueueFolderPath, FailedFolderPath = settings.FailedFolderPath, ProcessedFolderPath = settings.ProcessedFolderPath, MaxRetryAttempts = settings.MaxRetryAttempts, RetryDelayMs = settings.RetryDelayMs }; _queue = new FolderQueue<MailMessage>(folderSettings, mailSerializer, logger); _logger = logger; } public Task AddToQueueAsync(MailMessage message) => _queue.AddToQueueAsync(message); public Task ProcessQueueAsync() => _queue.ProcessQueueAsync(async mail => { using var smtp = new SmtpClientWrapper(new MailQueueSettings()); // of gebruik _settings await smtp.SendMailAsync(mail); }); } }
Simple Json serializer
using System.Text.Json; public class JsonSerializerAdapter<T> : ISerializer<T> { public string Serialize(T item) => JsonSerializer.Serialize(item, new JsonSerializerOptions { WriteIndented = true }); public T Deserialize(string serialized) => JsonSerializer.Deserialize<T>(serialized); }
979200cookie-checkC# FolderQueue