Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
Leigh Pointer
2025-08-31 12:56:43 +02:00
103 changed files with 553 additions and 575 deletions

View File

@@ -1,12 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Version>1.0.0</Version>
<AssemblyName>Oqtane.Application.AppHost</AssemblyName>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Server\Oqtane.Application.Server.csproj" />
</ItemGroup>
</Project>

View File

@@ -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"
}
}
}
}

View File

@@ -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

View File

@@ -1 +0,0 @@
This is the location where static resources such as images or style sheets should be located

View File

@@ -35,7 +35,7 @@
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" } new Stylesheet(ModulePath() + "Module.css")
}; };
private ElementReference form; private ElementReference form;

View File

@@ -38,12 +38,10 @@ else
} }
@code { @code {
public override string RenderMode => RenderModes.Static;
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }, new Stylesheet(ModulePath() + "Module.css"),
new Resource { ResourceType = ResourceType.Script, Url = ModulePath() + "Module.js" } new Script(ModulePath() + "Module.js")
}; };
List<Models.MyModule> _MyModules; List<Models.MyModule> _MyModules;

View File

@@ -1,23 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Razor"> <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Version>1.0.0</Version> <Version>1.0.0</Version>
<AssemblyName>Oqtane.Application.Client.Oqtane</AssemblyName> <AssemblyName>Oqtane.Application.Client.Oqtane</AssemblyName>
</PropertyGroup> <NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
<BlazorWebAssemblyEnableLinking>false</BlazorWebAssemblyEnableLinking>
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" /> <ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Oqtane.Client" Version="6.1.6" /> <PackageReference Include="Oqtane.Client" Version="6.1.6" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<!-- there may be other elements here -->
<BlazorWebAssemblyEnableLinking>false</BlazorWebAssemblyEnableLinking>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
</PropertyGroup>
</Project> </Project>

View File

@@ -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);
}
}
}

View File

@@ -8,7 +8,10 @@ namespace Oqtane.Application.Startup
{ {
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddScoped<IMyModuleService, MyModuleService>(); if (!services.Any(s => s.ServiceType == typeof(IMyModuleService)))
{
services.AddScoped<IMyModuleService, MyModuleService>();
}
} }
} }
} }

View File

@@ -16,8 +16,8 @@ namespace Oqtane.Application.MyTheme
ContainerSettingsType = "Oqtane.Application.MyTheme.ContainerSettings, Oqtane.Application.Client.Oqtane", ContainerSettingsType = "Oqtane.Application.MyTheme.ContainerSettings, Oqtane.Application.Client.Oqtane",
Resources = new List<Resource>() Resources = new List<Resource>()
{ {
new Script(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"), new Stylesheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"),
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" }, new Stylesheet("~/Theme.css"),
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous") new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
} }
}; };

View File

@@ -3,7 +3,7 @@
<metadata> <metadata>
<id>Oqtane.Application.Template</id> <id>Oqtane.Application.Template</id>
<version>6.1.6</version> <version>6.1.6</version>
<title>Oqtane Application Solution For Blazor</title> <title>Oqtane Application Template For Blazor</title>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>

View File

@@ -2,12 +2,10 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.12.35506.116 d17.12 VisualStudioVersion = 17.12.35506.116 d17.12
MinimumVisualStudioVersion = 10.0.40219.1 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 EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Client", "Client\Oqtane.Application.Client.csproj", "{AA8E58A1-CD09-4208-BF66-A8BB341FD669}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Client", "Client\Oqtane.Application.Client.csproj", "{AA8E58A1-CD09-4208-BF66-A8BB341FD669}"
EndProject 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}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Shared", "Shared\Oqtane.Application.Shared.csproj", "{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}"
EndProject EndProject
Global Global
@@ -16,18 +14,14 @@ Global
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution 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.ActiveCfg = Debug|Any CPU
{04B05448-788F-433D-92C0-FED35122D45A}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{04B05448-788F-433D-92C0-FED35122D45A}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Debug|Any CPU.Build.0 = 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 {18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -36,7 +30,4 @@ Global
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1D016F15-46FE-4726-8DFD-2E4FD4DC7668}
EndGlobalSection
EndGlobal EndGlobal

View File

