use deep cloning to not muttate cache

This commit is contained in:
sbwalker 2024-09-19 09:41:11 -04:00
parent f2c8d80ff8
commit 78177f7890
7 changed files with 275 additions and 98 deletions

View File

@ -441,7 +441,7 @@ namespace Oqtane.Repository
pageModule.Module.PermissionList = new List<Permission>();
foreach (var permission in pageTemplateModule.PermissionList)
{
pageModule.Module.PermissionList.Add(permission.Clone(permission));
pageModule.Module.PermissionList.Add(permission.Clone());
}
pageModule.Module.AllPages = false;
pageModule.Module.IsDeleted = false;

View File

@ -71,7 +71,7 @@ namespace Oqtane.Services
});
// clone object so that cache is not mutated
site = site.Clone(site);
site = site.Clone();
// trim site settings based on user permissions
site.Settings = site.Settings
@ -256,25 +256,28 @@ namespace Oqtane.Services
public Task<List<Module>> GetModulesAsync(int siteId, int pageId)
{
var alias = _tenantManager.GetAlias();
var sitemodules = _cache.GetOrCreate($"modules:{alias.SiteKey}", entry =>
var modules = _cache.GetOrCreate($"modules:{alias.SiteKey}", entry =>
{
entry.SlidingExpiration = TimeSpan.FromMinutes(30);
return GetPageModules(siteId);
});
// clone object so that cache is not mutated
modules = modules.ConvertAll(module => module.Clone());
// trim modules for current page based on user permissions
var modules = new List<Module>();
foreach (Module module in sitemodules.Where(item => (item.PageId == pageId || pageId == -1) && !item.IsDeleted && _userPermissions.IsAuthorized(_accessor.HttpContext.User, PermissionNames.View, item.PermissionList)))
var pagemodules = new List<Module>();
foreach (Module module in modules.Where(item => (item.PageId == pageId || pageId == -1) && !item.IsDeleted && _userPermissions.IsAuthorized(_accessor.HttpContext.User, PermissionNames.View, item.PermissionList)))
{
if (Utilities.IsEffectiveAndNotExpired(module.EffectiveDate, module.ExpiryDate) || _userPermissions.IsAuthorized(_accessor.HttpContext.User, PermissionNames.Edit, module.PermissionList))
{
module.Settings = module.Settings
.Where(item => !item.Value.StartsWith(_private) || _userPermissions.IsAuthorized(_accessor.HttpContext.User, PermissionNames.Edit, module.PermissionList))
.ToDictionary(setting => setting.Key, setting => setting.Value.Replace(_private, ""));
modules.Add(module);
pagemodules.Add(module);
}
}
return Task.FromResult(modules);
return Task.FromResult(pagemodules);
}
private List<Module> GetPageModules(int siteId)

View File

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace Oqtane.Models
@ -40,5 +39,18 @@ namespace Oqtane.Models
/// Version of the satellite assembly
/// </summary>
public string Version { get; set; }
public Language Clone()
{
return new Language
{
LanguageId = LanguageId,
SiteId = SiteId,
Name = Name,
Code = Code,
IsDefault = IsDefault,
Version = Version
};
}
}
}

View File

