using System; using System.IO; using System.Linq; using System.Net; using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.Diagnostics; 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(); app.UseNotFoundResponse(); // 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 .GetOqtaneAssemblies() .SelectMany(x => x.GetInstances()); foreach (var startup in startUps) { startup.Configure(app, env); } return app; } public static IApplicationBuilder UseOqtaneLocalization(this IApplicationBuilder app) { var localizationManager = app.ApplicationServices.GetService(); var defaultCulture = localizationManager.GetDefaultCulture(); var supportedCultures = localizationManager.GetSupportedCultures(); app.UseRequestLocalization(options => { options.SetDefaultCulture(defaultCulture) .AddSupportedCultures(supportedCultures) .AddSupportedUICultures(supportedCultures); foreach(var culture in options.SupportedCultures) { if (culture.TextInfo.IsRightToLeft) { RightToLeftCulture.ResolveFormat(culture); } } }); return app; } public static IApplicationBuilder UseTenantResolution(this IApplicationBuilder builder) => builder.UseMiddleware(); public static IApplicationBuilder UseJwtAuthorization(this IApplicationBuilder builder) => builder.UseMiddleware(); public static IApplicationBuilder UseExceptionMiddleWare(this IApplicationBuilder builder) => builder.UseMiddleware(); public static IApplicationBuilder UseNotFoundResponse(this IApplicationBuilder app) { const string notFoundRoute = "/404"; app.UseStatusCodePagesWithReExecute(notFoundRoute, createScopeForStatusCodePages: true); app.Use(async (context, next) => { var path = context.Request.Path.Value ?? string.Empty; if (string.IsNullOrEmpty(path) || ShouldSkipStatusCodeReExecution(path)) { var feature = context.Features.Get(); feature?.Enabled = false; } await next(); }); app.Use(async (context, next) => { var feature = context.Features.Get(); var handled = false; if (feature != null && context.Response.StatusCode == (int)HttpStatusCode.NotFound && notFoundRoute.Equals(context.Request.Path.Value, StringComparison.OrdinalIgnoreCase)) { var alias = context.GetAlias(); if (!string.IsNullOrEmpty(alias?.Path)) { var originalPath = context.Request.Path; context.Request.Path = new PathString($"/{alias.Path}{notFoundRoute}"); try { handled = true; await next(); } finally { context.Request.Path = originalPath; } } } if (!handled) { await next(); } }); return app; } static bool ShouldSkipStatusCodeReExecution(string path) { return Constants.ReservedRoutes.Any(item => path.Contains("/" + item + "/")) || HasStaticFileExtension(path); } static bool HasStaticFileExtension(string path) { return !string.IsNullOrEmpty(Path.GetExtension(path)); } } }