optimize startup

This commit is contained in:
sbwalker
2025-08-26 15:27:35 -04:00
parent 6a0c47f7b1
commit ec06c1cdf1
4 changed files with 241 additions and 238 deletions

View File

@ -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<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();
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<IInstallationManager, InstallationManager>();
services.AddSingleton<ISyncManager, SyncManager>();
@ -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<Oqtane.Shared.SiteState>();
services.AddScoped<IInstallationService, InstallationService>();
@ -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<ISiteService, ServerSiteService>();
@ -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));