@@ -1,25 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Razor"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc> <Version>1.0.0</Version>
<Version>1.0.0</Version> <AssemblyName>Oqtane.Application.Server.Oqtane</AssemblyName>
<AssemblyName>Oqtane.Application.Server.Oqtane</AssemblyName> </PropertyGroup>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Client\Oqtane.Application.Client.csproj" /> <ProjectReference Include="..\Client\Oqtane.Application.Client.csproj" />
<ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" /> <ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Oqtane.Server" Version="6.1.6" /> <PackageReference Include="Oqtane.Server" Version="6.1.6" />
</ItemGroup> </ItemGroup>
<Target Name="CopyStaticAssets" AfterTargets="Build">
<ItemGroup>
<StaticAssets Include="$(ProjectDir)wwwroot\**\*.*" />
</ItemGroup>
<Copy SourceFiles="@(StaticAssets)" DestinationFolder="$(ProjectDir)..\AppHost\wwwroot\%(RecursiveDir)" SkipUnchangedFiles="true" />
</Target>
</Project> </Project>

View File

@@ -1,17 +1,17 @@
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore; using Microsoft.AspNetCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration;
using Oqtane.Infrastructure;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Oqtane.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
namespace Oqtane.Application.AppHost namespace Oqtane.Application.Server
{ {
public class Program public class Program
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
// defer server startup to Oqtane - do not modify
var host = BuildWebHost(args); var host = BuildWebHost(args);
var databaseManager = host.Services.GetService<IDatabaseManager>(); var databaseManager = host.Services.GetService<IDatabaseManager>();
var install = databaseManager.Install(); var install = databaseManager.Install();
@@ -20,7 +20,7 @@ namespace Oqtane.Application.AppHost
var filelogger = host.Services.GetRequiredService<ILogger<Program>>(); var filelogger = host.Services.GetRequiredService<ILogger<Program>>();
if (filelogger != null) if (filelogger != null)
{ {
filelogger.LogError($"[Oqtane.Application.AppHost.Program.Main] {install.Message}"); filelogger.LogError($"[Oqtane.Application.Server.Program.Main] {install.Message}");
} }
} }
else else
@@ -35,9 +35,8 @@ namespace Oqtane.Application.AppHost
.AddCommandLine(args) .AddCommandLine(args)
.AddEnvironmentVariables() .AddEnvironmentVariables()
.Build()) .Build())
.UseStartup<Oqtane.Startup>() .UseStartup<Startup>()
.ConfigureLocalizationSettings() .ConfigureLocalizationSettings()
.Build(); .Build();
} }
} }

View File

@@ -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"
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -1,8 +1,8 @@
{ {
"RenderMode": "Interactive", "RenderMode": "Static",
"Runtime": "Server", "Runtime": "Server",
"Database": { "Database": {
"DefaultDBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Server" "DefaultDBType": ""
}, },
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "" "DefaultConnection": ""
@@ -57,8 +57,7 @@
} }
}, },
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information"
"Notify": "Error"
} }
} }
} }

View File

@@ -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

View File

Before

Width:  |  Height:  |  Size: 318 B

After

Width:  |  Height:  |  Size: 318 B

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 427 B

After

Width:  |  Height:  |  Size: 427 B

View File

Before

Width:  |  Height:  |  Size: 875 B

After

Width:  |  Height:  |  Size: 875 B

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 801 B

After

Width:  |  Height:  |  Size: 801 B

View File

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

Before

Width:  |  Height:  |  Size: 177 B

After

Width:  |  Height:  |  Size: 177 B

View File

Before

Width:  |  Height:  |  Size: 438 B

After

Width:  |  Height:  |  Size: 438 B

View File

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Version>1.0.0</Version> <Version>1.0.0</Version>
<AssemblyName>Oqtane.Application.Shared.Oqtane</AssemblyName> <AssemblyName>Oqtane.Application.Shared.Oqtane</AssemblyName>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Oqtane.Shared" Version="6.1.6" /> <PackageReference Include="Oqtane.Shared" Version="6.1.6" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -182,7 +182,7 @@
} }
else else
{ {
_databaseName = "LocalDB"; _databaseName = Constants.DefaultDBName;
} }
LoadDatabaseConfigComponent(); LoadDatabaseConfigComponent();
@@ -269,8 +269,8 @@
SiteName = Constants.DefaultSite, SiteName = Constants.DefaultSite,
Register = _register, Register = _register,
SiteTemplate = _template, SiteTemplate = _template,
RenderMode = RenderModes.Static, RenderMode = "", // provided by appsettings.json
Runtime = Runtimes.Server Runtime = "" // provided by appsettings.json
}; };
var installation = await InstallationService.Install(config); var installation = await InstallationService.Install(config);

View File

@@ -665,7 +665,8 @@
if (tenant != null) if (tenant != null)
{ {
_tenant = tenant.Name; _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; _connectionstring = tenant.DBConnectionString;
} }
} }

