performance improvements to reduce http and database interactions

This commit is contained in:
Shaun Walker 2022-08-12 16:47:51 -04:00
parent 4cae3f02ed
commit 3c6ebd7742
9 changed files with 163 additions and 30 deletions

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml.Linq;
using Oqtane.Models; using Oqtane.Models;
namespace Oqtane.UI namespace Oqtane.UI
@ -8,11 +9,8 @@ namespace Oqtane.UI
{ {
public Alias Alias { get; set; } public Alias Alias { get; set; }
public Site Site { get; set; } public Site Site { get; set; }
public List<Language> Languages { get; set; }
public List<Page> Pages { get; set; }
public Page Page { get; set; } public Page Page { get; set; }
public User User { get; set; } public User User { get; set; }
public List<Module> Modules { get; set; }
public Uri Uri { get; set; } public Uri Uri { get; set; }
public Dictionary<string, string> QueryString { get; set; } public Dictionary<string, string> QueryString { get; set; }
public string UrlParameters { get; set; } public string UrlParameters { get; set; }
@ -24,5 +22,18 @@ namespace Oqtane.UI
public int VisitorId { get; set; } public int VisitorId { get; set; }
public string RemoteIPAddress { get; set; } public string RemoteIPAddress { get; set; }
public string ReturnUrl { get; set; } public string ReturnUrl { get; set; }
public List<Page> Pages
{
get { return Site.Pages; }
}
public List<Module> Modules
{
get { return Site.Modules; }
}
public List<Language> Languages
{
get { return Site.Languages; }
}
} }
} }

View File

