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.UseNotFoundResponse(); 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 .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) { // set the route to be rendered on NavigationManager.NotFound() const string notFoundRoute = "/404"; app.UseStatusCodePagesWithReExecute(notFoundRoute, createScopeForStatusCodePages: true); // middleware to determine if status code pages should be skipped app.Use(async (context, next) => { var path = context.Request.Path.Value ?? string.Empty; if (ShouldSkipStatusCodeReExecution(path)) { var statusCodePagesFeature = context.Features.Get(); if (statusCodePagesFeature != null) { statusCodePagesFeature.Enabled = false; } } await next(); }); // middleware to rewrite the path for 404 status code responses on sites using subfolders app.Use(async (context, next) => { var statusCodeReExecuteFeature = context.Features.Get(); if (statusCodeReExecuteFeature != null && context.Response.StatusCode == (int)HttpStatusCode.NotFound && string.Equals(context.Request.Path.Value, notFoundRoute, 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 { await next(); } finally { context.Request.Path = originalPath; } return; } } await next(); }); return app; } static bool ShouldSkipStatusCodeReExecution(string path) { // skip requests for framework resources, reserved routes, and static files return path.StartsWith("/_framework/", StringComparison.OrdinalIgnoreCase) || path.StartsWith("/_content/", StringComparison.OrdinalIgnoreCase) || Constants.ReservedRoutes.Any(item => path.Contains("/" + item + "/")) || HasStaticFileExtension(path); } static bool HasStaticFileExtension(string path) { return !string.IsNullOrEmpty(Path.GetExtension(path)) && Path.GetExtension(path).Length != 1; } } }