using MagMan.Core; using MagMan.Core.Services; using MagMan.Data.Admin.Controllers; using MagMan.Data.Admin.DbModels; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.VisualBasic; using Newtonsoft.Json; using NLog; using StackExchange.Redis; using System.Diagnostics; namespace MagMan.Data.Admin.Services { public class MTAdminService : BaseServ { #region Public Constructors public MTAdminService(IConfiguration configuration, IConnectionMultiplexer redisConnMult, IEmailSender emailSender, UserManager userManager) { Log.Info("MTAdminService starting..."); _configuration = configuration; _emailSender = emailSender; _userManager = userManager; // Conf cache redisConn = redisConnMult; redisDb = this.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 }; // cod app CodApp = _configuration["CodApp"]; dbController = new MTAdminController(configuration); // chiudo log Log.Info("MTAdminService started!"); _userManager = userManager; } #endregion Public Constructors #region Public Properties public string CodApp { get; set; } = ""; #endregion Public Properties #region Public Methods /// /// Lista AuthKey /// /// 0 = tutti /// public async Task> AuthKeyByCustId(int CustomerId) { string source = "DB"; List? dbResult = new List(); try { string currKey = $"{Const.rKeyConfig}:Admin:AuthKeyList:{CustomerId}"; Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string? rawData = await redisDb.StringGetAsync(currKey); if (!string.IsNullOrEmpty(rawData) && rawData.Length > 2) { source = "REDIS"; var tempResult = JsonConvert.DeserializeObject>(rawData); if (tempResult == null) { dbResult = new List(); } else { dbResult = tempResult; } } else { dbResult = dbController.AuthKeyByCustId(CustomerId); rawData = JsonConvert.SerializeObject(dbResult, JSSettings); await redisDb.StringSetAsync(currKey, rawData, LongCache); } if (dbResult == null) { dbResult = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"AuthKeyByCustId | {source} in: {ts.TotalMilliseconds} ms"); } catch (Exception exc) { Log.Error($"Error during AuthKeyByCustId:{Environment.NewLine}{exc}"); } return dbResult; } /// /// Delete record AuthKey + refresh cache /// /// /// public async Task AuthKeyDelete(AuthKeyModel currItem) { bool fatto = false; try { fatto = dbController.AuthKeyDelete(currItem); if (fatto) { await FlushRedisCache(); } } catch (Exception exc) { Log.Error($"Error during AuthKeyDelete:{Environment.NewLine}{exc}"); } return fatto; } /// /// Lista AuthKey da Rest Token /// /// token di auth del cliente /// public async Task> AuthKeyGetByToken(string RestToken) { List? dbResult = new List(); // cerco in primis customer ID... int CustomerId = await CustomerIdByToken(RestToken); if (CustomerId >= 0) { dbResult = await AuthKeyByCustId(CustomerId); } return dbResult; } /// /// Update record AuthKey + refresh cache /// /// /// public async Task AuthKeyUpdate(AuthKeyModel currItem) { bool fatto = false; try { fatto = dbController.AuthKeyUpdate(currItem); if (fatto) { await FlushRedisCache(); } } catch (Exception exc) { Log.Error($"Error during AuthKeyUpdate:{Environment.NewLine}{exc}"); } return fatto; } /// /// Delete record customer + refresh cache /// /// /// public async Task CustomerDelete(CustomerModel currItem) { bool fatto = false; try { fatto = dbController.CustomerDelete(currItem); if (fatto) { await FlushRedisCache(); } } catch (Exception exc) { Log.Error($"Error during CustomerDelete:{Environment.NewLine}{exc}"); } return fatto; } /// /// Lista Customers /// /// public async Task> CustomerGetAll() { string source = "DB"; List? dbResult = new List(); try { string currKey = $"{Const.rKeyConfig}:Admin:CustomersList"; Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string? rawData = await redisDb.StringGetAsync(currKey); if (!string.IsNullOrEmpty(rawData)) { source = "REDIS"; var tempResult = JsonConvert.DeserializeObject>(rawData); if (tempResult == null) { dbResult = new List(); } else { dbResult = tempResult; } } else { dbResult = dbController.CustomerGetAll(); rawData = JsonConvert.SerializeObject(dbResult, JSSettings); await redisDb.StringSetAsync(currKey, rawData, LongCache); } if (dbResult == null) { dbResult = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"CustomerGetAll | {source} in: {ts.TotalMilliseconds} ms"); } catch (Exception exc) { Log.Error($"Error during CustomerGetAll:{Environment.NewLine}{exc}"); } return dbResult; } /// /// Recupera CustomerID dal dizionario dei token noti o cercando sul DB /// /// /// public async Task CustomerIdByToken(string RestToken) { int answ = -1; string currKey = $"{Const.rKeyConfig}:Admin:Dict:Token2CustID"; answ = RedisHashGetInt(currKey, RestToken); if (answ <= 0) { // cerco nel DB var custList = await CustomerGetAll(); var custRow = custList.FirstOrDefault(x => x.RestToken == RestToken); // se trovato salvo if (custRow != null) { answ = custRow.CustomerID; RedisHashSet(currKey, RestToken, $"{answ}"); Log.Info($"Token2CustID: added {RestToken} --> {answ}"); } } return answ; } /// /// Update record customer + refresh cache /// /// /// public async Task CustomerUpdate(CustomerModel currItem) { bool fatto = false; try { fatto = dbController.CustomerUpdate(currItem); if (fatto) { await FlushRedisCache(); } } catch (Exception exc) { Log.Error($"Error during CustomerUpdate:{Environment.NewLine}{exc}"); } return fatto; } /// /// Refresh globale cache redis /// /// public async Task FlushRedisCache() { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); await Task.Delay(1); RedisValue pattern = new RedisValue($"{Const.rKeyConfig}:*"); bool answ = await ExecFlushRedisPattern(pattern); stopWatch.Stop(); Log.Debug($"FlushRedisCache in {stopWatch.Elapsed.TotalMilliseconds} ms"); return answ; } /// /// Delete record Machine + refresh cache /// /// /// public async Task MachineDelete(MachineModel currItem) { bool fatto = false; try { fatto = dbController.MachineDelete(currItem); if (fatto) { await FlushRedisCache(); } } catch (Exception exc) { Log.Error($"Error during MachineDelete:{Environment.NewLine}{exc}"); } return fatto; } /// /// Lista Macchine /// /// 0 = tutti /// public async Task> MachineGetByCustId(int CustomerId) { string source = "DB"; List? dbResult = new List(); try { string currKey = $"{Const.rKeyConfig}:Admin:MachineList:{CustomerId}"; Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string? rawData = await redisDb.StringGetAsync(currKey); if (!string.IsNullOrEmpty(rawData)) { source = "REDIS"; var tempResult = JsonConvert.DeserializeObject>(rawData); if (tempResult == null) { dbResult = new List(); } else { dbResult = tempResult; } } else { dbResult = dbController.MachineGetByCustId(CustomerId); rawData = JsonConvert.SerializeObject(dbResult, JSSettings); await redisDb.StringSetAsync(currKey, rawData, LongCache); // per evitare loopback uso deserialize... var tempResult = JsonConvert.DeserializeObject>(rawData); if (tempResult != null) { dbResult = tempResult; } } if (dbResult == null) { dbResult = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"MachineGetByCustId | {source} in: {ts.TotalMilliseconds} ms"); } catch (Exception exc) { Log.Error($"Error during MachineGetByCustId:{Environment.NewLine}{exc}"); } return dbResult; } /// /// Lista Macchine da Rest Token /// /// token di auth del cliente /// public async Task> MachineGetByToken(string RestToken) { List? dbResult = new List(); // cerco in primis customer ID... int CustomerId = await CustomerIdByToken(RestToken); if (CustomerId >= 0) { dbResult = await MachineGetByCustId(CustomerId); } return dbResult; } /// /// Update record Machine + refresh cache /// /// /// public async Task MachineUpdate(MachineModel currItem) { bool fatto = false; try { fatto = dbController.MachineUpdate(currItem); if (fatto) { await FlushRedisCache(); } } catch (Exception exc) { Log.Error($"Error during MachineUpdate:{Environment.NewLine}{exc}"); } return fatto; } /// /// Recupera CustomerID dal dizionario dei token noti o cercando sul DB /// /// /// public async Task MainKeyByCustomer(int CustID) { int answ = -1; string currKey = $"{Const.rKeyConfig}:Admin:Dict:Cust2MKey"; answ = RedisHashGetInt(currKey, $"{CustID}"); if (answ <= 0) { // cerco nel DB var custList = await CustomerGetAll(); var custRow = custList.FirstOrDefault(x => x.CustomerID == CustID); // se trovato salvo if (custRow != null) { answ = custRow.MainKey; RedisHashSet(currKey, $"{CustID}", $"{answ}"); Log.Info($"Cust2MKey: added {CustID} --> {answ}"); } } return answ; } /// /// Recupera MainKey dal dizionario dei token noti o cercando sul DB /// /// /// public async Task MainKeyByToken(string RestToken) { int answ = -1; string currKey = $"{Const.rKeyConfig}:Admin:Dict:Token2MKey"; answ = RedisHashGetInt(currKey, RestToken); if (answ <= 0) { // cerco nel DB var custList = await CustomerGetAll(); var custRow = custList.FirstOrDefault(x => x.RestToken == RestToken); // se trovato salvo if (custRow != null) { answ = custRow.MainKey; RedisHashSet(currKey, RestToken, $"{answ}"); Log.Info($"TokenMKey: added {RestToken} --> {answ}"); } } return answ; } /// /// Reset dati cache redis /// /// public async Task ResetUserDataCache() { string currKey = $"{Const.rKeyConfig}:Admin:UserData:*"; await ExecFlushRedisPattern((RedisValue)currKey); } private readonly UserManager _userManager; public async Task> UserDataGetFilt(string searchVal) { // Collezione utenti List RawList = new List(); List dbResult = new List(); string source = "DB"; try { string typeSearch = string.IsNullOrEmpty(searchVal) ? "ALL" : searchVal; string currKey = $"{Const.rKeyConfig}:Admin:UserData:{typeSearch}"; Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string? rawData = await redisDb.StringGetAsync(currKey); if (!string.IsNullOrEmpty(rawData) && rawData.Length > 2) { source = "REDIS"; var tempResult = JsonConvert.DeserializeObject>(rawData); if (tempResult == null) { dbResult = new List(); } else { dbResult = tempResult; } } else { // recupero utenti da obj _userManager var allData = _userManager.Users.ToList(); if (!string.IsNullOrEmpty(searchVal)) { RawList = allData.Where(x => x.NormalizedEmail.Contains(searchVal.ToUpper()) || x.NormalizedUserName.Contains(searchVal.ToUpper())).ToList(); } else { RawList = allData; } var user = RawList.Select(x => new IdentityUser { Id = x.Id, UserName = x.UserName, Email = x.Email, PhoneNumber = x.PhoneNumber, PasswordHash = "*****", EmailConfirmed = x.EmailConfirmed }).ToList(); foreach (var item in user) { var UserRoles = await _userManager.GetRolesAsync(item); var UserClaims = await _userManager.GetClaimsAsync(item); var newItem = new UserData() { Identity = item, Roles = UserRoles.ToList(), Claims = UserClaims.ToList() }; dbResult.Add(newItem); } rawData = JsonConvert.SerializeObject(dbResult, JSSettings); await redisDb.StringSetAsync(currKey, rawData, LongCache); } if (dbResult == null) { dbResult = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"UserDataGetFilt | {source} in: {ts.TotalMilliseconds} ms"); } catch (Exception exc) { Log.Error($"Error during UserDataGetFilt:{Environment.NewLine}{exc}"); } return dbResult; } #endregion Public Methods #region Protected Fields protected static MTAdminController dbController = null!; #endregion Protected Fields #region Private Fields private static JsonSerializerSettings? JSSettings; private static Logger Log = LogManager.GetCurrentClassLogger(); private readonly IEmailSender _emailSender; /// /// Durata cache lunga IN SECONDI /// private int cacheTtlLong = 60 * 5; /// /// Durata cache breve IN SECONDI /// private int cacheTtlShort = 60 * 1; /// /// Oggetto per connessione a REDIS /// private IConnectionMultiplexer redisConn; /// /// Oggetto DB redis da impiegare x chiamate R/W /// private IDatabase redisDb = null!; /// /// Generatore random /// private Random rnd = new Random(); #endregion Private Fields #region Private Methods /// /// Esegue flush memoria redis dato pattern /// /// /// private async Task ExecFlushRedisPattern(RedisValue pattern) { bool answ = false; var listEndpoints = redisConn.GetEndPoints(); foreach (var endPoint in listEndpoints) { //var server = redisConnAdmin.GetServer(listEndpoints[0]); var server = redisConn.GetServer(endPoint); if (server != null) { var keyList = server.Keys(redisDb.Database, pattern); foreach (var item in keyList) { await redisDb.KeyDeleteAsync(item); } answ = true; } } return answ; } /// /// Recupera UN SINGOLO VALORE dalla hash per un dato field /// /// /// /// private string RedisHashGet(RedisKey hashKey, string hashField) { string answ = ""; var hasVal = redisDb.HashExists(hashKey, hashField); if (hasVal) { answ = redisDb.HashGet(hashKey, hashField).ToString(); } return answ; } /// /// Recupero HashSet redis come Dictionary /// /// private Dictionary RedisHashGetDict(RedisKey hashKey) { Dictionary answ = new Dictionary(); try { answ = redisDb .HashGetAll(hashKey) .ToDictionary(x => $"{x.Name}", x => $"{x.Value}"); } catch (Exception exc) { Log.Info($"Errore RedisHashGetDict | hashKey: {hashKey}{Environment.NewLine}{exc}"); } return answ; } /// /// Recupera UN SINGOLO VALORE dalla hash per un dato field come INT /// /// Redis Key for Hashlist /// Requested key on list /// Value as Int private int RedisHashGetInt(RedisKey hashKey, string hashField) { int result = 0; var rawRes = RedisHashGet(hashKey, hashField); if (!string.IsNullOrEmpty(rawRes)) { int.TryParse($"{rawRes}", out result); } return result; } /// /// Aggiunta KVP in HashSet Redis /// /// /// private bool RedisHashSet(RedisKey currKey, string hashField, string value) { bool fatto = false; try { // salvo! redisDb.HashSet(currKey, hashField, value); fatto = true; } catch (Exception exc) { Log.Error($"Eccezione in RedisHashSet | currKey: {currKey}{Environment.NewLine}{exc}"); } return fatto; } /// /// Salvataggio Dictionary come HashSet Redis /// /// /// private bool RedisHashSetDict(RedisKey hashKey, Dictionary dict) { bool fatto = false; try { HashEntry[] data2ins = new HashEntry[dict.Count]; int i = 0; foreach (KeyValuePair kvp in dict) { data2ins[i] = new HashEntry(kvp.Key, kvp.Value); i++; } // salvo! redisDb.HashSet(hashKey, data2ins); fatto = true; } catch (Exception exc) { Log.Error($"Eccezione in RedisHashSet | hashKey: {hashKey}{Environment.NewLine}{exc}"); } return fatto; } /// /// Salvataggio Dictionary come HashSet Redis /// /// /// /// private bool RedisHashSetDict(RedisKey hashKey, Dictionary dict, TimeSpan ttl) { bool fatto = false; try { HashEntry[] data2ins = new HashEntry[dict.Count]; int i = 0; foreach (KeyValuePair kvp in dict) { data2ins[i] = new HashEntry(kvp.Key, kvp.Value); i++; } // salvo! redisDb.HashSet(hashKey, data2ins); redisDb.KeyExpire(hashKey, ttl); fatto = true; } catch (Exception exc) { Log.Error($"Eccezione in RedisHashSet | hashKey: {hashKey}{Environment.NewLine}{exc}"); } return fatto; } #endregion Private Methods } }