@ -7,10 +7,8 @@
@inject INavigationInterception NavigationInterception @inject INavigationInterception NavigationInterception
@inject ISyncService SyncService @inject ISyncService SyncService
@inject ISiteService SiteService @inject ISiteService SiteService
@inject ILanguageService LanguageService
@inject IPageService PageService @inject IPageService PageService
@inject IUserService UserService @inject IUserService UserService
@inject IModuleService ModuleService
@inject IUrlMappingService UrlMappingService @inject IUrlMappingService UrlMappingService
@inject ILogService LogService @inject ILogService LogService
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@ -181,14 +179,13 @@
if (PageState == null || refresh == UI.Refresh.Site) if (PageState == null || refresh == UI.Refresh.Site)
{ {
languages = await LanguageService.GetLanguagesAsync(site.SiteId); pages = site.Pages.Where(item => !item.IsDeleted).ToList();
pages = await PageService.GetPagesAsync(site.SiteId); languages = site.Languages;
pages = pages.Where(item => !item.IsDeleted).ToList();
} }
else else
{ {
languages = PageState.Languages;
pages = PageState.Pages; pages = PageState.Pages;
languages = PageState.Languages;
} }
if (PageState == null || refresh == UI.Refresh.Site) if (PageState == null || refresh == UI.Refresh.Site)
@ -226,8 +223,7 @@
if (PageState == null || refresh == UI.Refresh.Site) if (PageState == null || refresh == UI.Refresh.Site)
{ {
modules = await ModuleService.GetModulesAsync(site.SiteId); modules = site.Modules.Where(item => !item.IsDeleted).ToList();
modules = modules.Where(item => !item.IsDeleted).ToList();
} }
else else
{ {
@ -240,11 +236,8 @@
{ {
Alias = SiteState.Alias, Alias = SiteState.Alias,
Site = site, Site = site,
Languages = languages,
Pages = pages,
Page = page, Page = page,
User = user, User = user,
Modules = modules,
Uri = new Uri(_absoluteUri, UriKind.Absolute), Uri = new Uri(_absoluteUri, UriKind.Absolute),
QueryString = querystring, QueryString = querystring,
UrlParameters = route.UrlParameters, UrlParameters = route.UrlParameters,

View File

@ -24,9 +24,9 @@ namespace Oqtane.Controllers
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly Alias _alias; private readonly Alias _alias;
public LanguageController(ILanguageRepository language, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) public LanguageController(ILanguageRepository languages, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
{ {
_languages = language; _languages = languages;
_syncManager = syncManager; _syncManager = syncManager;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();

View File

@ -8,6 +8,11 @@ using Oqtane.Enums;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using System.Net; using System.Net;
using Oqtane.Security;
using System.Globalization;
using Microsoft.Extensions.Caching.Memory;
using Oqtane.Extensions;
using System;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -15,17 +20,31 @@ namespace Oqtane.Controllers
public class SiteController : Controller public class SiteController : Controller
{ {
private readonly ISiteRepository _sites; private readonly ISiteRepository _sites;
private readonly IPageRepository _pages;
private readonly IModuleRepository _modules;
private readonly IPageModuleRepository _pageModules;
private readonly IModuleDefinitionRepository _moduleDefinitions;
private readonly ILanguageRepository _languages;
private readonly IUserPermissions _userPermissions;
private readonly ISettingRepository _settings; private readonly ISettingRepository _settings;
private readonly ISyncManager _syncManager; private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly IMemoryCache _cache;
private readonly Alias _alias; private readonly Alias _alias;
public SiteController(ISiteRepository sites, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger) public SiteController(ISiteRepository sites, IPageRepository pages, IModuleRepository modules, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger, IMemoryCache cache)
{ {
_sites = sites; _sites = sites;
_pages = pages;
_modules = modules;
_pageModules = pageModules;
_moduleDefinitions = moduleDefinitions;
_languages = languages;
_userPermissions = userPermissions;
_settings = settings; _settings = settings;
_syncManager = syncManager; _syncManager = syncManager;
_logger = logger; _logger = logger;
_cache = cache;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
} }
@ -41,17 +60,92 @@ namespace Oqtane.Controllers
[HttpGet("{id}")] [HttpGet("{id}")]
public Site Get(int id) public Site Get(int id)
{ {
var site = _sites.GetSite(id); if (!User.Identity.IsAuthenticated)
{
return _cache.GetOrCreate($"site:{HttpContext.GetAlias().SiteKey}", entry =>
{
entry.SlidingExpiration = TimeSpan.FromMinutes(30);
return GetSite(id);
});
}
else
{
return GetSite(id);
}
}
private Site GetSite(int siteid)
{
var site = _sites.GetSite(siteid);
if (site.SiteId == _alias.SiteId) if (site.SiteId == _alias.SiteId)
{ {
// site settings
site.Settings = _settings.GetSettings(EntityNames.Site, site.SiteId) site.Settings = _settings.GetSettings(EntityNames.Site, site.SiteId)
.Where(item => !item.IsPrivate || User.IsInRole(RoleNames.Admin)) .Where(item => !item.IsPrivate || User.IsInRole(RoleNames.Admin))
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
// pages
List<Setting> settings = _settings.GetSettings(EntityNames.Page).ToList();
site.Pages = new List<Page>();
foreach (Page page in _pages.GetPages(site.SiteId))
{
if (_userPermissions.IsAuthorized(User, PermissionNames.View, page.Permissions))
{
page.Settings = settings.Where(item => item.EntityId == page.PageId)
.Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, page.Permissions))
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
site.Pages.Add(page);
}
}
// modules
List<ModuleDefinition> moduledefinitions = _moduleDefinitions.GetModuleDefinitions(site.SiteId).ToList();
settings = _settings.GetSettings(EntityNames.Module).ToList();
site.Modules = new List<Module>();
foreach (PageModule pagemodule in _pageModules.GetPageModules(site.SiteId))
{
if (_userPermissions.IsAuthorized(User, PermissionNames.View, pagemodule.Module.Permissions))
{
Module module = new Module();
module.SiteId = pagemodule.Module.SiteId;
module.ModuleDefinitionName = pagemodule.Module.ModuleDefinitionName;
module.AllPages = pagemodule.Module.AllPages;
module.Permissions = pagemodule.Module.Permissions;
module.CreatedBy = pagemodule.Module.CreatedBy;
module.CreatedOn = pagemodule.Module.CreatedOn;
module.ModifiedBy = pagemodule.Module.ModifiedBy;
module.ModifiedOn = pagemodule.Module.ModifiedOn;
module.DeletedBy = pagemodule.DeletedBy;
module.DeletedOn = pagemodule.DeletedOn;
module.IsDeleted = pagemodule.IsDeleted;
module.PageModuleId = pagemodule.PageModuleId;
module.ModuleId = pagemodule.ModuleId;
module.PageId = pagemodule.PageId;
module.Title = pagemodule.Title;
module.Pane = pagemodule.Pane;
module.Order = pagemodule.Order;
module.ContainerType = pagemodule.ContainerType;
module.ModuleDefinition = moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName);
module.Settings = settings.Where(item => item.EntityId == pagemodule.ModuleId)
.Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, pagemodule.Module.Permissions))
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
site.Modules.Add(module);
}
}
// languages
site.Languages = _languages.GetLanguages(site.SiteId).ToList();
var defaultCulture = CultureInfo.GetCultureInfo(Constants.DefaultCulture);
site.Languages.Add(new Language { Code = defaultCulture.Name, Name = defaultCulture.DisplayName, Version = Constants.Version, IsDefault = !site.Languages.Any(l => l.IsDefault) });
return site; return site;
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Get Attempt {SiteId}", id); _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Get Attempt {SiteId}", siteid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null; return null;
} }

View File

@ -1,17 +1,21 @@
using Microsoft.Extensions.Caching.Memory;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Oqtane.Repository; using System.Reflection;
namespace Oqtane.Infrastructure namespace Oqtane.Infrastructure
{ {
public class SyncManager : ISyncManager public class SyncManager : ISyncManager
{ {
private readonly IMemoryCache _cache;
private List<SyncEvent> SyncEvents { get; set; } private List<SyncEvent> SyncEvents { get; set; }
public SyncManager() public SyncManager(IMemoryCache cache)
{ {
_cache = cache;
SyncEvents = new List<SyncEvent>(); SyncEvents = new List<SyncEvent>();
} }
@ -28,6 +32,10 @@ namespace Oqtane.Infrastructure
public void AddSyncEvent(int tenantId, string entityName, int entityId, bool reload) public void AddSyncEvent(int tenantId, string entityName, int entityId, bool reload)
{ {
SyncEvents.Add(new SyncEvent { TenantId = tenantId, EntityName = entityName, EntityId = entityId, Reload = reload, ModifiedOn = DateTime.UtcNow }); SyncEvents.Add(new SyncEvent { TenantId = tenantId, EntityName = entityName, EntityId = entityId, Reload = reload, ModifiedOn = DateTime.UtcNow });
if (entityName == EntityNames.Site)
{
_cache.Remove($"site:{tenantId}:{entityId}");
}
// trim sync events // trim sync events
SyncEvents.RemoveAll(item => item.ModifiedOn < DateTime.UtcNow.AddHours(-1)); SyncEvents.RemoveAll(item => item.ModifiedOn < DateTime.UtcNow.AddHours(-1));
} }

View File

@ -1,6 +1,9 @@
using System.Linq; using System.Linq;
using Oqtane.Documentation; using Oqtane.Documentation;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Extensions.Caching.Memory;
using Oqtane.Infrastructure;
using System;
namespace Oqtane.Modules.HtmlText.Repository namespace Oqtane.Modules.HtmlText.Repository
{ {
@ -8,15 +11,23 @@ namespace Oqtane.Modules.HtmlText.Repository
public class HtmlTextRepository : IHtmlTextRepository, ITransientService public class HtmlTextRepository : IHtmlTextRepository, ITransientService
{ {
private readonly HtmlTextContext _db; private readonly HtmlTextContext _db;
private readonly IMemoryCache _cache;
private readonly SiteState _siteState;
public HtmlTextRepository(HtmlTextContext context) public HtmlTextRepository(HtmlTextContext context, IMemoryCache cache, SiteState siteState)
{ {
_db = context; _db = context;
_cache = cache;
_siteState = siteState;
} }
public IEnumerable<Models.HtmlText> GetHtmlTexts(int moduleId) public IEnumerable<Models.HtmlText> GetHtmlTexts(int moduleId)
{ {
return _db.HtmlText.Where(item => item.ModuleId == moduleId); return _cache.GetOrCreate($"HtmlText:{_siteState.Alias.SiteKey}:{moduleId}", entry =>
{
entry.SlidingExpiration = TimeSpan.FromMinutes(30);
return _db.HtmlText.Where(item => item.ModuleId == moduleId).ToList();
});
} }
public Models.HtmlText GetHtmlText(int htmlTextId) public Models.HtmlText GetHtmlText(int htmlTextId)
@ -28,6 +39,7 @@ namespace Oqtane.Modules.HtmlText.Repository
{ {
_db.HtmlText.Add(htmlText); _db.HtmlText.Add(htmlText);
_db.SaveChanges(); _db.SaveChanges();
ClearCache(htmlText.ModuleId);
return htmlText; return htmlText;
} }
@ -35,7 +47,13 @@ namespace Oqtane.Modules.HtmlText.Repository
{ {
Models.HtmlText htmlText = _db.HtmlText.FirstOrDefault(item => item.HtmlTextId == htmlTextId); Models.HtmlText htmlText = _db.HtmlText.FirstOrDefault(item => item.HtmlTextId == htmlTextId);
if (htmlText != null) _db.HtmlText.Remove(htmlText); if (htmlText != null) _db.HtmlText.Remove(htmlText);
ClearCache(htmlText.ModuleId);
_db.SaveChanges(); _db.SaveChanges();
} }
private void ClearCache(int moduleId)
{
_cache.Remove($"HtmlText:{_siteState.Alias.SiteKey}:{moduleId}");
}
} }
} }

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Oqtane.Extensions; using Oqtane.Extensions;
using Oqtane.Models; using Oqtane.Models;

View File

@ -109,7 +109,6 @@ namespace Oqtane.Models
#endregion #endregion
#region IModuleControl properties #region IModuleControl properties
// TODO: unclear why these are IModuleControl properties - there is no such interface
[NotMapped] [NotMapped]
public SecurityAccessLevel SecurityAccessLevel { get; set; } public SecurityAccessLevel SecurityAccessLevel { get; set; }
[NotMapped] [NotMapped]

View File

@ -83,6 +83,18 @@ namespace Oqtane.Models
/// </summary> /// </summary>
public string Version { get; set; } public string Version { get; set; }
[NotMapped]
public Dictionary<string, string> Settings { get; set; }
[NotMapped]
public List<Page> Pages { get; set; }
[NotMapped]
public List<Module> Modules { get; set; }
[NotMapped]
public List<Language> Languages { get; set; }
#region IAuditable Properties #region IAuditable Properties
/// <inheritdoc/> /// <inheritdoc/>
@ -101,17 +113,16 @@ namespace Oqtane.Models
public string DeletedBy { get; set; } public string DeletedBy { get; set; }
public DateTime? DeletedOn { get; set; } public DateTime? DeletedOn { get; set; }
public bool IsDeleted { get; set; } public bool IsDeleted { get; set; }
#endregion #endregion
[NotMapped] [NotMapped]
public string SiteTemplateType { get; set; } public string SiteTemplateType { get; set; }
[NotMapped] #region Obsolete properties
public Dictionary<string, string> Settings { get; set; }
[NotMapped] [NotMapped]
[Obsolete("This property is deprecated.", false)] [Obsolete("This property is deprecated.", false)]
public string DefaultLayoutType { get; set; } public string DefaultLayoutType { get; set; }
#endregion
} }
} }