View File

@@ -237,7 +237,7 @@ else
} }
else else
{ {
_databaseName = "LocalDB"; _databaseName = Constants.DefaultDBName;
} }
LoadDatabaseConfigComponent(); LoadDatabaseConfigComponent();
} }

View File

@@ -200,7 +200,8 @@ else
if (tenant != null) if (tenant != null)
{ {
_tenant = tenant.Name; _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 else
@@ -211,7 +212,7 @@ else
} }
else else
{ {
_databasetype = "LocalDB"; _databasetype = Constants.DefaultDBName;
} }
_showConnectionString = false; _showConnectionString = false;
LoadDatabaseConfigComponent(); LoadDatabaseConfigComponent();

View File

@@ -114,6 +114,12 @@ else
<input id="cookiename" class="form-control" @bind="@_cookiename" /> <input id="cookiename" class="form-control" @bind="@_cookiename" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="cookiedomain" HelpText="If you would like to share cookies across subdomains you will need to specify a root domain with a leading dot (ie. '.example.com')" ResourceKey="CookieDomain">Cookie Domain:</Label>
<div class="col-sm-9">
<input id="cookiedomain" class="form-control" @bind="@_cookiedomain" />
</div>
</div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="cookieexpiration" HelpText="You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified." ResourceKey="CookieExpiration">Cookie Expiration Timespan:</Label> <Label Class="col-sm-3" For="cookieexpiration" HelpText="You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified." ResourceKey="CookieExpiration">Cookie Expiration Timespan:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
@@ -314,6 +320,15 @@ else
</select> </select>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="requirenonce" HelpText="Specify if Nonce validation is required for the ID token (the default is true)" ResourceKey="RequireNonce">Require Nonce?</Label>
<div class="col-sm-9">
<select id="requirenonce" class="form-select" @bind="@_requirenonce" required>
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
} }
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="scopes" HelpText="A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default." ResourceKey="Scopes">Scopes:</Label> <Label Class="col-sm-3" For="scopes" HelpText="A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default." ResourceKey="Scopes">Scopes:</Label>
@@ -516,6 +531,7 @@ else
private string _requireconfirmedemail; private string _requireconfirmedemail;
private string _twofactor; private string _twofactor;
private string _cookiename; private string _cookiename;
private string _cookiedomain;
private string _cookieexpiration; private string _cookieexpiration;
private string _alwaysremember; private string _alwaysremember;
private string _logouteverywhere; private string _logouteverywhere;
@@ -543,6 +559,7 @@ else
private string _clientsecrettype = "password"; private string _clientsecrettype = "password";
private string _toggleclientsecret = string.Empty; private string _toggleclientsecret = string.Empty;
private string _authresponsetype; private string _authresponsetype;
private string _requirenonce;
private string _scopes; private string _scopes;
private string _parameters; private string _parameters;
private string _pkce; private string _pkce;
@@ -590,6 +607,7 @@ else
{ {
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false"); _twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application"); _cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
_cookiedomain = SettingService.GetSetting(settings, "LoginOptions:CookieDomain", "");
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", ""); _cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
_alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false"); _alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false");
_logouteverywhere = SettingService.GetSetting(settings, "LoginOptions:LogoutEverywhere", "false"); _logouteverywhere = SettingService.GetSetting(settings, "LoginOptions:LogoutEverywhere", "false");
@@ -629,6 +647,7 @@ else
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", ""); _clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
_toggleclientsecret = SharedLocalizer["ShowPassword"]; _toggleclientsecret = SharedLocalizer["ShowPassword"];
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code"); _authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
_requirenonce = SettingService.GetSetting(settings, "ExternalLogin:RequireNonce", "true");
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", ""); _scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", ""); _parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false"); _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:RequireConfirmedEmail", _requireconfirmedemail, false);
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false); settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true); 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:CookieExpiration", _cookieexpiration, true);
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false); settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
settings = SettingService.SetSetting(settings, "LoginOptions:LogoutEverywhere", _logouteverywhere, 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:ClientId", _clientid, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true); settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, 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:Scopes", _scopes, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true); settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true); settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);

View File

