diff --git a/Oqtane.Client/App.razor b/Oqtane.Client/App.razor index 5ad3a790..cef437d1 100644 --- a/Oqtane.Client/App.razor +++ b/Oqtane.Client/App.razor @@ -62,21 +62,10 @@ _initialized = true; } - protected override async Task OnAfterRenderAsync(bool firstRender) + protected override void OnAfterRender(bool firstRender) { if (firstRender) { - if (string.IsNullOrEmpty(AntiForgeryToken)) - { - // parameter values are not set when running on WebAssembly (seems to be a .NET 5 bug) - need to retrieve using JSInterop - var interop = new Interop(JSRuntime); - - SiteState.AntiForgeryToken = await interop.GetElementByName(Constants.RequestVerificationToken); - InstallationService.SetAntiForgeryTokenHeader(SiteState.AntiForgeryToken); - - Runtime = await interop.GetElementByName("app_runtime"); - RenderMode = await interop.GetElementByName("app_rendermode"); - } _display = ""; StateHasChanged(); } diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 8602c80c..f6263f33 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -104,7 +104,6 @@ -
@@ -157,10 +156,7 @@

- - -
@@ -189,6 +185,28 @@
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) { +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
@@ -231,6 +249,8 @@ private string _name = string.Empty; private List _aliasList; private string _urls = string.Empty; + private string _runtime = ""; + private string _prerender = ""; private int _logofileid = -1; private FileManager _logofilemanager; private int _faviconfileid = -1; @@ -272,6 +292,8 @@ if (site != null) { _name = site.Name; + _runtime = site.Runtime; + _prerender = site.RenderMode.Replace(_runtime, ""); _allowregistration = site.AllowRegistration.ToString(); _isdeleted = site.IsDeleted.ToString(); @@ -413,9 +435,20 @@ var site = await SiteService.GetSiteAsync(PageState.Site.SiteId); if (site != null) { + bool reload = false; bool refresh = (site.DefaultThemeType != _themetype || site.DefaultContainerType != _containertype); site.Name = _name; + if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) + { + if (site.Runtime != _runtime || site.RenderMode != _runtime + _prerender) + { + site.Runtime = _runtime; + site.RenderMode = _runtime + _prerender; + refresh = true; + reload = true; + } + } site.AllowRegistration = (_allowregistration == null ? true : Boolean.Parse(_allowregistration)); site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted)); @@ -485,7 +518,7 @@ if (refresh) { - NavigationManager.NavigateTo(NavigateUrl()); // refresh to show new theme or container + NavigationManager.NavigateTo(NavigateUrl(), reload); // refresh to show new theme or container } else { diff --git a/Oqtane.Client/Modules/Admin/Sites/Add.razor b/Oqtane.Client/Modules/Admin/Sites/Add.razor index 448c6682..f369df79 100644 --- a/Oqtane.Client/Modules/Admin/Sites/Add.razor +++ b/Oqtane.Client/Modules/Admin/Sites/Add.razor @@ -82,6 +82,24 @@ else
+
+ +
+ +
+
+
+ +
+ +
+
@@ -177,6 +195,8 @@ else private string _containertype = "-"; private string _admincontainertype = ""; private string _sitetemplatetype = "-"; + private string _runtime = "Server"; + private string _prerender = "Prerendered"; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; @@ -340,6 +360,8 @@ else config.DefaultContainer = _containertype; config.DefaultAdminContainer = _admincontainertype; config.SiteTemplate = _sitetemplatetype; + config.Runtime = _runtime; + config.RenderMode = _runtime + _prerender; ShowProgressIndicator(); diff --git a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor index 9cd52ff2..f28731dc 100644 --- a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor +++ b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor @@ -50,24 +50,6 @@
-
- -
- -
-
-
- -
- -
-
@@ -126,8 +108,6 @@ private string _servertime = string.Empty; private string _installationid = string.Empty; - private string _runtime = string.Empty; - private string _rendermode = string.Empty; private string _detailederrors = string.Empty; private string _logginglevel = string.Empty; private string _swagger = string.Empty; @@ -146,8 +126,6 @@ _servertime = systeminfo["servertime"]; _installationid = systeminfo["installationid"]; - _runtime = systeminfo["runtime"]; - _rendermode = systeminfo["rendermode"]; _detailederrors = systeminfo["detailederrors"]; _logginglevel = systeminfo["logginglevel"]; _swagger = systeminfo["swagger"]; @@ -160,8 +138,6 @@ try { var settings = new Dictionary(); - settings.Add("runtime", _runtime); - settings.Add("rendermode", _rendermode); settings.Add("detailederrors", _detailederrors); settings.Add("logginglevel", _logginglevel); settings.Add("swagger", _swagger); diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs index f27f0c2e..68bcc7a2 100644 --- a/Oqtane.Client/Program.cs +++ b/Oqtane.Client/Program.cs @@ -24,7 +24,6 @@ namespace Oqtane.Client public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); - builder.RootComponents.Add("app"); var httpClient = new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)}; diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx index 74882e86..670a525a 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx @@ -282,4 +282,19 @@ Select Theme + + Hosting Model + + + Specifies if the site should be prerendered (for search crawlers, etc...) + + + Prerender? + + + The Blazor runtime hosting model + + + Runtime: + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx b/Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx index e0300472..6473d0ff 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Sites/Add.resx @@ -258,4 +258,16 @@ Error loading Database Configuration Control + + Specifies if the site should be prerendered (for search crawlers, etc...) + + + Prerender? + + + The Blazor runtime hosting model + + + Runtime: + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx b/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx index 84c6992b..3b60da81 100644 --- a/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx @@ -123,9 +123,6 @@ Framework Version - - Blazor Runtime (Server or WebAssembly) - Common Language Runtime Version @@ -141,9 +138,6 @@ Framework Version: - - Blazor Runtime: - CLR Version: @@ -165,18 +159,9 @@ An Error Occurred Updating The Configuration - - Server - - - ServerPrerendered - Configuration Updated. Please Select Restart Application For These Changes To Be Activated. - - WebAssembly - Installation ID: diff --git a/Oqtane.Client/Resources/SharedResources.resx b/Oqtane.Client/Resources/SharedResources.resx index b89dd91d..ce8bc2bb 100644 --- a/Oqtane.Client/Resources/SharedResources.resx +++ b/Oqtane.Client/Resources/SharedResources.resx @@ -312,4 +312,10 @@ Not Specified + + Blazor Server + + + Blazor WebAssembly + \ No newline at end of file diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index 8f73a00b..82df0175 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -85,18 +85,18 @@ // parse querystring var querystring = ParseQueryString(uri.Query); - // the refresh parameter is used to refresh the PageState - if (querystring.ContainsKey("refresh")) + // reload the client application if there is a forced reload or the user navigated to a site with a different alias + if (querystring.ContainsKey("reload") || (!path.ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path))) { - refresh = UI.Refresh.Site; + NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true); + return; } else { - // reload the client application if the user navigated to a site with a different alias or there is a forced reload - if ((!path.ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path)) || querystring.ContainsKey("reload")) + // the refresh parameter is used to refresh the PageState + if (querystring.ContainsKey("refresh")) { - NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true); - return; + refresh = UI.Refresh.Site; } } @@ -109,10 +109,10 @@ // process any sync events var sync = await SyncService.GetSyncAsync(lastsyncdate); lastsyncdate = sync.SyncDate; - if (refresh != UI.Refresh.Site && sync.SyncEvents.Any()) + if (sync.SyncEvents.Any()) { - // if running on WebAssembly reload the client application if the server application was restarted - if (runtime == Shared.Runtime.WebAssembly && PageState != null && sync.SyncEvents.Exists(item => item.TenantId == -1)) + // reload client application if server was restarted or site runtime/rendermode was modified + if (PageState != null && sync.SyncEvents.Exists(item => (item.TenantId == -1 || item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId) && item.Reload)) { NavigationManager.NavigateTo(_absoluteUri, true); return; diff --git a/Oqtane.Package/release.cmd b/Oqtane.Package/release.cmd index bc538a62..620e8117 100644 --- a/Oqtane.Package/release.cmd +++ b/Oqtane.Package/release.cmd @@ -17,6 +17,7 @@ del "..\Oqtane.Server\bin\Release\net5.0\publish\appsettings.json" ren "..\Oqtane.Server\bin\Release\net5.0\publish\appsettings.release.json" "appsettings.json" C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1" del "..\Oqtane.Server\bin\Release\net5.0\publish\appsettings.json" +del "..\Oqtane.Server\bin\Release\net5.0\publish\web.config" C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\upgrade.ps1" dotnet clean -c Release ..\Oqtane.Updater.sln dotnet build -c Release ..\Oqtane.Updater.sln diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs index ccf181b8..35e94b6f 100644 --- a/Oqtane.Server/Controllers/InstallationController.cs +++ b/Oqtane.Server/Controllers/InstallationController.cs @@ -102,15 +102,7 @@ namespace Oqtane.Controllers [HttpGet("load")] public IActionResult Load() { - if (_configManager.GetSection("Runtime").Value == "WebAssembly") - { - return File(GetAssemblies(), System.Net.Mime.MediaTypeNames.Application.Octet, "oqtane.dll"); - } - else - { - HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; - return null; - } + return File(GetAssemblies(), System.Net.Mime.MediaTypeNames.Application.Octet, "oqtane.dll"); } private byte[] GetAssemblies() diff --git a/Oqtane.Server/Controllers/SiteController.cs b/Oqtane.Server/Controllers/SiteController.cs index b67cc126..6b4d1c7b 100644 --- a/Oqtane.Server/Controllers/SiteController.cs +++ b/Oqtane.Server/Controllers/SiteController.cs @@ -84,10 +84,16 @@ namespace Oqtane.Controllers [Authorize(Roles = RoleNames.Admin)] public Site Put(int id, [FromBody] Site site) { - if (ModelState.IsValid && site.SiteId == _alias.SiteId && site.TenantId == _alias.TenantId && _sites.GetSite(site.SiteId, false) != null) + var current = _sites.GetSite(site.SiteId, false); + if (ModelState.IsValid && site.SiteId == _alias.SiteId && site.TenantId == _alias.TenantId && current != null) { site = _sites.UpdateSite(site); - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, site.SiteId); + bool reload = false; + if (current.Runtime != site.Runtime || current.RenderMode != site.RenderMode) + { + reload = true; + } + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, site.SiteId, reload); _logger.Log(site.SiteId, LogLevel.Information, this, LogFunction.Update, "Site Updated {Site}", site); } else diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index c81f63a9..4fb7e6e5 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -585,7 +585,9 @@ namespace Oqtane.Infrastructure DefaultThemeType = (!string.IsNullOrEmpty(install.DefaultTheme)) ? install.DefaultTheme : Constants.DefaultTheme, DefaultContainerType = (!string.IsNullOrEmpty(install.DefaultContainer)) ? install.DefaultContainer : Constants.DefaultContainer, AdminContainerType = (!string.IsNullOrEmpty(install.DefaultAdminContainer)) ? install.DefaultAdminContainer : Constants.DefaultAdminContainer, - SiteTemplateType = install.SiteTemplate + SiteTemplateType = install.SiteTemplate, + Runtime = (!string.IsNullOrEmpty(install.Runtime)) ? install.Runtime : _configManager.GetSection("Runtime").Value, + RenderMode = (!string.IsNullOrEmpty(install.RenderMode)) ? install.RenderMode : _configManager.GetSection("RenderMode").Value }; site = sites.AddSite(site); diff --git a/Oqtane.Server/Infrastructure/Interfaces/ISyncManager.cs b/Oqtane.Server/Infrastructure/Interfaces/ISyncManager.cs index b82e5196..b21d70af 100644 --- a/Oqtane.Server/Infrastructure/Interfaces/ISyncManager.cs +++ b/Oqtane.Server/Infrastructure/Interfaces/ISyncManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Oqtane.Models; @@ -8,5 +8,6 @@ namespace Oqtane.Infrastructure { List GetSyncEvents(int tenantId, DateTime lastSyncDate); void AddSyncEvent(int tenantId, string entityName, int entityId); + void AddSyncEvent(int tenantId, string entityName, int entityId, bool reload); } } diff --git a/Oqtane.Server/Infrastructure/SyncManager.cs b/Oqtane.Server/Infrastructure/SyncManager.cs index d70cc456..b4f4c551 100644 --- a/Oqtane.Server/Infrastructure/SyncManager.cs +++ b/Oqtane.Server/Infrastructure/SyncManager.cs @@ -22,7 +22,12 @@ namespace Oqtane.Infrastructure public void AddSyncEvent(int tenantId, string entityName, int entityId) { - SyncEvents.Add(new SyncEvent { TenantId = tenantId, EntityName = entityName, EntityId = entityId, ModifiedOn = DateTime.UtcNow }); + AddSyncEvent(tenantId, entityName, entityId, false); + } + + 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 }); // trim sync events SyncEvents.RemoveAll(item => item.ModifiedOn < DateTime.UtcNow.AddHours(-1)); } diff --git a/Oqtane.Server/Migrations/Framework/MultiDatabaseMigration.cs b/Oqtane.Server/Migrations/Framework/MultiDatabaseMigration.cs index 28ac2164..8379c3e3 100644 --- a/Oqtane.Server/Migrations/Framework/MultiDatabaseMigration.cs +++ b/Oqtane.Server/Migrations/Framework/MultiDatabaseMigration.cs @@ -1,8 +1,5 @@ -using System.Collections.Generic; -using System.Linq; using Microsoft.EntityFrameworkCore.Migrations; using Oqtane.Databases.Interfaces; -using Oqtane.Interfaces; namespace Oqtane.Migrations { diff --git a/Oqtane.Server/Migrations/Tenant/03000001_AddSiteRuntime.cs b/Oqtane.Server/Migrations/Tenant/03000001_AddSiteRuntime.cs new file mode 100644 index 00000000..90a7237a --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/03000001_AddSiteRuntime.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.03.00.00.01")] + public class AddSiteRuntime : MultiDatabaseMigration + { + public AddSiteRuntime(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + siteEntityBuilder.AddStringColumn("Runtime", 50, true, true); + siteEntityBuilder.UpdateColumn("Runtime", "'Server'"); + siteEntityBuilder.AddStringColumn("RenderMode", 50, true, true); + siteEntityBuilder.UpdateColumn("RenderMode", "'ServerPrerendered'"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + siteEntityBuilder.DropColumn("Runtime"); + siteEntityBuilder.DropColumn("RenderMode"); + } + } +} diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index 499f00ad..6fa559ca 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -23,14 +23,16 @@ namespace Oqtane.Pages private readonly ILocalizationManager _localizationManager; private readonly ILanguageRepository _languages; private readonly IAntiforgery _antiforgery; + private readonly ISiteRepository _sites; - public HostModel(IConfiguration configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery) + public HostModel(IConfiguration configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, ISiteRepository sites) { _configuration = configuration; _tenantManager = tenantManager; _localizationManager = localizationManager; _languages = languages; _antiforgery = antiforgery; + _sites = sites; } public string AntiForgeryToken = ""; @@ -48,7 +50,7 @@ namespace Oqtane.Pages Runtime = _configuration.GetSection("Runtime").Value; } - if (Runtime != "WebAssembly" && _configuration.GetSection("RenderMode").Exists()) + if (_configuration.GetSection("RenderMode").Exists()) { RenderMode = (RenderMode)Enum.Parse(typeof(RenderMode), _configuration.GetSection("RenderMode").Value, true); } @@ -67,6 +69,19 @@ namespace Oqtane.Pages var alias = _tenantManager.GetAlias(); if (alias != null) { + var site = _sites.GetSite(alias.SiteId); + if (site != null) + { + if (!string.IsNullOrEmpty(site.Runtime)) + { + Runtime = site.Runtime; + } + if (!string.IsNullOrEmpty(site.RenderMode)) + { + RenderMode = (RenderMode)Enum.Parse(typeof(RenderMode), site.RenderMode, true); + } + } + // if culture not specified if (HttpContext.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName] == null) { @@ -142,7 +157,6 @@ namespace Oqtane.Pages } } } - private void ProcessResource(Resource resource) { switch (resource.ResourceType) @@ -171,7 +185,6 @@ namespace Oqtane.Pages break; } } - private string CrossOrigin(string crossorigin) { if (!string.IsNullOrEmpty(crossorigin)) diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 4f5160ec..e82ee135 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -163,8 +163,8 @@ namespace Oqtane endpoints.MapFallbackToPage("/_Host"); }); - // create a sync event to identify server application startup - sync.AddSyncEvent(-1, "Application", -1); + // create a global sync event to identify server application startup + sync.AddSyncEvent(-1, "Application", -1, true); } } } diff --git a/Oqtane.Shared/Models/Site.cs b/Oqtane.Shared/Models/Site.cs index 82a7fc62..5ccf11d0 100644 --- a/Oqtane.Shared/Models/Site.cs +++ b/Oqtane.Shared/Models/Site.cs @@ -58,6 +58,16 @@ namespace Oqtane.Models /// public string SiteGuid { get; set; } + /// + /// The hosting model for the site (ie. Server or WebAssembly ). + /// + public string Runtime { get; set; } + + /// + /// The render mode for the site (ie. Server, ServerPrerendered, WebAssembly, WebAssemblyPrerendered ). + /// + public string RenderMode { get; set; } + #region IAuditable Properties /// diff --git a/Oqtane.Shared/Models/Sync.cs b/Oqtane.Shared/Models/Sync.cs index 9430ab2a..6e58185f 100644 --- a/Oqtane.Shared/Models/Sync.cs +++ b/Oqtane.Shared/Models/Sync.cs @@ -14,6 +14,7 @@ namespace Oqtane.Models public int TenantId { get; set; } public string EntityName { get; set; } public int EntityId { get; set; } + public bool Reload { get; set; } public DateTime ModifiedOn { get; set; } } } diff --git a/Oqtane.Shared/Shared/InstallConfig.cs b/Oqtane.Shared/Shared/InstallConfig.cs index 79a78602..f4e7d053 100644 --- a/Oqtane.Shared/Shared/InstallConfig.cs +++ b/Oqtane.Shared/Shared/InstallConfig.cs @@ -17,6 +17,8 @@ namespace Oqtane.Shared public string DefaultTheme { get; set; } public string DefaultContainer { get; set; } public string DefaultAdminContainer { get; set; } + public string Runtime { get; set; } + public string RenderMode { get; set; } public bool Register { get; set; } } }