From 95ba87945b5e405a331658e2d1b752e47a8b9854 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 25 May 2023 12:32:21 -0400 Subject: [PATCH] optimize client assembly download service, add support for site level scripts --- Oqtane.Client/UI/SiteRouter.razor | 9 +- .../Controllers/InstallationController.cs | 114 ++++++--------- .../OqtaneServiceCollectionExtensions.cs | 1 + Oqtane.Server/Infrastructure/ServerState.cs | 12 ++ .../Infrastructure/ServerStateManager.cs | 49 +++++++ Oqtane.Server/Pages/_Host.cshtml.cs | 137 +++++++----------- .../Repository/Interfaces/ISiteRepository.cs | 1 + .../Repository/ModuleDefinitionRepository.cs | 48 +++++- Oqtane.Server/Repository/SiteRepository.cs | 10 +- Oqtane.Server/Repository/ThemeRepository.cs | 48 +++++- Oqtane.Shared/Enums/ResourceLevel.cs | 1 + 11 files changed, 268 insertions(+), 162 deletions(-) create mode 100644 Oqtane.Server/Infrastructure/ServerState.cs create mode 100644 Oqtane.Server/Infrastructure/ServerStateManager.cs diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index 8465faa8..d2ef90a4 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -531,10 +531,13 @@ { foreach (var resource in resources) { - if (!resource.Url.Contains("://") && resource.Url.StartsWith("~/")) + if (resource.Url.StartsWith("~")) { - // create local path - resource.Url = resource.Url.Replace("~", alias.BaseUrl + "/" + type + "/" + name); + resource.Url = resource.Url.Replace("~", "/" + type + "/" + name + "/").Replace("//", "/"); + } + if (!resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl)) + { + resource.Url = alias.BaseUrl + resource.Url; } // ensure resource does not exist already diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs index 9de2e512..c94163ca 100644 --- a/Oqtane.Server/Controllers/InstallationController.cs +++ b/Oqtane.Server/Controllers/InstallationController.cs @@ -33,8 +33,10 @@ namespace Oqtane.Controllers private readonly IHttpContextAccessor _accessor; private readonly IAliasRepository _aliases; private readonly ILogger _filelogger; + private readonly ITenantManager _tenantManager; + private readonly ServerStateManager _serverState; - public InstallationController(IConfigManager configManager, IInstallationManager installationManager, IDatabaseManager databaseManager, ILocalizationManager localizationManager, IMemoryCache cache, IHttpContextAccessor accessor, IAliasRepository aliases, ILogger filelogger) + public InstallationController(IConfigManager configManager, IInstallationManager installationManager, IDatabaseManager databaseManager, ILocalizationManager localizationManager, IMemoryCache cache, IHttpContextAccessor accessor, IAliasRepository aliases, ILogger filelogger, ITenantManager tenantManager, ServerStateManager serverState) { _configManager = configManager; _installationManager = installationManager; @@ -44,6 +46,8 @@ namespace Oqtane.Controllers _accessor = accessor; _aliases = aliases; _filelogger = filelogger; + _tenantManager = tenantManager; + _serverState = serverState; } // POST api/ @@ -115,7 +119,9 @@ namespace Oqtane.Controllers private List GetAssemblyList() { - return _cache.GetOrCreate("assemblieslist", entry => + int siteId = _tenantManager.GetAlias().SiteId; + + return _cache.GetOrCreate($"assemblieslist:{siteId}", entry => { var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); var assemblyList = new List(); @@ -126,78 +132,43 @@ namespace Oqtane.Controllers { hashfilename = false; } - - // get list of assemblies which should be downloaded to client - var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies(); - var list = assemblies.Select(a => a.GetName().Name).ToList(); - // populate assemblies - for (int i = 0; i < list.Count; i++) + // get site assemblies which should be downloaded to client + var assemblies = _serverState.GetServerState(siteId).Assemblies; + + // populate assembly list + foreach (var assembly in assemblies) { - assemblyList.Add(new ClientAssembly(Path.Combine(binFolder, list[i] + ".dll"), hashfilename)); + if (assembly != Constants.ClientId) + { + var filepath = Path.Combine(binFolder, assembly) + ".dll"; + if (System.IO.File.Exists(filepath)) + { + assemblyList.Add(new ClientAssembly(Path.Combine(binFolder, assembly + ".dll"), hashfilename)); + } + } } // insert satellite assemblies at beginning of list foreach (var culture in _localizationManager.GetInstalledCultures()) { - var assembliesFolderPath = Path.Combine(binFolder, culture); - if (culture == Constants.DefaultCulture) + if (culture != Constants.DefaultCulture) { - continue; - } - - if (Directory.Exists(assembliesFolderPath)) - { - foreach (var resourceFile in Directory.EnumerateFiles(assembliesFolderPath)) + var assembliesFolderPath = Path.Combine(binFolder, culture); + if (Directory.Exists(assembliesFolderPath)) { - assemblyList.Insert(0, new ClientAssembly(resourceFile, hashfilename)); - } - } - else - { - _filelogger.LogError(Utilities.LogMessage(this, $"The Satellite Assembly Folder For {culture} Does Not Exist")); - } - } - - // insert module and theme dependencies at beginning of list - foreach (var assembly in assemblies) - { - foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModule)))) - { - var instance = Activator.CreateInstance(type) as IModule; - foreach (string name in instance.ModuleDefinition.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Reverse()) - { - var filepath = Path.Combine(binFolder, name.ToLower().EndsWith(".dll") ? name : name + ".dll"); - if (System.IO.File.Exists(filepath)) + foreach (var assembly in assemblies) { - if (!assemblyList.Exists(item => item.FilePath == filepath)) + var filepath = Path.Combine(assembliesFolderPath, assembly) + ".resources.dll"; + if (System.IO.File.Exists(filepath)) { - assemblyList.Insert(0, new ClientAssembly(filepath, hashfilename)); + assemblyList.Insert(0, new ClientAssembly(Path.Combine(assembliesFolderPath, assembly + ".resources.dll"), hashfilename)); } } - else - { - _filelogger.LogError(Utilities.LogMessage(this, $"Module {instance.ModuleDefinition.ModuleDefinitionName} Dependency {name}.dll Does Not Exist")); - } } - } - foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(ITheme)))) - { - var instance = Activator.CreateInstance(type) as ITheme; - foreach (string name in instance.Theme.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Reverse()) + else { - var filepath = Path.Combine(binFolder, name.ToLower().EndsWith(".dll") ? name : name + ".dll"); - if (System.IO.File.Exists(filepath)) - { - if (!assemblyList.Exists(item => item.FilePath == filepath)) - { - assemblyList.Insert(0, new ClientAssembly(filepath, hashfilename)); - } - } - else - { - _filelogger.LogError(Utilities.LogMessage(this, $"Theme {instance.Theme.ThemeName} Dependency {name}.dll Does Not Exist")); - } + _filelogger.LogError(Utilities.LogMessage(this, $"The Satellite Assembly Folder For {culture} Does Not Exist")); } } } @@ -240,21 +211,24 @@ namespace Oqtane.Controllers { foreach (var assembly in assemblies) { - if (System.IO.File.Exists(assembly.FilePath)) + if (Path.GetFileNameWithoutExtension(assembly.FilePath) != Constants.ClientId) { - using (var filestream = new FileStream(assembly.FilePath, FileMode.Open, FileAccess.Read)) - using (var entrystream = archive.CreateEntry(assembly.HashedName).Open()) + if (System.IO.File.Exists(assembly.FilePath)) { - filestream.CopyTo(entrystream); + using (var filestream = new FileStream(assembly.FilePath, FileMode.Open, FileAccess.Read)) + using (var entrystream = archive.CreateEntry(assembly.HashedName).Open()) + { + filestream.CopyTo(entrystream); + } } - } - var pdb = assembly.FilePath.Replace(".dll", ".pdb"); - if (System.IO.File.Exists(pdb)) - { - using (var filestream = new FileStream(pdb, FileMode.Open, FileAccess.Read)) - using (var entrystream = archive.CreateEntry(assembly.HashedName.Replace(".dll", ".pdb")).Open()) + var pdb = assembly.FilePath.Replace(".dll", ".pdb"); + if (System.IO.File.Exists(pdb)) { - filestream.CopyTo(entrystream); + using (var filestream = new FileStream(pdb, FileMode.Open, FileAccess.Read)) + using (var entrystream = archive.CreateEntry(assembly.HashedName.Replace(".dll", ".pdb")).Open()) + { + filestream.CopyTo(entrystream); + } } } } diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 2bb65c38..443d7c33 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -61,6 +61,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); return services; } diff --git a/Oqtane.Server/Infrastructure/ServerState.cs b/Oqtane.Server/Infrastructure/ServerState.cs new file mode 100644 index 00000000..c37d5e21 --- /dev/null +++ b/Oqtane.Server/Infrastructure/ServerState.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Oqtane.Models; + +namespace Oqtane.Infrastructure +{ + public class ServerState + { + public int SiteId { get; set; } + public List Assemblies { get; set; } = new List(); + public ListScripts { get; set; } = new List(); + } +} diff --git a/Oqtane.Server/Infrastructure/ServerStateManager.cs b/Oqtane.Server/Infrastructure/ServerStateManager.cs new file mode 100644 index 00000000..630ea691 --- /dev/null +++ b/Oqtane.Server/Infrastructure/ServerStateManager.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Linq; +using Oqtane.Models; + +namespace Oqtane.Infrastructure +{ + // singleton + public class ServerStateManager + { + private List _serverStates { get; set; } + + public ServerStateManager() + { + _serverStates = new List(); + } + + public ServerState GetServerState(int siteId) + { + var serverState = _serverStates.FirstOrDefault(item => item.SiteId == siteId); + if (serverState == null) + { + serverState = new ServerState(); + serverState.SiteId = siteId; + serverState.Assemblies = new List(); + serverState.Scripts = new List(); + return serverState; + } + else + { + return serverState; + } + } + + public void SetServerState(int siteId, ServerState serverState) + { + var serverstate = _serverStates.FirstOrDefault(item => item.SiteId == siteId); + if (serverstate == null) + { + serverState.SiteId = siteId; + _serverStates.Add(serverState); + } + else + { + serverstate.Assemblies = serverState.Assemblies; + serverstate.Scripts = serverState.Scripts; + } + } + } +} diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index 180115ba..2cae21ba 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -36,9 +36,10 @@ namespace Oqtane.Pages private readonly IVisitorRepository _visitors; private readonly IAliasRepository _aliases; private readonly ISettingRepository _settings; + private readonly ServerStateManager _serverState; private readonly ILogManager _logger; - public HostModel(IConfigManager configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, IJwtManager jwtManager, ISiteRepository sites, IPageRepository pages, IUrlMappingRepository urlMappings, IVisitorRepository visitors, IAliasRepository aliases, ISettingRepository settings, ILogManager logger) + public HostModel(IConfigManager configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, IJwtManager jwtManager, ISiteRepository sites, IPageRepository pages, IUrlMappingRepository urlMappings, IVisitorRepository visitors, IAliasRepository aliases, ISettingRepository settings, ServerStateManager serverState, ILogManager logger) { _configuration = configuration; _tenantManager = tenantManager; @@ -52,6 +53,7 @@ namespace Oqtane.Pages _visitors = visitors; _aliases = aliases; _settings = settings; + _serverState = serverState; _logger = logger; } @@ -117,33 +119,11 @@ namespace Oqtane.Pages { Runtime = site.Runtime; } - if (!string.IsNullOrEmpty(site.RenderMode)) + if (!string.IsNullOrEmpty(site.RenderMode)) { RenderMode = site.RenderMode; } - if (Runtime == "Server") - { - ReconnectScript = CreateReconnectScript(); - } - if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null) - { - PWAScript = CreatePWAScript(alias, site, route); - } - // site level scripts - HeadResources += ParseScripts(site.HeadContent); - BodyResources += ParseScripts(site.BodyContent); - - // get jwt token for downstream APIs - if (User.Identity.IsAuthenticated) - { - var sitesettings = HttpContext.GetSiteSettings(); - var secret = sitesettings.GetValue("JwtOptions:Secret", ""); - if (!string.IsNullOrEmpty(secret)) - { - AuthorizationToken = _jwtManager.GenerateToken(alias, (ClaimsIdentity)User.Identity, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), int.Parse(sitesettings.GetValue("JwtOptions:Lifetime", "20"))); - } - } - + if (site.VisitorTracking) { TrackVisitor(site.SiteId); @@ -172,11 +152,33 @@ namespace Oqtane.Pages } } - // include global resources - var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies(); - foreach (Assembly assembly in assemblies) + // get jwt token for downstream APIs + if (User.Identity.IsAuthenticated) { - ProcessHostResources(assembly, alias); + var sitesettings = HttpContext.GetSiteSettings(); + var secret = sitesettings.GetValue("JwtOptions:Secret", ""); + if (!string.IsNullOrEmpty(secret)) + { + AuthorizationToken = _jwtManager.GenerateToken(alias, (ClaimsIdentity)User.Identity, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), int.Parse(sitesettings.GetValue("JwtOptions:Lifetime", "20"))); + } + } + + // inject scripts + if (Runtime == "Server") + { + ReconnectScript = CreateReconnectScript(); + } + if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null) + { + PWAScript = CreatePWAScript(alias, site, route); + } + HeadResources += ParseScripts(site.HeadContent); + BodyResources += ParseScripts(site.BodyContent); + _sites.InitializeSite(site.SiteId); // populates server state + var scripts = _serverState.GetServerState(site.SiteId).Scripts; + foreach (var script in scripts) + { + AddScript(script, alias); } // set culture if not specified @@ -409,20 +411,6 @@ namespace Oqtane.Pages ""; } - private void ProcessHostResources(Assembly assembly, Alias alias) - { - var types = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IHostResources))); - foreach (var type in types) - { - var obj = Activator.CreateInstance(type) as IHostResources; - foreach (var resource in obj.Resources) - { - resource.Level = ResourceLevel.App; - ProcessResource(resource, 0, alias); - } - } - } - private string ParseScripts(string headcontent) { // iterate scripts @@ -439,60 +427,39 @@ namespace Oqtane.Pages return scripts; } - private void ProcessResource(Resource resource, int count, Alias alias) + private void AddScript(Resource resource, Alias alias) { - var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url; - switch (resource.ResourceType) + var script = CreateScript(resource, alias); + if (resource.Location == Shared.ResourceLocation.Head) { - case ResourceType.Stylesheet: - if (!HeadResources.Contains(url, StringComparison.OrdinalIgnoreCase)) - { - string id = ""; - if (resource.Level == ResourceLevel.Page) - { - id = "id=\"app-stylesheet-" + resource.Level.ToString().ToLower() + "-" + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff") + "-" + count.ToString("00") + "\" "; - } - HeadResources += "" + Environment.NewLine; - } - break; - case ResourceType.Script: - if (resource.Location == Shared.ResourceLocation.Body) - { - if (!BodyResources.Contains(url, StringComparison.OrdinalIgnoreCase)) - { - BodyResources += "" + Environment.NewLine; - } - } - else - { - if (!HeadResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase)) - { - HeadResources += "" + Environment.NewLine; - } - } - break; - } - } - private string CrossOrigin(string crossorigin) - { - if (!string.IsNullOrEmpty(crossorigin)) - { - return " crossorigin=\"" + crossorigin + "\""; + if (!HeadResources.Contains(script)) + { + HeadResources += script + Environment.NewLine; + } } else { - return ""; + if (!BodyResources.Contains(script)) + { + BodyResources += script + Environment.NewLine; + } } } - private string Integrity(string integrity) + + private string CreateScript(Resource resource, Alias alias) { - if (!string.IsNullOrEmpty(integrity)) + if (!string.IsNullOrEmpty(resource.Url)) { - return " integrity=\"" + integrity + "\""; + var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url; + return ""; } else { - return ""; + // inline script + return ""; } } diff --git a/Oqtane.Server/Repository/Interfaces/ISiteRepository.cs b/Oqtane.Server/Repository/Interfaces/ISiteRepository.cs index 8172efff..ebe3294b 100644 --- a/Oqtane.Server/Repository/Interfaces/ISiteRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/ISiteRepository.cs @@ -11,6 +11,7 @@ namespace Oqtane.Repository Site GetSite(int siteId); Site GetSite(int siteId, bool tracking); void DeleteSite(int siteId); + void InitializeSite(int siteId); void CreatePages(Site site, List pageTemplates); } } diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index 1d0c45f8..e44ca777 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -4,12 +4,14 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; +using System.Xml; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Oqtane.Infrastructure; using Oqtane.Models; using Oqtane.Modules; using Oqtane.Shared; +using Oqtane.Themes; namespace Oqtane.Repository { @@ -20,15 +22,17 @@ namespace Oqtane.Repository private readonly IPermissionRepository _permissions; private readonly ITenantManager _tenants; private readonly ISettingRepository _settings; + private readonly ServerStateManager _serverState; private readonly string settingprefix = "SiteEnabled:"; - public ModuleDefinitionRepository(MasterDBContext context, IMemoryCache cache, IPermissionRepository permissions, ITenantManager tenants, ISettingRepository settings) + public ModuleDefinitionRepository(MasterDBContext context, IMemoryCache cache, IPermissionRepository permissions, ITenantManager tenants, ISettingRepository settings, ServerStateManager serverState) { _db = context; _cache = cache; _permissions = permissions; _tenants = tenants; _settings = settings; + _serverState = serverState; } public IEnumerable GetModuleDefinitions() @@ -182,6 +186,7 @@ namespace Oqtane.Repository var settings = _settings.GetSettings(EntityNames.ModuleDefinition).ToList(); // populate module definition site settings and permissions + var serverState = _serverState.GetServerState(siteId); foreach (ModuleDefinition moduledefinition in ModuleDefinitions) { moduledefinition.SiteId = siteId; @@ -196,6 +201,36 @@ namespace Oqtane.Repository moduledefinition.IsEnabled = moduledefinition.IsAutoEnabled; } + if (moduledefinition.IsEnabled) + { + // build list of assemblies for site + if (!serverState.Assemblies.Contains(moduledefinition.AssemblyName)) + { + serverState.Assemblies.Add(moduledefinition.AssemblyName); + } + if (!string.IsNullOrEmpty(moduledefinition.Dependencies)) + { + foreach (var assembly in moduledefinition.Dependencies.Replace(".dll", "").Split(',', StringSplitOptions.RemoveEmptyEntries).Reverse()) + { + if (!serverState.Assemblies.Contains(assembly)) + { + serverState.Assemblies.Insert(0, assembly); + } + } + } + // build list of scripts for site + if (moduledefinition.Resources != null) + { + foreach (var resource in moduledefinition.Resources.Where(item => item.Level == ResourceLevel.Site)) + { + if (!serverState.Scripts.Contains(resource)) + { + serverState.Scripts.Add(resource); + } + } + } + } + if (permissions.Count == 0) { // no module definition permissions exist for this site @@ -216,6 +251,7 @@ namespace Oqtane.Repository } } } + _serverState.SetServerState(siteId, serverState); // clean up any orphaned permissions var ids = new HashSet(ModuleDefinitions.Select(item => item.ModuleDefinitionId)); @@ -295,6 +331,16 @@ namespace Oqtane.Repository moduledefinition.ModuleDefinitionName = qualifiedModuleType; moduledefinition.ControlTypeTemplate = modulecontroltype.Namespace + "." + Constants.ActionToken + ", " + modulecontroltype.Assembly.GetName().Name; moduledefinition.AssemblyName = assembly.GetName().Name; + if (moduledefinition.Resources != null) + { + foreach (var resource in moduledefinition.Resources) + { + if (resource.Url.StartsWith("~")) + { + resource.Url = resource.Url.Replace("~", "/Modules/" + Utilities.GetTypeName(moduledefinition.ModuleDefinitionName) + "/").Replace("//", "/"); + } + } + } moduledefinition.IsPortable = false; if (!string.IsNullOrEmpty(moduledefinition.ServerManagerType)) diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index b17ca189..492b3af0 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -24,11 +24,12 @@ namespace Oqtane.Repository private readonly IModuleRepository _moduleRepository; private readonly IPageModuleRepository _pageModuleRepository; private readonly IModuleDefinitionRepository _moduleDefinitionRepository; + private readonly IThemeRepository _themeRepository; private readonly IServiceProvider _serviceProvider; private readonly IConfigurationRoot _config; public SiteRepository(TenantDBContext context, IRoleRepository roleRepository, IProfileRepository profileRepository, IFolderRepository folderRepository, IPageRepository pageRepository, - IModuleRepository moduleRepository, IPageModuleRepository pageModuleRepository, IModuleDefinitionRepository moduleDefinitionRepository, IServiceProvider serviceProvider, + IModuleRepository moduleRepository, IPageModuleRepository pageModuleRepository, IModuleDefinitionRepository moduleDefinitionRepository, IThemeRepository themeRepository, IServiceProvider serviceProvider, IConfigurationRoot config) { _db = context; @@ -39,6 +40,7 @@ namespace Oqtane.Repository _moduleRepository = moduleRepository; _pageModuleRepository = pageModuleRepository; _moduleDefinitionRepository = moduleDefinitionRepository; + _themeRepository = themeRepository; _serviceProvider = serviceProvider; _config = config; } @@ -88,6 +90,12 @@ namespace Oqtane.Repository _db.SaveChanges(); } + public void InitializeSite(int siteId) + { + _moduleDefinitionRepository.GetModuleDefinitions(siteId); + _themeRepository.GetThemes(); + } + private void CreateSite(Site site) { // create default entities for site diff --git a/Oqtane.Server/Repository/ThemeRepository.cs b/Oqtane.Server/Repository/ThemeRepository.cs index 413ef6bb..3465bee0 100644 --- a/Oqtane.Server/Repository/ThemeRepository.cs +++ b/Oqtane.Server/Repository/ThemeRepository.cs @@ -21,14 +21,16 @@ namespace Oqtane.Repository private readonly IMemoryCache _cache; private readonly ITenantManager _tenants; private readonly ISettingRepository _settings; + private readonly ServerStateManager _serverState; private readonly string settingprefix = "SiteEnabled:"; - public ThemeRepository(MasterDBContext context, IMemoryCache cache, ITenantManager tenants, ISettingRepository settings) + public ThemeRepository(MasterDBContext context, IMemoryCache cache, ITenantManager tenants, ISettingRepository settings, ServerStateManager serverState) { _db = context; _cache = cache; _tenants = tenants; _settings = settings; + _serverState = serverState; } public IEnumerable GetThemes() @@ -147,6 +149,7 @@ namespace Oqtane.Repository var settings = _settings.GetSettings(EntityNames.Theme).ToList(); // populate theme site settings + var serverState = _serverState.GetServerState(siteId); foreach (Theme theme in Themes) { theme.SiteId = siteId; @@ -160,7 +163,38 @@ namespace Oqtane.Repository { theme.IsEnabled = theme.IsAutoEnabled; } + + if (theme.IsEnabled) + { + // build list of assemblies for site + if (!serverState.Assemblies.Contains(theme.AssemblyName)) + { + serverState.Assemblies.Add(theme.AssemblyName); + } + if (!string.IsNullOrEmpty(theme.Dependencies)) + { + foreach (var assembly in theme.Dependencies.Replace(".dll", "").Split(',', StringSplitOptions.RemoveEmptyEntries).Reverse()) + { + if (!serverState.Assemblies.Contains(assembly)) + { + serverState.Assemblies.Insert(0, assembly); + } + } + } + // build list of scripts for site + if (theme.Resources != null) + { + foreach (var resource in theme.Resources.Where(item => item.Level == ResourceLevel.Site)) + { + if (!serverState.Scripts.Contains(resource)) + { + serverState.Scripts.Add(resource); + } + } + } + } } + _serverState.SetServerState(siteId, serverState); } return Themes; @@ -225,12 +259,22 @@ namespace Oqtane.Repository Version = new Version(1, 0, 0).ToString() }; } + // set internal properties theme.ThemeName = qualifiedThemeType; theme.Themes = new List(); theme.Containers = new List(); theme.AssemblyName = assembly.FullName.Split(",")[0]; - + if (theme.Resources != null) + { + foreach (var resource in theme.Resources) + { + if (resource.Url.StartsWith("~")) + { + resource.Url = resource.Url.Replace("~", "/Themes/" + Utilities.GetTypeName(theme.ThemeName) + "/").Replace("//", "/"); + } + } + } Debug.WriteLine($"Oqtane Info: Registering Theme {theme.ThemeName}"); themes.Add(theme); index = themes.FindIndex(item => item.ThemeName == qualifiedThemeType); diff --git a/Oqtane.Shared/Enums/ResourceLevel.cs b/Oqtane.Shared/Enums/ResourceLevel.cs index 2c839c85..5fb9c9af 100644 --- a/Oqtane.Shared/Enums/ResourceLevel.cs +++ b/Oqtane.Shared/Enums/ResourceLevel.cs @@ -3,6 +3,7 @@ namespace Oqtane.Shared public enum ResourceLevel { App, + Site, Page, Module }