using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using NLog; using StackExchange.Redis; using System.Diagnostics; using WebDoorCreator.Core; using WebDoorCreator.Data.User; namespace WebDoorCreator.UI.Data { public class WDCUserService { #region Public Constructors public WDCUserService(IConnectionMultiplexer redisConnMult, UserManager userManager) { _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 }; // chiudo log Log.Info("Avviata classe WDCUserService"); } #endregion Public Constructors #region Public Events public event Action EA_CurrLanguage = null!; public event Action EA_UserClaims = null!; public event Action EA_UserCurrCompany = null!; public event Action EA_UserId = null!; public event Action EA_UserRole = null!; #endregion Public Events #region Public Properties public string? currLanguage { get => _currLanguage; set { if (_currLanguage != value) { _currLanguage = value; reportCurrentLanguage(); } } } public List? userClaims { get => _userClaims; set { if (_userClaims != value) { _userClaims = value; reportUserClaims(); } } } public int userCurrComp { get => _userCurrComp; set { if (_userCurrComp != value) { _userCurrComp = value; reportUserCurrCompany(); } } } public string userId { get => _userId; set { if (_userId != value) { _userId = value; reportUserId(); } } } public Dictionary UserPref { get; set; } = new Dictionary(); public string userRole { get => _userRole; set { if (_userRole != value) { _userRole = value; reportUserRole(); } } } #endregion Public Properties #region Public Methods /// /// Dati utente (TUTTI) da REDIS o DB /// /// public async Task> UserDataGetAll() { string source = "DB"; List dataResult = new List(); // cerco da cache string currKey = $"{Constants.rKeyUsersAll}"; 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) { dataResult = new List(); } else { dataResult = tempResult; } } else { dataResult = await _userManager.Users.ToListAsync(); rawData = JsonConvert.SerializeObject(dataResult, JSSettings); await redisDb.StringSetAsync(currKey, rawData, LongCache); } if (dataResult == null) { dataResult = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"UserDataGetAll | {source} in: {ts.TotalMilliseconds} ms"); return dataResult; } /// /// Dati utente (singolo x UserId) /// /// /// public async Task UserDataGetById(string UserId) { string source = "DB"; UserData dataResult = new UserData(); // cerco da cache string currKey = $"{Constants.rKeyUsersDataId}:{UserId}"; 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) { dataResult = new UserData(); } else { dataResult = tempResult; } } else { // Collezione dati raw var userRaw = await _userManager.FindByIdAsync(UserId); if (userRaw != null) { var userIdent = new IdentityUser { Id = userRaw.Id, UserName = userRaw.UserName, Email = userRaw.Email, PhoneNumber = userRaw.PhoneNumber, PasswordHash = "*****", EmailConfirmed = userRaw.EmailConfirmed }; // cerco ruoli & claims var UserRoles = await _userManager.GetRolesAsync(userIdent); var UserClaims = await _userManager.GetClaimsAsync(userIdent); // compongo output dataResult = new UserData() { Identity = userIdent, Roles = UserRoles.ToList(), Claims = UserClaims.ToList() }; rawData = JsonConvert.SerializeObject(dataResult, JSSettings); await redisDb.StringSetAsync(currKey, rawData, FastCache); } } if (dataResult == null) { dataResult = new UserData(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"UserDataGetById | {source} in: {ts.TotalMilliseconds} ms"); return dataResult; } /// /// Dati utente (filtrati da UserId) /// /// /// public async Task> UserDataGetFilt(string searchVal) { string source = "DB"; List dataResult = new List(); // cerco da cache string currKey = $"{Constants.rKeyUsersDataSearch}:{searchVal}"; 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) { dataResult = new List(); } else { dataResult = tempResult; } } else { // Collezione dati raw List RawList = new List(); //List UsersList = new List(); var allData = await UserDataGetAll(); 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() }; dataResult.Add(newItem); } rawData = JsonConvert.SerializeObject(dataResult, JSSettings); await redisDb.StringSetAsync(currKey, rawData, FastCache); } if (dataResult == null) { dataResult = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"UserDataGetFilt | {source} in: {ts.TotalMilliseconds} ms"); return dataResult; } /// /// Effettua reset dati utente... /// /// Indica reset completo (anche dati elenco di base) /// public async Task ResetUserDataCache(bool resetAll) { bool fatto = false; RedisValue pattern = new RedisValue($"{Constants.rKeyUsersAll}"); if (resetAll) { fatto = await ExecFlushRedisPattern(pattern); } // ora resetto il resto pattern = new RedisValue($"{Constants.rKeyUsersDataId}:*"); fatto = fatto && await ExecFlushRedisPattern(pattern); pattern = new RedisValue($"{Constants.rKeyUsersDataSearch}:*"); fatto = fatto && await ExecFlushRedisPattern(pattern); return fatto; } #endregion Public Methods #region Protected Methods protected void reportCurrentLanguage() { if (EA_CurrLanguage != null) { EA_CurrLanguage?.Invoke(); } } protected void reportUserClaims() { if (EA_UserClaims != null) { EA_UserClaims?.Invoke(); } } protected void reportUserCurrCompany() { if (EA_UserCurrCompany != null) { EA_UserCurrCompany?.Invoke(); } } protected void reportUserId() { if (EA_UserId != null) { EA_UserId?.Invoke(); } } protected void reportUserRole() { if (EA_UserRole != null) { EA_UserRole?.Invoke(); } } #endregion Protected Methods #region Private Fields private static JsonSerializerSettings? JSSettings; private static Logger Log = LogManager.GetCurrentClassLogger(); private readonly UserManager _userManager; /// /// 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!; private Random rnd = new Random(); #endregion Private Fields #region Private Properties private string? _currLanguage { get; set; } = null; private List? _userClaims { get; set; } = null; private int _userCurrComp { get; set; } private string _userId { get; set; } = ""; private string _userRole { get; set; } = ""; /// /// Durata cache breve (1 min circa + perturbazione percentuale +/-10%) /// private TimeSpan FastCache { get => TimeSpan.FromSeconds(cacheTtlShort * rnd.Next(900, 1100) / 1000); } /// /// Durata cache lunga (+ perturbazione percentuale +/-10%) /// private TimeSpan LongCache { get => TimeSpan.FromSeconds(cacheTtlLong * rnd.Next(900, 1100) / 1000); } /// /// Durata cache molto breve (10 sec circa + perturbazione percentuale +/-10%) /// private TimeSpan UltraFastCache { get => TimeSpan.FromSeconds(cacheTtlShort / 6 * rnd.Next(900, 1100) / 1000); } /// /// Durata cache lunga (+ perturbazione percentuale +/-10%) /// private TimeSpan UltraLongCache { get => TimeSpan.FromSeconds(cacheTtlLong * 10 * rnd.Next(900, 1100) / 1000); } #endregion Private Properties #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; } #endregion Private Methods //= -1; } }