Files
OPC/ControlPlane.Core/Services/TenantRegistryService.cs
T
2026-04-25 18:05:57 -04:00

78 lines
2.8 KiB
C#

using System.Xml.Serialization;
using ControlPlane.Core.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace ControlPlane.Core.Services;
/// <summary>
/// Reads and writes per-tenant XML config files under the ClientAssets folder.
/// One file per tenant: {subdomain}.xml
/// Thread-safe for concurrent reads; writes are serialized per subdomain via per-file locking.
/// </summary>
public class TenantRegistryService
{
private readonly string _folder;
private readonly ILogger<TenantRegistryService> _logger;
private static readonly XmlSerializer Serializer = new(typeof(TenantRecord));
// One lock object per subdomain so writes to different tenants never block each other
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, object> _locks = new(StringComparer.OrdinalIgnoreCase);
public TenantRegistryService(IConfiguration configuration, ILogger<TenantRegistryService> logger)
{
_folder = configuration["ClientAssets__Folder"] ?? configuration["ClientAssets:Folder"]
?? Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "ClientAssets"));
_logger = logger;
Directory.CreateDirectory(_folder);
}
// -- Write --
public void Save(TenantRecord record)
{
var path = FilePath(record.Subdomain);
var gate = _locks.GetOrAdd(record.Subdomain, _ => new object());
lock (gate)
{
using var writer = new StreamWriter(path, append: false, System.Text.Encoding.UTF8);
Serializer.Serialize(writer, record);
}
_logger.LogInformation("Saved tenant record: {Path}", path);
}
// -- Read --
public TenantRecord? TryGet(string subdomain)
{
var path = FilePath(subdomain);
if (!File.Exists(path)) return null;
using var reader = new StreamReader(path, System.Text.Encoding.UTF8);
return (TenantRecord?)Serializer.Deserialize(reader);
}
public IReadOnlyList<TenantRecord> GetAll()
{
var results = new List<TenantRecord>();
foreach (var file in Directory.EnumerateFiles(_folder, "*.xml"))
{
try
{
using var reader = new StreamReader(file, System.Text.Encoding.UTF8);
if (Serializer.Deserialize(reader) is TenantRecord record)
results.Add(record);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Skipping malformed tenant file: {File}", file);
}
}
return results;
}
public bool Exists(string subdomain) => File.Exists(FilePath(subdomain));
private string FilePath(string subdomain) =>
Path.Combine(_folder, $"{subdomain.ToLowerInvariant()}.xml");
}