diff --git a/Oqtane.Application/AppHost/Oqtane.Application.AppHost.csproj b/Oqtane.Application/AppHost/Oqtane.Application.AppHost.csproj deleted file mode 100644 index 9a9cb9fa..00000000 --- a/Oqtane.Application/AppHost/Oqtane.Application.AppHost.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - net9.0 - 1.0.0 - Oqtane.Application.AppHost - - - - - - diff --git a/Oqtane.Application/AppHost/Properties/launchSettings.json b/Oqtane.Application/AppHost/Properties/launchSettings.json deleted file mode 100644 index 11cab4d9..00000000 --- a/Oqtane.Application/AppHost/Properties/launchSettings.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:44358/", - "sslPort": 0 - } - }, - "profiles": { - "Oqtane.Application": { - "commandName": "Project", - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:44358/" - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/Oqtane.Application/AppHost/wwwroot/_content/Placeholder.txt b/Oqtane.Application/AppHost/wwwroot/_content/Placeholder.txt deleted file mode 100644 index 5a324d79..00000000 --- a/Oqtane.Application/AppHost/wwwroot/_content/Placeholder.txt +++ /dev/null @@ -1,11 +0,0 @@ -The _content folder should only contain static resources from shared razor component libraries (RCLs). Static resources can be extracted from shared RCL Nuget packages by executing a Publish task on the module's Server project to a local folder and copying the files from the _content folder which is created. Each shared RCL would have its own appropriately named subfolder within the module's _content folder. - -ie. - -/_content - /Radzen.Blazor - /css - /fonts - /syncfusion.blazor - /scripts - /styles diff --git a/Oqtane.Application/AppHost/wwwroot/resources.txt b/Oqtane.Application/AppHost/wwwroot/resources.txt deleted file mode 100644 index 2542de03..00000000 --- a/Oqtane.Application/AppHost/wwwroot/resources.txt +++ /dev/null @@ -1 +0,0 @@ -This is the location where static resources such as images or style sheets should be located \ No newline at end of file diff --git a/Oqtane.Application/Client/Modules/MyModule/Edit.razor b/Oqtane.Application/Client/Modules/MyModule/Edit.razor index f84c4daf..3e9caa2a 100644 --- a/Oqtane.Application/Client/Modules/MyModule/Edit.razor +++ b/Oqtane.Application/Client/Modules/MyModule/Edit.razor @@ -35,7 +35,7 @@ public override List Resources => new List() { - new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" } + new Stylesheet(ModulePath() + "Module.css") }; private ElementReference form; diff --git a/Oqtane.Application/Client/Modules/MyModule/Index.razor b/Oqtane.Application/Client/Modules/MyModule/Index.razor index 637ba533..fc3744d5 100644 --- a/Oqtane.Application/Client/Modules/MyModule/Index.razor +++ b/Oqtane.Application/Client/Modules/MyModule/Index.razor @@ -38,12 +38,10 @@ else } @code { - public override string RenderMode => RenderModes.Static; - public override List Resources => new List() { - new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }, - new Resource { ResourceType = ResourceType.Script, Url = ModulePath() + "Module.js" } + new Stylesheet(ModulePath() + "Module.css"), + new Script(ModulePath() + "Module.js") }; List _MyModules; diff --git a/Oqtane.Application/Client/Oqtane.Application.Client.csproj b/Oqtane.Application/Client/Oqtane.Application.Client.csproj index 6d83fd5c..638acb68 100644 --- a/Oqtane.Application/Client/Oqtane.Application.Client.csproj +++ b/Oqtane.Application/Client/Oqtane.Application.Client.csproj @@ -1,23 +1,21 @@ - + - - net9.0 - 1.0.0 - Oqtane.Application.Client.Oqtane - + + net9.0 + 1.0.0 + Oqtane.Application.Client.Oqtane + true + Default + false + true + - - - + + + - - - - - - - false - false - + + + diff --git a/Oqtane.Application/Client/Program.cs b/Oqtane.Application/Client/Program.cs new file mode 100644 index 00000000..0dfa0b4a --- /dev/null +++ b/Oqtane.Application/Client/Program.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +namespace Oqtane.Application.Client +{ + internal class Program + { + static async Task Main(string[] args) + { + // defer client startup to Oqtane - do not modify + await Oqtane.Client.Program.Main(args); + } + } +} diff --git a/Oqtane.Application/Client/Startup/ClientStartup.cs b/Oqtane.Application/Client/Startup/ClientStartup.cs index 1cdbd905..cf29f81d 100644 --- a/Oqtane.Application/Client/Startup/ClientStartup.cs +++ b/Oqtane.Application/Client/Startup/ClientStartup.cs @@ -8,7 +8,10 @@ namespace Oqtane.Application.Startup { public void ConfigureServices(IServiceCollection services) { - services.AddScoped(); + if (!services.Any(s => s.ServiceType == typeof(IMyModuleService))) + { + services.AddScoped(); + } } } } diff --git a/Oqtane.Application/Client/Themes/MyTheme/ThemeInfo.cs b/Oqtane.Application/Client/Themes/MyTheme/ThemeInfo.cs index 55047946..f46efe9a 100644 --- a/Oqtane.Application/Client/Themes/MyTheme/ThemeInfo.cs +++ b/Oqtane.Application/Client/Themes/MyTheme/ThemeInfo.cs @@ -16,8 +16,8 @@ namespace Oqtane.Application.MyTheme ContainerSettingsType = "Oqtane.Application.MyTheme.ContainerSettings, Oqtane.Application.Client.Oqtane", Resources = new List() { - new Script(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"), - new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" }, + new Stylesheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"), + new Stylesheet("~/Theme.css"), new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous") } }; diff --git a/Oqtane.Application/Oqtane.Application.Template.nuspec b/Oqtane.Application/Oqtane.Application.Template.nuspec index 964286a2..31d80f81 100644 --- a/Oqtane.Application/Oqtane.Application.Template.nuspec +++ b/Oqtane.Application/Oqtane.Application.Template.nuspec @@ -3,7 +3,7 @@ Oqtane.Application.Template 6.1.6 - Oqtane Application Solution For Blazor + Oqtane Application Template For Blazor Shaun Walker false MIT diff --git a/Oqtane.Application/Oqtane.Application.sln b/Oqtane.Application/Oqtane.Application.sln index a1054772..ea06ef57 100644 --- a/Oqtane.Application/Oqtane.Application.sln +++ b/Oqtane.Application/Oqtane.Application.sln @@ -2,12 +2,10 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.12.35506.116 d17.12 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Oqtane.Application.AppHost", "AppHost\Oqtane.Application.AppHost.csproj", "{5BDDA15B-05CF-41B2-BF12-D532D1A561D1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Server", "Server\Oqtane.Application.Server.csproj", "{04B05448-788F-433D-92C0-FED35122D45A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Client", "Client\Oqtane.Application.Client.csproj", "{AA8E58A1-CD09-4208-BF66-A8BB341FD669}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Server", "Server\Oqtane.Application.Server.csproj", "{04B05448-788F-433D-92C0-FED35122D45A}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Shared", "Shared\Oqtane.Application.Shared.csproj", "{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}" EndProject Global @@ -16,18 +14,14 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {5BDDA15B-05CF-41B2-BF12-D532D1A561D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5BDDA15B-05CF-41B2-BF12-D532D1A561D1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5BDDA15B-05CF-41B2-BF12-D532D1A561D1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5BDDA15B-05CF-41B2-BF12-D532D1A561D1}.Release|Any CPU.Build.0 = Release|Any CPU - {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.Build.0 = Release|Any CPU {04B05448-788F-433D-92C0-FED35122D45A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {04B05448-788F-433D-92C0-FED35122D45A}.Debug|Any CPU.Build.0 = Debug|Any CPU {04B05448-788F-433D-92C0-FED35122D45A}.Release|Any CPU.ActiveCfg = Release|Any CPU {04B05448-788F-433D-92C0-FED35122D45A}.Release|Any CPU.Build.0 = Release|Any CPU + {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.Build.0 = Release|Any CPU {18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Debug|Any CPU.Build.0 = Debug|Any CPU {18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -36,7 +30,4 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1D016F15-46FE-4726-8DFD-2E4FD4DC7668} - EndGlobalSection EndGlobal diff --git a/Oqtane.Application/Server/Oqtane.Application.Server.csproj b/Oqtane.Application/Server/Oqtane.Application.Server.csproj index 7e9ad243..5259f3b5 100644 --- a/Oqtane.Application/Server/Oqtane.Application.Server.csproj +++ b/Oqtane.Application/Server/Oqtane.Application.Server.csproj @@ -1,25 +1,18 @@ - + - - net9.0 - true - 1.0.0 - Oqtane.Application.Server.Oqtane - + + net9.0 + 1.0.0 + Oqtane.Application.Server.Oqtane + - - - - + + + + - - - - - - - - - - + + + + diff --git a/Oqtane.Application/AppHost/Program.cs b/Oqtane.Application/Server/Program.cs similarity index 83% rename from Oqtane.Application/AppHost/Program.cs rename to Oqtane.Application/Server/Program.cs index e2a7714c..55a18e50 100644 --- a/Oqtane.Application/AppHost/Program.cs +++ b/Oqtane.Application/Server/Program.cs @@ -1,17 +1,17 @@ using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.AspNetCore; -using Microsoft.Extensions.DependencyInjection; -using Oqtane.Infrastructure; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Oqtane.Infrastructure; +using Microsoft.Extensions.DependencyInjection; -namespace Oqtane.Application.AppHost +namespace Oqtane.Application.Server { public class Program { public static void Main(string[] args) { + // defer server startup to Oqtane - do not modify var host = BuildWebHost(args); var databaseManager = host.Services.GetService(); var install = databaseManager.Install(); @@ -20,7 +20,7 @@ namespace Oqtane.Application.AppHost var filelogger = host.Services.GetRequiredService>(); if (filelogger != null) { - filelogger.LogError($"[Oqtane.Application.AppHost.Program.Main] {install.Message}"); + filelogger.LogError($"[Oqtane.Application.Server.Program.Main] {install.Message}"); } } else @@ -35,9 +35,8 @@ namespace Oqtane.Application.AppHost .AddCommandLine(args) .AddEnvironmentVariables() .Build()) - .UseStartup() + .UseStartup() .ConfigureLocalizationSettings() .Build(); } } - diff --git a/Oqtane.Application/Server/Properties/launchSettings.json b/Oqtane.Application/Server/Properties/launchSettings.json new file mode 100644 index 00000000..80a48970 --- /dev/null +++ b/Oqtane.Application/Server/Properties/launchSettings.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "http://localhost:5084", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:7035;http://localhost:5084", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } + } diff --git a/Oqtane.Application/Server/Startup.cs b/Oqtane.Application/Server/Startup.cs new file mode 100644 index 00000000..71f5bd19 --- /dev/null +++ b/Oqtane.Application/Server/Startup.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Oqtane.Extensions; +using Oqtane.Infrastructure; +using Oqtane.Shared; +using Microsoft.AspNetCore.Cors.Infrastructure; + +namespace Oqtane.Application.Server +{ + public class Startup + { + private readonly IConfigurationRoot _configuration; + private readonly IWebHostEnvironment _environment; + + public Startup(IWebHostEnvironment environment) + { + AppDomain.CurrentDomain.SetData(Constants.DataDirectory, Path.Combine(environment.ContentRootPath, "Data")); + + var builder = new ConfigurationBuilder() + .SetBasePath(environment.ContentRootPath) + .AddJsonFile("appsettings.json", false, true) + .AddJsonFile($"appsettings.{environment.EnvironmentName}.json", true, true) + .AddEnvironmentVariables(); + + _configuration = builder.Build(); + _environment = environment; + } + + public void ConfigureServices(IServiceCollection services) + { + // defer server startup to Oqtane - do not modify + services.AddOqtane(_configuration, _environment); + } + + public void Configure(IApplicationBuilder app, IConfigurationRoot configuration, IWebHostEnvironment environment, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider, ISyncManager sync) + { + // defer server startup to Oqtane - do not modify + app.UseOqtane(configuration, environment, corsService, corsPolicyProvider, sync); + } + } +} diff --git a/Oqtane.Application/AppHost/appsettings.json b/Oqtane.Application/Server/appsettings.json similarity index 90% rename from Oqtane.Application/AppHost/appsettings.json rename to Oqtane.Application/Server/appsettings.json index cbf901bd..9f96daeb 100644 --- a/Oqtane.Application/AppHost/appsettings.json +++ b/Oqtane.Application/Server/appsettings.json @@ -1,8 +1,8 @@ { - "RenderMode": "Interactive", + "RenderMode": "Static", "Runtime": "Server", "Database": { - "DefaultDBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Server" + "DefaultDBType": "" }, "ConnectionStrings": { "DefaultConnection": "" @@ -57,8 +57,7 @@ } }, "LogLevel": { - "Default": "Information", - "Notify": "Error" + "Default": "Information" } } -} \ No newline at end of file +} diff --git a/Oqtane.Application/AppHost/wwwroot/Modules/Oqtane.Modules.Admin.Login/Module.css b/Oqtane.Application/Server/wwwroot/Modules/Oqtane.Modules.Admin.Login/Module.css similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/Modules/Oqtane.Modules.Admin.Login/Module.css rename to Oqtane.Application/Server/wwwroot/Modules/Oqtane.Modules.Admin.Login/Module.css diff --git a/Oqtane.Application/AppHost/wwwroot/Modules/Oqtane.Modules.HtmlText/Module.css b/Oqtane.Application/Server/wwwroot/Modules/Oqtane.Modules.HtmlText/Module.css similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/Modules/Oqtane.Modules.HtmlText/Module.css rename to Oqtane.Application/Server/wwwroot/Modules/Oqtane.Modules.HtmlText/Module.css diff --git a/Oqtane.Application/AppHost/wwwroot/Oqtane.Server.lib.module.js b/Oqtane.Application/Server/wwwroot/Oqtane.Server.lib.module.js similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/Oqtane.Server.lib.module.js rename to Oqtane.Application/Server/wwwroot/Oqtane.Server.lib.module.js diff --git a/Oqtane.Application/AppHost/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css b/Oqtane.Application/Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css rename to Oqtane.Application/Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css diff --git a/Oqtane.Application/AppHost/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css b/Oqtane.Application/Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css rename to Oqtane.Application/Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css diff --git a/Oqtane.Application/Server/wwwroot/_content/Placeholder.txt b/Oqtane.Application/Server/wwwroot/_content/Placeholder.txt deleted file mode 100644 index 5a324d79..00000000 --- a/Oqtane.Application/Server/wwwroot/_content/Placeholder.txt +++ /dev/null @@ -1,11 +0,0 @@ -The _content folder should only contain static resources from shared razor component libraries (RCLs). Static resources can be extracted from shared RCL Nuget packages by executing a Publish task on the module's Server project to a local folder and copying the files from the _content folder which is created. Each shared RCL would have its own appropriately named subfolder within the module's _content folder. - -ie. - -/_content - /Radzen.Blazor - /css - /fonts - /syncfusion.blazor - /scripts - /styles diff --git a/Oqtane.Application/AppHost/wwwroot/app_offline.bak b/Oqtane.Application/Server/wwwroot/app_offline.bak similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/app_offline.bak rename to Oqtane.Application/Server/wwwroot/app_offline.bak diff --git a/Oqtane.Application/AppHost/wwwroot/css/app.css b/Oqtane.Application/Server/wwwroot/css/app.css similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/css/app.css rename to Oqtane.Application/Server/wwwroot/css/app.css diff --git a/Oqtane.Application/AppHost/wwwroot/css/open-iconic/FONT-LICENSE b/Oqtane.Application/Server/wwwroot/css/open-iconic/FONT-LICENSE similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/css/open-iconic/FONT-LICENSE rename to Oqtane.Application/Server/wwwroot/css/open-iconic/FONT-LICENSE diff --git a/Oqtane.Application/AppHost/wwwroot/css/open-iconic/ICON-LICENSE b/Oqtane.Application/Server/wwwroot/css/open-iconic/ICON-LICENSE similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/css/open-iconic/ICON-LICENSE rename to Oqtane.Application/Server/wwwroot/css/open-iconic/ICON-LICENSE diff --git a/Oqtane.Application/AppHost/wwwroot/css/open-iconic/README.md b/Oqtane.Application/Server/wwwroot/css/open-iconic/README.md similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/css/open-iconic/README.md rename to Oqtane.Application/Server/wwwroot/css/open-iconic/README.md diff --git a/Oqtane.Application/AppHost/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css b/Oqtane.Application/Server/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css rename to Oqtane.Application/Server/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css diff --git a/Oqtane.Application/AppHost/wwwroot/css/open-iconic/font/fonts/open-iconic.eot b/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.eot similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/css/open-iconic/font/fonts/open-iconic.eot rename to Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.eot diff --git a/Oqtane.Application/AppHost/wwwroot/css/open-iconic/font/fonts/open-iconic.otf b/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.otf similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/css/open-iconic/font/fonts/open-iconic.otf rename to Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.otf diff --git a/Oqtane.Application/AppHost/wwwroot/css/open-iconic/font/fonts/open-iconic.svg b/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.svg similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/css/open-iconic/font/fonts/open-iconic.svg rename to Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.svg diff --git a/Oqtane.Application/AppHost/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf b/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf rename to Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf diff --git a/Oqtane.Application/AppHost/wwwroot/css/open-iconic/font/fonts/open-iconic.woff b/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.woff similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/css/open-iconic/font/fonts/open-iconic.woff rename to Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.woff diff --git a/Oqtane.Application/AppHost/wwwroot/css/quill/quill.bubble.css b/Oqtane.Application/Server/wwwroot/css/quill/quill.bubble.css similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/css/quill/quill.bubble.css rename to Oqtane.Application/Server/wwwroot/css/quill/quill.bubble.css diff --git a/Oqtane.Application/AppHost/wwwroot/css/quill/quill.snow.css b/Oqtane.Application/Server/wwwroot/css/quill/quill.snow.css similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/css/quill/quill.snow.css rename to Oqtane.Application/Server/wwwroot/css/quill/quill.snow.css diff --git a/Oqtane.Application/AppHost/wwwroot/css/quill/quill1.3.7.bubble.css b/Oqtane.Application/Server/wwwroot/css/quill/quill1.3.7.bubble.css similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/css/quill/quill1.3.7.bubble.css rename to Oqtane.Application/Server/wwwroot/css/quill/quill1.3.7.bubble.css diff --git a/Oqtane.Application/AppHost/wwwroot/css/quill/quill1.3.7.snow.css b/Oqtane.Application/Server/wwwroot/css/quill/quill1.3.7.snow.css similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/css/quill/quill1.3.7.snow.css rename to Oqtane.Application/Server/wwwroot/css/quill/quill1.3.7.snow.css diff --git a/Oqtane.Application/AppHost/wwwroot/favicon.ico b/Oqtane.Application/Server/wwwroot/favicon.ico similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/favicon.ico rename to Oqtane.Application/Server/wwwroot/favicon.ico diff --git a/Oqtane.Application/AppHost/wwwroot/icon.png b/Oqtane.Application/Server/wwwroot/icon.png similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/icon.png rename to Oqtane.Application/Server/wwwroot/icon.png diff --git a/Oqtane.Application/AppHost/wwwroot/images/checked.png b/Oqtane.Application/Server/wwwroot/images/checked.png similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/images/checked.png rename to Oqtane.Application/Server/wwwroot/images/checked.png diff --git a/Oqtane.Application/AppHost/wwwroot/images/disabled.png b/Oqtane.Application/Server/wwwroot/images/disabled.png similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/images/disabled.png rename to Oqtane.Application/Server/wwwroot/images/disabled.png diff --git a/Oqtane.Application/AppHost/wwwroot/images/error.png b/Oqtane.Application/Server/wwwroot/images/error.png similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/images/error.png rename to Oqtane.Application/Server/wwwroot/images/error.png diff --git a/Oqtane.Application/AppHost/wwwroot/images/help.png b/Oqtane.Application/Server/wwwroot/images/help.png similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/images/help.png rename to Oqtane.Application/Server/wwwroot/images/help.png diff --git a/Oqtane.Application/AppHost/wwwroot/images/logo-black.png b/Oqtane.Application/Server/wwwroot/images/logo-black.png similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/images/logo-black.png rename to Oqtane.Application/Server/wwwroot/images/logo-black.png diff --git a/Oqtane.Application/AppHost/wwwroot/images/logo-white.png b/Oqtane.Application/Server/wwwroot/images/logo-white.png similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/images/logo-white.png rename to Oqtane.Application/Server/wwwroot/images/logo-white.png diff --git a/Oqtane.Application/AppHost/wwwroot/images/null.png b/Oqtane.Application/Server/wwwroot/images/null.png similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/images/null.png rename to Oqtane.Application/Server/wwwroot/images/null.png diff --git a/Oqtane.Application/AppHost/wwwroot/images/unchecked.png b/Oqtane.Application/Server/wwwroot/images/unchecked.png similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/images/unchecked.png rename to Oqtane.Application/Server/wwwroot/images/unchecked.png diff --git a/Oqtane.Application/AppHost/wwwroot/js/app.js b/Oqtane.Application/Server/wwwroot/js/app.js similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/js/app.js rename to Oqtane.Application/Server/wwwroot/js/app.js diff --git a/Oqtane.Application/AppHost/wwwroot/js/interop.js b/Oqtane.Application/Server/wwwroot/js/interop.js similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/js/interop.js rename to Oqtane.Application/Server/wwwroot/js/interop.js diff --git a/Oqtane.Application/AppHost/wwwroot/js/loadjs.min.js b/Oqtane.Application/Server/wwwroot/js/loadjs.min.js similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/js/loadjs.min.js rename to Oqtane.Application/Server/wwwroot/js/loadjs.min.js diff --git a/Oqtane.Application/AppHost/wwwroot/js/quill-blot-formatter.min.js b/Oqtane.Application/Server/wwwroot/js/quill-blot-formatter.min.js similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/js/quill-blot-formatter.min.js rename to Oqtane.Application/Server/wwwroot/js/quill-blot-formatter.min.js diff --git a/Oqtane.Application/AppHost/wwwroot/js/quill-interop.js b/Oqtane.Application/Server/wwwroot/js/quill-interop.js similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/js/quill-interop.js rename to Oqtane.Application/Server/wwwroot/js/quill-interop.js diff --git a/Oqtane.Application/AppHost/wwwroot/js/quill.min.js b/Oqtane.Application/Server/wwwroot/js/quill.min.js similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/js/quill.min.js rename to Oqtane.Application/Server/wwwroot/js/quill.min.js diff --git a/Oqtane.Application/AppHost/wwwroot/js/quill.min.js.map b/Oqtane.Application/Server/wwwroot/js/quill.min.js.map similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/js/quill.min.js.map rename to Oqtane.Application/Server/wwwroot/js/quill.min.js.map diff --git a/Oqtane.Application/AppHost/wwwroot/js/quill1.3.7.min.js b/Oqtane.Application/Server/wwwroot/js/quill1.3.7.min.js similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/js/quill1.3.7.min.js rename to Oqtane.Application/Server/wwwroot/js/quill1.3.7.min.js diff --git a/Oqtane.Application/AppHost/wwwroot/js/reload.js b/Oqtane.Application/Server/wwwroot/js/reload.js similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/js/reload.js rename to Oqtane.Application/Server/wwwroot/js/reload.js diff --git a/Oqtane.Application/AppHost/wwwroot/loading.gif b/Oqtane.Application/Server/wwwroot/loading.gif similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/loading.gif rename to Oqtane.Application/Server/wwwroot/loading.gif diff --git a/Oqtane.Application/AppHost/wwwroot/oqtane-black.png b/Oqtane.Application/Server/wwwroot/oqtane-black.png similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/oqtane-black.png rename to Oqtane.Application/Server/wwwroot/oqtane-black.png diff --git a/Oqtane.Application/AppHost/wwwroot/oqtane-glow.png b/Oqtane.Application/Server/wwwroot/oqtane-glow.png similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/oqtane-glow.png rename to Oqtane.Application/Server/wwwroot/oqtane-glow.png diff --git a/Oqtane.Application/AppHost/wwwroot/oqtane-white.png b/Oqtane.Application/Server/wwwroot/oqtane-white.png similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/oqtane-white.png rename to Oqtane.Application/Server/wwwroot/oqtane-white.png diff --git a/Oqtane.Application/AppHost/wwwroot/oqtane.ico b/Oqtane.Application/Server/wwwroot/oqtane.ico similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/oqtane.ico rename to Oqtane.Application/Server/wwwroot/oqtane.ico diff --git a/Oqtane.Application/AppHost/wwwroot/oqtane.png b/Oqtane.Application/Server/wwwroot/oqtane.png similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/oqtane.png rename to Oqtane.Application/Server/wwwroot/oqtane.png diff --git a/Oqtane.Application/AppHost/wwwroot/package.png b/Oqtane.Application/Server/wwwroot/package.png similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/package.png rename to Oqtane.Application/Server/wwwroot/package.png diff --git a/Oqtane.Application/AppHost/wwwroot/service-worker.js b/Oqtane.Application/Server/wwwroot/service-worker.js similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/service-worker.js rename to Oqtane.Application/Server/wwwroot/service-worker.js diff --git a/Oqtane.Application/AppHost/wwwroot/users.txt b/Oqtane.Application/Server/wwwroot/users.txt similarity index 100% rename from Oqtane.Application/AppHost/wwwroot/users.txt rename to Oqtane.Application/Server/wwwroot/users.txt diff --git a/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj b/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj index 865461b1..90fcda5d 100644 --- a/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj +++ b/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj @@ -1,13 +1,13 @@ - - net9.0 - 1.0.0 - Oqtane.Application.Shared.Oqtane - + + net9.0 + 1.0.0 + Oqtane.Application.Shared.Oqtane + - - - + + + diff --git a/Oqtane.Client/Installer/Installer.razor b/Oqtane.Client/Installer/Installer.razor index 5d2a487a..c5becbb0 100644 --- a/Oqtane.Client/Installer/Installer.razor +++ b/Oqtane.Client/Installer/Installer.razor @@ -182,7 +182,7 @@ } else { - _databaseName = "LocalDB"; + _databaseName = Constants.DefaultDBName; } LoadDatabaseConfigComponent(); @@ -269,8 +269,8 @@ SiteName = Constants.DefaultSite, Register = _register, SiteTemplate = _template, - RenderMode = RenderModes.Static, - Runtime = Runtimes.Server + RenderMode = "", // provided by appsettings.json + Runtime = "" // provided by appsettings.json }; var installation = await InstallationService.Install(config); diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index d76f56a5..b921ab38 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -665,7 +665,8 @@ if (tenant != null) { _tenant = tenant.Name; - _database = _databases.Find(item => item.DBType == tenant.DBType && item.Name != "LocalDB")?.Name; + // hack - there are 3 providers with SqlServerDatabase DBTypes - so we are choosing the last one in alphabetical order + _database = _databases.Where(item => item.DBType == tenant.DBType).OrderBy(item => item.Name).Last()?.Name; _connectionstring = tenant.DBConnectionString; } } diff --git a/Oqtane.Client/Modules/Admin/Sites/Add.razor b/Oqtane.Client/Modules/Admin/Sites/Add.razor index 459e5143..96949077 100644 --- a/Oqtane.Client/Modules/Admin/Sites/Add.razor +++ b/Oqtane.Client/Modules/Admin/Sites/Add.razor @@ -237,7 +237,7 @@ else } else { - _databaseName = "LocalDB"; + _databaseName = Constants.DefaultDBName; } LoadDatabaseConfigComponent(); } diff --git a/Oqtane.Client/Modules/Admin/Sql/Index.razor b/Oqtane.Client/Modules/Admin/Sql/Index.razor index 476ebd1e..bf2eefec 100644 --- a/Oqtane.Client/Modules/Admin/Sql/Index.razor +++ b/Oqtane.Client/Modules/Admin/Sql/Index.razor @@ -200,7 +200,8 @@ else if (tenant != null) { _tenant = tenant.Name; - _databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType && item.Name != "LocalDB").Name; + // hack - there are 3 providers with SqlServerDatabase DBTypes - so we are choosing the last one in alphabetical order + _databasetype = _databases.Where(item => item.DBType == tenant.DBType).OrderBy(item => item.Name).Last()?.Name; } } else @@ -211,7 +212,7 @@ else } else { - _databasetype = "LocalDB"; + _databasetype = Constants.DefaultDBName; } _showConnectionString = false; LoadDatabaseConfigComponent(); diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index 53218b3c..0e76dc49 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -114,6 +114,12 @@ else +
+ +
+ +
+
@@ -314,6 +320,15 @@ else
+
+ +
+ +
+
}
@@ -516,6 +531,7 @@ else private string _requireconfirmedemail; private string _twofactor; private string _cookiename; + private string _cookiedomain; private string _cookieexpiration; private string _alwaysremember; private string _logouteverywhere; @@ -543,6 +559,7 @@ else private string _clientsecrettype = "password"; private string _toggleclientsecret = string.Empty; private string _authresponsetype; + private string _requirenonce; private string _scopes; private string _parameters; private string _pkce; @@ -590,6 +607,7 @@ else { _twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false"); _cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application"); + _cookiedomain = SettingService.GetSetting(settings, "LoginOptions:CookieDomain", ""); _cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", ""); _alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false"); _logouteverywhere = SettingService.GetSetting(settings, "LoginOptions:LogoutEverywhere", "false"); @@ -629,6 +647,7 @@ else _clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", ""); _toggleclientsecret = SharedLocalizer["ShowPassword"]; _authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code"); + _requirenonce = SettingService.GetSetting(settings, "ExternalLogin:RequireNonce", "true"); _scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", ""); _parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", ""); _pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false"); @@ -725,6 +744,7 @@ else settings = SettingService.SetSetting(settings, "LoginOptions:RequireConfirmedEmail", _requireconfirmedemail, false); settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false); settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true); + settings = SettingService.SetSetting(settings, "LoginOptions:CookieDomain", _cookiedomain, true); settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true); settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false); settings = SettingService.SetSetting(settings, "LoginOptions:LogoutEverywhere", _logouteverywhere, false); @@ -750,6 +770,7 @@ else settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true); settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true); settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:RequireNonce", _requirenonce, true); settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true); settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true); settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true); diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index 94cef77c..782a4b0a 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -140,13 +140,22 @@ namespace Oqtane.Modules } } - // path method + // path methods public string ModulePath() { return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/"; } + public string StaticAssetPath + { + get + { + // requires module to have implemented IModule + return PageState?.Alias.BaseUrl + "_content/" + ModuleState.ModuleDefinition?.PackageName + "/"; + } + } + // fingerprint hash code for static assets public string Fingerprint diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx index 16e0d40e..e9bc7d13 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx @@ -513,6 +513,12 @@ OpenID Connect (OIDC) + + Require Nonce? + + + Specify if Nonce validation is required for the ID token (the default is true) + Save Tokens? @@ -543,4 +549,10 @@ Deleted Users + + Cookie Domain: + + + If you would like to share cookies across subdomains you will need to specify a root domain with a leading dot (ie. '.example.com') + \ No newline at end of file diff --git a/Oqtane.Client/Themes/ThemeBase.cs b/Oqtane.Client/Themes/ThemeBase.cs index 4359f48e..d66ddb76 100644 --- a/Oqtane.Client/Themes/ThemeBase.cs +++ b/Oqtane.Client/Themes/ThemeBase.cs @@ -108,13 +108,22 @@ namespace Oqtane.Themes } } - // path method + // path methods public string ThemePath() { return PageState?.Alias.BaseUrl + "/Themes/" + GetType().Namespace + "/"; } + public string StaticAssetPath + { + get + { + // requires theme to have implemented ITheme + return PageState?.Alias.BaseUrl + "_content/" + ThemeState?.PackageName + "/"; + } + } + // fingerprint hash code for static assets public string Fingerprint { diff --git a/Oqtane.Package/FixProps.exe b/Oqtane.Package/FixProps.exe new file mode 100644 index 00000000..58d79183 Binary files /dev/null and b/Oqtane.Package/FixProps.exe differ diff --git a/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs b/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs index 3e31ac60..fa3835ea 100644 --- a/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/ApplicationBuilderExtensions.cs @@ -2,14 +2,105 @@ using System; using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Net.Http.Headers; +using Oqtane.Components; using Oqtane.Infrastructure; +using Oqtane.Shared; +using Oqtane.UI; +using OqtaneSSR.Extensions; namespace Oqtane.Extensions { public static class ApplicationBuilderExtensions { + public static IApplicationBuilder UseOqtane(this IApplicationBuilder app, IConfigurationRoot configuration, IWebHostEnvironment environment, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider, ISyncManager sync) + { + ServiceActivator.Configure(app.ApplicationServices); + + if (environment.IsDevelopment()) + { + app.UseWebAssemblyDebugging(); + app.UseForwardedHeaders(); + } + else + { + app.UseForwardedHeaders(); + app.UseExceptionHandler("/Error", createScopeForErrors: true); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + // allow oqtane localization middleware + app.UseOqtaneLocalization(); + + app.UseHttpsRedirection(); + app.UseStaticFiles(new StaticFileOptions + { + OnPrepareResponse = (ctx) => + { + // static asset caching + var cachecontrol = configuration.GetSection("CacheControl"); + if (!string.IsNullOrEmpty(cachecontrol.Value)) + { + ctx.Context.Response.Headers.Append(HeaderNames.CacheControl, cachecontrol.Value); + } + // CORS headers for .NET MAUI clients + var policy = corsPolicyProvider.GetPolicyAsync(ctx.Context, Constants.MauiCorsPolicy) + .ConfigureAwait(false).GetAwaiter().GetResult(); + corsService.ApplyResult(corsService.EvaluatePolicy(ctx.Context, policy), ctx.Context.Response); + } + }); + app.UseExceptionMiddleWare(); + app.UseTenantResolution(); + app.UseJwtAuthorization(); + app.UseRouting(); + app.UseCors(); + app.UseOutputCache(); + app.UseAuthentication(); + app.UseAuthorization(); + app.UseAntiforgery(); + + // execute any IServerStartup logic + app.ConfigureOqtaneAssemblies(environment); + + if (configuration.GetSection("UseSwagger").Value != "false") + { + app.UseSwagger(); + app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/" + Constants.Version + "/swagger.json", Constants.PackageId + " " + Constants.Version); }); + } + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + endpoints.MapRazorPages(); + }); + + app.UseEndpoints(endpoints => + { + endpoints.MapRazorComponents() + .AddInteractiveServerRenderMode() + .AddInteractiveWebAssemblyRenderMode() + .AddAdditionalAssemblies(typeof(SiteRouter).Assembly); + }); + + // simulate the fallback routing approach of traditional Blazor - allowing the custom SiteRouter to handle all routing concerns + app.UseEndpoints(endpoints => + { + endpoints.MapFallback(); + }); + + // create a global sync event to identify server application startup + sync.AddSyncEvent(-1, -1, EntityNames.Host, -1, SyncEventActions.Reload); + + return app; + } + public static IApplicationBuilder ConfigureOqtaneAssemblies(this IApplicationBuilder app, IWebHostEnvironment env) { var startUps = AppDomain.CurrentDomain diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 20a11ced..ccd9f3a3 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IdentityModel.Tokens.Jwt; using System.IO; @@ -11,12 +12,17 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; +using Oqtane.Extensions; using Oqtane.Infrastructure; using Oqtane.Interfaces; using Oqtane.Managers; @@ -31,10 +37,126 @@ namespace Microsoft.Extensions.DependencyInjection { public static class OqtaneServiceCollectionExtensions { - public static IServiceCollection AddOqtane(this IServiceCollection services, string[] installedCultures) + public static IServiceCollection AddOqtane(this IServiceCollection services, IConfigurationRoot configuration, IWebHostEnvironment environment) + { + // process forwarded headers on load balancers and proxy servers + services.Configure(options => + { + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + }); + + // register localization services + services.AddLocalization(options => options.ResourcesPath = "Resources"); + + services.AddOptions>().Bind(configuration.GetSection(SettingKeys.AvailableDatabasesSection)); + + // register scoped core services + services.AddScoped() + .AddOqtaneServerScopedServices(); + + services.AddSingleton(); + + // setup HttpClient for server side in a client side compatible fashion ( with auth cookie ) + services.AddHttpClients(); + + // register singleton scoped core services + services.AddSingleton(configuration) + .AddOqtaneSingletonServices(); + + // install any modules or themes ( this needs to occur BEFORE the assemblies are loaded into the app domain ) + InstallationManager.InstallPackages(environment.WebRootPath, environment.ContentRootPath); + + // register transient scoped core services + services.AddOqtaneTransientServices(); + + // load the external assemblies into the app domain, install services + services.AddOqtaneAssemblies(); + services.AddOqtaneDbContext(); + + services.AddAntiforgery(options => + { + options.HeaderName = Constants.AntiForgeryTokenHeaderName; + options.Cookie.Name = Constants.AntiForgeryTokenCookieName; + options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict; + options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; + options.Cookie.HttpOnly = true; + }); + + services.AddIdentityCore(options => { }) + .AddEntityFrameworkStores() + .AddSignInManager() + .AddDefaultTokenProviders() + .AddClaimsPrincipalFactory>(); // role claims + + services.ConfigureOqtaneIdentityOptions(configuration); + + services.AddCascadingAuthenticationState(); + services.AddScoped(); + services.AddAuthorization(); + + services.AddAuthentication(options => + { + options.DefaultScheme = Constants.AuthenticationScheme; + }) + .AddCookie(Constants.AuthenticationScheme) + .AddOpenIdConnect(AuthenticationProviderTypes.OpenIDConnect, options => { }) + .AddOAuth(AuthenticationProviderTypes.OAuth2, options => { }); + + services.ConfigureOqtaneCookieOptions(); + services.ConfigureOqtaneAuthenticationOptions(configuration); + + services.AddOqtaneSiteOptions() + .WithSiteIdentity() + .WithSiteAuthentication(); + + services.AddCors(options => + { + options.AddPolicy(Constants.MauiCorsPolicy, + policy => + { + // allow .NET MAUI client cross origin calls + policy.WithOrigins("https://0.0.0.1", "http://0.0.0.1", "app://0.0.0.1") + .AllowAnyHeader().AllowAnyMethod().AllowCredentials(); + }); + }); + + services.AddOutputCache(); + + services.AddMvc(options => + { + options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); + }) + .AddOqtaneApplicationParts() // register any Controllers from custom modules + .ConfigureOqtaneMvc(); // any additional configuration from IStartup classes + + services.AddRazorPages(); + + services.AddRazorComponents() + .AddInteractiveServerComponents(options => + { + if (environment.IsDevelopment()) + { + options.DetailedErrors = true; + } + }).AddHubOptions(options => + { + options.MaximumReceiveMessageSize = null; // no limit (for large amounts of data ie. textarea components) + }) + .AddInteractiveWebAssemblyComponents(); + + services.AddSwaggerGen(options => + { + options.CustomSchemaIds(type => type.ToString()); // Handle SchemaId already used for different type + }); + services.TryAddSwagger(configuration); + + return services; + } + + public static IServiceCollection AddOqtaneAssemblies(this IServiceCollection services) { LoadAssemblies(); - LoadSatelliteAssemblies(installedCultures); + LoadSatelliteAssemblies(); services.AddOqtaneServices(); return services; @@ -53,7 +175,7 @@ namespace Microsoft.Extensions.DependencyInjection return new OqtaneSiteOptionsBuilder(services); } - internal static IServiceCollection AddOqtaneSingletonServices(this IServiceCollection services) + public static IServiceCollection AddOqtaneSingletonServices(this IServiceCollection services) { services.AddSingleton(); services.AddSingleton(); @@ -66,7 +188,7 @@ namespace Microsoft.Extensions.DependencyInjection return services; } - internal static IServiceCollection AddOqtaneServerScopedServices(this IServiceCollection services) + public static IServiceCollection AddOqtaneServerScopedServices(this IServiceCollection services) { services.AddScoped(); services.AddScoped(); @@ -112,7 +234,7 @@ namespace Microsoft.Extensions.DependencyInjection return services; } - internal static IServiceCollection AddOqtaneTransientServices(this IServiceCollection services) + public static IServiceCollection AddOqtaneTransientServices(this IServiceCollection services) { // services services.AddTransient(); @@ -242,7 +364,7 @@ namespace Microsoft.Extensions.DependencyInjection return services; } - internal static IServiceCollection AddHttpClients(this IServiceCollection services) + public static IServiceCollection AddHttpClients(this IServiceCollection services) { if (!services.Any(x => x.ServiceType == typeof(HttpClient))) { @@ -285,9 +407,9 @@ namespace Microsoft.Extensions.DependencyInjection return services; } - internal static IServiceCollection TryAddSwagger(this IServiceCollection services, bool useSwagger) + public static IServiceCollection TryAddSwagger(this IServiceCollection services, IConfigurationRoot configuration) { - if (useSwagger) + if (configuration.GetSection("UseSwagger").Value != "false") { services.AddSwaggerGen(c => { @@ -386,10 +508,11 @@ namespace Microsoft.Extensions.DependencyInjection } } - private static void LoadSatelliteAssemblies(string[] installedCultures) + private static void LoadSatelliteAssemblies() { AssemblyLoadContext.Default.Resolving += ResolveDependencies; + var installedCultures = LocalizationManager.GetSatelliteAssemblyCultures(); foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories)) { var code = Path.GetFileName(Path.GetDirectoryName(file)); diff --git a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs index f142c602..8c8f1990 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs @@ -31,6 +31,10 @@ namespace Oqtane.Extensions builder.AddSiteNamedOptions(Constants.AuthenticationScheme, (options, alias, sitesettings) => { options.Cookie.Name = sitesettings.GetValue("LoginOptions:CookieName", ".AspNetCore.Identity.Application"); + if (!string.IsNullOrEmpty(sitesettings.GetValue("LoginOptions:CookieDomain", ""))) + { + options.Cookie.Domain = sitesettings.GetValue("LoginOptions:CookieDomain", ""); + } string cookieExpStr = sitesettings.GetValue("LoginOptions:CookieExpiration", ""); if (!string.IsNullOrEmpty(cookieExpStr) && TimeSpan.TryParse(cookieExpStr, out TimeSpan cookieExpTS)) { @@ -61,6 +65,7 @@ namespace Oqtane.Extensions options.ClientId = sitesettings.GetValue("ExternalLogin:ClientId", ""); options.ClientSecret = sitesettings.GetValue("ExternalLogin:ClientSecret", ""); options.ResponseType = sitesettings.GetValue("ExternalLogin:AuthResponseType", "code"); // default is authorization code flow + options.ProtocolValidator.RequireNonce = bool.Parse(sitesettings.GetValue("ExternalLogin:RequireNonce", "true")); options.UsePkce = bool.Parse(sitesettings.GetValue("ExternalLogin:PKCE", "false")); options.SaveTokens = bool.Parse(sitesettings.GetValue("ExternalLogin:SaveTokens", "false")); if (!string.IsNullOrEmpty(sitesettings.GetValue("ExternalLogin:RoleClaimType", ""))) @@ -476,8 +481,26 @@ namespace Oqtane.Extensions else { var logins = await _identityUserManager.GetLoginsAsync(identityuser); - var login = logins.FirstOrDefault(item => item.LoginProvider == (providerType + ":" + alias.SiteId.ToString())); - if (login == null) + // check if any logins exist for this user and provider type for any site + var login = logins.FirstOrDefault(item => item.LoginProvider.StartsWith(providerType)); + if (login != null || !bool.Parse(httpContext.GetSiteSettings().GetValue("ExternalLogin:VerifyUsers", "true"))) + { + // external login using existing user account - link automatically + user = _users.GetUser(identityuser.UserName); + user.SiteId = alias.SiteId; + + var _notifications = httpContext.RequestServices.GetRequiredService(); + string url = httpContext.Request.Scheme + "://" + alias.Name; + string body = "You Recently Used An External Account To Sign In To Our Site.\n\n" + url + "\n\nThank You!"; + var notification = new Notification(user.SiteId, user, "User Account Notification", body); + _notifications.AddNotification(notification); + + // add user login + await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(providerType + ":" + user.SiteId.ToString(), id, providerName)); + + _logger.Log(user.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "External Login Linkage Created For User {Username} And Provider {Provider}", user.Username, providerName); + } + else { if (bool.Parse(httpContext.GetSiteSettings().GetValue("ExternalLogin:VerifyUsers", "true"))) { @@ -496,28 +519,11 @@ namespace Oqtane.Extensions } else { - // external login using existing user account - link automatically - user = _users.GetUser(identityuser.UserName); - user.SiteId = alias.SiteId; - - var _notifications = httpContext.RequestServices.GetRequiredService(); - string url = httpContext.Request.Scheme + "://" + alias.Name; - string body = "You Recently Used An External Account To Sign In To Our Site.\n\n" + url + "\n\nThank You!"; - var notification = new Notification(user.SiteId, user, "User Account Notification", body); - _notifications.AddNotification(notification); - - // add user login - await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(providerType + ":" + user.SiteId.ToString(), id, providerName)); - - _logger.Log(user.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "External Login Linkage Created For User {Username} And Provider {Provider}", user.Username, providerName); + // provider keys do not match + identity.Label = ExternalLoginStatus.ProviderKeyMismatch; + _logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Key Does Not Match For User {Username}. Login Denied.", identityuser.UserName); } } - else - { - // provider keys do not match - identity.Label = ExternalLoginStatus.ProviderKeyMismatch; - _logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Key Does Not Match For User {Username}. Login Denied.", identityuser.UserName); - } } } @@ -525,14 +531,34 @@ namespace Oqtane.Extensions if (user != null) { // manage roles + var _roles = httpContext.RequestServices.GetRequiredService(); var _userRoles = httpContext.RequestServices.GetRequiredService(); var userRoles = _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList(); + + // if user is signing in to a new site + if (userRoles.Count == 0) + { + // add auto assigned roles to user for site + var roles = _roles.GetRoles(user.SiteId).Where(item => item.IsAutoAssigned).ToList(); + foreach (var role in roles) + { + var userrole = new UserRole(); + userrole.UserId = user.UserId; + userrole.RoleId = role.RoleId; + userrole.EffectiveDate = null; + userrole.ExpiryDate = null; + userrole.IgnoreSecurityStamp = true; + _userRoles.AddUserRole(userrole); + } + userRoles = _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList(); + } + + // process any role claims if (!string.IsNullOrEmpty(httpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimType", ""))) { // external roles if (claimsPrincipal.Claims.Any(item => item.Type == httpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimType", ""))) { - var _roles = httpContext.RequestServices.GetRequiredService(); var allowhostrole = bool.Parse(httpContext.GetSiteSettings().GetValue("ExternalLogin:AllowHostRole", "false")); var roles = _roles.GetRoles(user.SiteId, allowhostrole).ToList(); diff --git a/Oqtane.Server/Infrastructure/InstallationManager.cs b/Oqtane.Server/Infrastructure/InstallationManager.cs index 883997e6..8da91ec6 100644 --- a/Oqtane.Server/Infrastructure/InstallationManager.cs +++ b/Oqtane.Server/Infrastructure/InstallationManager.cs @@ -93,12 +93,21 @@ namespace Oqtane.Infrastructure { id = node.InnerText; } - // get framework dependency - node = doc.SelectSingleNode("/package/metadata/dependencies/dependency[@id='Oqtane.Framework']"); + // get minimum framework version using packageType + node = doc.SelectSingleNode("/package/metadata/packageTypes/packageType[@name='Oqtane.Framework']"); if (node != null) { frameworkversion = node.Attributes["version"].Value; } + if (string.IsNullOrEmpty(frameworkversion)) + { + // legacy packages used the dependency metadata + node = doc.SelectSingleNode("/package/metadata/dependencies/dependency[@id='Oqtane.Framework']"); + if (node != null) + { + frameworkversion = node.Attributes["version"].Value; + } + } reader.Close(); break; } diff --git a/Oqtane.Server/Infrastructure/LocalizationManager.cs b/Oqtane.Server/Infrastructure/LocalizationManager.cs index 0d083c4f..3dfc0b54 100644 --- a/Oqtane.Server/Infrastructure/LocalizationManager.cs +++ b/Oqtane.Server/Infrastructure/LocalizationManager.cs @@ -45,6 +45,12 @@ namespace Oqtane.Infrastructure } public string[] GetInstalledCultures() + { + return GetSatelliteAssemblyCultures(); + } + + // method is static as it is called during startup + public static string[] GetSatelliteAssemblyCultures() { var cultures = new List(); foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories)) diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 3642addd..979c57e4 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -1,260 +1,43 @@ using System; -using System.Collections.Generic; using System.IO; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Oqtane.Extensions; using Oqtane.Infrastructure; -using Oqtane.Repository; -using Oqtane.Security; using Oqtane.Shared; -using Microsoft.AspNetCore.HttpOverrides; -using Microsoft.Extensions.Logging; -using Oqtane.Components; -using Oqtane.UI; -using OqtaneSSR.Extensions; -using Microsoft.AspNetCore.Components.Authorization; -using Oqtane.Providers; using Microsoft.AspNetCore.Cors.Infrastructure; -using Microsoft.Net.Http.Headers; namespace Oqtane { public class Startup { - private readonly bool _useSwagger; - private readonly IWebHostEnvironment _env; - private readonly string[] _installedCultures; - private string _configureServicesErrors; + private readonly IConfigurationRoot _configuration; + private readonly IWebHostEnvironment _environment; - public IConfigurationRoot Configuration { get; } - - public Startup(IWebHostEnvironment env, ILocalizationManager localizationManager) + public Startup(IWebHostEnvironment environment) { + AppDomain.CurrentDomain.SetData(Constants.DataDirectory, Path.Combine(environment.ContentRootPath, "Data")); + var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) + .SetBasePath(environment.ContentRootPath) .AddJsonFile("appsettings.json", false, true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true) + .AddJsonFile($"appsettings.{environment.EnvironmentName}.json", true, true) .AddEnvironmentVariables(); - Configuration = builder.Build(); - - _installedCultures = localizationManager.GetInstalledCultures(); - - //add possibility to switch off swagger on production. - _useSwagger = Configuration.GetSection("UseSwagger").Value != "false"; - - AppDomain.CurrentDomain.SetData(Constants.DataDirectory, Path.Combine(env.ContentRootPath, "Data")); - - _env = env; + _configuration = builder.Build(); + _environment = environment; } - // 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 public void ConfigureServices(IServiceCollection services) { - // process forwarded headers on load balancers and proxy servers - services.Configure(options => - { - options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; - }); - - // register localization services - services.AddLocalization(options => options.ResourcesPath = "Resources"); - - services.AddOptions>().Bind(Configuration.GetSection(SettingKeys.AvailableDatabasesSection)); - - // register scoped core services - services.AddScoped() - .AddOqtaneServerScopedServices(); - - services.AddSingleton(); - - // setup HttpClient for server side in a client side compatible fashion ( with auth cookie ) - services.AddHttpClients(); - - // register singleton scoped core services - services.AddSingleton(Configuration) - .AddOqtaneSingletonServices(); - - // install any modules or themes ( this needs to occur BEFORE the assemblies are loaded into the app domain ) - _configureServicesErrors += InstallationManager.InstallPackages(_env.WebRootPath, _env.ContentRootPath); - - // register transient scoped core services - services.AddOqtaneTransientServices(); - - // load the external assemblies into the app domain, install services - services.AddOqtane(_installedCultures); - services.AddOqtaneDbContext(); - - services.AddAntiforgery(options => - { - options.HeaderName = Constants.AntiForgeryTokenHeaderName; - options.Cookie.Name = Constants.AntiForgeryTokenCookieName; - options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict; - options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; - options.Cookie.HttpOnly = true; - }); - - services.AddIdentityCore(options => { }) - .AddEntityFrameworkStores() - .AddSignInManager() - .AddDefaultTokenProviders() - .AddClaimsPrincipalFactory>(); // role claims - - services.ConfigureOqtaneIdentityOptions(Configuration); - - services.AddCascadingAuthenticationState(); - services.AddScoped(); - services.AddAuthorization(); - - services.AddAuthentication(options => - { - options.DefaultScheme = Constants.AuthenticationScheme; - }) - .AddCookie(Constants.AuthenticationScheme) - .AddOpenIdConnect(AuthenticationProviderTypes.OpenIDConnect, options => { }) - .AddOAuth(AuthenticationProviderTypes.OAuth2, options => { }); - - services.ConfigureOqtaneCookieOptions(); - services.ConfigureOqtaneAuthenticationOptions(Configuration); - - services.AddOqtaneSiteOptions() - .WithSiteIdentity() - .WithSiteAuthentication(); - - services.AddCors(options => - { - options.AddPolicy(Constants.MauiCorsPolicy, - policy => - { - // allow .NET MAUI client cross origin calls - policy.WithOrigins("https://0.0.0.1", "http://0.0.0.1", "app://0.0.0.1") - .AllowAnyHeader().AllowAnyMethod().AllowCredentials(); - }); - }); - - services.AddOutputCache(); - - services.AddMvc(options => - { - options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); - }) - .AddOqtaneApplicationParts() // register any Controllers from custom modules - .ConfigureOqtaneMvc(); // any additional configuration from IStartup classes - - services.AddRazorPages(); - - services.AddRazorComponents() - .AddInteractiveServerComponents(options => - { - if (_env.IsDevelopment()) - { - options.DetailedErrors = true; - } - }).AddHubOptions(options => - { - options.MaximumReceiveMessageSize = null; // no limit (for large amounts of data ie. textarea components) - }) - .AddInteractiveWebAssemblyComponents(); - - services.AddSwaggerGen(options => - { - options.CustomSchemaIds(type => type.ToString()); // Handle SchemaId already used for different type - }); - services.TryAddSwagger(_useSwagger); + services.AddOqtane(_configuration, _environment); } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISyncManager sync, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider, ILogger logger) + public void Configure(IApplicationBuilder app, IConfigurationRoot configuration, IWebHostEnvironment environment, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider, ISyncManager sync) { - if (!string.IsNullOrEmpty(_configureServicesErrors)) - { - logger.LogError(_configureServicesErrors); - } - - ServiceActivator.Configure(app.ApplicationServices); - - if (env.IsDevelopment()) - { - app.UseWebAssemblyDebugging(); - app.UseForwardedHeaders(); - } - else - { - app.UseForwardedHeaders(); - app.UseExceptionHandler("/Error", createScopeForErrors: true); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - - // allow oqtane localization middleware - app.UseOqtaneLocalization(); - - app.UseHttpsRedirection(); - app.UseStaticFiles(new StaticFileOptions - { - OnPrepareResponse = (ctx) => - { - // static asset caching - var cachecontrol = Configuration.GetSection("CacheControl"); - if (!string.IsNullOrEmpty(cachecontrol.Value)) - { - ctx.Context.Response.Headers.Append(HeaderNames.CacheControl, cachecontrol.Value); - } - // CORS headers for .NET MAUI clients - var policy = corsPolicyProvider.GetPolicyAsync(ctx.Context, Constants.MauiCorsPolicy) - .ConfigureAwait(false).GetAwaiter().GetResult(); - corsService.ApplyResult(corsService.EvaluatePolicy(ctx.Context, policy), ctx.Context.Response); - } - }); - app.UseExceptionMiddleWare(); - app.UseTenantResolution(); - app.UseJwtAuthorization(); - app.UseRouting(); - app.UseCors(); - app.UseOutputCache(); - app.UseAuthentication(); - app.UseAuthorization(); - app.UseAntiforgery(); - - // execute any IServerStartup logic - app.ConfigureOqtaneAssemblies(env); - - if (_useSwagger) - { - app.UseSwagger(); - app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/" + Constants.Version + "/swagger.json", Constants.PackageId + " " + Constants.Version); }); - } - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - endpoints.MapRazorPages(); - }); - - app.UseEndpoints(endpoints => - { - endpoints.MapRazorComponents() - .AddInteractiveServerRenderMode() - .AddInteractiveWebAssemblyRenderMode() - .AddAdditionalAssemblies(typeof(SiteRouter).Assembly); - }); - - // simulate the fallback routing approach of traditional Blazor - allowing the custom SiteRouter to handle all routing concerns - app.UseEndpoints(endpoints => - { - endpoints.MapFallback(); - }); - - // create a global sync event to identify server application startup - sync.AddSyncEvent(-1, -1, EntityNames.Host, -1, SyncEventActions.Reload); + app.UseOqtane(configuration, environment, corsService, corsPolicyProvider, sync); } } } diff --git a/Oqtane.Server/appsettings.json b/Oqtane.Server/appsettings.json index 46ae54ac..9f96daeb 100644 --- a/Oqtane.Server/appsettings.json +++ b/Oqtane.Server/appsettings.json @@ -1,5 +1,5 @@ { - "RenderMode": "Interactive", + "RenderMode": "Static", "Runtime": "Server", "Database": { "DefaultDBType": "" diff --git a/Oqtane.Server/appsettings.release.json b/Oqtane.Server/appsettings.release.json index 9269136e..307008c8 100644 --- a/Oqtane.Server/appsettings.release.json +++ b/Oqtane.Server/appsettings.release.json @@ -1,5 +1,5 @@ { - "RenderMode": "Interactive", + "RenderMode": "Static", "Runtime": "Server", "Database": { "DefaultDBType": "" diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Modules/[Owner].Module.[Module]/Edit.razor b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Modules/[Owner].Module.[Module]/Edit.razor index 23ca8f4f..a3d25ab5 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Modules/[Owner].Module.[Module]/Edit.razor +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Modules/[Owner].Module.[Module]/Edit.razor @@ -35,7 +35,7 @@ public override List Resources => new List() { - new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" } + new Stylesheet("_content/[Owner].Module.[Module]/Module.css") }; private ElementReference form; diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Modules/[Owner].Module.[Module]/Index.razor b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Modules/[Owner].Module.[Module]/Index.razor index 4c68ada6..6f43dcb3 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Modules/[Owner].Module.[Module]/Index.razor +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Modules/[Owner].Module.[Module]/Index.razor @@ -42,8 +42,8 @@ else public override List Resources => new List() { - new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }, - new Resource { ResourceType = ResourceType.Script, Url = ModulePath() + "Module.js" } + new Stylesheet("_content/[Owner].Module.[Module]/Module.css"), + new Script("_content/[Owner].Module.[Module]/Module.js") }; List<[Module]> _[Module]s; diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Startup/ClientStartup.cs b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Startup/ClientStartup.cs index 611b5a8e..5050abee 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Startup/ClientStartup.cs +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Startup/ClientStartup.cs @@ -8,7 +8,10 @@ namespace [Owner].Module.[Module].Startup { public void ConfigureServices(IServiceCollection services) { - services.AddScoped(); + if (!services.Any(s => s.ServiceType == typeof(I[Module]Service))) + { + services.AddScoped(); + } } } } diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].nuspec b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].nuspec index 67af3899..bf919a3d 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].nuspec +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].nuspec @@ -15,9 +15,10 @@ oqtane module - - - + + + + @@ -26,7 +27,12 @@ - + + + + + + \ No newline at end of file diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.cmd b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.cmd index af7654c7..35093663 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.cmd +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.cmd @@ -8,4 +8,4 @@ XCOPY "..\Server\bin\Debug\%TargetFramework%\%ProjectName%.Server.Oqtane.dll" ". XCOPY "..\Server\bin\Debug\%TargetFramework%\%ProjectName%.Server.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y XCOPY "..\Shared\bin\Debug\%TargetFramework%\%ProjectName%.Shared.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y XCOPY "..\Shared\bin\Debug\%TargetFramework%\%ProjectName%.Shared.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y -XCOPY "..\Server\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\" /Y /S /I \ No newline at end of file +XCOPY "..\Server\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\_content\%ProjectName%\" /Y /S /I \ No newline at end of file diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.sh b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.sh index bcdee757..74703939 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.sh +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/debug.sh @@ -9,4 +9,4 @@ cp -f "../Server/bin/Debug/$TargetFramework/$ProjectName$.Server.Oqtane.dll" ".. cp -f "../Server/bin/Debug/$TargetFramework/$ProjectName$.Server.Oqtane.pdb" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/" cp -f "../Shared/bin/Debug/$TargetFramework/$ProjectName$.Shared.Oqtane.dll" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/" cp -f "../Shared/bin/Debug/$TargetFramework/$ProjectName$.Shared.Oqtane.pdb" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/" -cp -rf "../Server/wwwroot/"* "../../[RootFolder]/Oqtane.Server/wwwroot/" \ No newline at end of file +cp -rf "../Server/wwwroot/"* "../../[RootFolder]/Oqtane.Server/wwwroot/_content/%ProjectName%/" diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/release.cmd b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/release.cmd index 1785fa66..5b49099f 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/release.cmd +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/release.cmd @@ -3,5 +3,6 @@ set TargetFramework=%1 set ProjectName=%2 del "*.nupkg" +"..\..\oqtane.framework\oqtane.package\FixProps.exe" "..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName% XCOPY "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\" /Y \ No newline at end of file diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/release.sh b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/release.sh index 1334e6a7..98526bc0 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/release.sh +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/release.sh @@ -1,5 +1,7 @@ TargetFramework=$1 ProjectName=$2 +find . -name "*.nupkg" -delete +"..\..\oqtane.framework\oqtane.package\FixProps.exe" "..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName% cp -f "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\" \ No newline at end of file diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/Modules/[Owner].Module.[Module]/Module.css b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/Module.css similarity index 100% rename from Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/Modules/[Owner].Module.[Module]/Module.css rename to Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/Module.css diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/Modules/[Owner].Module.[Module]/Module.js b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/Module.js similarity index 100% rename from Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/Modules/[Owner].Module.[Module]/Module.js rename to Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/Module.js diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/_content/Placeholder.txt b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/_content/Placeholder.txt deleted file mode 100644 index 5a324d79..00000000 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/_content/Placeholder.txt +++ /dev/null @@ -1,11 +0,0 @@ -The _content folder should only contain static resources from shared razor component libraries (RCLs). Static resources can be extracted from shared RCL Nuget packages by executing a Publish task on the module's Server project to a local folder and copying the files from the _content folder which is created. Each shared RCL would have its own appropriately named subfolder within the module's _content folder. - -ie. - -/_content - /Radzen.Blazor - /css - /fonts - /syncfusion.blazor - /scripts - /styles diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/ThemeInfo.cs b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/ThemeInfo.cs index 4dea5a8f..3d007129 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/ThemeInfo.cs +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/ThemeInfo.cs @@ -16,11 +16,10 @@ namespace [Owner].Theme.[Theme] ContainerSettingsType = "[Owner].Theme.[Theme].ContainerSettings, [Owner].Theme.[Theme].Client.Oqtane", Resources = new List() { - // obtained from https://cdnjs.com/libraries - new StyleSheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"), - new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" }, + // obtained from https://cdnjs.com/libraries + new Stylesheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"), + new Stylesheet("_content/[Owner].Theme.[Theme]/Theme.css"), new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous") - } }; diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/wwwroot/Themes/[Owner].Theme.[Theme]/Theme.css b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/wwwroot/Themes/[Owner].Theme.[Theme]/Theme.css deleted file mode 100644 index 2a6101ef..00000000 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/wwwroot/Themes/[Owner].Theme.[Theme]/Theme.css +++ /dev/null @@ -1,123 +0,0 @@ -/* Oqtane Styles */ - -body { - padding-top: 7rem; -} - -/* App Logo */ -.app-logo .img-fluid { - max-height: 90px; - padding: 0 5px 0 5px; -} - -.table > :not(caption) > * > * { - box-shadow: none; -} - -.table .form-control { - background-color: #ffffff !important; - border-width: 0.5px !important; - border-bottom-color: #ccc !important; -} - -.table .form-select { - background-color: #ffffff !important; - border-width: 0.5px !important; - border-bottom-color: #ccc !important; -} - -.table .btn-primary { - background-color: var(--bs-primary); -} - -.table .btn-secondary { - background-color: var(--bs-secondary); -} - -.alert-dismissible .btn-close { - z-index: 1; -} - -.controls { - z-index: 2000; - padding-top: 15px; - padding-bottom: 15px; - margin-right: 10px; -} - -.app-menu .nav-item { - font-size: 0.9rem; - padding-bottom: 0.5rem; - white-space: nowrap; -} - -.app-menu .nav-item a { - border-radius: 4px; - height: 3rem; - display: flex; - align-items: center; - line-height: 3rem; - padding-left: 1rem; -} - -.app-menu .nav-item a.active { - background-color: rgba(255,255,255,0.25); - color: white; -} - -.app-menu .nav-item a:hover { - background-color: rgba(255,255,255,0.1); - color: white; -} - -.app-menu .nav-link .oi { - width: 1.5rem; - font-size: 1.1rem; - vertical-align: text-top; - top: -2px; -} - -.navbar-toggler { - background-color: rgba(255, 255, 255, 0.1); - margin: .5rem; -} - -div.app-moduleactions a.dropdown-toggle, div.app-moduleactions div.dropdown-menu { - color: #000000; -} - -.dropdown-menu span { - mix-blend-mode: difference; -} - -@media (max-width: 767.98px) { - - .app-menu { - width: 100%; - } - - .navbar { - position: fixed; - top: 60px; - width: 100%; - } - - .controls { - height: 60px; - top: 15px; - position: fixed; - top: 0px; - width: 100%; - background-color: rgb(0, 0, 0); - } - - .controls-group { - float: right; - margin-right: 25px; - } - - .content { - position: relative; - top: 60px; - } -} diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].nuspec b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].nuspec index 40f5f3b3..363ebaa9 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].nuspec +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].nuspec @@ -15,14 +15,20 @@ oqtane theme - - - + + + + - + + + + + + \ No newline at end of file diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/debug.cmd b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/debug.cmd index 196fb916..e7073b94 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/debug.cmd +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/debug.cmd @@ -4,4 +4,4 @@ set ProjectName=%2 XCOPY "..\Client\bin\Debug\%TargetFramework%\%ProjectName%.Client.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y XCOPY "..\Client\bin\Debug\%TargetFramework%\%ProjectName%.Client.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y -XCOPY "..\Client\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\" /Y /S /I \ No newline at end of file +XCOPY "..\Client\wwwroot\*" "..\..\oqtane.framework\Oqtane.Server\wwwroot\_content\%ProjectName%\" /Y /S /I \ No newline at end of file diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/debug.sh b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/debug.sh index 0caca359..47311fc8 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/debug.sh +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/debug.sh @@ -5,4 +5,4 @@ ProjectName=$2 cp -f "../Client/bin/Debug/$TargetFramework/$ProjectName$.Client.Oqtane.dll" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/" cp -f "../Client/bin/Debug/$TargetFramework/$ProjectName$.Client.Oqtane.pdb" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/" -cp -rf "../Server/wwwroot/"* "../../[RootFolder]/Oqtane.Server/wwwroot/" \ No newline at end of file +cp -rf "../Client/wwwroot/"* "../../[RootFolder]/Oqtane.Server/wwwroot/_content/%ProjectName%/" \ No newline at end of file diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/release.cmd b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/release.cmd index 1785fa66..31809574 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/release.cmd +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/release.cmd @@ -3,5 +3,6 @@ set TargetFramework=%1 set ProjectName=%2 del "*.nupkg" +"..\..\[RootFolder]\oqtane.package\FixProps.exe" "..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName% XCOPY "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\" /Y \ No newline at end of file diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/release.sh b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/release.sh index 1334e6a7..98526bc0 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/release.sh +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/release.sh @@ -1,5 +1,7 @@ TargetFramework=$1 ProjectName=$2 +find . -name "*.nupkg" -delete +"..\..\oqtane.framework\oqtane.package\FixProps.exe" "..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName% cp -f "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\" \ No newline at end of file diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index bf30580b..0348e7c2 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -12,6 +12,7 @@ namespace Oqtane.Shared public const string PackageRegistryUrl = "https://www.oqtane.net"; public const string DataDirectory = "DataDirectory"; + public const string DefaultDBName = "LocalDB"; public const string DefaultDBType = "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Server"; public const string DefaultTheme = "Oqtane.Themes.OqtaneTheme.Default, Oqtane.Client";