using Microsoft.Extensions.Configuration;
using MP.Core.Objects;
using Newtonsoft.Json;
using NLog;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace MP.Data.Services
{
///
/// Classe di partenza x costruzione servizi di accesso dati + cache
///
public class BaseServ : IDisposable
{
#region Public Constructors
public BaseServ(IConfiguration configuration, IConnectionMultiplexer redConn)
{
_configuration = configuration;
// Verifica conf trace...
_traceEnabled = _configuration.GetValue("Otel:EnableTracing", false);
// setup componenti REDIS
_redisConn = redConn;
_redisDb = _redisConn.GetDatabase();
// json serializer... FIX errore loop circolare https://www.ryadel.com/en/jsonserializationexception-self-referencing-loop-detected-error-fix-entity-framework-asp-net-core/
JSSettings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
// recupero conf speciali
MpIoNS = _configuration.GetValue("ServerConf:MpIoNS");
}
#endregion Public Constructors
#region Public Methods
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Recupero info IOB x TAB (da info registrate IOB-WIN--> MP-IO)
///
///
///
public IOB_data IobInfo(string IdxMacchina)
{
string source = "DB";
Stopwatch sw = new Stopwatch();
sw.Start();
IOB_data? result = new IOB_data();
// cerco in _redisConn...
string currKey = RedHashMpIO($"hM2IOB:{IdxMacchina}");
RedisValue rawData = _redisDb.StringGet(currKey);
//if (!string.IsNullOrEmpty($"{rawData}"))
if (rawData.HasValue)
{
result = JsonConvert.DeserializeObject($"{rawData}");
source = "REDIS";
}
else
{
Log.Error($"Errore: non trovato valore valido in REDIS | key: {currKey}");
Log.Info($"REDIS | conf: {_redisConn.Configuration}");
Log.Info($" --> Valore trovato:{Environment.NewLine}{rawData}");
}
if (result == null)
{
result = new IOB_data();
Log.Debug($"Init valore default | IdxMacchina: {IdxMacchina}");
}
sw.Stop();
Log.Debug($"IobInfo per {IdxMacchina} | {source} | {sw.Elapsed.TotalMilliseconds}ms");
return result;
}
///
/// Recupero info Machine-IOB x TAB (da info registrate IOB-WIN --> MP-IO)
///
///
///
public Dictionary MachIobConf(string IdxMacchina)
{
string source = "NA";
Stopwatch sw = new Stopwatch();
sw.Start();
Dictionary result = new Dictionary();
// cerco in _redisConn...
string currKey = RedHashMpIO($"IOB:{IdxMacchina}:MachIobConf");
try
{
result = _redisDb
.HashGetAll(currKey)
.ToDictionary(x => $"{x.Name}", x => $"{x.Value}");
source = "REDIS";
}
catch (Exception exc)
{
Log.Error($"Errore in MachIobConf{Environment.NewLine}{exc}");
}
if (result == null)
{
result = new Dictionary();
Log.Debug($"Init valore default MachIobConf | IdxMacchina: {IdxMacchina}");
}
sw.Stop();
Log.Debug($"MachIobConf per {IdxMacchina} | {source} | {sw.Elapsed.TotalMilliseconds}ms");
return result;
}
///
/// Recupero Conf Yaml Machine-IOB
///
///
///
public string MachIobYamlConf(string IdxMacchina)
{
string source = "NA";
Stopwatch sw = new Stopwatch();
sw.Start();
string result = "";
// cerco in _redisConn...
string currKey = RedHashMpIO($"IOB:{IdxMacchina}:ConfYaml");
try
{
result = _redisDb.StringGet(currKey);
source = "REDIS";
}
catch (Exception exc)
{
Log.Error($"Errore in MachIobYamlConf{Environment.NewLine}{exc}");
}
if (result == null)
{
result = "";
Log.Debug($"Init valore default MachIobYamlConf | IdxMacchina: {IdxMacchina}");
}
sw.Stop();
Log.Debug($"MachIobYamlConf per {IdxMacchina} | {source} | {sw.Elapsed.TotalMilliseconds}ms");
return result;
}
#endregion Public Methods
#region Protected Fields
///
/// Oggetto per collezione dati Activity (span in Uptrace)
///
protected static readonly ActivitySource ActivitySource = new ActivitySource("MP.IOC");
protected static IConfiguration _configuration = null!;
///
/// Abilitazione operazioni tracing generiche
///
protected readonly bool _traceEnabled = false;
///
/// Oggetto per connessione a REDIS
///
protected IConnectionMultiplexer _redisConn = null!;
//ISubscriber sub = _redisConn.GetSubscriber();
///
/// Oggetto DB redis da impiegare x chiamate R/W
///
protected IDatabase _redisDb = null!;
protected JsonSerializerSettings? JSSettings;
protected string MpIoNS = "";
#endregion Protected Fields
#region Protected Properties
///
/// Durata cache breve (1 min circa + perturbazione percentuale +/-10%)
///
protected TimeSpan FastCache
{
get => TimeSpan.FromSeconds(cacheTtlShort * rnd.Next(900, 1100) / 1000);
}
///
/// Durata cache lunga (+ perturbazione percentuale +/-10%)
///
protected TimeSpan LongCache
{
get => TimeSpan.FromSeconds(cacheTtlLong * rnd.Next(900, 1100) / 1000);
}
///
/// Durata cache MOLTO breve (10 sec circa + perturbazione percentuale +/-10%)
///
protected TimeSpan UltraFastCache
{
get => TimeSpan.FromSeconds(cacheTtlShort / 6 * rnd.Next(900, 1100) / 1000);
}
///
/// Durata cache MOLTO lunga (+ perturbazione percentuale +/-10%)
///
protected TimeSpan UltraLongCache
{
get => TimeSpan.FromSeconds(cacheTtlLong * 10 * rnd.Next(900, 1100) / 1000);
}
#endregion Protected Properties
#region Protected Methods
///
/// Helper avvio attività per la funzione tracciata
///
///
///
protected static Activity? StartActivity([CallerMemberName] string? methodName = null)
{
var activity = ActivitySource.StartActivity(methodName ?? "UNDEF");
activity?.SetTag("host.name", Environment.MachineName);
return activity;
}
///
/// Invalida una o più chiavi/pattern in Redis
///
protected async Task ClearCacheAsync(params string[] patterns)
{
foreach (var pattern in patterns)
{
// Chiamata al tuo metodo esistente
await ExecFlushRedisPatternAsync((RedisValue)pattern);
}
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Free managed resources here
_redisDb = null;
}
// Free unmanaged resources here
_disposed = true;
}
}
///
/// Metodo di flush dati cache Redis
///
///
///
protected async Task ExecFlushRedisPatternAsync(RedisValue pattern)
{
// Qui inserisci la tua logica attuale (es. via Lua script o Keys/Scan)
// Esempio rapido via server scan:
var endpoints = _redisConn.GetEndPoints();
foreach (var endpoint in endpoints)
{
var server = _redisConn.GetServer(endpoint);
await foreach (var key in server.KeysAsync(_redisDb.Database, pattern))
{
await _redisDb.KeyDeleteAsync(key);
}
}
}
///
/// Helper generale di lettura da cache o da funzione (DB) con caching successivo
///
///
///
///
///
///
protected async Task GetOrSetCacheAsync(string key, Func> factory, TimeSpan? expiration = null, [CallerMemberName] string? caller = null)
{
using var activity = StartActivity();
string source = "DB";
// 1. Provo Redis
var cached = await _redisDb.StringGetAsync(key);
if (cached.HasValue)
{
source = "REDIS";
var cachedResult = JsonConvert.DeserializeObject(cached!)!;
activity?.SetTag("data.source", source);
LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms", NLog.LogLevel.Trace, caller);
return cachedResult;
}
// 2. Chiamo il factory (DB)
T result = await factory();
if (result != null)
{
// 3. Salva in Redis per la prossima volta
var serialized = JsonConvert.SerializeObject(result, JSSettings);
await _redisDb.StringSetAsync(key, serialized, expiration ?? LongCache);
}
// sistemo activity tracking data
activity?.SetTag("data.source", source);
activity?.Stop();
// log in console
LogTrace($"GetOrSetCacheAsync | {source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds:N3}ms", NLog.LogLevel.Trace, caller);
return result!;
}
///
/// Helper trace messaggio log (SE abilitato)
///
///
///
///
protected void LogTrace(string traceMsg, NLog.LogLevel? reqLevel = null, [CallerMemberName] string? methodName = null)
{
if (!_traceEnabled)
return;
reqLevel ??= NLog.LogLevel.Debug;
// Loggo!
Log.Log(reqLevel, $"{methodName} | {traceMsg}");
}
protected string RedHashMpIO(string keyName)
{
string result = keyName;
try
{
result = $"{MpIoNS}:{keyName}".Replace("\\", "_");
}
catch (Exception exc)
{
Log.Error($"Errore in RedHashMpIO{Environment.NewLine}{exc}");
}
return result;
}
///
/// Helper generale per la telemetria e gestione eccezioni
///
///
///
///
///
///
protected async Task TraceAsync(string name, Func> body, object? parameters = null)
{
using var activity = ActivitySource.StartActivity(name);
try
{
if (parameters != null)
{
activity?.SetTag("params", JsonConvert.SerializeObject(parameters));
}
var result = await body(activity);
activity?.SetStatus(ActivityStatusCode.Ok);
activity?.Stop();
LogTrace($"TraceAsync | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms", methodName: name);
return result;
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
//Log.Error(ex, "Errore in {MethodName}", name);
LogTrace($"Errore in {name}", NLog.LogLevel.Error, name);
throw; // Riesponi l'eccezione per il tracking globale
}
}
#endregion Protected Methods
#region Private Fields
private static Logger Log = LogManager.GetCurrentClassLogger();
private bool _disposed = false;
///
/// Durata cache lunga IN SECONDI
///
private int cacheTtlLong = 60 * 5;
///
/// Durata cache breve IN SECONDI
///
private int cacheTtlShort = 60 * 1;
private Random rnd = new Random();
///
/// Path base chiavi REDIS
///
protected readonly string _redisBaseKey = "MP:IOC";
#endregion Private Fields
}
}