Merge pull request #1840 from sbwalker/dev
Route parsing abstraction and optimization, site router performance improvements, migrate site-based concepts (favicon, PWA support) to server for performance and prerendering benefits, move ThemeBuilder interop logic to OnAfterRenderAsync, upgrade SqlClient to release version, update installer to Bootstrap 5.1.3
This commit is contained in:
commit
2f8a580854
@ -158,8 +158,8 @@
|
|||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
await interop.IncludeLink("", "stylesheet", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.0.2/css/bootstrap.min.css", "text/css", "sha512-usVBAd66/NpVNfBge19gws2j6JZinnca12rAe2l+d+QkLU9fiG02O1X8Q6hepIpr/EYKZvKx/I9WsnujJuOmBA==", "anonymous", "");
|
await interop.IncludeLink("", "stylesheet", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css", "text/css", "sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==", "anonymous", "");
|
||||||
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.0.2/js/bootstrap.min.js", "sha512-a6ctI6w1kg3J4dSjknHj3aWLEbjitAXAjLDRUxo2wyYmDFRcz2RJuQr5M3Kt8O/TtUSp8n2rAyaXYy1sjoKmrQ==", "anonymous", "", "head", "");
|
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js", "sha512-pax4MlgXjHEPfCwcJLQhigY7+N8rt6bVvWLFyUMuxShv170X53TRzGPmPkZmGBhk+jikR8WBM4yl7A9WMHHqvg==", "anonymous", "", "head", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@
|
|||||||
<td><ActionLink Action="Settings" Text="Edit" ModuleId="@context.ModuleId" Security="SecurityAccessLevel.Edit" Permissions="@context.Permissions" ResourceKey="ModuleSettings" /></td>
|
<td><ActionLink Action="Settings" Text="Edit" ModuleId="@context.ModuleId" Security="SecurityAccessLevel.Edit" Permissions="@context.Permissions" ResourceKey="ModuleSettings" /></td>
|
||||||
<td><ActionDialog Header="Delete Module" Message="Are You Sure You Wish To Delete This Module?" Action="Delete" Security="SecurityAccessLevel.Edit" Permissions="@context.Permissions" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
|
<td><ActionDialog Header="Delete Module" Message="Are You Sure You Wish To Delete This Module?" Action="Delete" Security="SecurityAccessLevel.Edit" Permissions="@context.Permissions" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
|
||||||
<td>@context.Title</td>
|
<td>@context.Title</td>
|
||||||
<td>@context.ModuleDefinition.Name</td>
|
<td>@context.ModuleDefinition?.Name</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
}
|
}
|
||||||
|
@ -69,24 +69,18 @@
|
|||||||
Page page;
|
Page page;
|
||||||
User user = null;
|
User user = null;
|
||||||
List<Module> modules;
|
List<Module> modules;
|
||||||
var moduleid = -1;
|
|
||||||
var action = Constants.DefaultAction;
|
|
||||||
var urlparameters = string.Empty;
|
|
||||||
var editmode = false;
|
var editmode = false;
|
||||||
var refresh = UI.Refresh.None;
|
var refresh = UI.Refresh.None;
|
||||||
var lastsyncdate = DateTime.UtcNow.AddHours(-1);
|
var lastsyncdate = DateTime.UtcNow.AddHours(-1);
|
||||||
var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime);
|
var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime);
|
||||||
|
|
||||||
Uri uri = new Uri(_absoluteUri);
|
Route route = new Route(_absoluteUri, SiteState.Alias.Path);
|
||||||
|
var moduleid = (int.TryParse(route.ModuleId, out int mid)) ? mid : -1;
|
||||||
// get path
|
var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction;
|
||||||
var path = uri.LocalPath.Substring(1);
|
var querystring = ParseQueryString(route.Query);
|
||||||
|
|
||||||
// parse querystring
|
|
||||||
var querystring = ParseQueryString(uri.Query);
|
|
||||||
|
|
||||||
// reload the client application if there is a forced reload or the user navigated to a site with a different alias
|
// 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)))
|
if (querystring.ContainsKey("reload") || (!route.AbsolutePath.Substring(1).ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path)))
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true);
|
NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true);
|
||||||
return;
|
return;
|
||||||
@ -168,72 +162,9 @@
|
|||||||
pages = PageState.Pages;
|
pages = PageState.Pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
// format path and remove alias
|
|
||||||
path = path.Replace("//", "/"); // in case of doubleslash at end
|
|
||||||
path += (!path.EndsWith("/")) ? "/" : "";
|
|
||||||
if (SiteState.Alias.Path != "" && path.StartsWith(SiteState.Alias.Path))
|
|
||||||
{
|
|
||||||
path = path.Substring(SiteState.Alias.Path.Length + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract admin route elements from path
|
|
||||||
var segments = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
int result;
|
|
||||||
|
|
||||||
int modIdPos = 0;
|
|
||||||
int actionPos = 0;
|
|
||||||
int urlParametersPos = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < segments.Length; i++)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (segments[i] == Constants.UrlParametersDelimiter)
|
|
||||||
{
|
|
||||||
urlParametersPos = i + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i >= urlParametersPos && urlParametersPos != 0)
|
|
||||||
{
|
|
||||||
urlparameters += "/" + segments[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (segments[i] == Constants.ModuleDelimiter)
|
|
||||||
{
|
|
||||||
modIdPos = i + 1;
|
|
||||||
actionPos = modIdPos + 1;
|
|
||||||
if (actionPos <= segments.Length - 1)
|
|
||||||
{
|
|
||||||
action = segments[actionPos];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if path has moduleid and action specification ie. pagename/*/moduleid/action/
|
|
||||||
if (modIdPos > 0)
|
|
||||||
{
|
|
||||||
int.TryParse(segments[modIdPos], out result);
|
|
||||||
moduleid = result;
|
|
||||||
if (actionPos > segments.Length - 1)
|
|
||||||
{
|
|
||||||
path = path.Replace(segments[modIdPos - 1] + "/" + segments[modIdPos] + "/", "");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
path = path.Replace(segments[modIdPos - 1] + "/" + segments[modIdPos] + "/" + segments[actionPos] + "/", "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (urlParametersPos > 0)
|
|
||||||
{
|
|
||||||
path = path.Replace(segments[urlParametersPos - 1] + urlparameters + "/", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove trailing slash so it can be used as a key for Pages
|
|
||||||
if (path.EndsWith("/")) path = path.Substring(0, path.Length - 1);
|
|
||||||
|
|
||||||
if (PageState == null || refresh == UI.Refresh.Site)
|
if (PageState == null || refresh == UI.Refresh.Site)
|
||||||
{
|
{
|
||||||
page = pages.FirstOrDefault(item => item.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
|
page = pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -241,14 +172,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get the page if the path has changed
|
// get the page if the path has changed
|
||||||
if (page == null || page.Path != path)
|
if (page == null || page.Path != route.PagePath)
|
||||||
{
|
{
|
||||||
page = pages.FirstOrDefault(item => item.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
|
page = pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
|
||||||
// if the home page path does not exist then use the first page in the collection (a future enhancement would allow the admin to specify a home page)
|
// if the home page path does not exist then use the first page in the collection (a future enhancement would allow the admin to specify the home page)
|
||||||
if (page == null && path == "")
|
if (page == null && route.PagePath == "")
|
||||||
{
|
{
|
||||||
page = pages.FirstOrDefault();
|
page = pages.FirstOrDefault();
|
||||||
path = page.Path;
|
|
||||||
}
|
}
|
||||||
editmode = false;
|
editmode = false;
|
||||||
}
|
}
|
||||||
@ -286,7 +216,7 @@
|
|||||||
Modules = modules,
|
Modules = modules,
|
||||||
Uri = new Uri(_absoluteUri, UriKind.Absolute),
|
Uri = new Uri(_absoluteUri, UriKind.Absolute),
|
||||||
QueryString = querystring,
|
QueryString = querystring,
|
||||||
UrlParameters = urlparameters,
|
UrlParameters = route.UrlParameters,
|
||||||
ModuleId = moduleid,
|
ModuleId = moduleid,
|
||||||
Action = action,
|
Action = action,
|
||||||
EditMode = editmode,
|
EditMode = editmode,
|
||||||
@ -302,12 +232,12 @@
|
|||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
// redirect to login page
|
// redirect to login page
|
||||||
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + path));
|
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + route.AbsolutePath));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await LogService.Log(null, null, user.UserId, GetType().AssemblyQualifiedName, Utilities.GetTypeNameLastSegment(GetType().AssemblyQualifiedName, 1), LogFunction.Security, LogLevel.Error, null, "Page Does Not Exist Or User Is Not Authorized To View Page {Path}", path);
|
await LogService.Log(null, null, user.UserId, GetType().AssemblyQualifiedName, Utilities.GetTypeNameLastSegment(GetType().AssemblyQualifiedName, 1), LogFunction.Security, LogLevel.Error, null, "Page Does Not Exist Or User Is Not Authorized To View Page {Path}", route.PagePath);
|
||||||
if (path != "")
|
if (route.PagePath != "")
|
||||||
{
|
{
|
||||||
// redirect to home page
|
// redirect to home page
|
||||||
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", ""));
|
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", ""));
|
||||||
|
@ -9,10 +9,8 @@
|
|||||||
|
|
||||||
RenderFragment DynamicComponent { get; set; }
|
RenderFragment DynamicComponent { get; set; }
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
var interop = new Interop(JsRuntime);
|
|
||||||
|
|
||||||
// handle page redirection
|
// handle page redirection
|
||||||
if (!string.IsNullOrEmpty(PageState.Page.Url))
|
if (!string.IsNullOrEmpty(PageState.Page.Url))
|
||||||
{
|
{
|
||||||
@ -20,6 +18,20 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DynamicComponent = builder =>
|
||||||
|
{
|
||||||
|
var themeType = Type.GetType(PageState.Page.ThemeType);
|
||||||
|
builder.OpenComponent(0, themeType);
|
||||||
|
builder.CloseComponent();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (!firstRender)
|
||||||
|
{
|
||||||
|
var interop = new Interop(JsRuntime);
|
||||||
|
|
||||||
// set page title
|
// set page title
|
||||||
if (!string.IsNullOrEmpty(PageState.Page.Title))
|
if (!string.IsNullOrEmpty(PageState.Page.Title))
|
||||||
{
|
{
|
||||||
@ -42,66 +54,6 @@
|
|||||||
await interop.IncludeLinks(links.ToArray());
|
await interop.IncludeLinks(links.ToArray());
|
||||||
}
|
}
|
||||||
await interop.RemoveElementsById("app-stylesheet", "", "app-stylesheet-" + batch + "-00");
|
await interop.RemoveElementsById("app-stylesheet", "", "app-stylesheet-" + batch + "-00");
|
||||||
|
}
|
||||||
// add favicon
|
|
||||||
if (PageState.Site.FaviconFileId != null)
|
|
||||||
{
|
|
||||||
await interop.IncludeLink("app-favicon", "shortcut icon", Utilities.ContentUrl(PageState.Alias, PageState.Site.FaviconFileId.Value), "image/x-icon", "", "", "id");
|
|
||||||
}
|
|
||||||
// add PWA support
|
|
||||||
if (PageState.Site.PwaIsEnabled && PageState.Site.PwaAppIconFileId != null && PageState.Site.PwaSplashIconFileId != null)
|
|
||||||
{
|
|
||||||
await InitializePwa(interop);
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicComponent = builder =>
|
|
||||||
{
|
|
||||||
var themeType = Type.GetType(PageState.Page.ThemeType);
|
|
||||||
builder.OpenComponent(0, themeType);
|
|
||||||
builder.CloseComponent();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task InitializePwa(Interop interop)
|
|
||||||
{
|
|
||||||
string url = NavigationManager.BaseUri;
|
|
||||||
url = url.Substring(0, url.Length - 1);
|
|
||||||
|
|
||||||
// dynamically create manifest.json and add to page
|
|
||||||
string manifest = "setTimeout(() => { " +
|
|
||||||
"var manifest = { " +
|
|
||||||
"\"name\": \"" + PageState.Site.Name + "\", " +
|
|
||||||
"\"short_name\": \"" + PageState.Site.Name + "\", " +
|
|
||||||
"\"start_url\": \"" + url + "/\", " +
|
|
||||||
"\"display\": \"standalone\", " +
|
|
||||||
"\"background_color\": \"#fff\", " +
|
|
||||||
"\"description\": \"" + PageState.Site.Name + "\", " +
|
|
||||||
"\"icons\": [{ " +
|
|
||||||
"\"src\": \"" + url + Utilities.ContentUrl(PageState.Alias, PageState.Site.PwaAppIconFileId.Value) + "\", " +
|
|
||||||
"\"sizes\": \"192x192\", " +
|
|
||||||
"\"type\": \"image/png\" " +
|
|
||||||
"}, { " +
|
|
||||||
"\"src\": \"" + url + Utilities.ContentUrl(PageState.Alias, PageState.Site.PwaSplashIconFileId.Value) + "\", " +
|
|
||||||
"\"sizes\": \"512x512\", " +
|
|
||||||
"\"type\": \"image/png\" " +
|
|
||||||
"}] " +
|
|
||||||
"}; " +
|
|
||||||
"const serialized = JSON.stringify(manifest); " +
|
|
||||||
"const blob = new Blob([serialized], {type: 'application/javascript'}); " +
|
|
||||||
"const url = URL.createObjectURL(blob); " +
|
|
||||||
"document.getElementById('app-manifest').setAttribute('href', url); " +
|
|
||||||
"} " +
|
|
||||||
", 1000);";
|
|
||||||
await interop.IncludeScript("app-pwa", "", "", "", manifest, "body", "id");
|
|
||||||
|
|
||||||
// service worker must be in root of site
|
|
||||||
string serviceworker = "if ('serviceWorker' in navigator) { " +
|
|
||||||
"navigator.serviceWorker.register('/service-worker.js').then(function(registration) { " +
|
|
||||||
"console.log('ServiceWorker Registration Successful'); " +
|
|
||||||
"}).catch (function(err) { " +
|
|
||||||
"console.log('ServiceWorker Registration Failed ', err); " +
|
|
||||||
"}); " +
|
|
||||||
"}";
|
|
||||||
await interop.IncludeScript("app-serviceworker", "", "", "", serviceworker, "body", "id");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="4.0.0-preview3.21293.2" />
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="4.0.0" />
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
|
||||||
|
@ -9,12 +9,14 @@
|
|||||||
<meta name="viewport" content="width=device-width">
|
<meta name="viewport" content="width=device-width">
|
||||||
<title>@Model.Title</title>
|
<title>@Model.Title</title>
|
||||||
<base href="~/" />
|
<base href="~/" />
|
||||||
<link id="app-favicon" rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
|
<link id="app-favicon" rel="shortcut icon" type="image/x-icon" href="@Model.FavIcon" />
|
||||||
<!-- stub the PWA manifest but defer the assignment of href -->
|
@if (!string.IsNullOrEmpty(Model.PWAScript))
|
||||||
|
{
|
||||||
<link id="app-manifest" rel="manifest" />
|
<link id="app-manifest" rel="manifest" />
|
||||||
<link rel="stylesheet" href="css/app.css" />
|
}
|
||||||
<script src="js/app.js"></script>
|
<script src="js/app.js"></script>
|
||||||
<script src="js/loadjs.min.js"></script>
|
<script src="js/loadjs.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="css/app.css" />
|
||||||
@Html.Raw(@Model.HeadResources)
|
@Html.Raw(@Model.HeadResources)
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -44,6 +46,10 @@
|
|||||||
{
|
{
|
||||||
<script src="_framework/blazor.server.js"></script>
|
<script src="_framework/blazor.server.js"></script>
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(Model.PWAScript))
|
||||||
|
{
|
||||||
|
@Model.PWAScript
|
||||||
|
}
|
||||||
@Html.Raw(@Model.BodyResources)
|
@Html.Raw(@Model.BodyResources)
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Localization;
|
|||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.AspNetCore.Antiforgery;
|
using Microsoft.AspNetCore.Antiforgery;
|
||||||
|
using Microsoft.AspNetCore.Http.Extensions;
|
||||||
|
|
||||||
namespace Oqtane.Pages
|
namespace Oqtane.Pages
|
||||||
{
|
{
|
||||||
@ -24,8 +25,9 @@ namespace Oqtane.Pages
|
|||||||
private readonly ILanguageRepository _languages;
|
private readonly ILanguageRepository _languages;
|
||||||
private readonly IAntiforgery _antiforgery;
|
private readonly IAntiforgery _antiforgery;
|
||||||
private readonly ISiteRepository _sites;
|
private readonly ISiteRepository _sites;
|
||||||
|
private readonly IPageRepository _pages;
|
||||||
|
|
||||||
public HostModel(IConfiguration configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, ISiteRepository sites)
|
public HostModel(IConfiguration configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, ISiteRepository sites, IPageRepository pages)
|
||||||
{
|
{
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_tenantManager = tenantManager;
|
_tenantManager = tenantManager;
|
||||||
@ -33,6 +35,7 @@ namespace Oqtane.Pages
|
|||||||
_languages = languages;
|
_languages = languages;
|
||||||
_antiforgery = antiforgery;
|
_antiforgery = antiforgery;
|
||||||
_sites = sites;
|
_sites = sites;
|
||||||
|
_pages = pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string AntiForgeryToken = "";
|
public string AntiForgeryToken = "";
|
||||||
@ -41,6 +44,9 @@ namespace Oqtane.Pages
|
|||||||
public string HeadResources = "";
|
public string HeadResources = "";
|
||||||
public string BodyResources = "";
|
public string BodyResources = "";
|
||||||
public string Title = "";
|
public string Title = "";
|
||||||
|
public string FavIcon = "favicon.ico";
|
||||||
|
public string PWAScript = "";
|
||||||
|
public string ThemeType = "";
|
||||||
|
|
||||||
public void OnGet()
|
public void OnGet()
|
||||||
{
|
{
|
||||||
@ -56,20 +62,14 @@ namespace Oqtane.Pages
|
|||||||
RenderMode = (RenderMode)Enum.Parse(typeof(RenderMode), _configuration.GetSection("RenderMode").Value, true);
|
RenderMode = (RenderMode)Enum.Parse(typeof(RenderMode), _configuration.GetSection("RenderMode").Value, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
|
|
||||||
foreach (Assembly assembly in assemblies)
|
|
||||||
{
|
|
||||||
ProcessHostResources(assembly);
|
|
||||||
ProcessModuleControls(assembly);
|
|
||||||
ProcessThemeControls(assembly);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if framework is installed
|
// if framework is installed
|
||||||
if (!string.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection")))
|
if (!string.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection")))
|
||||||
{
|
{
|
||||||
var alias = _tenantManager.GetAlias();
|
var alias = _tenantManager.GetAlias();
|
||||||
if (alias != null)
|
if (alias != null)
|
||||||
{
|
{
|
||||||
|
Route route = new Route(HttpContext.Request.GetEncodedUrl(), alias.Path);
|
||||||
|
|
||||||
var site = _sites.GetSite(alias.SiteId);
|
var site = _sites.GetSite(alias.SiteId);
|
||||||
if (site != null)
|
if (site != null)
|
||||||
{
|
{
|
||||||
@ -81,10 +81,48 @@ namespace Oqtane.Pages
|
|||||||
{
|
{
|
||||||
RenderMode = (RenderMode)Enum.Parse(typeof(RenderMode), site.RenderMode, true);
|
RenderMode = (RenderMode)Enum.Parse(typeof(RenderMode), site.RenderMode, true);
|
||||||
}
|
}
|
||||||
|
if (site.FaviconFileId != null)
|
||||||
|
{
|
||||||
|
FavIcon = Utilities.ContentUrl(alias, site.FaviconFileId.Value);
|
||||||
|
}
|
||||||
|
if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null)
|
||||||
|
{
|
||||||
|
PWAScript = CreatePWAScript(alias, site, route);
|
||||||
|
}
|
||||||
Title = site.Name;
|
Title = site.Name;
|
||||||
|
ThemeType = site.DefaultThemeType;
|
||||||
|
|
||||||
|
var page = _pages.GetPage(route.PagePath, site.SiteId);
|
||||||
|
if (page != null)
|
||||||
|
{
|
||||||
|
// set page title
|
||||||
|
if (!string.IsNullOrEmpty(page.Title))
|
||||||
|
{
|
||||||
|
Title = page.Title;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Title = Title + " - " + page.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if culture not specified
|
// include theme resources
|
||||||
|
if (!string.IsNullOrEmpty(page.ThemeType))
|
||||||
|
{
|
||||||
|
ThemeType = page.ThemeType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// include global resources
|
||||||
|
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
|
||||||
|
foreach (Assembly assembly in assemblies)
|
||||||
|
{
|
||||||
|
ProcessHostResources(assembly);
|
||||||
|
ProcessModuleControls(assembly);
|
||||||
|
ProcessThemeControls(assembly);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set culture if not specified
|
||||||
if (HttpContext.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName] == null)
|
if (HttpContext.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName] == null)
|
||||||
{
|
{
|
||||||
// set default language for site if the culture is not supported
|
// set default language for site if the culture is not supported
|
||||||
@ -103,6 +141,44 @@ namespace Oqtane.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string CreatePWAScript(Alias alias, Site site, Route route)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
"<script>" +
|
||||||
|
"setTimeout(() => { " +
|
||||||
|
"var manifest = { " +
|
||||||
|
"\"name\": \"" + site.Name + "\", " +
|
||||||
|
"\"short_name\": \"" + site.Name + "\", " +
|
||||||
|
"\"start_url\": \"" + route.Scheme + "://" + route.Authority + "/\", " +
|
||||||
|
"\"display\": \"standalone\", " +
|
||||||
|
"\"background_color\": \"#fff\", " +
|
||||||
|
"\"description\": \"" + site.Name + "\", " +
|
||||||
|
"\"icons\": [{ " +
|
||||||
|
"\"src\": \"" + route.Scheme + "://" + route.Authority + Utilities.ContentUrl(alias, site.PwaAppIconFileId.Value) + "\", " +
|
||||||
|
"\"sizes\": \"192x192\", " +
|
||||||
|
"\"type\": \"image/png\" " +
|
||||||
|
"}, { " +
|
||||||
|
"\"src\": \"" + route.Scheme + "://" + route.Authority + Utilities.ContentUrl(alias, site.PwaSplashIconFileId.Value) + "\", " +
|
||||||
|
"\"sizes\": \"512x512\", " +
|
||||||
|
"\"type\": \"image/png\" " +
|
||||||
|
"}] " +
|
||||||
|
"}; " +
|
||||||
|
"const serialized = JSON.stringify(manifest); " +
|
||||||
|
"const blob = new Blob([serialized], {type: 'application/javascript'}); " +
|
||||||
|
"const url = URL.createObjectURL(blob); " +
|
||||||
|
"document.getElementById('app-manifest').setAttribute('href', url); " +
|
||||||
|
"} " +
|
||||||
|
", 1000);" +
|
||||||
|
"if ('serviceWorker' in navigator) { " +
|
||||||
|
"navigator.serviceWorker.register('/service-worker.js').then(function(registration) { " +
|
||||||
|
"console.log('ServiceWorker Registration Successful'); " +
|
||||||
|
"}).catch (function(err) { " +
|
||||||
|
"console.log('ServiceWorker Registration Failed ', err); " +
|
||||||
|
"}); " +
|
||||||
|
"};" +
|
||||||
|
"</script>";
|
||||||
|
}
|
||||||
|
|
||||||
private void ProcessHostResources(Assembly assembly)
|
private void ProcessHostResources(Assembly assembly)
|
||||||
{
|
{
|
||||||
var types = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IHostResources)));
|
var types = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IHostResources)));
|
||||||
@ -151,7 +227,7 @@ namespace Oqtane.Pages
|
|||||||
{
|
{
|
||||||
foreach (var resource in obj.Resources)
|
foreach (var resource in obj.Resources)
|
||||||
{
|
{
|
||||||
if (resource.Declaration == ResourceDeclaration.Global)
|
if (resource.Declaration == ResourceDeclaration.Global || (Utilities.GetFullTypeName(type.AssemblyQualifiedName) == ThemeType && resource.ResourceType == ResourceType.Stylesheet))
|
||||||
{
|
{
|
||||||
ProcessResource(resource);
|
ProcessResource(resource);
|
||||||
}
|
}
|
||||||
@ -166,7 +242,8 @@ namespace Oqtane.Pages
|
|||||||
case ResourceType.Stylesheet:
|
case ResourceType.Stylesheet:
|
||||||
if (!HeadResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase))
|
if (!HeadResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
HeadResources += "<link rel=\"stylesheet\" href=\"" + resource.Url + "\"" + CrossOrigin(resource.CrossOrigin) + Integrity(resource.Integrity) + " />" + Environment.NewLine;
|
var id = (resource.Declaration == ResourceDeclaration.Global) ? "" : "id=\"app-stylesheet-" + DateTime.Now.ToString("yyyyMMddHHmmssfff") + "-00\" ";
|
||||||
|
HeadResources += "<link " + id + "rel=\"stylesheet\" href=\"" + resource.Url + "\"" + CrossOrigin(resource.CrossOrigin) + Integrity(resource.Integrity) + " />" + Environment.NewLine;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ResourceType.Script:
|
case ResourceType.Script:
|
||||||
|
124
Oqtane.Shared/Models/Route.cs
Normal file
124
Oqtane.Shared/Models/Route.cs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
using System;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace Oqtane.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A route is comprised of multiple components:
|
||||||
|
/// {scheme}://{hostname}/{aliaspath}/{pagepath}/*/{moduleid}/{action}/!/{urlparameters}?{query}#{fragment}
|
||||||
|
/// </summary>
|
||||||
|
public class Route
|
||||||
|
{
|
||||||
|
// default constructor accepts an absolute route url and alias
|
||||||
|
public Route(string route, string aliaspath)
|
||||||
|
{
|
||||||
|
Uri uri = new Uri(route);
|
||||||
|
Authority = uri.Authority;
|
||||||
|
Scheme = uri.Scheme;
|
||||||
|
Host = uri.Host;
|
||||||
|
Port = uri.Port.ToString();
|
||||||
|
Query = uri.Query;
|
||||||
|
Fragment = uri.Fragment;
|
||||||
|
AbsolutePath = uri.AbsolutePath;
|
||||||
|
AliasPath = aliaspath;
|
||||||
|
PagePath = AbsolutePath;
|
||||||
|
ModuleId = "";
|
||||||
|
Action = "";
|
||||||
|
UrlParameters = "";
|
||||||
|
|
||||||
|
if (AliasPath.Length != 0)
|
||||||
|
{
|
||||||
|
PagePath = PagePath.Substring(AliasPath.Length + 1);
|
||||||
|
}
|
||||||
|
int pos = PagePath.IndexOf("/" + Constants.UrlParametersDelimiter + "/");
|
||||||
|
if (pos != -1)
|
||||||
|
{
|
||||||
|
UrlParameters = PagePath.Substring(pos + 3);
|
||||||
|
PagePath = PagePath.Substring(1, pos);
|
||||||
|
}
|
||||||
|
pos = PagePath.IndexOf("/" + Constants.ModuleDelimiter + "/");
|
||||||
|
if (pos != -1)
|
||||||
|
{
|
||||||
|
ModuleId = PagePath.Substring(pos + 3);
|
||||||
|
PagePath = PagePath.Substring(1, pos);
|
||||||
|
}
|
||||||
|
if (ModuleId.Length != 0)
|
||||||
|
{
|
||||||
|
pos = ModuleId.IndexOf("/");
|
||||||
|
if (pos != -1)
|
||||||
|
{
|
||||||
|
Action = ModuleId.Substring(pos + 1);
|
||||||
|
ModuleId = ModuleId.Substring(0, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (PagePath.StartsWith("/"))
|
||||||
|
{
|
||||||
|
PagePath = (PagePath.Length == 1) ? "" : PagePath.Substring(1);
|
||||||
|
}
|
||||||
|
if (PagePath.EndsWith("/"))
|
||||||
|
{
|
||||||
|
PagePath = PagePath.Substring(0, PagePath.Length - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The host name or IP address and port number (does not include scheme or trailing slash)
|
||||||
|
/// </summary>
|
||||||
|
public string Authority { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A fully qualified route contains a scheme (ie. http, https )
|
||||||
|
/// </summary>
|
||||||
|
public string Scheme { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A fully qualified route contains a host name. The host name may include a port number.
|
||||||
|
/// </summary>
|
||||||
|
public string Host { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A host name may contain a port number
|
||||||
|
/// </summary>
|
||||||
|
public string Port { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The absolute path for the route
|
||||||
|
/// </summary>
|
||||||
|
public string AbsolutePath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An absolute path may contain an alias path
|
||||||
|
/// </summary>
|
||||||
|
public string AliasPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A absolute path may contain a page path.
|
||||||
|
/// </summary>
|
||||||
|
public string PagePath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A route may contain a module id (ie. when created using EditUrl) located after the module delimiter segment (/*/).
|
||||||
|
/// </summary>
|
||||||
|
public string ModuleId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A route may contain an action (ie. when created using EditUrl) located after the module id.
|
||||||
|
/// </summary>
|
||||||
|
public string Action { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A route may contain parameters located after the url parameter delimiter segment (/!/).
|
||||||
|
/// </summary>
|
||||||
|
public string UrlParameters { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A route may contain querystring parameters located after the ? delimiter
|
||||||
|
/// </summary>
|
||||||
|
public string Query { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A route may contain a fragment located after the # delimiter
|
||||||
|
/// </summary>
|
||||||
|
public string Fragment { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="6.0.0-preview.4.21253.7" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
<PackageReference Include="System.Text.Json" Version="6.0.0" />
|
<PackageReference Include="System.Text.Json" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user