diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml index 26d816ebc5..c40fc14ca7 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml @@ -1,24 +1,19 @@ -@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.Extensions.Options -@using Umbraco.Cms.Core.Configuration @using Umbraco.Cms.Core.Configuration.Models @using Umbraco.Cms.Core.Hosting -@using Umbraco.Cms.Core.WebAssets +@using Umbraco.Cms.Web.Common.Hosting @using Umbraco.Extensions -@using UrlHelperExtensions = Umbraco.Extensions.UrlHelperExtensions -@inject IUmbracoVersion UmbracoVersion @inject IHostingEnvironment HostingEnvironment @inject IOptions GlobalSettings -@inject IRuntimeMinifier RuntimeMinifier -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@inject IStaticFilePathGenerator StaticFilePathGenerator @{ var backOfficePath = GlobalSettings.Value.GetBackOfficePath(HostingEnvironment); - var cacheBust = "?umb__rnd=" + UrlHelperExtensions.GetCacheBustHash(HostingEnvironment, UmbracoVersion, RuntimeMinifier); + var backofficeAssetsPath = StaticFilePathGenerator.BackofficeAssetsPath; } @functions { - private static string ImportMapValue(string alias, string path, string cacheBust) => $"\"{alias}\": \"{path}{cacheBust}\""; + private static string ImportMapValue(string alias, string path) => $"\"{alias}\": \"{path}\""; } @@ -26,94 +21,95 @@ - + Umbraco - - + + - + diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoInstall/Index.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoInstall/Index.cshtml index 26d816ebc5..c40fc14ca7 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoInstall/Index.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoInstall/Index.cshtml @@ -1,24 +1,19 @@ -@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.Extensions.Options -@using Umbraco.Cms.Core.Configuration @using Umbraco.Cms.Core.Configuration.Models @using Umbraco.Cms.Core.Hosting -@using Umbraco.Cms.Core.WebAssets +@using Umbraco.Cms.Web.Common.Hosting @using Umbraco.Extensions -@using UrlHelperExtensions = Umbraco.Extensions.UrlHelperExtensions -@inject IUmbracoVersion UmbracoVersion @inject IHostingEnvironment HostingEnvironment @inject IOptions GlobalSettings -@inject IRuntimeMinifier RuntimeMinifier -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@inject IStaticFilePathGenerator StaticFilePathGenerator @{ var backOfficePath = GlobalSettings.Value.GetBackOfficePath(HostingEnvironment); - var cacheBust = "?umb__rnd=" + UrlHelperExtensions.GetCacheBustHash(HostingEnvironment, UmbracoVersion, RuntimeMinifier); + var backofficeAssetsPath = StaticFilePathGenerator.BackofficeAssetsPath; } @functions { - private static string ImportMapValue(string alias, string path, string cacheBust) => $"\"{alias}\": \"{path}{cacheBust}\""; + private static string ImportMapValue(string alias, string path) => $"\"{alias}\": \"{path}\""; } @@ -26,94 +21,95 @@ - + Umbraco - - + + - + diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index 994493e761..4021687826 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -20,6 +20,7 @@ using Umbraco.Cms.Web.BackOffice.Security; using Umbraco.Cms.Web.BackOffice.Services; using Umbraco.Cms.Web.BackOffice.SignalR; using Umbraco.Cms.Web.BackOffice.Trees; +using Umbraco.Cms.Web.Common.Hosting; namespace Umbraco.Extensions; @@ -81,6 +82,7 @@ public static partial class UmbracoBuilderExtensions public static IUmbracoBuilder AddBackOfficeCore(this IUmbracoBuilder builder) { + builder.Services.AddUnique(); builder.Services.AddSingleton(); builder.Services.ConfigureOptions(); builder.Services.AddSingleton(); diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs index 93fb6f3143..23c62b010e 100644 --- a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs +++ b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs @@ -73,6 +73,8 @@ public class UmbracoApplicationBuilder : IUmbracoApplicationBuilder, IUmbracoEnd AppBuilder.UseUmbracoMediaFileProvider(); + AppBuilder.UseUmbracoBackOfficeRewrites(); + AppBuilder.UseStaticFiles(); AppBuilder.UseUmbracoPluginsStaticFiles(); diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index 08a22cfda3..155d5785f3 100644 --- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -1,6 +1,7 @@ using Dazinator.Extensions.FileProviders.PrependBasePath; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Rewrite; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; @@ -14,6 +15,7 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Logging.Serilog.Enrichers; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.ApplicationBuilder; +using Umbraco.Cms.Web.Common.Hosting; using Umbraco.Cms.Web.Common.Media; using Umbraco.Cms.Web.Common.Middleware; using Umbraco.Cms.Web.Common.Plugins; @@ -166,4 +168,20 @@ public static class ApplicationBuilderExtensions return app; } + + /// + /// Configure a virtual path with IApplicationBuilder.UseRewriter for backoffice assets to allow cache-busting using the url + /// /umbraco/backoffice/!cache-busting-id!/assets/index.js => /umbraco/backoffice/assets/index.js. + /// + public static IApplicationBuilder UseUmbracoBackOfficeRewrites(this IApplicationBuilder builder) + { + IStaticFilePathGenerator staticFilePathGenerator = builder.ApplicationServices.GetRequiredService(); + var backofficeAssetsPath = staticFilePathGenerator.BackofficeAssetsPath.TrimStart("/").EnsureEndsWith("/"); + + builder.UseRewriter(new RewriteOptions() + // The destination needs to be hardcoded to "/umbraco/backoffice" because this is where they are located in the Umbraco.Cms.StaticAssets RCL + .AddRewrite(@"^" + backofficeAssetsPath + "(.+)", "/umbraco/backoffice/$1", true)); + + return builder; + } } diff --git a/src/Umbraco.Web.Common/Hosting/IStaticFileHostGenerator.cs b/src/Umbraco.Web.Common/Hosting/IStaticFileHostGenerator.cs new file mode 100644 index 0000000000..3cd4b8d3b7 --- /dev/null +++ b/src/Umbraco.Web.Common/Hosting/IStaticFileHostGenerator.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Cms.Web.Common.Hosting; + +/// +/// Umbraco-specific settings for static files from the Umbraco.Cms.StaticAssets RCL. +/// +public interface IStaticFilePathGenerator +{ + /// + /// The virtual path of the static assets used in the Backoffice. + /// + string BackofficeAssetsPath { get; } +} diff --git a/src/Umbraco.Web.Common/Hosting/UmbracoStaticFilePathGenerator.cs b/src/Umbraco.Web.Common/Hosting/UmbracoStaticFilePathGenerator.cs new file mode 100644 index 0000000000..564d7d39b3 --- /dev/null +++ b/src/Umbraco.Web.Common/Hosting/UmbracoStaticFilePathGenerator.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.WebAssets; +using Umbraco.Cms.Web.Common.Hosting; +using Umbraco.Extensions; + +public class UmbracoStaticFilePathGenerator : IStaticFilePathGenerator +{ + private string? _backofficeAssetsPath; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IUmbracoVersion _umbracoVersion; + private readonly IRuntimeMinifier _runtimeMinifier; + private readonly IOptions _globalSettings; + + public UmbracoStaticFilePathGenerator(IHostingEnvironment hostingEnvironment, IUmbracoVersion umbracoVersion, IRuntimeMinifier runtimeMinifier, IOptions globalSettings) + { + _hostingEnvironment = hostingEnvironment; + _umbracoVersion = umbracoVersion; + _runtimeMinifier = runtimeMinifier; + _globalSettings = globalSettings; + } + + /// + /// Get the virtual path for the Backoffice assets coming from the Umbraco.Cms.StaticAssets RCL. + /// The path will contain a generated SHA1 hash that is based on a number of parameters including the UmbracoVersion and runtime minifier. + /// + /// /umbraco/backoffice/addf120b430021c36c232c99ef8d926aea2acd6b + /// + public string BackofficeAssetsPath + { + get + { + if (_backofficeAssetsPath is null) + { + var umbracoHash = UrlHelperExtensions.GetCacheBustHash(_hostingEnvironment, _umbracoVersion, _runtimeMinifier); + var backOfficePath = _globalSettings.Value.GetBackOfficePath(_hostingEnvironment); + _backofficeAssetsPath = backOfficePath.EnsureEndsWith('/') + "backoffice/" + umbracoHash; + } + + return _backofficeAssetsPath; + } + } +}