@ -2,6 +2,7 @@ using Oqtane.Shared;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
@ -28,11 +29,18 @@ namespace Oqtane.Models
public string ModuleDefinitionName { get; set; }
/// <summary>
/// Determines if this Module Instance should be shown on all pages of the current <see cref="Site"/>
/// Determines if this module should be shown on all pages of the current <see cref="Site"/>
/// </summary>
public bool AllPages { get; set; }
#region IDeletable Properties (note that these are NotMapped and are only used for storing PageModule properties)
/// <summary>
/// Reference to the <see cref="ModuleDefinition"/> used for this module.
/// </summary>
[NotMapped]
public ModuleDefinition ModuleDefinition { get; set; }
#region IDeletable Properties
[NotMapped]
public string DeletedBy { get; set; }
@ -43,14 +51,23 @@ namespace Oqtane.Models
#endregion
/// <summary>
/// list of permissions for this module
/// </summary>
[NotMapped]
public List<Permission> PermissionList { get; set; }
/// <summary>
/// List of settings for this module
/// </summary>
[NotMapped]
public Dictionary<string, string> Settings { get; set; }
#region PageModule properties
/// <summary>
/// The id of the PageModule instance
/// </summary>
[NotMapped]
public int PageModuleId { get; set; }
@ -60,24 +77,39 @@ namespace Oqtane.Models
[NotMapped]
public int PageId { get; set; }
/// <summary>
/// Title of the pagemodule instance
/// </summary>
[NotMapped]
public string Title { get; set; }
/// <summary>
/// The Pane this module is shown in.
/// The pane where this pagemodule instance will be injected on the page
/// </summary>
[NotMapped]
public string Pane { get; set; }
/// <summary>
/// The order of the pagemodule instance within the Pane
/// </summary>
[NotMapped]
public int Order { get; set; }
/// <summary>
/// The container for the pagemodule instance
/// </summary>
[NotMapped]
public string ContainerType { get; set; }
/// <summary>
/// Start of when this module is visible. See also <see cref="ExpiryDate"/>
/// </summary>
[NotMapped]
public DateTime? EffectiveDate { get; set; }
/// <summary>
/// End of when this module is visible. See also <see cref="EffectiveDate"/>
/// </summary>
[NotMapped]
public DateTime? ExpiryDate { get; set; }
@ -85,38 +117,67 @@ namespace Oqtane.Models
#region SiteRouter properties
/// <summary>
/// Stores the type name for the module component being rendered
/// </summary>
[NotMapped]
public string ModuleType { get; set; }
/// <summary>
/// The position of the module instance in a pane
/// </summary>
[NotMapped]
public int PaneModuleIndex { get; set; }
/// <summary>
/// The number of modules in a pane
/// </summary>
[NotMapped]
public int PaneModuleCount { get; set; }
/// <summary>
/// A unique id to help determine if a component should be rendered
/// </summary>
[NotMapped]
public Guid RenderId { get; set; }
#endregion
#region ModuleDefinition
#region IModuleControl properties
/// <summary>
/// Reference to the <see cref="ModuleDefinition"/> used for this module.
/// TODO: todoc - unclear if this is always populated
/// The minimum access level to view the component being rendered
/// </summary>
[NotMapped]
public ModuleDefinition ModuleDefinition { get; set; }
#endregion
#region IModuleControl properties
[NotMapped]
public SecurityAccessLevel SecurityAccessLevel { get; set; }
/// <summary>
/// An optional title for the component
/// </summary>
[NotMapped]
public string ControlTitle { get; set; }
/// <summary>
/// Optional mapping of Url actions to a component
/// </summary>
[NotMapped]
public string Actions { get; set; }
/// <summary>
/// Optionally indicate if a compoent should not be rendered with the default modal admin container
/// </summary>
[NotMapped]
public bool UseAdminContainer { get; set; }
/// <summary>
/// Optionally specify the render mode for the component (overrides the Site setting)
/// </summary>
[NotMapped]
public string RenderMode{ get; set; }
public string RenderMode { get; set; }
/// <summary>
/// Optionally specify id the component should be prerendered (overrides the Site setting)
/// </summary>
[NotMapped]
public bool? Prerender { get; set; }
@ -140,5 +201,34 @@ namespace Oqtane.Models
}
#endregion
public Module Clone()
{
return new Module
{
ModuleId = ModuleId,
SiteId = SiteId,
ModuleDefinitionName = ModuleDefinitionName,
AllPages = AllPages,
PageModuleId = PageModuleId,
PageId = PageId,
Title = Title,
Pane = Pane,
Order = Order,
ContainerType = ContainerType,
EffectiveDate = EffectiveDate,
ExpiryDate = ExpiryDate,
CreatedBy = CreatedBy,
CreatedOn = CreatedOn,
ModifiedBy = ModifiedBy,
ModifiedOn = ModifiedOn,
DeletedBy = DeletedBy,
DeletedOn = DeletedOn,
IsDeleted = IsDeleted,
ModuleDefinition = ModuleDefinition,
PermissionList = PermissionList.ConvertAll(permission => permission.Clone()),
Settings = Settings.ToDictionary(setting => setting.Key, setting => setting.Value)
};
}
}
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
@ -75,33 +76,68 @@ namespace Oqtane.Models
public string BodyContent { get; set; }
/// <summary>
/// Icon file for this page.
/// TODO: unclear what this is for, and what icon library is used. Probably FontAwesome?
/// Icon class name for this page
/// </summary>
public string Icon { get; set; }
/// <summary>
/// Indicates if this page should be included in navigation menu
/// </summary>
public bool IsNavigation { get; set; }
/// <summary>
/// Indicates if this page should be clickable in navigation menu
/// </summary>
public bool IsClickable { get; set; }
public int? UserId { get; set; }
/// <summary>
/// Start of when this assignment is valid. See also <see cref="ExpiryDate"/>
/// Indicates if page is personalizable ie. allows users to create custom versions of the page
/// </summary>
public DateTime? EffectiveDate { get; set; }
/// <summary>
/// End of when this assignment is valid. See also <see cref="EffectiveDate"/>
/// </summary>
public DateTime? ExpiryDate { get; set; }
public bool IsPersonalizable { get; set; }
#region IDeletable Properties
public string DeletedBy { get; set; }
public DateTime? DeletedOn { get; set; }
public bool IsDeleted { get; set; }
#endregion
/// <summary>
/// Reference to the user <see cref="User"/> who owns the personalized page
/// </summary>
public int? UserId { get; set; }
/// <summary>
/// List of Pane-names which this Page has.
/// Start of when this page is visible. See also <see cref="ExpiryDate"/>
/// </summary>
public DateTime? EffectiveDate { get; set; }
/// <summary>
/// End of when this page is visible. See also <see cref="EffectiveDate"/>
/// </summary>
public DateTime? ExpiryDate { get; set; }
/// <summary>
/// The hierarchical level of the page
/// </summary>
[NotMapped]
public int Level { get; set; }
/// <summary>
/// Determines if there are sub-pages. True if this page has sub-pages.
/// </summary>
[NotMapped]
public bool HasChildren { get; set; }
/// <summary>
/// List of permissions for this page
/// </summary>
[NotMapped]
public List<Permission> PermissionList { get; set; }
/// <summary>
/// List of settings for this page
/// </summary>
[NotMapped]
public Dictionary<string, string> Settings { get; set; }
#region SiteRouter properties
/// <summary>
/// List of Pane names for the Theme assigned to this page
/// </summary>
[NotMapped]
public List<string> Panes { get; set; }
@ -112,20 +148,15 @@ namespace Oqtane.Models
[NotMapped]
public List<Resource> Resources { get; set; }
[NotMapped]
public List<Permission> PermissionList { get; set; }
#endregion
[NotMapped]
public Dictionary<string, string> Settings { get; set; }
#region IDeletable Properties
[NotMapped]
public int Level { get; set; }
public string DeletedBy { get; set; }
public DateTime? DeletedOn { get; set; }
public bool IsDeleted { get; set; }
/// <summary>
/// Determines if there are sub-pages. True if this page has sub-pages.
/// </summary>
[NotMapped]
public bool HasChildren { get; set; }
#endregion
#region Deprecated Properties
@ -152,5 +183,42 @@ namespace Oqtane.Models
}
#endregion
public Page Clone()
{
return new Page
{
PageId = PageId,
SiteId = SiteId,
Path = Path,
ParentId = ParentId,
Name = Name,
Title = Title,
Order = Order,
Url = Url,
ThemeType = ThemeType,
DefaultContainerType = DefaultContainerType,
HeadContent = HeadContent,
BodyContent = BodyContent,
Icon = Icon,
IsNavigation = IsNavigation,
IsClickable = IsClickable,
UserId = UserId,
IsPersonalizable = IsPersonalizable,
EffectiveDate = EffectiveDate,
ExpiryDate = ExpiryDate,
Level = Level,
HasChildren = HasChildren,
CreatedBy = CreatedBy,
CreatedOn = CreatedOn,
ModifiedBy = ModifiedBy,
ModifiedOn = ModifiedOn,
DeletedBy = DeletedBy,
DeletedOn = DeletedOn,
IsDeleted = IsDeleted,
PermissionList = PermissionList.ConvertAll(permission => permission.Clone()),
Settings = Settings.ToDictionary(setting => setting.Key, setting => setting.Value)
};
}
}
}

