diff --git a/Oqtane.Client/Modules/Admin/Files/Edit.razor b/Oqtane.Client/Modules/Admin/Files/Edit.razor
index 0083cce0..1d7ddc81 100644
--- a/Oqtane.Client/Modules/Admin/Files/Edit.razor
+++ b/Oqtane.Client/Modules/Admin/Files/Edit.razor
@@ -149,10 +149,16 @@
{
folder = await FolderService.AddFolderAsync(folder);
}
-
- await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId);
- await logger.LogInformation("Folder Saved {Folder}", folder);
- NavigationManager.NavigateTo(NavigateUrl());
+ if (folder != null)
+ {
+ await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId);
+ await logger.LogInformation("Folder Saved {Folder}", folder);
+ NavigationManager.NavigateTo(NavigateUrl());
+ }
+ else
+ {
+ AddModuleMessage("An Error Was Encountered Saving The Folder", MessageType.Error);
+ }
}
else
{
diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Client/Edit.razor b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Client/Edit.razor
index 84e83aae..4faa3dd5 100644
--- a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Client/Edit.razor
+++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Client/Edit.razor
@@ -4,9 +4,8 @@
@namespace [Owner].[Module]s.Modules
@inherits ModuleBase
+@inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager
-@inject HttpClient http
-@inject SiteState sitestate
@@ -31,7 +30,6 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Actions => "Add,Edit";
- I[Module]Service [Module]Service;
int _id;
string _name;
string _createdby;
@@ -43,7 +41,6 @@
{
try
{
- [Module]Service = new [Module]Service(http, sitestate);
if (PageState.Action == "Edit")
{
_id = Int32.Parse(PageState.QueryString["id"]);
diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Client/Index.razor b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Client/Index.razor
index 1c92fc1a..fceedfea 100644
--- a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Client/Index.razor
+++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Client/Index.razor
@@ -3,9 +3,8 @@
@namespace [Owner].[Module]s.Modules
@inherits ModuleBase
+@inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager
-@inject HttpClient http
-@inject SiteState sitestate
@if (_[Module]s == null)
{
@@ -71,14 +70,12 @@ else
@code {
- I[Module]Service [Module]Service;
List<[Module]> _[Module]s;
protected override async Task OnInitializedAsync()
{
try
{
- [Module]Service = new [Module]Service(http, sitestate);
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
}
catch (Exception ex)
diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Client/ModuleInfo.cs b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Client/ModuleInfo.cs
index 35a74a27..6a1f21b1 100644
--- a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Client/ModuleInfo.cs
+++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Client/ModuleInfo.cs
@@ -10,7 +10,6 @@ namespace [Owner].[Module]s.Modules
Name = "[Module]",
Description = "[Module]",
Version = "1.0.0",
- Dependencies = "[Owner].[Module]s.Shared.Oqtane",
ServerManagerType = "[ServerManagerType]",
ReleaseVersions = "1.0.0"
};
diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Client/Modules/[Module]/Edit.razor b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Client/Modules/[Module]/Edit.razor
index 84e83aae..4faa3dd5 100644
--- a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Client/Modules/[Module]/Edit.razor
+++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Client/Modules/[Module]/Edit.razor
@@ -4,9 +4,8 @@
@namespace [Owner].[Module]s.Modules
@inherits ModuleBase
+@inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager
-@inject HttpClient http
-@inject SiteState sitestate
@@ -31,7 +30,6 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Actions => "Add,Edit";
- I[Module]Service [Module]Service;
int _id;
string _name;
string _createdby;
@@ -43,7 +41,6 @@
{
try
{
- [Module]Service = new [Module]Service(http, sitestate);
if (PageState.Action == "Edit")
{
_id = Int32.Parse(PageState.QueryString["id"]);
diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Client/Modules/[Module]/Index.razor b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Client/Modules/[Module]/Index.razor
index fdb11b98..c2c129de 100644
--- a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Client/Modules/[Module]/Index.razor
+++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Client/Modules/[Module]/Index.razor
@@ -3,9 +3,8 @@
@namespace [Owner].[Module]s.Modules
@inherits ModuleBase
+@inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager
-@inject HttpClient http
-@inject SiteState sitestate
@if (_[Module]s == null)
{
@@ -62,14 +61,12 @@ else
@code {
- I[Module]Service [Module]Service;
List<[Module]> _[Module]s;
protected override async Task OnInitializedAsync()
{
try
{
- [Module]Service = new [Module]Service(http, sitestate);
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
}
catch (Exception ex)
diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Client/Modules/[Module]/ModuleInfo.cs b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Client/Modules/[Module]/ModuleInfo.cs
index a95a461e..6a1f21b1 100644
--- a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Client/Modules/[Module]/ModuleInfo.cs
+++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Client/Modules/[Module]/ModuleInfo.cs
@@ -10,7 +10,6 @@ namespace [Owner].[Module]s.Modules
Name = "[Module]",
Description = "[Module]",
Version = "1.0.0",
- Dependencies = "[Owner].[Module]s.Module.Shared",
ServerManagerType = "[ServerManagerType]",
ReleaseVersions = "1.0.0"
};
diff --git a/Oqtane.Client/Modules/HtmlText/Edit.razor b/Oqtane.Client/Modules/HtmlText/Edit.razor
index 9df38687..c9c94bc7 100644
--- a/Oqtane.Client/Modules/HtmlText/Edit.razor
+++ b/Oqtane.Client/Modules/HtmlText/Edit.razor
@@ -3,9 +3,8 @@
@using Oqtane.Modules.Controls
@namespace Oqtane.Modules.HtmlText
@inherits ModuleBase
+@inject IHtmlTextService HtmlTextService
@inject NavigationManager NavigationManager
-@inject HttpClient http
-@inject SiteState sitestate
@if (_content != null)
{
@@ -14,7 +13,8 @@
Cancel
@if (!string.IsNullOrEmpty(_content))
{
-
+
+
}
}
@@ -35,8 +35,7 @@
{
try
{
- var htmltextservice = new HtmlTextService(http, sitestate);
- var htmltext = await htmltextservice.GetHtmlTextAsync(ModuleState.ModuleId);
+ var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
{
_content = htmltext.Content;
@@ -65,19 +64,18 @@
try
{
- var htmltextservice = new HtmlTextService(http, sitestate);
- var htmltext = await htmltextservice.GetHtmlTextAsync(ModuleState.ModuleId);
+ var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
{
htmltext.Content = content;
- await htmltextservice.UpdateHtmlTextAsync(htmltext);
+ await HtmlTextService.UpdateHtmlTextAsync(htmltext);
}
else
{
htmltext = new HtmlTextInfo();
htmltext.ModuleId = ModuleState.ModuleId;
htmltext.Content = content;
- await htmltextservice.AddHtmlTextAsync(htmltext);
+ await HtmlTextService.AddHtmlTextAsync(htmltext);
}
await logger.LogInformation("Html/Text Content Saved {HtmlText}", htmltext);
diff --git a/Oqtane.Client/Modules/HtmlText/Index.razor b/Oqtane.Client/Modules/HtmlText/Index.razor
index c76afa02..6821c425 100644
--- a/Oqtane.Client/Modules/HtmlText/Index.razor
+++ b/Oqtane.Client/Modules/HtmlText/Index.razor
@@ -1,21 +1,13 @@
@using Oqtane.Modules.HtmlText.Services
-@using Oqtane.Modules.HtmlText.Models
@namespace Oqtane.Modules.HtmlText
@inherits ModuleBase
-@inject NavigationManager NavigationManager
-@inject HttpClient http
-@inject SiteState sitestate
+@inject IHtmlTextService HtmlTextService
@((MarkupString)content)
@if (PageState.EditMode)
{
-
-}
-
-@if (PageState.EditMode)
-{
-
+
}
@code {
@@ -25,8 +17,7 @@
{
try
{
- var htmltextservice = new HtmlTextService(http, sitestate);
- var htmltext = await htmltextservice.GetHtmlTextAsync(ModuleState.ModuleId);
+ var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
{
content = htmltext.Content;
diff --git a/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs b/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs
index cb73b9a4..51ab7887 100644
--- a/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs
+++ b/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs
@@ -8,7 +8,7 @@ using Oqtane.Shared;
namespace Oqtane.Modules.HtmlText.Services
{
- public class HtmlTextService : ServiceBase, IHtmlTextService
+ public class HtmlTextService : ServiceBase, IHtmlTextService, IService
{
private readonly SiteState _siteState;
diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs
index c81f8d10..188da769 100644
--- a/Oqtane.Client/Modules/ModuleBase.cs
+++ b/Oqtane.Client/Modules/ModuleBase.cs
@@ -9,7 +9,7 @@ using Oqtane.UI;
namespace Oqtane.Modules
{
- public class ModuleBase : ComponentBase, IModuleControl
+ public abstract class ModuleBase : ComponentBase, IModuleControl
{
private Logger _logger;
diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs
index 74fa8de4..26bac719 100644
--- a/Oqtane.Client/Program.cs
+++ b/Oqtane.Client/Program.cs
@@ -4,8 +4,10 @@ using System.Threading.Tasks;
using Oqtane.Services;
using System.Reflection;
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
+using System.Net.Http.Json;
using Oqtane.Modules;
using Oqtane.Shared;
using Oqtane.Providers;
@@ -19,10 +21,9 @@ namespace Oqtane.Client
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add("app");
+ HttpClient httpClient = new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)};
- builder.Services.AddSingleton(
- new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }
- );
+ builder.Services.AddSingleton(httpClient);
builder.Services.AddOptions();
// register auth services
@@ -57,14 +58,16 @@ namespace Oqtane.Client
builder.Services.AddScoped();
builder.Services.AddScoped();
+ await LoadClientAssemblies(httpClient);
+
// dynamically register module contexts and repository services
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
- Type[] implementationtypes = assembly.GetTypes()
- .Where(item => item.GetInterfaces().Contains(typeof(IService)))
- .ToArray();
- foreach (Type implementationtype in implementationtypes)
+ var implementationTypes = assembly.GetTypes()
+ .Where(item => item.GetInterfaces().Contains(typeof(IService)));
+
+ foreach (Type implementationtype in implementationTypes)
{
Type servicetype = Type.GetType(implementationtype.AssemblyQualifiedName.Replace(implementationtype.Name, "I" + implementationtype.Name));
if (servicetype != null)
@@ -76,9 +79,27 @@ namespace Oqtane.Client
builder.Services.AddScoped(implementationtype, implementationtype); // no interface defined for service
}
}
+
+ assembly.GetInstances()
+ .ToList()
+ .ForEach(x => x.ConfigureServices(builder.Services));
}
await builder.Build().RunAsync();
}
+
+ private static async Task LoadClientAssemblies(HttpClient http)
+ {
+ var list = await http.GetFromJsonAsync>($"/~/api/ModuleDefinition/load");
+ // get list of loaded assemblies on the client ( in the client-side hosting module the browser client has its own app domain )
+ var assemblyList = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).ToList();
+ foreach (var name in list)
+ {
+ if (assemblyList.Contains(name)) continue;
+ // download assembly from server and load
+ var bytes = await http.GetByteArrayAsync($"/~/api/ModuleDefinition/load/{name}.dll");
+ Assembly.Load(bytes);
+ }
+ }
}
}
diff --git a/Oqtane.Client/Services/AliasService.cs b/Oqtane.Client/Services/AliasService.cs
index 1e0f5ac8..21df7dd0 100644
--- a/Oqtane.Client/Services/AliasService.cs
+++ b/Oqtane.Client/Services/AliasService.cs
@@ -5,16 +5,21 @@ using System.Linq;
using System.Collections.Generic;
using System.Net;
using System;
-
+using Oqtane.Shared;
namespace Oqtane.Services
{
public class AliasService : ServiceBase, IAliasService
{
-
- public AliasService(HttpClient http) :base(http) { }
- private string Apiurl => CreateApiUrl("Alias");
+ private readonly SiteState _siteState;
+
+ public AliasService(HttpClient http, SiteState siteState) : base(http)
+ {
+ _siteState = siteState;
+ }
+
+ private string Apiurl => CreateApiUrl(_siteState.Alias, "Alias");
public async Task> GetAliasesAsync()
{
diff --git a/Oqtane.Client/Services/JobLogService.cs b/Oqtane.Client/Services/JobLogService.cs
index c15940b5..aa518771 100644
--- a/Oqtane.Client/Services/JobLogService.cs
+++ b/Oqtane.Client/Services/JobLogService.cs
@@ -3,14 +3,20 @@ using System.Threading.Tasks;
using System.Net.Http;
using System.Linq;
using System.Collections.Generic;
+using Oqtane.Shared;
namespace Oqtane.Services
{
public class JobLogService : ServiceBase, IJobLogService
{
- public JobLogService(HttpClient http) :base(http) { }
+ private readonly SiteState _siteState;
- private string Apiurl => CreateApiUrl("JobLog");
+ public JobLogService(HttpClient http, SiteState siteState) : base(http)
+ {
+ _siteState = siteState;
+ }
+
+ private string Apiurl => CreateApiUrl(_siteState.Alias, "JobLog");
public async Task> GetJobLogsAsync()
{
diff --git a/Oqtane.Client/Services/JobService.cs b/Oqtane.Client/Services/JobService.cs
index 14cd96ad..3161cd90 100644
--- a/Oqtane.Client/Services/JobService.cs
+++ b/Oqtane.Client/Services/JobService.cs
@@ -3,15 +3,21 @@ using System.Threading.Tasks;
using System.Net.Http;
using System.Linq;
using System.Collections.Generic;
+using Oqtane.Shared;
namespace Oqtane.Services
{
public class JobService : ServiceBase, IJobService
{
- public JobService(HttpClient http) : base(http) { }
+ private readonly SiteState _siteState;
- private string Apiurl => CreateApiUrl("Job");
+ public JobService(HttpClient http, SiteState siteState) : base(http)
+ {
+ _siteState = siteState;
+ }
+ private string Apiurl => CreateApiUrl(_siteState.Alias, "Job");
+
public async Task> GetJobsAsync()
{
List jobs = await GetJsonAsync>(Apiurl);
diff --git a/Oqtane.Client/Services/ServiceBase.cs b/Oqtane.Client/Services/ServiceBase.cs
index 57bad2b7..091709f2 100644
--- a/Oqtane.Client/Services/ServiceBase.cs
+++ b/Oqtane.Client/Services/ServiceBase.cs
@@ -135,13 +135,13 @@ namespace Oqtane.Services
//TODO Missing content JSON validation
}
- // create an API Url which is tenant agnostic ( for use with entities in the MasterDB )
+ // create an API Url which is tenant agnostic ( for use during installation )
public string CreateApiUrl(string serviceName)
{
return CreateApiUrl(null, serviceName);
}
- // create an API Url which is tenant aware ( for use with entities in the TenantDB )
+ // create an API Url which is tenant aware ( for use with repositories )
public string CreateApiUrl(Alias alias, string serviceName)
{
string apiurl = "/";
diff --git a/Oqtane.Client/Services/SqlService.cs b/Oqtane.Client/Services/SqlService.cs
index a99fbd23..719156b2 100644
--- a/Oqtane.Client/Services/SqlService.cs
+++ b/Oqtane.Client/Services/SqlService.cs
@@ -1,4 +1,5 @@
using Oqtane.Models;
+using Oqtane.Shared;
using System.Net.Http;
using System.Threading.Tasks;
@@ -6,9 +7,14 @@ namespace Oqtane.Services
{
public class SqlService : ServiceBase, ISqlService
{
- public SqlService(HttpClient http) : base(http) { }
+ private readonly SiteState _siteState;
- private string Apiurl => CreateApiUrl("Sql");
+ public SqlService(HttpClient http, SiteState siteState) : base(http)
+ {
+ _siteState = siteState;
+ }
+
+ private string Apiurl => CreateApiUrl(_siteState.Alias, "Sql");
public async Task ExecuteQueryAsync(SqlQuery sqlquery)
{
diff --git a/Oqtane.Client/Services/TenantService.cs b/Oqtane.Client/Services/TenantService.cs
index d8e9be02..d644348e 100644
--- a/Oqtane.Client/Services/TenantService.cs
+++ b/Oqtane.Client/Services/TenantService.cs
@@ -3,14 +3,20 @@ using System.Net.Http;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
+using Oqtane.Shared;
namespace Oqtane.Services
{
public class TenantService : ServiceBase, ITenantService
{
- public TenantService(HttpClient http) : base(http) { }
+ private readonly SiteState _siteState;
- private string Apiurl => CreateApiUrl("Tenant");
+ public TenantService(HttpClient http, SiteState siteState) : base(http)
+ {
+ _siteState = siteState;
+ }
+
+ private string Apiurl => CreateApiUrl(_siteState.Alias, "Tenant");
public async Task> GetTenantsAsync()
{
diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs
index 6c3223c9..0c30bbdf 100644
--- a/Oqtane.Server/Controllers/FileController.cs
+++ b/Oqtane.Server/Controllers/FileController.cs
@@ -16,6 +16,7 @@ using System.Net;
using Oqtane.Enums;
using Oqtane.Infrastructure;
using Oqtane.Repository;
+using Microsoft.AspNetCore.Routing.Constraints;
// ReSharper disable StringIndexOfIsCultureSpecific.1
@@ -194,7 +195,7 @@ namespace Oqtane.Controllers
CreateDirectory(folderPath);
string filename = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1);
// check for allowable file extensions
- if (Constants.UploadableFiles.Contains(Path.GetExtension(filename).Replace(".", "")))
+ if (Constants.UploadableFiles.Split(',').Contains(Path.GetExtension(filename).ToLower().Replace(".", "")))
{
try
{
@@ -317,7 +318,7 @@ namespace Oqtane.Controllers
}
// check for allowable file extensions
- if (!Constants.UploadableFiles.Contains(Path.GetExtension(filename)?.Replace(".", "")))
+ if (!Constants.UploadableFiles.Split(',').Contains(Path.GetExtension(filename)?.ToLower().Replace(".", "")))
{
System.IO.File.Delete(Path.Combine(folder, filename + ".tmp"));
}
@@ -396,12 +397,13 @@ namespace Oqtane.Controllers
[HttpGet("download/{id}")]
public IActionResult Download(int id)
{
+ string errorpath = Path.Combine(GetFolderPath("images"), "error.png");
Models.File file = _files.GetFile(id);
if (file != null)
{
if (_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions))
{
- string filepath = Path.Combine(GetFolderPath(file.Folder) , file.Name);
+ string filepath = Path.Combine(GetFolderPath(file.Folder), file.Name);
if (System.IO.File.Exists(filepath))
{
byte[] filebytes = System.IO.File.ReadAllBytes(filepath);
@@ -411,21 +413,24 @@ namespace Oqtane.Controllers
{
_logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FileId} {FilePath}", id, filepath);
HttpContext.Response.StatusCode = 404;
- return null;
+ byte[] filebytes = System.IO.File.ReadAllBytes(errorpath);
+ return File(filebytes, "application/octet-stream", file.Name);
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access File {FileId}", id);
HttpContext.Response.StatusCode = 401;
- return null;
+ byte[] filebytes = System.IO.File.ReadAllBytes(errorpath);
+ return File(filebytes, "application/octet-stream", file.Name);
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Read, "File Not Found {FileId}", id);
HttpContext.Response.StatusCode = 404;
- return null;
+ byte[] filebytes = System.IO.File.ReadAllBytes(errorpath);
+ return File(filebytes, "application/octet-stream", "error.png");
}
}
@@ -469,7 +474,7 @@ namespace Oqtane.Controllers
file.ImageHeight = 0;
file.ImageWidth = 0;
- if (Constants.ImageFiles.Contains(file.Extension))
+ if (Constants.ImageFiles.Split(',').Contains(file.Extension.ToLower()))
{
FileStream stream = new FileStream(filepath, FileMode.Open, FileAccess.Read);
using (var image = Image.FromStream(stream))
diff --git a/Oqtane.Server/Controllers/FolderController.cs b/Oqtane.Server/Controllers/FolderController.cs
index 68a01e23..34c13a1b 100644
--- a/Oqtane.Server/Controllers/FolderController.cs
+++ b/Oqtane.Server/Controllers/FolderController.cs
@@ -10,7 +10,6 @@ using Oqtane.Extensions;
using Oqtane.Infrastructure;
using Oqtane.Repository;
using Oqtane.Security;
-using System.IO;
namespace Oqtane.Controllers
{
@@ -106,13 +105,23 @@ namespace Oqtane.Controllers
}
if (_userPermissions.IsAuthorized(User,PermissionNames.Edit, permissions))
{
- if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null)
+ if (FolderPathValid(folder))
{
- Folder parent = _folders.GetFolder(folder.ParentId.Value);
- folder.Path = Utilities.PathCombine(parent.Path, folder.Name,"\\");
+ if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null)
+ {
+ Folder parent = _folders.GetFolder(folder.ParentId.Value);
+ folder.Path = Utilities.PathCombine(parent.Path, folder.Name);
+ }
+ folder.Path = Utilities.PathCombine(folder.Path, "\\");
+ folder = _folders.AddFolder(folder);
+ _logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", folder);
+ }
+ else
+ {
+ _logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Name Not Valid {Folder}", folder);
+ HttpContext.Response.StatusCode = 401;
+ folder = null;
}
- folder = _folders.AddFolder(folder);
- _logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", folder);
}
else
{
@@ -131,13 +140,23 @@ namespace Oqtane.Controllers
{
if (ModelState.IsValid && _userPermissions.IsAuthorized(User, EntityNames.Folder, folder.FolderId, PermissionNames.Edit))
{
- if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null)
+ if (FolderPathValid(folder))
{
- Folder parent = _folders.GetFolder(folder.ParentId.Value);
- folder.Path = Utilities.PathCombine(parent.Path, folder.Name,"\\");
+ if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null)
+ {
+ Folder parent = _folders.GetFolder(folder.ParentId.Value);
+ folder.Path = Utilities.PathCombine(parent.Path, folder.Name);
+ }
+ folder.Path = Utilities.PathCombine(folder.Path, "\\");
+ folder = _folders.UpdateFolder(folder);
+ _logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Updated {Folder}", folder);
+ }
+ else
+ {
+ _logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Name Not Valid {Folder}", folder);
+ HttpContext.Response.StatusCode = 401;
+ folder = null;
}
- folder = _folders.UpdateFolder(folder);
- _logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Updated {Folder}", folder);
}
else
{
@@ -191,5 +210,11 @@ namespace Oqtane.Controllers
HttpContext.Response.StatusCode = 401;
}
}
+
+ private bool FolderPathValid(Folder folder)
+ {
+ // prevent folder path traversal and reserved devices
+ return (!folder.Name.Contains("\\") && !folder.Name.Contains("/") && !Constants.ReservedDevices.Split(',').Contains(folder.Name.ToUpper()));
+ }
}
}
diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs
index e1b9534c..fcd9dcd8 100644
--- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs
+++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs
@@ -170,6 +170,16 @@ namespace Oqtane.Controllers
return null;
}
}
+ // GET api//load/assembyname
+ [HttpGet("load")]
+ public List Load()
+ {
+ var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies();
+ var list = AppDomain.CurrentDomain.GetOqtaneClientAssemblies().Select(a => a.GetName().Name).ToList();
+ var deps = assemblies.SelectMany(a => a.GetReferencedAssemblies()).Distinct();
+ list.AddRange(deps.Where(a=>a.Name.EndsWith(".oqtane",StringComparison.OrdinalIgnoreCase)).Select(a=>a.Name));
+ return list;
+ }
// POST api/?moduleid=x
[HttpPost]
diff --git a/Oqtane.Server/Controllers/SiteTemplateController.cs b/Oqtane.Server/Controllers/SiteTemplateController.cs
index 2f709fb1..c63170c1 100644
--- a/Oqtane.Server/Controllers/SiteTemplateController.cs
+++ b/Oqtane.Server/Controllers/SiteTemplateController.cs
@@ -1,7 +1,9 @@
using System.Collections.Generic;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Oqtane.Models;
using Oqtane.Repository;
+using Oqtane.Shared;
namespace Oqtane.Controllers
{
@@ -17,6 +19,7 @@ namespace Oqtane.Controllers
// GET: api/
[HttpGet]
+ [Authorize(Roles = Constants.HostRole)]
public IEnumerable Get()
{
return _siteTemplates.GetSiteTemplates();
diff --git a/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs b/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs
new file mode 100644
index 00000000..d1c77301
--- /dev/null
+++ b/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Oqtane.Infrastructure;
+
+namespace Oqtane.Extensions
+{
+ public static class ApplicationBuilderExtensions
+ {
+ public static IApplicationBuilder ConfigureOqtaneAssemblies(this IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ var startUps = AppDomain.CurrentDomain
+ .GetOqtaneAssemblies()
+ .SelectMany(x => x.GetInstances());
+
+ foreach (var startup in startUps)
+ {
+ startup.Configure(app, env);
+ }
+
+ return app;
+ }
+ }
+}
diff --git a/Oqtane.Server/Extensions/OqtaneMvcBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneMvcBuilderExtensions.cs
index fdb801df..b3f489bf 100644
--- a/Oqtane.Server/Extensions/OqtaneMvcBuilderExtensions.cs
+++ b/Oqtane.Server/Extensions/OqtaneMvcBuilderExtensions.cs
@@ -3,6 +3,7 @@ using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
+using Oqtane.Infrastructure;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
@@ -30,6 +31,22 @@ namespace Microsoft.Extensions.DependencyInjection
}
}
}
+
+ return mvcBuilder;
+ }
+
+
+ public static IMvcBuilder ConfigureOqtaneMvc(this IMvcBuilder mvcBuilder)
+ {
+ var startUps = AppDomain.CurrentDomain
+ .GetOqtaneAssemblies()
+ .SelectMany(x => x.GetInstances());
+
+ foreach (var startup in startUps)
+ {
+ startup.ConfigureMvc(mvcBuilder);
+ }
+
return mvcBuilder;
}
}
diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs
index 5c093f48..3e778869 100644
--- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs
+++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs
@@ -8,21 +8,23 @@ using Microsoft.Extensions.Hosting;
using Oqtane.Extensions;
using Oqtane.Infrastructure;
using Oqtane.Modules;
+using Oqtane.Services;
using Oqtane.Shared;
+using Oqtane.UI;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
{
public static class OqtaneServiceCollectionExtensions
{
- public static IServiceCollection AddOqtaneParts(this IServiceCollection services)
+ public static IServiceCollection AddOqtaneParts(this IServiceCollection services, Runtime runtime)
{
LoadAssemblies();
- services.AddOqtaneServices();
+ services.AddOqtaneServices(runtime);
return services;
}
- private static IServiceCollection AddOqtaneServices(this IServiceCollection services)
+ private static IServiceCollection AddOqtaneServices(this IServiceCollection services, Runtime runtime)
{
if (services is null)
{
@@ -53,11 +55,24 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddSingleton(hostedServiceType, serviceType);
}
}
+
+ var startUps = assembly.GetInstances();
+ foreach (var startup in startUps)
+ {
+ startup.ConfigureServices(services);
+ }
+
+ if (runtime == Runtime.Server)
+ {
+ assembly.GetInstances()
+ .ToList()
+ .ForEach(x => x.ConfigureServices(services));
+ }
}
-
return services;
}
+
private static void LoadAssemblies()
{
var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
diff --git a/Oqtane.Server/Infrastructure/Interfaces/IServerStartup.cs b/Oqtane.Server/Infrastructure/Interfaces/IServerStartup.cs
new file mode 100644
index 00000000..bbdff5d6
--- /dev/null
+++ b/Oqtane.Server/Infrastructure/Interfaces/IServerStartup.cs
@@ -0,0 +1,17 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Oqtane.Infrastructure
+{
+ public interface IServerStartup
+ {
+ // This method gets called by the runtime. Use this method to add services to the container.
+ // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
+ void ConfigureServices(IServiceCollection services);
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ void Configure(IApplicationBuilder app, IWebHostEnvironment env);
+ void ConfigureMvc(IMvcBuilder mvcBuilder);
+ }
+}
+
diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs
index de4c89a7..34f73ffe 100644
--- a/Oqtane.Server/Startup.cs
+++ b/Oqtane.Server/Startup.cs
@@ -14,11 +14,13 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
+using Oqtane.Extensions;
using Oqtane.Infrastructure;
using Oqtane.Repository;
using Oqtane.Security;
using Oqtane.Services;
-using Oqtane.Shared;
+using Oqtane.Shared;
+using Oqtane.UI;
namespace Oqtane
{
@@ -26,6 +28,7 @@ namespace Oqtane
{
public IConfigurationRoot Configuration { get; }
private string _webRoot;
+ private Runtime _runtime;
public Startup(IWebHostEnvironment env)
{
@@ -33,6 +36,9 @@ namespace Oqtane
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
Configuration = builder.Build();
+
+ _runtime = (Configuration.GetSection("Runtime").Value == "WebAssembly") ? Runtime.WebAssembly : Runtime.Server;
+
_webRoot = env.WebRootPath;
AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(env.ContentRootPath, "Data"));
}
@@ -187,14 +193,13 @@ namespace Oqtane
services.AddTransient();
services.AddTransient();
- // load the external assemblies into the app domain
- services.AddOqtaneParts();
+ // load the external assemblies into the app domain, install services
+ services.AddOqtaneParts(_runtime);
services.AddMvc()
+ .AddNewtonsoftJson()
.AddOqtaneApplicationParts() // register any Controllers from custom modules
- .AddNewtonsoftJson();
-
-
+ .ConfigureOqtaneMvc(); // any additional configuration from IStart classes.
services.AddSwaggerGen(c =>
{
@@ -217,14 +222,12 @@ namespace Oqtane
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
-
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseBlazorFrameworkFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
-
app.UseSwagger();
app.UseSwaggerUI(c =>
{
@@ -237,6 +240,7 @@ namespace Oqtane
endpoints.MapControllers();
endpoints.MapFallbackToPage("/_Host");
});
+ app.ConfigureOqtaneAssemblies(env);
}
}
}
diff --git a/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css b/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css
index a9e5c15e..fba5b909 100644
--- a/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css
+++ b/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css
@@ -101,6 +101,12 @@
flex-direction: row;
}
+ .app-logo {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+ }
+
.breadcrumbs {
position: fixed;
left: 275px;
@@ -163,7 +169,13 @@
}
}
-@media (max-width: 767px) {
+@media (max-width: 767px) {
+ .app-logo {
+ height: 80px;
+ display: flex;
+ align-items: center;
+ }
+
.breadcrumbs {
position: fixed;
top: 150px;
diff --git a/Oqtane.Server/wwwroot/images/error.png b/Oqtane.Server/wwwroot/images/error.png
new file mode 100644
index 00000000..0095d2f1
Binary files /dev/null and b/Oqtane.Server/wwwroot/images/error.png differ
diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js
index 50c8aa9b..5f41709c 100644
--- a/Oqtane.Server/wwwroot/js/interop.js
+++ b/Oqtane.Server/wwwroot/js/interop.js
@@ -81,14 +81,26 @@ window.interop = {
if (link.href !== url) {
link.setAttribute('href', url);
}
- if (type !== "" && link.type !== type) {
- link.setAttribute('type', type);
+ if (type !== "") {
+ if (link.type !== type) {
+ link.setAttribute('type', type);
+ }
+ } else {
+ link.removeAttribute('type');
}
- if (integrity !== "" && link.integrity !== integrity) {
- link.setAttribute('integrity', integrity);
+ if (integrity !== "") {
+ if (link.integrity !== integrity) {
+ link.setAttribute('integrity', integrity);
+ }
+ } else {
+ link.removeAttribute('integrity');
}
- if (crossorigin !== "" && link.crossOrigin !== crossorigin) {
- link.setAttribute('crossorigin', crossorigin);
+ if (crossorigin !== "") {
+ if (link.crossOrigin !== crossorigin) {
+ link.setAttribute('crossorigin', crossorigin);
+ }
+ } else {
+ link.removeAttribute('crossorigin');
}
}
},
@@ -126,11 +138,19 @@ window.interop = {
if (script.src !== src) {
script.src = src;
}
- if (integrity !== "" && script.integrity !== integrity) {
- script.setAttribute('integrity', integrity);
+ if (integrity !== "") {
+ if (script.integrity !== integrity) {
+ script.setAttribute('integrity', integrity);
+ }
+ } else {
+ script.removeAttribute('integrity');
}
- if (crossorigin !== "" && script.crossorigin !== crossorigin) {
- script.setAttribute('crossorigin', crossorigin);
+ if (crossorigin !== "") {
+ if (script.crossOrigin !== crossorigin) {
+ script.setAttribute('crossorigin', crossorigin);
+ }
+ } else {
+ script.removeAttribute('crossorigin');
}
}
else {
diff --git a/Oqtane.Server/Extensions/AssemblyExtensions.cs b/Oqtane.Shared/Extensions/AssemblyExtensions.cs
similarity index 54%
rename from Oqtane.Server/Extensions/AssemblyExtensions.cs
rename to Oqtane.Shared/Extensions/AssemblyExtensions.cs
index 443b8cc6..4742d368 100644
--- a/Oqtane.Server/Extensions/AssemblyExtensions.cs
+++ b/Oqtane.Shared/Extensions/AssemblyExtensions.cs
@@ -1,7 +1,10 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using Oqtane.Modules;
+using Oqtane.Services;
using Oqtane.Shared;
+using Oqtane.Themes;
// ReSharper disable once CheckNamespace
namespace System.Reflection
@@ -31,7 +34,29 @@ namespace System.Reflection
}
return assembly.GetTypes()
- .Where(t => t.GetInterfaces().Contains(interfaceType));
+ //.Where(t => t.GetInterfaces().Contains(interfaceType));
+ .Where(x => interfaceType.IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract);
+ }
+
+ public static IEnumerable GetTypes(this Assembly assembly)
+ {
+ return assembly.GetTypes(typeof(T));
+ }
+
+ public static IEnumerable GetInstances(this Assembly assembly) where T : class
+ {
+ if (assembly is null)
+ {
+ throw new ArgumentNullException(nameof(assembly));
+ }
+ var type = typeof(T);
+ var list = assembly.GetTypes()
+ .Where(x => type.IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract && !x.IsGenericType);
+
+ foreach (var type1 in list)
+ {
+ if (Activator.CreateInstance(type1) is T instance) yield return instance;
+ }
}
public static bool IsOqtaneAssembly(this Assembly assembly)
@@ -48,5 +73,10 @@ namespace System.Reflection
{
return appDomain.GetAssemblies().Where(a => a.IsOqtaneAssembly());
}
+ public static IEnumerable GetOqtaneClientAssemblies(this AppDomain appDomain)
+ {
+ return appDomain.GetOqtaneAssemblies()
+ .Where(a => a.GetTypes().Any() || a.GetTypes().Any() || a.GetTypes().Any());
+ }
}
}
diff --git a/Oqtane.Shared/Interfaces/IClientStartup.cs b/Oqtane.Shared/Interfaces/IClientStartup.cs
new file mode 100644
index 00000000..c063431c
--- /dev/null
+++ b/Oqtane.Shared/Interfaces/IClientStartup.cs
@@ -0,0 +1,11 @@
+
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Oqtane.Services
+{
+ public interface IClientStartup
+ {
+ // This method gets called by the runtime. Use this method to add services to the container.
+ void ConfigureServices(IServiceCollection services);
+ }
+}
diff --git a/Oqtane.Client/Modules/IModuleControl.cs b/Oqtane.Shared/Interfaces/IModuleControl.cs
similarity index 80%
rename from Oqtane.Client/Modules/IModuleControl.cs
rename to Oqtane.Shared/Interfaces/IModuleControl.cs
index c4f2fdee..ee5120ba 100644
--- a/Oqtane.Client/Modules/IModuleControl.cs
+++ b/Oqtane.Shared/Interfaces/IModuleControl.cs
@@ -7,6 +7,6 @@ namespace Oqtane.Modules
SecurityAccessLevel SecurityAccessLevel { get; } // defines the security access level for this control - defaults to View
string Title { get; } // title to display for this control - defaults to module title
string Actions { get; } // allows for routing by configuration rather than by convention ( comma delimited ) - defaults to using component file name
- bool UseAdminContainer { get; } // container for embedding module control - defaults to true
+ bool UseAdminContainer { get; } // container for embedding module control - defaults to true. false will suppress the default modal UI popup behavior and render the component in the page.
}
}
diff --git a/Oqtane.Client/Themes/IThemeControl.cs b/Oqtane.Shared/Interfaces/IThemeControl.cs
similarity index 100%
rename from Oqtane.Client/Themes/IThemeControl.cs
rename to Oqtane.Shared/Interfaces/IThemeControl.cs
diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj
index c50111cd..eca4e4ee 100644
--- a/Oqtane.Shared/Oqtane.Shared.csproj
+++ b/Oqtane.Shared/Oqtane.Shared.csproj
@@ -18,6 +18,7 @@
+
diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs
index c50347e2..30ca2950 100644
--- a/Oqtane.Shared/Shared/Constants.cs
+++ b/Oqtane.Shared/Shared/Constants.cs
@@ -43,5 +43,6 @@
public const string ImageFiles = "jpg,jpeg,jpe,gif,bmp,png";
public const string UploadableFiles = "jpg,jpeg,jpe,gif,bmp,png,mov,wmv,avi,mp4,mp3,doc,docx,xls,xlsx,ppt,pptx,pdf,txt,zip,nupkg";
+ public const string ReservedDevices = "CON,NUL,PRN,COM1,COM2,COM3,COM4,COM5,COM6,COM7,COM8,COM9,LPT1,LPT2,LPT3,LPT4,LPT5,LPT6,LPT7,LPT8,LPT9";
}
}