@@ -140,13 +140,22 @@ namespace Oqtane.Modules
} }
} }
// path method // path methods
public string ModulePath() public string ModulePath()
{ {
return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/"; 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 // fingerprint hash code for static assets
public string Fingerprint public string Fingerprint

View File

@@ -513,6 +513,12 @@
<data name="OIDC" xml:space="preserve"> <data name="OIDC" xml:space="preserve">
<value>OpenID Connect (OIDC)</value> <value>OpenID Connect (OIDC)</value>
</data> </data>
<data name="RequireNonce.Text" xml:space="preserve">
<value>Require Nonce?</value>
</data>
<data name="RequireNonce.HelpText" xml:space="preserve">
<value>Specify if Nonce validation is required for the ID token (the default is true)</value>
</data>
<data name="SaveTokens.Text" xml:space="preserve"> <data name="SaveTokens.Text" xml:space="preserve">
<value>Save Tokens?</value> <value>Save Tokens?</value>
</data> </data>
@@ -543,4 +549,10 @@
<data name="Deleted Users" xml:space="preserve"> <data name="Deleted Users" xml:space="preserve">
<value>Deleted Users</value> <value>Deleted Users</value>
</data> </data>
<data name="CookieDomain.Text" xml:space="preserve">
<value>Cookie Domain:</value>
</data>
<data name="CookieDomain.HelpText" xml:space="preserve">
<value>If you would like to share cookies across subdomains you will need to specify a root domain with a leading dot (ie. '.example.com')</value>
</data>
</root> </root>

View File

@@ -108,13 +108,22 @@ namespace Oqtane.Themes
} }
} }
// path method // path methods
public string ThemePath() public string ThemePath()
{ {
return PageState?.Alias.BaseUrl + "/Themes/" + GetType().Namespace + "/"; 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 // fingerprint hash code for static assets
public string Fingerprint public string Fingerprint
{ {

BIN
Oqtane.Package/FixProps.exe Normal file

Binary file not shown.

View File

@@ -2,14 +2,105 @@ using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Net.Http.Headers;
using Oqtane.Components;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Shared;
using Oqtane.UI;
using OqtaneSSR.Extensions;
namespace Oqtane.Extensions namespace Oqtane.Extensions
{ {
public static class ApplicationBuilderExtensions 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<App>()
.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) public static IApplicationBuilder ConfigureOqtaneAssemblies(this IApplicationBuilder app, IWebHostEnvironment env)
{ {
var startUps = AppDomain.CurrentDomain var startUps = AppDomain.CurrentDomain

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.IO; using System.IO;
@@ -11,12 +12,17 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Oqtane.Extensions;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Interfaces; using Oqtane.Interfaces;
using Oqtane.Managers; using Oqtane.Managers;
@@ -31,10 +37,126 @@ namespace Microsoft.Extensions.DependencyInjection
{ {
public static class OqtaneServiceCollectionExtensions 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<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
// register localization services
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddOptions<List<Oqtane.Models.Database>>().Bind(configuration.GetSection(SettingKeys.AvailableDatabasesSection));
// register scoped core services
services.AddScoped<IAuthorizationHandler, PermissionHandler>()
.AddOqtaneServerScopedServices();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// 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<IdentityUser>(options => { })
.AddEntityFrameworkStores<TenantDBContext>()
.AddSignInManager()
.AddDefaultTokenProviders()
.AddClaimsPrincipalFactory<ClaimsPrincipalFactory<IdentityUser>>(); // role claims
services.ConfigureOqtaneIdentityOptions(configuration);
services.AddCascadingAuthenticationState();
services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();
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(); LoadAssemblies();
LoadSatelliteAssemblies(installedCultures); LoadSatelliteAssemblies();
services.AddOqtaneServices(); services.AddOqtaneServices();
return services; return services;
@@ -53,7 +175,7 @@ namespace Microsoft.Extensions.DependencyInjection
return new OqtaneSiteOptionsBuilder(services); return new OqtaneSiteOptionsBuilder(services);
} }
internal static IServiceCollection AddOqtaneSingletonServices(this IServiceCollection services) public static IServiceCollection AddOqtaneSingletonServices(this IServiceCollection services)
{ {
services.AddSingleton<IInstallationManager, InstallationManager>(); services.AddSingleton<IInstallationManager, InstallationManager>();
services.AddSingleton<ISyncManager, SyncManager>(); services.AddSingleton<ISyncManager, SyncManager>();
@@ -66,7 +188,7 @@ namespace Microsoft.Extensions.DependencyInjection
return services; return services;
} }
internal static IServiceCollection AddOqtaneServerScopedServices(this IServiceCollection services) public static IServiceCollection AddOqtaneServerScopedServices(this IServiceCollection services)
{ {
services.AddScoped<Oqtane.Shared.SiteState>(); services.AddScoped<Oqtane.Shared.SiteState>();
services.AddScoped<IInstallationService, InstallationService>(); services.AddScoped<IInstallationService, InstallationService>();
@@ -112,7 +234,7 @@ namespace Microsoft.Extensions.DependencyInjection
return services; return services;
} }
internal static IServiceCollection AddOqtaneTransientServices(this IServiceCollection services) public static IServiceCollection AddOqtaneTransientServices(this IServiceCollection services)
{ {
// services // services
services.AddTransient<ISiteService, ServerSiteService>(); services.AddTransient<ISiteService, ServerSiteService>();
@@ -242,7 +364,7 @@ namespace Microsoft.Extensions.DependencyInjection
return services; return services;
} }
internal static IServiceCollection AddHttpClients(this IServiceCollection services) public static IServiceCollection AddHttpClients(this IServiceCollection services)
{ {
if (!services.Any(x => x.ServiceType == typeof(HttpClient))) if (!services.Any(x => x.ServiceType == typeof(HttpClient)))
{ {
@@ -285,9 +407,9 @@ namespace Microsoft.Extensions.DependencyInjection
return services; 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 => 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; AssemblyLoadContext.Default.Resolving += ResolveDependencies;
var installedCultures = LocalizationManager.GetSatelliteAssemblyCultures();
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories)) foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))
{ {
var code = Path.GetFileName(Path.GetDirectoryName(file)); var code = Path.GetFileName(Path.GetDirectoryName(file));

View File

@@ -31,6 +31,10 @@ namespace Oqtane.Extensions
builder.AddSiteNamedOptions<CookieAuthenticationOptions>(Constants.AuthenticationScheme, (options, alias, sitesettings) => builder.AddSiteNamedOptions<CookieAuthenticationOptions>(Constants.AuthenticationScheme, (options, alias, sitesettings) =>
{ {
options.Cookie.Name = sitesettings.GetValue("LoginOptions:CookieName", ".AspNetCore.Identity.Application"); 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", ""); string cookieExpStr = sitesettings.GetValue("LoginOptions:CookieExpiration", "");
if (!string.IsNullOrEmpty(cookieExpStr) && TimeSpan.TryParse(cookieExpStr, out TimeSpan cookieExpTS)) if (!string.IsNullOrEmpty(cookieExpStr) && TimeSpan.TryParse(cookieExpStr, out TimeSpan cookieExpTS))
{ {
@@ -61,6 +65,7 @@ namespace Oqtane.Extensions
options.ClientId = sitesettings.GetValue("ExternalLogin:ClientId", ""); options.ClientId = sitesettings.GetValue("ExternalLogin:ClientId", "");
options.ClientSecret = sitesettings.GetValue("ExternalLogin:ClientSecret", ""); options.ClientSecret = sitesettings.GetValue("ExternalLogin:ClientSecret", "");
options.ResponseType = sitesettings.GetValue("ExternalLogin:AuthResponseType", "code"); // default is authorization code flow 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.UsePkce = bool.Parse(sitesettings.GetValue("ExternalLogin:PKCE", "false"));
options.SaveTokens = bool.Parse(sitesettings.GetValue("ExternalLogin:SaveTokens", "false")); options.SaveTokens = bool.Parse(sitesettings.GetValue("ExternalLogin:SaveTokens", "false"));
if (!string.IsNullOrEmpty(sitesettings.GetValue("ExternalLogin:RoleClaimType", ""))) if (!string.IsNullOrEmpty(sitesettings.GetValue("ExternalLogin:RoleClaimType", "")))
@@ -476,8 +481,26 @@ namespace Oqtane.Extensions
else else
{ {
var logins = await _identityUserManager.GetLoginsAsync(identityuser); var logins = await _identityUserManager.GetLoginsAsync(identityuser);
var login = logins.FirstOrDefault(item => item.LoginProvider == (providerType + ":" + alias.SiteId.ToString())); // check if any logins exist for this user and provider type for any site
if (login == null) 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<INotificationRepository>();
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"))) if (bool.Parse(httpContext.GetSiteSettings().GetValue("ExternalLogin:VerifyUsers", "true")))
{ {
@@ -496,28 +519,11 @@ namespace Oqtane.Extensions
} }
else else
{ {
// external login using existing user account - link automatically // provider keys do not match
user = _users.GetUser(identityuser.UserName); identity.Label = ExternalLoginStatus.ProviderKeyMismatch;
user.SiteId = alias.SiteId; _logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Key Does Not Match For User {Username}. Login Denied.", identityuser.UserName);
var _notifications = httpContext.RequestServices.GetRequiredService<INotificationRepository>();
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
{
// 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) if (user != null)
{ {
// manage roles // manage roles
var _roles = httpContext.RequestServices.GetRequiredService<IRoleRepository>();
var _userRoles = httpContext.RequestServices.GetRequiredService<IUserRoleRepository>(); var _userRoles = httpContext.RequestServices.GetRequiredService<IUserRoleRepository>();
var userRoles = _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList(); 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", ""))) if (!string.IsNullOrEmpty(httpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimType", "")))
{ {
// external roles // external roles
if (claimsPrincipal.Claims.Any(item => item.Type == httpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimType", ""))) if (claimsPrincipal.Claims.Any(item => item.Type == httpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimType", "")))
{ {
var _roles = httpContext.RequestServices.GetRequiredService<IRoleRepository>();
var allowhostrole = bool.Parse(httpContext.GetSiteSettings().GetValue("ExternalLogin:AllowHostRole", "false")); var allowhostrole = bool.Parse(httpContext.GetSiteSettings().GetValue("ExternalLogin:AllowHostRole", "false"));
var roles = _roles.GetRoles(user.SiteId, allowhostrole).ToList(); var roles = _roles.GetRoles(user.SiteId, allowhostrole).ToList();

View File

@@ -93,12 +93,21 @@ namespace Oqtane.Infrastructure
{ {
id = node.InnerText; id = node.InnerText;
} }
// get framework dependency // get minimum framework version using packageType
node = doc.SelectSingleNode("/package/metadata/dependencies/dependency[@id='Oqtane.Framework']"); node = doc.SelectSingleNode("/package/metadata/packageTypes/packageType[@name='Oqtane.Framework']");
if (node != null) if (node != null)
{ {
frameworkversion = node.Attributes["version"].Value; 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(); reader.Close();
break; break;
} }

View File

@@ -45,6 +45,12 @@ namespace Oqtane.Infrastructure
} }
public string[] GetInstalledCultures() public string[] GetInstalledCultures()
{
return GetSatelliteAssemblyCultures();
}
// method is static as it is called during startup
public static string[] GetSatelliteAssemblyCultures()
{ {
var cultures = new List<string>(); var cultures = new List<string>();
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories)) foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))

View File

@@ -1,260 +1,43 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Oqtane.Extensions; using Oqtane.Extensions;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Repository;
using Oqtane.Security;
using Oqtane.Shared; 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.AspNetCore.Cors.Infrastructure;
using Microsoft.Net.Http.Headers;
namespace Oqtane namespace Oqtane
{ {
public class Startup public class Startup
{ {
private readonly bool _useSwagger; private readonly IConfigurationRoot _configuration;
private readonly IWebHostEnvironment _env; private readonly IWebHostEnvironment _environment;
private readonly string[] _installedCultures;
private string _configureServicesErrors;
public IConfigurationRoot Configuration { get; } public Startup(IWebHostEnvironment environment)
public Startup(IWebHostEnvironment env, ILocalizationManager localizationManager)
{ {
AppDomain.CurrentDomain.SetData(Constants.DataDirectory, Path.Combine(environment.ContentRootPath, "Data"));
var builder = new ConfigurationBuilder() var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath) .SetBasePath(environment.ContentRootPath)
.AddJsonFile("appsettings.json", false, true) .AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true) .AddJsonFile($"appsettings.{environment.EnvironmentName}.json", true, true)
.AddEnvironmentVariables(); .AddEnvironmentVariables();
Configuration = builder.Build(); _configuration = builder.Build();
_environment = environment;
_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;
} }
// 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) public void ConfigureServices(IServiceCollection services)
{ {
// process forwarded headers on load balancers and proxy servers services.AddOqtane(_configuration, _environment);
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
// register localization services
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddOptions<List<Models.Database>>().Bind(Configuration.GetSection(SettingKeys.AvailableDatabasesSection));
// register scoped core services
services.AddScoped<IAuthorizationHandler, PermissionHandler>()
.AddOqtaneServerScopedServices();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// 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<IdentityUser>(options => { })
.AddEntityFrameworkStores<TenantDBContext>()
.AddSignInManager()
.AddDefaultTokenProviders()
.AddClaimsPrincipalFactory<ClaimsPrincipalFactory<IdentityUser>>(); // role claims
services.ConfigureOqtaneIdentityOptions(Configuration);
services.AddCascadingAuthenticationState();
services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();
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);
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IConfigurationRoot configuration, IWebHostEnvironment environment, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider, ISyncManager sync)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISyncManager sync, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider, ILogger<Startup> logger)
{ {
if (!string.IsNullOrEmpty(_configureServicesErrors)) app.UseOqtane(configuration, environment, corsService, corsPolicyProvider, sync);
{
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<App>()
.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);
} }
} }
} }

View File

@@ -1,5 +1,5 @@
{ {
"RenderMode": "Interactive", "RenderMode": "Static",
"Runtime": "Server", "Runtime": "Server",
"Database": { "Database": {
"DefaultDBType": "" "DefaultDBType": ""

View File

@@ -1,5 +1,5 @@
{ {
"RenderMode": "Interactive", "RenderMode": "Static",
"Runtime": "Server", "Runtime": "Server",
"Database": { "Database": {
"DefaultDBType": "" "DefaultDBType": ""

View File

@@ -35,7 +35,7 @@
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" } new Stylesheet("_content/[Owner].Module.[Module]/Module.css")
}; };
private ElementReference form; private ElementReference form;

View File

@@ -42,8 +42,8 @@ else
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }, new Stylesheet("_content/[Owner].Module.[Module]/Module.css"),
new Resource { ResourceType = ResourceType.Script, Url = ModulePath() + "Module.js" } new Script("_content/[Owner].Module.[Module]/Module.js")
}; };
List<[Module]> _[Module]s; List<[Module]> _[Module]s;

View File

@@ -8,7 +8,10 @@ namespace [Owner].Module.[Module].Startup
{ {
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddScoped<I[Module]Service, [Module]Service>(); if (!services.Any(s => s.ServiceType == typeof(I[Module]Service)))
{
services.AddScoped<I[Module]Service, [Module]Service>();
}
} }
} }
} }

View File

@@ -15,9 +15,10 @@
<tags>oqtane module</tags> <tags>oqtane module</tags>
<releaseNotes></releaseNotes> <releaseNotes></releaseNotes>
<summary></summary> <summary></summary>
<dependencies> <packageTypes>
<dependency id="Oqtane.Framework" version="[FrameworkVersion]" /> <packageType name="Dependency" />
</dependencies> <packageType name="Oqtane.Framework" version="[FrameworkVersion]" />
</packageTypes>
</metadata> </metadata>
<files> <files>
<file src="..\Client\bin\Release\$targetframework$\$ProjectName$.Client.Oqtane.dll" target="lib\$targetframework$" /> <file src="..\Client\bin\Release\$targetframework$\$ProjectName$.Client.Oqtane.dll" target="lib\$targetframework$" />
@@ -26,7 +27,12 @@
<file src="..\Server\bin\Release\$targetframework$\$ProjectName$.Server.Oqtane.pdb" target="lib\$targetframework$" /> <file src="..\Server\bin\Release\$targetframework$\$ProjectName$.Server.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Shared\bin\Release\$targetframework$\$ProjectName$.Shared.Oqtane.dll" target="lib\$targetframework$" /> <file src="..\Shared\bin\Release\$targetframework$\$ProjectName$.Shared.Oqtane.dll" target="lib\$targetframework$" />
<file src="..\Shared\bin\Release\$targetframework$\$ProjectName$.Shared.Oqtane.pdb" target="lib\$targetframework$" /> <file src="..\Shared\bin\Release\$targetframework$\$ProjectName$.Shared.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Server\wwwroot\**\*.*" target="wwwroot" /> <file src="..\Server\obj\Release\net9.0\staticwebassets\msbuild.$ProjectName$.Microsoft.AspNetCore.StaticWebAssetEndpoints.props" target="build\Microsoft.AspNetCore.StaticWebAssetEndpoints.props" />
<file src="..\Server\obj\Release\net9.0\staticwebassets\msbuild.$ProjectName$.Microsoft.AspNetCore.StaticWebAssets.props" target="build\Microsoft.AspNetCore.StaticWebAssets.props" />
<file src="..\Server\obj\Release\net9.0\staticwebassets\msbuild.build.$ProjectName$.props" target="build\$ProjectName$.props" />
<file src="..\Server\obj\Release\net9.0\staticwebassets\msbuild.buildMultiTargeting.$ProjectName$.props" target="buildMultiTargeting\$ProjectName$.props" />
<file src="..\Server\obj\Release\net9.0\staticwebassets\msbuild.buildTransitive.$ProjectName$.props" target="buildTransitive\$ProjectName$.props" />
<file src="..\Server\wwwroot\**\*.*" target="staticwebassets" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

View File

@@ -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 "..\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.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 "..\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 XCOPY "..\Server\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\_content\%ProjectName%\" /Y /S /I

View File

@@ -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 "../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.dll" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -f "../Shared/bin/Debug/$TargetFramework/$ProjectName$.Shared.Oqtane.pdb" "../../[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/" cp -rf "../Server/wwwroot/"* "../../[RootFolder]/Oqtane.Server/wwwroot/_content/%ProjectName%/"

View File

@@ -3,5 +3,6 @@ set TargetFramework=%1
set ProjectName=%2 set ProjectName=%2
del "*.nupkg" del "*.nupkg"
"..\..\oqtane.framework\oqtane.package\FixProps.exe"
"..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName% "..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName%
XCOPY "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\" /Y XCOPY "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\" /Y

View File

@@ -1,5 +1,7 @@
TargetFramework=$1 TargetFramework=$1
ProjectName=$2 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% "..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName%
cp -f "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\" cp -f "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\"

View File

@@ -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

View File

@@ -16,11 +16,10 @@ namespace [Owner].Theme.[Theme]
ContainerSettingsType = "[Owner].Theme.[Theme].ContainerSettings, [Owner].Theme.[Theme].Client.Oqtane", ContainerSettingsType = "[Owner].Theme.[Theme].ContainerSettings, [Owner].Theme.[Theme].Client.Oqtane",
Resources = new List<Resource>() Resources = new List<Resource>()
{ {
// obtained from https://cdnjs.com/libraries // obtained from https://cdnjs.com/libraries
new StyleSheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"), new Stylesheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"),
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" }, new Stylesheet("_content/[Owner].Theme.[Theme]/Theme.css"),
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous") new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
} }
}; };

View File

@@ -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;
}
}

View File

@@ -15,14 +15,20 @@
<tags>oqtane theme</tags> <tags>oqtane theme</tags>
<releaseNotes></releaseNotes> <releaseNotes></releaseNotes>
<summary></summary> <summary></summary>
<dependencies> <packageTypes>
<dependency id="Oqtane.Framework" version="[FrameworkVersion]" /> <packageType name="Dependency" />
</dependencies> <packageType name="Oqtane.Framework" version="[FrameworkVersion]" />
</packageTypes>
</metadata> </metadata>
<files> <files>
<file src="..\Client\bin\Release\$targetframework$\$projectname$.Client.Oqtane.dll" target="lib\$targetframework$" /> <file src="..\Client\bin\Release\$targetframework$\$projectname$.Client.Oqtane.dll" target="lib\$targetframework$" />
<file src="..\Client\bin\Release\$targetframework$\$projectname$.Client.Oqtane.pdb" target="lib\$targetframework$" /> <file src="..\Client\bin\Release\$targetframework$\$projectname$.Client.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Client\wwwroot\**\*.*" target="wwwroot" /> <file src="..\Client\obj\Release\net9.0\staticwebassets\msbuild.$ProjectName$.Microsoft.AspNetCore.StaticWebAssetEndpoints.props" target="build\Microsoft.AspNetCore.StaticWebAssetEndpoints.props" />
<file src="..\Client\obj\Release\net9.0\staticwebassets\msbuild.$ProjectName$.Microsoft.AspNetCore.StaticWebAssets.props" target="build\Microsoft.AspNetCore.StaticWebAssets.props" />
<file src="..\Client\obj\Release\net9.0\staticwebassets\msbuild.build.$ProjectName$.props" target="build\$ProjectName$.props" />
<file src="..\Client\obj\Release\net9.0\staticwebassets\msbuild.buildMultiTargeting.$ProjectName$.props" target="buildMultiTargeting\$ProjectName$.props" />
<file src="..\Client\obj\Release\net9.0\staticwebassets\msbuild.buildTransitive.$ProjectName$.props" target="buildTransitive\$ProjectName$.props" />
<file src="..\Client\wwwroot\**\*.*" target="staticwebassets" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

View File

@@ -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.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\bin\Debug\%TargetFramework%\%ProjectName%.Client.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Client\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\" /Y /S /I XCOPY "..\Client\wwwroot\*" "..\..\oqtane.framework\Oqtane.Server\wwwroot\_content\%ProjectName%\" /Y /S /I

View File

@@ -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.dll" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -f "../Client/bin/Debug/$TargetFramework/$ProjectName$.Client.Oqtane.pdb" "../../[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/" cp -rf "../Client/wwwroot/"* "../../[RootFolder]/Oqtane.Server/wwwroot/_content/%ProjectName%/"

Some files were not shown because too many files have changed in this diff Show More