View File

@ -101,17 +101,21 @@ namespace Oqtane.Models
IsAuthorized = isAuthorized;
}
public Permission Clone(Permission permission)
public Permission Clone()
{
return new Permission
{
SiteId = permission.SiteId,
EntityName = permission.EntityName,
EntityId = permission.EntityId,
PermissionName = permission.PermissionName,
RoleName = permission.RoleName,
UserId = permission.UserId,
IsAuthorized = permission.IsAuthorized
SiteId = SiteId,
EntityName = EntityName,
EntityId = EntityId,
PermissionName = PermissionName,
RoleName = RoleName,
UserId = UserId,
IsAuthorized = IsAuthorized,
CreatedBy = CreatedBy,
CreatedOn = CreatedOn,
ModifiedBy = ModifiedBy,
ModifiedOn = ModifiedOn
};
}

View File

@ -187,47 +187,47 @@ namespace Oqtane.Models
[NotMapped]
public List<Theme> Themes { get; set; }
public Site Clone(Site site)
public Site Clone()
{
return new Site
{
SiteId = site.SiteId,
TenantId = site.TenantId,
Name = site.Name,
LogoFileId = site.LogoFileId,
FaviconFileId = site.FaviconFileId,
DefaultThemeType = site.DefaultThemeType,
DefaultContainerType = site.DefaultContainerType,
AdminContainerType = site.AdminContainerType,
PwaIsEnabled = site.PwaIsEnabled,
PwaAppIconFileId = site.PwaAppIconFileId,
PwaSplashIconFileId = site.PwaSplashIconFileId,
AllowRegistration = site.AllowRegistration,
VisitorTracking = site.VisitorTracking,
CaptureBrokenUrls = site.CaptureBrokenUrls,
SiteGuid = site.SiteGuid,
RenderMode = site.RenderMode,
Runtime = site.Runtime,
Prerender = site.Prerender,
Hybrid = site.Hybrid,
Version = site.Version,
HomePageId = site.HomePageId,
HeadContent = site.HeadContent,
BodyContent = site.BodyContent,
IsDeleted = site.IsDeleted,
DeletedBy = site.DeletedBy,
DeletedOn = site.DeletedOn,
ImageFiles = site.ImageFiles,
UploadableFiles = site.UploadableFiles,
SiteTemplateType = site.SiteTemplateType,
CreatedBy = site.CreatedBy,
CreatedOn = site.CreatedOn,
ModifiedBy = site.ModifiedBy,
ModifiedOn = site.ModifiedOn,
Settings = site.Settings.ToDictionary(),
Pages = site.Pages.ToList(),
Languages = site.Languages.ToList(),
Themes = site.Themes.ToList()
SiteId = SiteId,
TenantId = TenantId,
Name = Name,
LogoFileId = LogoFileId,
FaviconFileId = FaviconFileId,
DefaultThemeType = DefaultThemeType,
DefaultContainerType = DefaultContainerType,
AdminContainerType = AdminContainerType,
PwaIsEnabled = PwaIsEnabled,
PwaAppIconFileId = PwaAppIconFileId,
PwaSplashIconFileId = PwaSplashIconFileId,
AllowRegistration = AllowRegistration,
VisitorTracking = VisitorTracking,
CaptureBrokenUrls = CaptureBrokenUrls,
SiteGuid = SiteGuid,
RenderMode = RenderMode,
Runtime = Runtime,
Prerender = Prerender,
Hybrid = Hybrid,
Version = Version,
HomePageId = HomePageId,
HeadContent = HeadContent,
BodyContent = BodyContent,
IsDeleted = IsDeleted,
DeletedBy = DeletedBy,
DeletedOn = DeletedOn,
ImageFiles = ImageFiles,
UploadableFiles = UploadableFiles,
SiteTemplateType = SiteTemplateType,
CreatedBy = CreatedBy,
CreatedOn = CreatedOn,
ModifiedBy = ModifiedBy,
ModifiedOn = ModifiedOn,
Settings = Settings.ToDictionary(setting => setting.Key, setting => setting.Value),
Pages = Pages.ConvertAll(page => page.Clone()),
Languages = Languages.ConvertAll(language => language.Clone()),
Themes = Themes
};
}