using System.ComponentModel; using System.Linq.Expressions; using System.Reflection; using Nop.Core; using Nop.Core.Caching; using Nop.Core.Configuration; using Nop.Core.Domain.Configuration; using Nop.Data; namespace Nop.Services.Configuration; /// /// Setting manager /// public partial class SettingService : ISettingService { #region Fields protected readonly IRepository _settingRepository; protected readonly IStaticCacheManager _staticCacheManager; #endregion #region Ctor public SettingService(IRepository settingRepository, IStaticCacheManager staticCacheManager) { _settingRepository = settingRepository; _staticCacheManager = staticCacheManager; } #endregion #region Utilities /// /// Gets all settings /// /// /// A task that represents the asynchronous operation /// The task result contains the settings /// protected virtual async Task>> GetAllSettingsDictionaryAsync() { return await _staticCacheManager.GetAsync(NopSettingsDefaults.SettingsAllAsDictionaryCacheKey, async () => { var settings = await GetAllSettingsAsync(); var dictionary = new Dictionary>(); foreach (var s in settings) { var resourceName = s.Name.ToLowerInvariant(); var settingForCaching = new Setting { Id = s.Id, Name = s.Name, Value = s.Value, StoreId = s.StoreId }; if (!dictionary.TryGetValue(resourceName, out var value)) //first setting dictionary.Add(resourceName, new List { settingForCaching }); else //already added //most probably it's the setting with the same name but for some certain store (storeId > 0) value.Add(settingForCaching); } return dictionary; }); } /// /// Gets all settings /// /// /// Settings /// protected virtual IDictionary> GetAllSettingsDictionary() { return _staticCacheManager.Get(NopSettingsDefaults.SettingsAllAsDictionaryCacheKey, () => { var settings = GetAllSettings(); var dictionary = new Dictionary>(); foreach (var s in settings) { var resourceName = s.Name.ToLowerInvariant(); var settingForCaching = new Setting { Id = s.Id, Name = s.Name, Value = s.Value, StoreId = s.StoreId }; if (!dictionary.TryGetValue(resourceName, out var value)) //first setting dictionary.Add(resourceName, new List { settingForCaching }); else //already added //most probably it's the setting with the same name but for some certain store (storeId > 0) value.Add(settingForCaching); } return dictionary; }); } /// /// Set setting value /// /// Type /// Key /// Value /// Store identifier /// A value indicating whether to clear cache after setting update /// A task that represents the asynchronous operation protected virtual async Task SetSettingAsync(Type type, string key, object value, int storeId = 0, bool clearCache = true) { ArgumentNullException.ThrowIfNull(key); key = key.Trim().ToLowerInvariant(); var valueStr = TypeDescriptor.GetConverter(type).ConvertToInvariantString(value); var allSettings = await GetAllSettingsDictionaryAsync(); var settingForCaching = allSettings.TryGetValue(key, out var settings) ? settings.FirstOrDefault(x => x.StoreId == storeId) : null; if (settingForCaching != null) { //update var setting = await GetSettingByIdAsync(settingForCaching.Id); setting.Value = valueStr; await UpdateSettingAsync(setting, clearCache); } else { //insert var setting = new Setting { Name = key, Value = valueStr, StoreId = storeId }; await InsertSettingAsync(setting, clearCache); } } /// /// Set setting value /// /// Type /// Key /// Value /// Store identifier /// A value indicating whether to clear cache after setting update protected virtual void SetSetting(Type type, string key, object value, int storeId = 0, bool clearCache = true) { ArgumentNullException.ThrowIfNull(key); key = key.Trim().ToLowerInvariant(); var valueStr = TypeDescriptor.GetConverter(type).ConvertToInvariantString(value); var allSettings = GetAllSettingsDictionary(); var settingForCaching = allSettings.TryGetValue(key, out var settings) ? settings.FirstOrDefault(x => x.StoreId == storeId) : null; if (settingForCaching != null) { //update var setting = GetSettingById(settingForCaching.Id); setting.Value = valueStr; UpdateSetting(setting, clearCache); } else { //insert var setting = new Setting { Name = key, Value = valueStr, StoreId = storeId }; InsertSetting(setting, clearCache); } } #endregion #region Methods /// /// Adds a setting /// /// Setting /// A value indicating whether to clear cache after setting update /// A task that represents the asynchronous operation public virtual async Task InsertSettingAsync(Setting setting, bool clearCache = true) { await _settingRepository.InsertAsync(setting); //cache if (clearCache) await ClearCacheAsync(); } /// /// Adds a setting /// /// Setting /// A value indicating whether to clear cache after setting update public virtual void InsertSetting(Setting setting, bool clearCache = true) { _settingRepository.Insert(setting); //cache if (clearCache) ClearCache(); } /// /// Updates a setting /// /// Setting /// A value indicating whether to clear cache after setting update /// A task that represents the asynchronous operation public virtual async Task UpdateSettingAsync(Setting setting, bool clearCache = true) { ArgumentNullException.ThrowIfNull(setting); await _settingRepository.UpdateAsync(setting); //cache if (clearCache) await ClearCacheAsync(); } /// /// Updates a setting /// /// Setting /// A value indicating whether to clear cache after setting update public virtual void UpdateSetting(Setting setting, bool clearCache = true) { ArgumentNullException.ThrowIfNull(setting); _settingRepository.Update(setting); //cache if (clearCache) ClearCache(); } /// /// Deletes a setting /// /// Setting /// A task that represents the asynchronous operation public virtual async Task DeleteSettingAsync(Setting setting) { await _settingRepository.DeleteAsync(setting); //cache await ClearCacheAsync(); } /// /// Deletes a setting /// /// Setting public virtual void DeleteSetting(Setting setting) { _settingRepository.Delete(setting); //cache ClearCache(); } /// /// Deletes settings /// /// Settings /// A task that represents the asynchronous operation public virtual async Task DeleteSettingsAsync(IList settings) { await _settingRepository.DeleteAsync(settings); //cache await ClearCacheAsync(); } /// /// Gets a setting by identifier /// /// Setting identifier /// /// A task that represents the asynchronous operation /// The task result contains the setting /// public virtual async Task GetSettingByIdAsync(int settingId) { return await _settingRepository.GetByIdAsync(settingId, cache => default); } /// /// Gets a setting by identifier /// /// Setting identifier /// /// The setting /// public virtual Setting GetSettingById(int settingId) { return _settingRepository.GetById(settingId, cache => default); } /// /// Get setting by key /// /// Key /// Store identifier /// A value indicating whether a shared (for all stores) value should be loaded if a value specific for a certain is not found /// /// A task that represents the asynchronous operation /// The task result contains the setting /// public virtual async Task GetSettingAsync(string key, int storeId = 0, bool loadSharedValueIfNotFound = false) { if (string.IsNullOrEmpty(key)) return null; var settings = await GetAllSettingsDictionaryAsync(); key = key.Trim().ToLowerInvariant(); if (!settings.TryGetValue(key, out var value)) return null; var settingsByKey = value; var setting = settingsByKey.FirstOrDefault(x => x.StoreId == storeId); //load shared value? if (setting == null && storeId > 0 && loadSharedValueIfNotFound) setting = settingsByKey.FirstOrDefault(x => x.StoreId == 0); return setting != null ? await GetSettingByIdAsync(setting.Id) : null; } /// /// Get setting by key /// /// Key /// Store identifier /// A value indicating whether a shared (for all stores) value should be loaded if a value specific for a certain is not found /// /// The setting /// public virtual Setting GetSetting(string key, int storeId = 0, bool loadSharedValueIfNotFound = false) { if (string.IsNullOrEmpty(key)) return null; var settings = GetAllSettingsDictionary(); key = key.Trim().ToLowerInvariant(); if (!settings.TryGetValue(key, out var value)) return null; var settingsByKey = value; var setting = settingsByKey.FirstOrDefault(x => x.StoreId == storeId); //load shared value? if (setting == null && storeId > 0 && loadSharedValueIfNotFound) setting = settingsByKey.FirstOrDefault(x => x.StoreId == 0); return setting != null ? GetSettingById(setting.Id) : null; } /// /// Get setting value by key /// /// Type /// Key /// Default value /// Store identifier /// A value indicating whether a shared (for all stores) value should be loaded if a value specific for a certain is not found /// /// A task that represents the asynchronous operation /// The task result contains the setting value /// public virtual async Task GetSettingByKeyAsync(string key, T defaultValue = default, int storeId = 0, bool loadSharedValueIfNotFound = false) { if (string.IsNullOrEmpty(key)) return defaultValue; var settings = await GetAllSettingsDictionaryAsync(); key = key.Trim().ToLowerInvariant(); if (!settings.TryGetValue(key, out var value)) return defaultValue; var settingsByKey = value; var setting = settingsByKey.FirstOrDefault(x => x.StoreId == storeId); //load shared value? if (setting == null && storeId > 0 && loadSharedValueIfNotFound) setting = settingsByKey.FirstOrDefault(x => x.StoreId == 0); return setting != null ? CommonHelper.To(setting.Value) : defaultValue; } /// /// Get setting value by key /// /// Type /// Key /// Default value /// Store identifier /// A value indicating whether a shared (for all stores) value should be loaded if a value specific for a certain is not found /// /// Setting value /// public virtual T GetSettingByKey(string key, T defaultValue = default, int storeId = 0, bool loadSharedValueIfNotFound = false) { if (string.IsNullOrEmpty(key)) return defaultValue; var settings = GetAllSettingsDictionary(); key = key.Trim().ToLowerInvariant(); if (!settings.TryGetValue(key, out var value)) return defaultValue; var settingsByKey = value; var setting = settingsByKey.FirstOrDefault(x => x.StoreId == storeId); //load shared value? if (setting == null && storeId > 0 && loadSharedValueIfNotFound) setting = settingsByKey.FirstOrDefault(x => x.StoreId == 0); return setting != null ? CommonHelper.To(setting.Value) : defaultValue; } /// /// Set setting value /// /// Type /// Key /// Value /// Store identifier /// A value indicating whether to clear cache after setting update /// A task that represents the asynchronous operation public virtual async Task SetSettingAsync(string key, T value, int storeId = 0, bool clearCache = true) { await SetSettingAsync(typeof(T), key, value, storeId, clearCache); } /// /// Set setting value /// /// Type /// Key /// Value /// Store identifier /// A value indicating whether to clear cache after setting update public virtual void SetSetting(string key, T value, int storeId = 0, bool clearCache = true) { SetSetting(typeof(T), key, value, storeId, clearCache); } /// /// Gets all settings /// /// /// A task that represents the asynchronous operation /// The task result contains the settings /// public virtual async Task> GetAllSettingsAsync() { var settings = await _settingRepository.GetAllAsync(query => { return from s in query orderby s.Name, s.StoreId select s; }, cache => default); return settings; } /// /// Gets all settings /// /// /// Settings /// public virtual IList GetAllSettings() { var settings = _settingRepository.GetAll(query => from s in query orderby s.Name, s.StoreId select s, cache => default); return settings; } /// /// Determines whether a setting exists /// /// Entity type /// Property type /// Entity /// Key selector /// Store identifier /// /// A task that represents the asynchronous operation /// The task result contains the true -setting exists; false - does not exist /// public virtual async Task SettingExistsAsync(T settings, Expression> keySelector, int storeId = 0) where T : ISettings, new() { var key = GetSettingKey(settings, keySelector); var setting = await GetSettingByKeyAsync(key, storeId: storeId); return setting != null; } /// /// Determines whether a setting exists /// /// Entity type /// Property type /// Entity /// Key selector /// Store identifier /// /// The true -setting exists; false - does not exist /// public virtual bool SettingExists(T settings, Expression> keySelector, int storeId = 0) where T : ISettings, new() { var key = GetSettingKey(settings, keySelector); var setting = GetSettingByKey(key, storeId: storeId); return setting != null; } /// /// Load settings /// /// Type /// Store identifier for which settings should be loaded /// A task that represents the asynchronous operation public virtual async Task LoadSettingAsync(int storeId = 0) where T : ISettings, new() { return (T)await LoadSettingAsync(typeof(T), storeId); } /// /// Load settings /// /// Type /// Store identifier for which settings should be loaded public virtual T LoadSetting(int storeId = 0) where T : ISettings, new() { return (T)LoadSetting(typeof(T), storeId); } /// /// Load settings /// /// Type /// Store identifier for which settings should be loaded /// A task that represents the asynchronous operation public virtual async Task LoadSettingAsync(Type type, int storeId = 0) { var settings = Activator.CreateInstance(type); if (!DataSettingsManager.IsDatabaseInstalled()) return settings as ISettings; foreach (var prop in type.GetProperties()) { // get properties we can read and write to if (!prop.CanRead || !prop.CanWrite) continue; var key = type.Name + "." + prop.Name; //load by store var setting = await GetSettingByKeyAsync(key, storeId: storeId, loadSharedValueIfNotFound: true); if (setting == null) continue; if (!TypeDescriptor.GetConverter(prop.PropertyType).CanConvertFrom(typeof(string))) continue; if (!TypeDescriptor.GetConverter(prop.PropertyType).IsValid(setting)) continue; var value = TypeDescriptor.GetConverter(prop.PropertyType).ConvertFromInvariantString(setting); //set property prop.SetValue(settings, value, null); } return settings as ISettings; } /// /// Load settings /// /// Type /// Store identifier for which settings should be loaded /// Settings public virtual ISettings LoadSetting(Type type, int storeId = 0) { var settings = Activator.CreateInstance(type); if (!DataSettingsManager.IsDatabaseInstalled()) return settings as ISettings; foreach (var prop in type.GetProperties()) { // get properties we can read and write to if (!prop.CanRead || !prop.CanWrite) continue; var key = type.Name + "." + prop.Name; //load by store var setting = GetSettingByKey(key, storeId: storeId, loadSharedValueIfNotFound: true); if (setting == null) continue; if (!TypeDescriptor.GetConverter(prop.PropertyType).CanConvertFrom(typeof(string))) continue; if (!TypeDescriptor.GetConverter(prop.PropertyType).IsValid(setting)) continue; var value = TypeDescriptor.GetConverter(prop.PropertyType).ConvertFromInvariantString(setting); //set property prop.SetValue(settings, value, null); } return settings as ISettings; } /// /// Save settings object /// /// Type /// Store identifier /// Setting instance /// A task that represents the asynchronous operation public virtual async Task SaveSettingAsync(T settings, int storeId = 0) where T : ISettings, new() { /* We do not clear cache after each setting update. * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ foreach (var prop in typeof(T).GetProperties()) { // get properties we can read and write to if (!prop.CanRead || !prop.CanWrite) continue; if (!TypeDescriptor.GetConverter(prop.PropertyType).CanConvertFrom(typeof(string))) continue; var key = typeof(T).Name + "." + prop.Name; var value = prop.GetValue(settings, null); if (value != null) await SetSettingAsync(prop.PropertyType, key, value, storeId, false); else await SetSettingAsync(key, string.Empty, storeId, false); } //and now clear cache await ClearCacheAsync(); } /// /// Save settings object /// /// Type /// Store identifier /// Setting instance public virtual void SaveSetting(T settings, int storeId = 0) where T : ISettings, new() { /* We do not clear cache after each setting update. * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ foreach (var prop in typeof(T).GetProperties()) { // get properties we can read and write to if (!prop.CanRead || !prop.CanWrite) continue; if (!TypeDescriptor.GetConverter(prop.PropertyType).CanConvertFrom(typeof(string))) continue; var key = typeof(T).Name + "." + prop.Name; var value = prop.GetValue(settings, null); if (value != null) SetSetting(prop.PropertyType, key, value, storeId, false); else SetSetting(key, string.Empty, storeId, false); } //and now clear cache ClearCache(); } /// /// Save settings object /// /// Entity type /// Property type /// Settings /// Key selector /// Store ID /// A value indicating whether to clear cache after setting update /// A task that represents the asynchronous operation public virtual async Task SaveSettingAsync(T settings, Expression> keySelector, int storeId = 0, bool clearCache = true) where T : ISettings, new() { if (keySelector.Body is not MemberExpression member) throw new ArgumentException($"Expression '{keySelector}' refers to a method, not a property."); var propInfo = member.Member as PropertyInfo ?? throw new ArgumentException($"Expression '{keySelector}' refers to a field, not a property."); var key = GetSettingKey(settings, keySelector); var value = (TPropType)propInfo.GetValue(settings, null); if (value != null) await SetSettingAsync(key, value, storeId, clearCache); else await SetSettingAsync(key, string.Empty, storeId, clearCache); } /// /// Save settings object /// /// Entity type /// Property type /// Settings /// Key selector /// Store ID /// A value indicating whether to clear cache after setting update public virtual void SaveSetting(T settings, Expression> keySelector, int storeId = 0, bool clearCache = true) where T : ISettings, new() { if (keySelector.Body is not MemberExpression member) throw new ArgumentException($"Expression '{keySelector}' refers to a method, not a property."); var propInfo = member.Member as PropertyInfo ?? throw new ArgumentException($"Expression '{keySelector}' refers to a field, not a property."); var key = GetSettingKey(settings, keySelector); var value = (TPropType)propInfo.GetValue(settings, null); if (value != null) SetSetting(key, value, storeId, clearCache); else SetSetting(key, string.Empty, storeId, clearCache); } /// /// Save settings object (per store). If the setting is not overridden per store then it'll be delete /// /// Entity type /// Property type /// Settings /// Key selector /// A value indicating whether to setting is overridden in some store /// Store ID /// A value indicating whether to clear cache after setting update /// A task that represents the asynchronous operation public virtual async Task SaveSettingOverridablePerStoreAsync(T settings, Expression> keySelector, bool overrideForStore, int storeId = 0, bool clearCache = true) where T : ISettings, new() { if (overrideForStore || storeId == 0) await SaveSettingAsync(settings, keySelector, storeId, clearCache); else if (storeId > 0) await DeleteSettingAsync(settings, keySelector, storeId); } /// /// Delete all settings /// /// Type /// A task that represents the asynchronous operation public virtual async Task DeleteSettingAsync() where T : ISettings, new() { var settingsToDelete = new List(); var allSettings = await GetAllSettingsAsync(); foreach (var prop in typeof(T).GetProperties()) { var key = typeof(T).Name + "." + prop.Name; settingsToDelete.AddRange(allSettings.Where(x => x.Name.Equals(key, StringComparison.InvariantCultureIgnoreCase))); } await DeleteSettingsAsync(settingsToDelete); } /// /// Delete settings object /// /// Entity type /// Property type /// Settings /// Key selector /// Store ID /// A task that represents the asynchronous operation public virtual async Task DeleteSettingAsync(T settings, Expression> keySelector, int storeId = 0) where T : ISettings, new() { var key = GetSettingKey(settings, keySelector); key = key.Trim().ToLowerInvariant(); var allSettings = await GetAllSettingsDictionaryAsync(); var settingForCaching = allSettings.TryGetValue(key, out var settings_) ? settings_.FirstOrDefault(x => x.StoreId == storeId) : null; if (settingForCaching == null) return; //update var setting = await GetSettingByIdAsync(settingForCaching.Id); await DeleteSettingAsync(setting); } /// /// Clear cache /// /// A task that represents the asynchronous operation public virtual async Task ClearCacheAsync() { await _staticCacheManager.RemoveByPrefixAsync(NopEntityCacheDefaults.Prefix); } /// /// Clear cache /// public virtual void ClearCache() { _staticCacheManager.RemoveByPrefix(NopEntityCacheDefaults.Prefix); } /// /// Get setting key (stored into database) /// /// Type of settings /// Property type /// Settings /// Key selector /// Key public virtual string GetSettingKey(TSettings settings, Expression> keySelector) where TSettings : ISettings, new() { if (keySelector.Body is not MemberExpression member) throw new ArgumentException($"Expression '{keySelector}' refers to a method, not a property."); if (member.Member is not PropertyInfo propInfo) throw new ArgumentException($"Expression '{keySelector}' refers to a field, not a property."); var key = $"{typeof(TSettings).Name}.{propInfo.Name}"; return key; } #endregion }