using System; using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Serilog.Context; using SixLabors.ImageSharp.Web.DependencyInjection; using StackExchange.Profiling; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Logging.Serilog.Enrichers; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.ApplicationBuilder; using Umbraco.Cms.Web.Common.Middleware; using Umbraco.Cms.Web.Common.Plugins; namespace Umbraco.Extensions { /// /// extensions for Umbraco /// public static class ApplicationBuilderExtensions { /// /// Configures and use services required for using Umbraco /// public static IUmbracoApplicationBuilder UseUmbraco(this IApplicationBuilder app) { IOptions startupOptions = app.ApplicationServices.GetRequiredService>(); app.RunPrePipeline(startupOptions.Value); // TODO: Should we do some checks like this to verify that the corresponding "Add" methods have been called for the // corresponding "Use" methods? // https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Mvc/Mvc.Core/src/Builder/MvcApplicationBuilderExtensions.cs#L132 if (app == null) { throw new ArgumentNullException(nameof(app)); } app.UseUmbracoCore(); app.UseUmbracoRequestLogging(); // We need to add this before UseRouting so that the UmbracoContext and other middlewares are executed // before endpoint routing middleware. app.UseUmbracoRouting(); app.UseStatusCodePages(); // Important we handle image manipulations before the static files, otherwise the querystring is just ignored. // TODO: Since we are dependent on these we need to register them but what happens when we call this multiple times since we are dependent on this for UseUmbracoBackOffice too? app.UseImageSharp(); app.UseStaticFiles(); app.UseUmbracoPlugins(); // UseRouting adds endpoint routing middleware, this means that middlewares registered after this one // will execute after endpoint routing. The ordering of everything is quite important here, see // https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-5.0 // where we need to have UseAuthentication and UseAuthorization proceeding this call but before // endpoints are defined. app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); // This must come after auth because the culture is based on the auth'd user app.UseRequestLocalization(); // Must be called after UseRouting and before UseEndpoints app.UseSession(); // DO NOT PUT ANY UseEndpoints declarations here!! Those must all come very last in the pipeline, // endpoints are terminating middleware. All of our endpoints are declared in ext of IUmbracoApplicationBuilder return ActivatorUtilities.CreateInstance( app.ApplicationServices, new object[] { app }); } private static void RunPrePipeline(this IApplicationBuilder app, UmbracoPipelineOptions startupOptions) { foreach (IUmbracoPipelineFilter filter in startupOptions.PipelineFilters) { filter.OnPrePipeline(app); } } /// /// Returns true if Umbraco is greater than /// public static bool UmbracoCanBoot(this IApplicationBuilder app) => app.ApplicationServices.GetRequiredService().UmbracoCanBoot(); /// /// Enables core Umbraco functionality /// public static IApplicationBuilder UseUmbracoCore(this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException(nameof(app)); } if (!app.UmbracoCanBoot()) { return app; } // Register our global threadabort enricher for logging ThreadAbortExceptionEnricher threadAbortEnricher = app.ApplicationServices.GetRequiredService(); LogContext.Push(threadAbortEnricher); // NOTE: We are not in a using clause because we are not removing it, it is on the global context return app; } /// /// Enables middlewares required to run Umbraco /// /// /// Must occur before UseRouting /// public static IApplicationBuilder UseUmbracoRouting(this IApplicationBuilder app) { // TODO: This method could be internal or part of another call - this is a required system so should't be 'opt-in' if (app == null) { throw new ArgumentNullException(nameof(app)); } if (!app.UmbracoCanBoot()) { app.UseStaticFiles(); // We need static files to show the nice error page. app.UseMiddleware(); } else { app.UseMiddleware(); app.UseMiddleware(); app.UseMiddleware(); } return app; } /// /// Adds request based serilog enrichers to the LogContext for each request /// public static IApplicationBuilder UseUmbracoRequestLogging(this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException(nameof(app)); } if (!app.UmbracoCanBoot()) return app; app.UseMiddleware(); return app; } public static IApplicationBuilder UseUmbracoPlugins(this IApplicationBuilder app) { var hostingEnvironment = app.ApplicationServices.GetRequiredService(); var umbracoPluginSettings = app.ApplicationServices.GetRequiredService>(); var pluginFolder = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins); // Ensure the plugin folder exists Directory.CreateDirectory(pluginFolder); var fileProvider = new UmbracoPluginPhysicalFileProvider( pluginFolder, umbracoPluginSettings); app.UseStaticFiles(new StaticFileOptions { FileProvider = fileProvider, RequestPath = Constants.SystemDirectories.AppPlugins }); return app; } } }