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:
parent
03106526e9
commit
43d166fb7d
|
@ -158,8 +158,8 @@
|
|||
if (firstRender)
|
||||
{
|
||||
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.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.0.2/js/bootstrap.min.js", "sha512-a6ctI6w1kg3J4dSjknHj3aWLEbjitAXAjLDRUxo2wyYmDFRcz2RJuQr5M3Kt8O/TtUSp8n2rAyaXYy1sjoKmrQ==", "anonymous", "", "head", "");
|
||||
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.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><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.ModuleDefinition.Name</td>
|
||||
<td>@context.ModuleDefinition?.Name</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
}
|
||||
|
|
|
@ -15,78 +15,72 @@
|
|||
@DynamicComponent
|
||||
|
||||
@code {
|
||||
private string _absoluteUri;
|
||||
private bool _navigationInterceptionEnabled;
|
||||
private PageState _pagestate;
|
||||
private string _absoluteUri;
|
||||
private bool _navigationInterceptionEnabled;
|
||||
private PageState _pagestate;
|
||||
|
||||
[Parameter]
|
||||
public string Runtime { get; set; }
|
||||
[Parameter]
|
||||
public string Runtime { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string RenderMode { get; set; }
|
||||
[Parameter]
|
||||
public string RenderMode { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
PageState PageState { get; set; }
|
||||
[CascadingParameter]
|
||||
PageState PageState { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Action<PageState> OnStateChange { get; set; }
|
||||
[Parameter]
|
||||
public Action<PageState> OnStateChange { get; set; }
|
||||
|
||||
private RenderFragment DynamicComponent { get; set; }
|
||||
private RenderFragment DynamicComponent { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_absoluteUri = NavigationManager.Uri;
|
||||
NavigationManager.LocationChanged += LocationChanged;
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_absoluteUri = NavigationManager.Uri;
|
||||
NavigationManager.LocationChanged += LocationChanged;
|
||||
|
||||
DynamicComponent = builder =>
|
||||
{
|
||||
if (PageState != null)
|
||||
{
|
||||
builder.OpenComponent(0, Type.GetType(Constants.PageComponent));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
};
|
||||
}
|
||||
DynamicComponent = builder =>
|
||||
{
|
||||
if (PageState != null)
|
||||
{
|
||||
builder.OpenComponent(0, Type.GetType(Constants.PageComponent));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
NavigationManager.LocationChanged -= LocationChanged;
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
NavigationManager.LocationChanged -= LocationChanged;
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (PageState == null)
|
||||
{
|
||||
await Refresh();
|
||||
}
|
||||
}
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (PageState == null)
|
||||
{
|
||||
await Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")]
|
||||
private async Task Refresh()
|
||||
{
|
||||
Site site;
|
||||
List<Page> pages;
|
||||
Page page;
|
||||
User user = null;
|
||||
List<Module> modules;
|
||||
var moduleid = -1;
|
||||
var action = Constants.DefaultAction;
|
||||
var urlparameters = string.Empty;
|
||||
var editmode = false;
|
||||
var refresh = UI.Refresh.None;
|
||||
var lastsyncdate = DateTime.UtcNow.AddHours(-1);
|
||||
var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime);
|
||||
[SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")]
|
||||
private async Task Refresh()
|
||||
{
|
||||
Site site;
|
||||
List<Page> pages;
|
||||
Page page;
|
||||
User user = null;
|
||||
List<Module> modules;
|
||||
var editmode = false;
|
||||
var refresh = UI.Refresh.None;
|
||||
var lastsyncdate = DateTime.UtcNow.AddHours(-1);
|
||||
var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime);
|
||||
|
||||
Uri uri = new Uri(_absoluteUri);
|
||||
|
||||
// get path
|
||||
var path = uri.LocalPath.Substring(1);
|
||||
|
||||
// parse querystring
|
||||
var querystring = ParseQueryString(uri.Query);
|
||||
Route route = new Route(_absoluteUri, SiteState.Alias.Path);
|
||||
var moduleid = (int.TryParse(route.ModuleId, out int mid)) ? mid : -1;
|
||||
var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction;
|
||||
var querystring = ParseQueryString(route.Query);
|
||||
|
||||
// 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);
|
||||
return;
|
||||
|
@ -168,72 +162,9 @@
|
|||
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)
|
||||
{
|
||||
page = pages.FirstOrDefault(item => item.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
|
||||
page = pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -241,14 +172,13 @@
|
|||
}
|
||||
|
||||
// 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));
|
||||
// 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 (page == null && path == "")
|
||||
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 the home page)
|
||||
if (page == null && route.PagePath == "")
|
||||
{
|
||||
page = pages.FirstOrDefault();
|
||||
path = page.Path;
|
||||
}
|
||||
editmode = false;
|
||||
}
|
||||
|
@ -286,7 +216,7 @@
|
|||
Modules = modules,
|
||||
Uri = new Uri(_absoluteUri, UriKind.Absolute),
|
||||
QueryString = querystring,
|
||||
UrlParameters = urlparameters,
|
||||
UrlParameters = route.UrlParameters,
|
||||
ModuleId = moduleid,
|
||||
Action = action,
|
||||
EditMode = editmode,
|
||||
|
@ -302,12 +232,12 @@
|
|||
if (user == null)
|
||||
{
|
||||
// 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
|
||||
{
|
||||
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);
|
||||
if (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 (route.PagePath != "")
|
||||
{
|
||||
// redirect to home page
|
||||
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", ""));
|
||||
|
|
|
@ -5,103 +5,55 @@
|
|||
@DynamicComponent
|
||||
|
||||
@code {
|
||||
[CascadingParameter] PageState PageState { get; set; }
|
||||
[CascadingParameter] PageState PageState { get; set; }
|
||||
|
||||
RenderFragment DynamicComponent { get; set; }
|
||||
RenderFragment DynamicComponent { get; set; }
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
var interop = new Interop(JsRuntime);
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
// handle page redirection
|
||||
if (!string.IsNullOrEmpty(PageState.Page.Url))
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.Page.Url);
|
||||
return;
|
||||
}
|
||||
|
||||
// handle page redirection
|
||||
if (!string.IsNullOrEmpty(PageState.Page.Url))
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.Page.Url);
|
||||
return;
|
||||
}
|
||||
DynamicComponent = builder =>
|
||||
{
|
||||
var themeType = Type.GetType(PageState.Page.ThemeType);
|
||||
builder.OpenComponent(0, themeType);
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
|
||||
// set page title
|
||||
if (!string.IsNullOrEmpty(PageState.Page.Title))
|
||||
{
|
||||
await interop.UpdateTitle(PageState.Page.Title);
|
||||
}
|
||||
else
|
||||
{
|
||||
await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name);
|
||||
}
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!firstRender)
|
||||
{
|
||||
var interop = new Interop(JsRuntime);
|
||||
|
||||
// manage stylesheets for this page
|
||||
string batch = DateTime.Now.ToString("yyyyMMddHHmmssfff");
|
||||
var links = new List<object>();
|
||||
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet && item.Declaration != ResourceDeclaration.Global))
|
||||
{
|
||||
links.Add(new { id = "app-stylesheet-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = resource.Url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", key = "" });
|
||||
}
|
||||
if (links.Any())
|
||||
{
|
||||
await interop.IncludeLinks(links.ToArray());
|
||||
}
|
||||
await interop.RemoveElementsById("app-stylesheet", "", "app-stylesheet-" + batch + "-00");
|
||||
// set page title
|
||||
if (!string.IsNullOrEmpty(PageState.Page.Title))
|
||||
{
|
||||
await interop.UpdateTitle(PageState.Page.Title);
|
||||
}
|
||||
else
|
||||
{
|
||||
await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name);
|
||||
}
|
||||
|
||||
// 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");
|
||||
}
|
||||
// manage stylesheets for this page
|
||||
string batch = DateTime.Now.ToString("yyyyMMddHHmmssfff");
|
||||
var links = new List<object>();
|
||||
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet && item.Declaration != ResourceDeclaration.Global))
|
||||
{
|
||||
links.Add(new { id = "app-stylesheet-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = resource.Url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", key = "" });
|
||||
}
|
||||
if (links.Any())
|
||||
{
|
||||
await interop.IncludeLinks(links.ToArray());
|
||||
}
|
||||
await interop.RemoveElementsById("app-stylesheet", "", "app-stylesheet-" + batch + "-00");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<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.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.EntityFrameworkCore" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
|
||||
|
|
|
@ -9,12 +9,14 @@
|
|||
<meta name="viewport" content="width=device-width">
|
||||
<title>@Model.Title</title>
|
||||
<base href="~/" />
|
||||
<link id="app-favicon" rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
|
||||
<!-- stub the PWA manifest but defer the assignment of href -->
|
||||
<link id="app-manifest" rel="manifest" />
|
||||
<link rel="stylesheet" href="css/app.css" />
|
||||
<link id="app-favicon" rel="shortcut icon" type="image/x-icon" href="@Model.FavIcon" />
|
||||
@if (!string.IsNullOrEmpty(Model.PWAScript))
|
||||
{
|
||||
<link id="app-manifest" rel="manifest" />
|
||||
}
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/loadjs.min.js"></script>
|
||||
<link rel="stylesheet" href="css/app.css" />
|
||||
@Html.Raw(@Model.HeadResources)
|
||||
</head>
|
||||
<body>
|
||||
|
@ -44,6 +46,10 @@
|
|||
{
|
||||
<script src="_framework/blazor.server.js"></script>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.PWAScript))
|
||||
{
|
||||
@Model.PWAScript
|
||||
}
|
||||
@Html.Raw(@Model.BodyResources)
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Localization;
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Antiforgery;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
|
||||
namespace Oqtane.Pages
|
||||
{
|
||||
|
@ -24,8 +25,9 @@ namespace Oqtane.Pages
|
|||
private readonly ILanguageRepository _languages;
|
||||
private readonly IAntiforgery _antiforgery;
|
||||
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;
|
||||
_tenantManager = tenantManager;
|
||||
|
@ -33,6 +35,7 @@ namespace Oqtane.Pages
|
|||
_languages = languages;
|
||||
_antiforgery = antiforgery;
|
||||
_sites = sites;
|
||||
_pages = pages;
|
||||
}
|
||||
|
||||
public string AntiForgeryToken = "";
|
||||
|
@ -41,6 +44,9 @@ namespace Oqtane.Pages
|
|||
public string HeadResources = "";
|
||||
public string BodyResources = "";
|
||||
public string Title = "";
|
||||
public string FavIcon = "favicon.ico";
|
||||
public string PWAScript = "";
|
||||
public string ThemeType = "";
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
|
@ -55,14 +61,6 @@ namespace Oqtane.Pages
|
|||
{
|
||||
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 (!string.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection")))
|
||||
|
@ -70,6 +68,8 @@ namespace Oqtane.Pages
|
|||
var alias = _tenantManager.GetAlias();
|
||||
if (alias != null)
|
||||
{
|
||||
Route route = new Route(HttpContext.Request.GetEncodedUrl(), alias.Path);
|
||||
|
||||
var site = _sites.GetSite(alias.SiteId);
|
||||
if (site != null)
|
||||
{
|
||||
|
@ -81,10 +81,48 @@ namespace Oqtane.Pages
|
|||
{
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
// include theme resources
|
||||
if (!string.IsNullOrEmpty(page.ThemeType))
|
||||
{
|
||||
ThemeType = page.ThemeType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if culture not specified
|
||||
// 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)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
var types = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IHostResources)));
|
||||
|
@ -151,7 +227,7 @@ namespace Oqtane.Pages
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
@ -166,7 +242,8 @@ namespace Oqtane.Pages
|
|||
case ResourceType.Stylesheet:
|
||||
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;
|
||||
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.Relational" 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" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user