oqtane.framework/Oqtane.Client/UI/SiteRouter.razor

486 lines
17 KiB
Plaintext

@using System.Diagnostics.CodeAnalysis
@using System.Runtime.InteropServices
@namespace Oqtane.UI
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject SiteState SiteState
@inject NavigationManager NavigationManager
@inject INavigationInterception NavigationInterception
@inject IAliasService AliasService
@inject ITenantService TenantService
@inject ISiteService SiteService
@inject IPageService PageService
@inject IUserService UserService
@inject IModuleService ModuleService
@inject ILogService LogService
@implements IHandleAfterRender
@DynamicComponent
@code {
private string _absoluteUri;
private bool _navigationInterceptionEnabled;
private PageState _pagestate;
[CascadingParameter]
PageState PageState { get; set; }
[Parameter]
public Action<PageState> OnStateChange { get; set; }
private RenderFragment DynamicComponent { get; set; }
protected override void OnInitialized()
{
_absoluteUri = NavigationManager.Uri;
NavigationManager.LocationChanged += LocationChanged;
DynamicComponent = builder =>
{
if (PageState != null)
{
builder.OpenComponent(0, Type.GetType(Constants.PageComponent));
builder.CloseComponent();
}
};
}
public void Dispose()
{
NavigationManager.LocationChanged -= LocationChanged;
}
protected override async Task OnParametersSetAsync()
{
if (PageState == null)
{
// misconfigured api calls should not be processed through the router
if (!_absoluteUri.Contains("~/api/"))
{
await Refresh();
}
else
{
System.Diagnostics.Debug.WriteLine(GetType().FullName + ": Error: API call to " + _absoluteUri + " is not mapped to a Controller");
}
}
}
[SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")]
private async Task Refresh()
{
Alias alias = null;
Site site;
List<Page> pages;
Page page;
User user = null;
List<Module> modules;
var moduleid = -1;
var action = "";
var editmode = false;
var reload = Reload.None;
var lastsyncdate = DateTime.UtcNow;
var runtime = GetRuntime();
Uri uri = new Uri(_absoluteUri);
// get path
var path = uri.LocalPath.Substring(1);
// parse querystring
var querystring = ParseQueryString(uri.Query);
// the reload parameter is used during user login/logout
if (querystring.ContainsKey("reload"))
{
reload = Reload.Site;
}
if (PageState != null)
{
editmode = PageState.EditMode;
lastsyncdate = PageState.LastSyncDate;
}
alias = await AliasService.GetAliasAsync(path, lastsyncdate);
SiteState.Alias = alias; // set state for services
lastsyncdate = alias.SyncDate;
// process any sync events for site or page
if (reload != Reload.Site && alias.SyncEvents.Any())
{
if (PageState != null && alias.SyncEvents.Exists(item => item.EntityName == EntityNames.Page && item.EntityId == PageState.Page.PageId))
{
reload = Reload.Page;
}
if (alias.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == alias.SiteId))
{
reload = Reload.Site;
}
}
if (reload == Reload.Site || PageState == null || alias.SiteId != PageState.Alias.SiteId)
{
site = await SiteService.GetSiteAsync(alias.SiteId);
reload = Reload.Site;
}
else
{
site = PageState.Site;
}
if (site != null)
{
if (PageState == null || reload == Reload.Site)
{
// get user
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authState.User.Identity.IsAuthenticated)
{
user = await UserService.GetUserAsync(authState.User.Identity.Name, site.SiteId);
}
}
else
{
user = PageState.User;
}
// process any sync events for user
if (reload != Reload.Site && user != null && alias.SyncEvents.Any())
{
if (alias.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId))
{
reload = Reload.Site;
}
}
if (PageState == null || reload >= Reload.Site)
{
pages = await PageService.GetPagesAsync(site.SiteId);
}
else
{
pages = PageState.Pages;
}
// format path and remove alias
path = path.Replace("//", "/");
if (!path.EndsWith("/"))
{
path += "/";
}
if (alias.Path != "")
{
path = path.Replace(alias.Path + "/", "");
}
// extract admin route elements from path
var segments = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
int result;
// check if path has moduleid and control specification ie. page/moduleid/control/
if (segments.Length >= 2 && int.TryParse(segments[segments.Length - 2], out result))
{
action = segments[segments.Length - 1];
moduleid = result;
path = path.Replace(moduleid.ToString() + "/" + action + "/", "");
}
else
{
// check if path has only moduleid specification ie. page/moduleid/
if (segments.Length >= 1 && int.TryParse(segments[segments.Length - 1], out result))
{
moduleid = result;
path = path.Replace(moduleid.ToString() + "/", "");
}
}
// 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 || reload >= Reload.Page)
{
page = pages.Where(item => item.Path == path).FirstOrDefault();
}
else
{
page = PageState.Page;
}
// failsafe in case router cannot locate the home page for the site
if (page == null && path == "")
{
page = pages.FirstOrDefault();
path = page.Path;
}
// check if page has changed
if (page != null && page.Path != path)
{
page = pages.Where(item => item.Path == path).FirstOrDefault();
reload = Reload.Page;
if (page != null)
{
editmode = page.EditMode;
}
}
if (page != null)
{
if (PageState == null)
{
editmode = page.EditMode;
}
// check if user is authorized to view page
if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.Permissions))
{
page = await ProcessPage(page, site, user);
_pagestate = new PageState
{
Alias = alias,
Site = site,
Pages = pages,
Page = page,
User = user,
Uri = new Uri(_absoluteUri, UriKind.Absolute),
QueryString = querystring,
ModuleId = moduleid,
Action = action,
Runtime = runtime
};
if (PageState != null && (PageState.ModuleId != _pagestate.ModuleId || PageState.Action != _pagestate.Action))
{
reload = Reload.Page;
}
if (PageState == null || reload >= Reload.Page)
{
modules = await ModuleService.GetModulesAsync(site.SiteId);
modules = ProcessModules(modules, page.PageId, _pagestate.ModuleId, _pagestate.Action, page.Panes, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType);
}
else
{
modules = PageState.Modules;
}
_pagestate.Modules = modules;
_pagestate.EditMode = editmode;
_pagestate.LastSyncDate = lastsyncdate;
OnStateChange?.Invoke(_pagestate);
}
}
else
{
if (user == null)
{
await LogService.Log(null, null, null, 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);
// redirect to login page
NavigationManager.NavigateTo(Utilities.NavigateUrl(alias.Path, "login", "returnurl=" + path));
}
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 != "")
{
// redirect to home page
NavigationManager.NavigateTo(Utilities.NavigateUrl(alias.Path, "", ""));
}
}
}
}
else
{
// site does not exist
}
}
private async void LocationChanged(object sender, LocationChangedEventArgs args)
{
_absoluteUri = args.Location;
await Refresh();
}
Task IHandleAfterRender.OnAfterRenderAsync()
{
if (!_navigationInterceptionEnabled)
{
_navigationInterceptionEnabled = true;
return NavigationInterception.EnableNavigationInterceptionAsync();
}
return Task.CompletedTask;
}
private Dictionary<string, string> ParseQueryString(string query)
{
Dictionary<string, string> querystring = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(query))
{
query = query.Substring(1); // ignore "?"
foreach (string kvp in query.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries))
{
if (kvp != "")
{
if (kvp.Contains("="))
{
string[] pair = kvp.Split('=');
querystring.Add(pair[0], pair[1]);
}
else
{
querystring.Add(kvp, "true"); // default parameter when no value is provided
}
}
}
}
return querystring;
}
private async Task<Page> ProcessPage(Page page, Site site, User user)
{
try
{
if (page.IsPersonalizable && user != null)
{
// load the personalized page
page = await PageService.GetPageAsync(page.PageId, user.UserId);
}
if (string.IsNullOrEmpty(page.ThemeType))
{
page.ThemeType = site.DefaultThemeType;
page.LayoutType = site.DefaultLayoutType;
}
page.Resources = new List<Resource>();
Type themetype = Type.GetType(page.ThemeType);
var themeobject = Activator.CreateInstance(themetype);
if (themeobject != null)
{
page.Panes = (string)themetype.GetProperty("Panes").GetValue(themeobject, null);
var resources = (List<Resource>)themetype.GetProperty("Resources").GetValue(themeobject, null);
if (resources != null)
{
page.Resources.AddRange(resources);
}
}
if (!string.IsNullOrEmpty(page.LayoutType))
{
themetype = Type.GetType(page.LayoutType);
themeobject = Activator.CreateInstance(themetype);
if (themeobject != null)
{
page.Panes = (string)themetype.GetProperty("Panes").GetValue(themeobject, null);
}
}
}
catch
{
// error loading theme or layout
}
return page;
}
private List<Module> ProcessModules(List<Module> modules, int pageid, int moduleid, string control, string panes, string defaultcontainertype)
{
var paneindex = new Dictionary<string, int>();
foreach (Module module in modules)
{
if (module.PageId == pageid || module.ModuleId == moduleid)
{
var typename = string.Empty;
if (module.ModuleDefinition != null)
{
typename = module.ModuleDefinition.ControlTypeTemplate;
}
else
{
typename = Constants.ErrorModule;
}
if (module.ModuleId == moduleid && control != "")
{
// check if the module defines custom routes
if (module.ModuleDefinition.ControlTypeRoutes != "")
{
foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
if (route.StartsWith(control + "="))
{
typename = route.Replace(control + "=", "");
}
}
}
module.ModuleType = typename.Replace(Constants.ActionToken, control);
// admin controls need to load additional metadata from the IModuleControl interface
if (moduleid == module.ModuleId)
{
typename = module.ModuleType;
// check for core module actions component
if (Constants.DefaultModuleActions.Contains(control))
{
typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, control);
}
Type moduletype = Type.GetType(typename);
if (moduletype != null)
{
var moduleobject = Activator.CreateInstance(moduletype);
module.SecurityAccessLevel = (SecurityAccessLevel)moduletype.GetProperty("SecurityAccessLevel").GetValue(moduleobject, null);
module.ControlTitle = (string)moduletype.GetProperty("Title").GetValue(moduleobject);
module.Actions = (string)moduletype.GetProperty("Actions").GetValue(moduleobject);
module.UseAdminContainer = (bool)moduletype.GetProperty("UseAdminContainer").GetValue(moduleobject);
}
}
}
else
{
module.ModuleType = typename.Replace(Constants.ActionToken, Constants.DefaultAction);
}
// ensure module's pane exists in current page and if not, assign it to the Admin pane
if (panes == null || !panes.ToLower().Contains(module.Pane.ToLower()))
{
module.Pane = Constants.AdminPane;
}
// calculate module position within pane
if (paneindex.ContainsKey(module.Pane))
{
paneindex[module.Pane] += 1;
}
else
{
paneindex.Add(module.Pane, 0);
}
module.PaneModuleIndex = paneindex[module.Pane];
if (string.IsNullOrEmpty(module.ContainerType))
{
module.ContainerType = defaultcontainertype;
}
}
}
foreach (Module module in modules.Where(item => item.PageId == pageid))
{
module.PaneModuleCount = paneindex[module.Pane] + 1;
}
return modules;
}
private Runtime GetRuntime()
=> RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"))
? Runtime.WebAssembly
: Runtime.Server;
}