diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
index 97fb91b0ecbf..7e3e1a27006d 100644
--- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
@@ -31,21 +31,19 @@ public class GlobalSettings
internal const bool StaticSanitizeTinyMce = false;
///
- /// Gets or sets a value for the reserved URLs.
- /// It must end with a comma
+ /// Gets or sets a value for the reserved URLs (must end with a comma).
///
[DefaultValue(StaticReservedUrls)]
public string ReservedUrls { get; set; } = StaticReservedUrls;
///
- /// Gets or sets a value for the reserved paths.
- /// It must end with a comma
+ /// Gets or sets a value for the reserved paths (must end with a comma).
///
[DefaultValue(StaticReservedPaths)]
public string ReservedPaths { get; set; } = StaticReservedPaths;
///
- /// Gets or sets a value for the timeout
+ /// Gets or sets a value for the back-office login timeout.
///
[DefaultValue(StaticTimeOut)]
public TimeSpan TimeOut { get; set; } = TimeSpan.Parse(StaticTimeOut);
@@ -104,11 +102,19 @@ public class GlobalSettings
public string UmbracoScriptsPath { get; set; } = StaticUmbracoScriptsPath;
///
- /// Gets or sets a value for the Umbraco media path.
+ /// Gets or sets a value for the Umbraco media request path.
///
[DefaultValue(StaticUmbracoMediaPath)]
public string UmbracoMediaPath { get; set; } = StaticUmbracoMediaPath;
+ ///
+ /// Gets or sets a value for the physical Umbraco media root path (falls back to when empty).
+ ///
+ ///
+ /// If the value is a virtual path, it's resolved relative to the webroot.
+ ///
+ public string UmbracoMediaPhysicalRootPath { get; set; }
+
///
/// Gets or sets a value indicating whether to install the database when it is missing.
///
@@ -131,6 +137,9 @@ public class GlobalSettings
///
public string MainDomLock { get; set; } = string.Empty;
+ ///
+ /// Gets or sets the telemetry ID.
+ ///
public string Id { get; set; } = string.Empty;
///
@@ -164,19 +173,19 @@ public class GlobalSettings
///
public bool IsPickupDirectoryLocationConfigured => !string.IsNullOrWhiteSpace(Smtp?.PickupDirectoryLocation);
- /// Gets a value indicating whether TinyMCE scripting sanitization should be applied
+ ///
+ /// Gets a value indicating whether TinyMCE scripting sanitization should be applied.
///
[DefaultValue(StaticSanitizeTinyMce)]
public bool SanitizeTinyMce => StaticSanitizeTinyMce;
///
- /// An int value representing the time in milliseconds to lock the database for a write operation
+ /// Gets a value representing the time in milliseconds to lock the database for a write operation.
///
///
- /// The default value is 5000 milliseconds
+ /// The default value is 5000 milliseconds.
///
- /// The timeout in milliseconds.
[DefaultValue(StaticSqlWriteLockTimeOut)]
public TimeSpan SqlWriteLockTimeOut { get; } = TimeSpan.Parse(StaticSqlWriteLockTimeOut);
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Core/Constants-SystemDirectories.cs b/src/Umbraco.Core/Constants-SystemDirectories.cs
index 80b49781ecf9..bf34aab989c4 100644
--- a/src/Umbraco.Core/Constants-SystemDirectories.cs
+++ b/src/Umbraco.Core/Constants-SystemDirectories.cs
@@ -43,6 +43,8 @@ public static class SystemDirectories
public const string AppPlugins = "/App_Plugins";
public static string AppPluginIcons => "/Backoffice/Icons";
+ public const string CreatedPackages = "/created-packages";
+
public const string MvcViews = "~/Views";
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs
index 6ef87464e851..ce2e4f230404 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs
@@ -13,23 +13,25 @@ namespace Umbraco.Cms.Core.DependencyInjection
public static partial class UmbracoBuilderExtensions
{
- private static IUmbracoBuilder AddUmbracoOptions(this IUmbracoBuilder builder)
+ private static IUmbracoBuilder AddUmbracoOptions(this IUmbracoBuilder builder, Action> configure = null)
where TOptions : class
{
var umbracoOptionsAttribute = typeof(TOptions).GetCustomAttribute();
-
if (umbracoOptionsAttribute is null)
{
- throw new ArgumentException("typeof(TOptions) do not have the UmbracoOptionsAttribute");
+ throw new ArgumentException($"{typeof(TOptions)} do not have the UmbracoOptionsAttribute.");
}
-
- builder.Services.AddOptions()
- .Bind(builder.Config.GetSection(umbracoOptionsAttribute.ConfigurationKey),
- o => o.BindNonPublicProperties = umbracoOptionsAttribute.BindNonPublicProperties)
+ var optionsBuilder = builder.Services.AddOptions()
+ .Bind(
+ builder.Config.GetSection(umbracoOptionsAttribute.ConfigurationKey),
+ o => o.BindNonPublicProperties = umbracoOptionsAttribute.BindNonPublicProperties
+ )
.ValidateDataAnnotations();
- return builder;
+ configure?.Invoke(optionsBuilder);
+
+ return builder;
}
///
@@ -52,7 +54,13 @@ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder)
.AddUmbracoOptions()
.AddUmbracoOptions()
.AddUmbracoOptions()
- .AddUmbracoOptions()
+ .AddUmbracoOptions(optionsBuilder => optionsBuilder.PostConfigure(options =>
+ {
+ if (string.IsNullOrEmpty(options.UmbracoMediaPhysicalRootPath))
+ {
+ options.UmbracoMediaPhysicalRootPath = options.UmbracoMediaPath;
+ }
+ }))
.AddUmbracoOptions()
.AddUmbracoOptions()
.AddUmbracoOptions()
diff --git a/src/Umbraco.Core/IO/FileSystemExtensions.cs b/src/Umbraco.Core/IO/FileSystemExtensions.cs
index 23be195e4b16..c95d37e1c3c2 100644
--- a/src/Umbraco.Core/IO/FileSystemExtensions.cs
+++ b/src/Umbraco.Core/IO/FileSystemExtensions.cs
@@ -3,6 +3,7 @@
using System.Security.Cryptography;
using System.Text;
using System.Threading;
+using Microsoft.Extensions.FileProviders;
using Umbraco.Cms.Core.IO;
namespace Umbraco.Extensions
@@ -87,5 +88,24 @@ public static void CreateFolder(this IFileSystem fs, string folderPath)
}
fs.DeleteFile(tempFile);
}
+
+ ///
+ /// Creates a new from the file system.
+ ///
+ /// The file system.
+ /// When this method returns, contains an created from the file system.
+ ///
+ /// true if the was successfully created; otherwise, false.
+ ///
+ public static bool TryCreateFileProvider(this IFileSystem fileSystem, out IFileProvider fileProvider)
+ {
+ fileProvider = fileSystem switch
+ {
+ IFileProviderFactory fileProviderFactory => fileProviderFactory.Create(),
+ _ => null
+ };
+
+ return fileProvider != null;
+ }
}
}
diff --git a/src/Umbraco.Core/IO/IFileProviderFactory.cs b/src/Umbraco.Core/IO/IFileProviderFactory.cs
new file mode 100644
index 000000000000..742467ccc843
--- /dev/null
+++ b/src/Umbraco.Core/IO/IFileProviderFactory.cs
@@ -0,0 +1,18 @@
+using Microsoft.Extensions.FileProviders;
+
+namespace Umbraco.Cms.Core.IO
+{
+ ///
+ /// Factory for creating instances.
+ ///
+ public interface IFileProviderFactory
+ {
+ ///
+ /// Creates a new instance.
+ ///
+ ///
+ /// The newly created instance (or null if not supported).
+ ///
+ IFileProvider Create();
+ }
+}
diff --git a/src/Umbraco.Core/IO/MediaFileManager.cs b/src/Umbraco.Core/IO/MediaFileManager.cs
index 96680d3f845f..c769b9801e41 100644
--- a/src/Umbraco.Core/IO/MediaFileManager.cs
+++ b/src/Umbraco.Core/IO/MediaFileManager.cs
@@ -8,7 +8,6 @@
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
-using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Strings;
using Umbraco.Extensions;
@@ -22,29 +21,37 @@ public sealed class MediaFileManager
private readonly IShortStringHelper _shortStringHelper;
private readonly IServiceProvider _serviceProvider;
private MediaUrlGeneratorCollection _mediaUrlGenerators;
- private readonly ContentSettings _contentSettings;
-
- ///
- /// Gets the media filesystem.
- ///
- public IFileSystem FileSystem { get; }
public MediaFileManager(
IFileSystem fileSystem,
IMediaPathScheme mediaPathScheme,
ILogger logger,
IShortStringHelper shortStringHelper,
- IServiceProvider serviceProvider,
- IOptions contentSettings)
+ IServiceProvider serviceProvider)
{
_mediaPathScheme = mediaPathScheme;
_logger = logger;
_shortStringHelper = shortStringHelper;
_serviceProvider = serviceProvider;
- _contentSettings = contentSettings.Value;
FileSystem = fileSystem;
}
+ [Obsolete("Use the ctr that doesn't include unused parameters.")]
+ public MediaFileManager(
+ IFileSystem fileSystem,
+ IMediaPathScheme mediaPathScheme,
+ ILogger logger,
+ IShortStringHelper shortStringHelper,
+ IServiceProvider serviceProvider,
+ IOptions contentSettings)
+ : this(fileSystem, mediaPathScheme, logger, shortStringHelper, serviceProvider)
+ { }
+
+ ///
+ /// Gets the media filesystem.
+ ///
+ public IFileSystem FileSystem { get; }
+
///
/// Delete media files.
///
diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs
index db10cae4162a..3da09a499c66 100644
--- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs
+++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs
@@ -3,14 +3,17 @@
using System.IO;
using System.Linq;
using System.Threading;
+using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.IO
{
- public interface IPhysicalFileSystem : IFileSystem {}
- public class PhysicalFileSystem : IPhysicalFileSystem
+ public interface IPhysicalFileSystem : IFileSystem
+ { }
+
+ public class PhysicalFileSystem : IPhysicalFileSystem, IFileProviderFactory
{
private readonly IIOHelper _ioHelper;
private readonly ILogger _logger;
@@ -28,7 +31,7 @@ public class PhysicalFileSystem : IPhysicalFileSystem
// eg "" or "/Views" or "/Media" or "//Media" in case of a virtual path
private readonly string _rootUrl;
- public PhysicalFileSystem(IIOHelper ioHelper,IHostingEnvironment hostingEnvironment, ILogger logger, string rootPath, string rootUrl)
+ public PhysicalFileSystem(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, ILogger logger, string rootPath, string rootUrl)
{
_ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -270,7 +273,7 @@ public string GetRelativePath(string fullPathOrUrl)
return path.Substring(_rootUrl.Length).TrimStart(Constants.CharArrays.ForwardSlash);
// unchanged - what else?
- return path;
+ return path.TrimStart(Constants.CharArrays.ForwardSlash);
}
///
@@ -285,7 +288,7 @@ public string GetRelativePath(string fullPathOrUrl)
public string GetFullPath(string path)
{
// normalize
- var opath = path;
+ var originalPath = path;
path = EnsureDirectorySeparatorChar(path);
// FIXME: this part should go!
@@ -318,7 +321,7 @@ public string GetFullPath(string path)
// nothing prevents us to reach the file, security-wise, yet it is outside
// this filesystem's root - throw
- throw new UnauthorizedAccessException($"File original: [{opath}] full: [{path}] is outside this filesystem's root.");
+ throw new UnauthorizedAccessException($"File original: [{originalPath}] full: [{path}] is outside this filesystem's root.");
}
///
@@ -450,6 +453,9 @@ protected void WithRetry(Action action)
}
}
+ ///
+ public IFileProvider Create() => new PhysicalFileProvider(_rootPath);
+
#endregion
}
}
diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs
index cda61cf7b50c..7bf315a5756f 100644
--- a/src/Umbraco.Core/IO/ShadowWrapper.cs
+++ b/src/Umbraco.Core/IO/ShadowWrapper.cs
@@ -1,14 +1,15 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.IO
{
- internal class ShadowWrapper : IFileSystem
+ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
{
private static readonly string ShadowFsPath = Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs";
@@ -220,5 +221,8 @@ public void AddFile(string path, string physicalPath, bool overrideIfExists = tr
{
FileSystem.AddFile(path, physicalPath, overrideIfExists, copy);
}
+
+ ///
+ public IFileProvider Create() => _innerFileSystem.TryCreateFileProvider(out IFileProvider fileProvider) ? fileProvider : null;
}
}
diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs
index 331034e78758..36b7a5d5d5fe 100644
--- a/src/Umbraco.Core/Packaging/PackagesRepository.cs
+++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs
@@ -93,7 +93,7 @@ public PackagesRepository(
_tempFolderPath = tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles";
_packagesFolderPath = packagesFolderPath ?? Constants.SystemDirectories.Packages;
- _mediaFolderPath = mediaFolderPath ?? globalSettings.Value.UmbracoMediaPath + "/created-packages";
+ _mediaFolderPath = mediaFolderPath ?? Path.Combine(globalSettings.Value.UmbracoMediaPhysicalRootPath, Constants.SystemDirectories.CreatedPackages);
_parser = new PackageDefinitionXmlParser();
_mediaService = mediaService;
diff --git a/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs
index a9564712c332..6c45e4d96915 100644
--- a/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs
+++ b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs
@@ -25,7 +25,7 @@ public void Handle(UmbracoApplicationStartingNotification notification)
// ensure we have some essential directories
// every other component can then initialize safely
_ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data));
- _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPath));
+ _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPhysicalRootPath));
_ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MvcViews));
_ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.PartialViews));
_ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials));
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index f36d7a8ee5c0..ee83d0677ac3 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -1,4 +1,4 @@
-
+
netstandard2.0
@@ -17,6 +17,7 @@
+
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs
index 6582cfb0c6e6..f66991fb6858 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs
@@ -49,7 +49,7 @@ internal static IUmbracoBuilder AddFileSystems(this IUmbracoBuilder builder)
ILogger logger = factory.GetRequiredService>();
GlobalSettings globalSettings = factory.GetRequiredService>().Value;
- var rootPath = hostingEnvironment.MapPathWebRoot(globalSettings.UmbracoMediaPath);
+ var rootPath = hostingEnvironment.MapPathWebRoot(globalSettings.UmbracoMediaPhysicalRootPath);
var rootUrl = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaPath);
return new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, rootPath, rootUrl);
});
diff --git a/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs b/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs
index 0ad2271d7e05..1d228ebf9883 100644
--- a/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs
+++ b/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs
@@ -44,7 +44,7 @@ public FilePermissionHelper(IOptions globalSettings, IIOHelper i
hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoCssPath),
hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Config),
hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data),
- hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPath),
+ hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPhysicalRootPath),
hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Preview)
};
_packagesPermissionsDirs = new[]
@@ -70,7 +70,7 @@ public bool RunFilePermissionTestSuite(out Dictionary x.Value.Count()) == 0;
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs
index 0c4f876bb182..be1a31c2c9d2 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs
@@ -76,7 +76,7 @@ public CreatedPackageSchemaRepository(
_macroService = macroService;
_contentTypeService = contentTypeService;
_xmlParser = new PackageDefinitionXmlParser();
- _mediaFolderPath = mediaFolderPath ?? globalSettings.Value.UmbracoMediaPath + "/created-packages";
+ _mediaFolderPath = mediaFolderPath ?? Path.Combine(globalSettings.Value.UmbracoMediaPhysicalRootPath, Constants.SystemDirectories.CreatedPackages);
_tempFolderPath =
tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles";
}
diff --git a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs
index 9d30551071e7..58f66a6fb8c5 100644
--- a/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs
+++ b/src/Umbraco.Web.Common/ApplicationBuilder/UmbracoApplicationBuilder.cs
@@ -1,10 +1,16 @@
using System;
+using Dazinator.Extensions.FileProviders.PrependBasePath;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using SixLabors.ImageSharp.Web.DependencyInjection;
+using Umbraco.Cms.Core.Configuration.Models;
+using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;
+using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
namespace Umbraco.Cms.Web.Common.ApplicationBuilder
{
@@ -22,12 +28,14 @@ public UmbracoApplicationBuilder(IApplicationBuilder appBuilder)
{
AppBuilder = appBuilder ?? throw new ArgumentNullException(nameof(appBuilder));
ApplicationServices = appBuilder.ApplicationServices;
- RuntimeState = appBuilder.ApplicationServices.GetRequiredService();
+ RuntimeState = appBuilder.ApplicationServices.GetRequiredService();
_umbracoPipelineStartupOptions = ApplicationServices.GetRequiredService>();
}
public IServiceProvider ApplicationServices { get; }
+
public IRuntimeState RuntimeState { get; }
+
public IApplicationBuilder AppBuilder { get; }
///
@@ -78,18 +86,32 @@ public void WithEndpoints(Action configureUmbrac
}
///
- /// Registers the default required middleware to run Umbraco
+ /// Registers the default required middleware to run Umbraco.
///
- ///
public void RegisterDefaultRequiredMiddleware()
{
UseUmbracoCoreMiddleware();
AppBuilder.UseStatusCodePages();
- // Important we handle image manipulations before the static files, otherwise the querystring is just ignored.
+ // Important we handle image manipulations before the static files, otherwise the querystring is just ignored.
AppBuilder.UseImageSharp();
+
+ // Get media file provider and request path/URL
+ var mediaFileManager = AppBuilder.ApplicationServices.GetRequiredService();
+ if (mediaFileManager.FileSystem.TryCreateFileProvider(out IFileProvider mediaFileProvider))
+ {
+ GlobalSettings globalSettings = AppBuilder.ApplicationServices.GetRequiredService>().Value;
+ IHostingEnvironment hostingEnvironment = AppBuilder.ApplicationServices.GetService();
+ string mediaRequestPath = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaPath);
+
+ // Configure custom file provider for media
+ IWebHostEnvironment webHostEnvironment = AppBuilder.ApplicationServices.GetService();
+ webHostEnvironment.WebRootFileProvider = webHostEnvironment.WebRootFileProvider.ConcatComposite(new PrependBasePathFileProvider(mediaRequestPath, mediaFileProvider));
+ }
+
AppBuilder.UseStaticFiles();
+
AppBuilder.UseUmbracoPluginsStaticFiles();
// UseRouting adds endpoint routing middleware, this means that middlewares registered after this one
diff --git a/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs b/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs
index 628345dcd67a..f8897e522cd6 100644
--- a/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs
+++ b/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs
@@ -7,7 +7,7 @@ namespace Umbraco.Cms.Web.Common.DependencyInjection
///
/// Configures the ImageSharp middleware options to use the registered configuration.
///
- ///
+ ///
public sealed class ImageSharpConfigurationOptions : IConfigureOptions
{
///
@@ -22,7 +22,7 @@ public sealed class ImageSharpConfigurationOptions : IConfigureOptions _configuration = configuration;
///
- /// Invoked to configure a instance.
+ /// Invoked to configure an instance.
///
/// The options instance to configure.
public void Configure(ImageSharpMiddlewareOptions options) => options.Configuration = _configuration;
diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs
index 4d621d348cef..30331fd812fd 100644
--- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs
+++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs
@@ -74,6 +74,7 @@ public static IServiceCollection AddUmbracoImageSharp(this IUmbracoBuilder build
.Configure(options => options.CacheFolder = builder.BuilderHostingEnvironment.MapPathContentRoot(imagingSettings.Cache.CacheFolder))
.AddProcessor();
+ // Configure middleware to use the registered/shared ImageSharp configuration
builder.Services.AddTransient, ImageSharpConfigurationOptions>();
return builder.Services;
diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs
index cc035d116b8d..fc6a2904dfbe 100644
--- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs
+++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs
@@ -1,18 +1,20 @@
using System;
using System.IO;
+using Dazinator.Extensions.FileProviders.PrependBasePath;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Serilog.Context;
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;
+using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
namespace Umbraco.Extensions
{
@@ -94,7 +96,8 @@ public static IApplicationBuilder UseUmbracoRequestLogging(this IApplicationBuil
throw new ArgumentNullException(nameof(app));
}
- if (!app.UmbracoCanBoot()) return app;
+ if (!app.UmbracoCanBoot())
+ return app;
app.UseMiddleware();
@@ -109,25 +112,21 @@ public static IApplicationBuilder UseUmbracoRequestLogging(this IApplicationBuil
public static IApplicationBuilder UseUmbracoPluginsStaticFiles(this IApplicationBuilder app)
{
var hostingEnvironment = app.ApplicationServices.GetRequiredService();
- var umbracoPluginSettings = app.ApplicationServices.GetRequiredService>();
var pluginFolder = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins);
+ if (Directory.Exists(pluginFolder))
+ {
+ var umbracoPluginSettings = app.ApplicationServices.GetRequiredService>();
- // Ensure the plugin folder exists
- Directory.CreateDirectory(pluginFolder);
-
- var fileProvider = new UmbracoPluginPhysicalFileProvider(
- pluginFolder,
- umbracoPluginSettings);
+ var pluginFileProvider = new UmbracoPluginPhysicalFileProvider(
+ pluginFolder,
+ umbracoPluginSettings);
- app.UseStaticFiles(new StaticFileOptions
- {
- FileProvider = fileProvider,
- RequestPath = Constants.SystemDirectories.AppPlugins
- });
+ IWebHostEnvironment webHostEnvironment = app.ApplicationServices.GetService();
+ webHostEnvironment.WebRootFileProvider = webHostEnvironment.WebRootFileProvider.ConcatComposite(new PrependBasePathFileProvider(Constants.SystemDirectories.AppPlugins, pluginFileProvider));
+ }
return app;
}
}
-
}
diff --git a/src/Umbraco.Web.Common/Extensions/FileProviderExtensions.cs b/src/Umbraco.Web.Common/Extensions/FileProviderExtensions.cs
new file mode 100644
index 000000000000..35a2882bdd5d
--- /dev/null
+++ b/src/Umbraco.Web.Common/Extensions/FileProviderExtensions.cs
@@ -0,0 +1,19 @@
+using System.Linq;
+using Microsoft.Extensions.FileProviders;
+
+namespace Umbraco.Extensions
+{
+ internal static class FileProviderExtensions
+ {
+ public static IFileProvider ConcatComposite(this IFileProvider fileProvider, params IFileProvider[] fileProviders)
+ {
+ var existingFileProviders = fileProvider switch
+ {
+ CompositeFileProvider compositeFileProvider => compositeFileProvider.FileProviders,
+ _ => new[] { fileProvider }
+ };
+
+ return new CompositeFileProvider(existingFileProviders.Concat(fileProviders));
+ }
+ }
+}
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs
index 02709f7f84c2..4c6204ee4edb 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs
@@ -53,7 +53,7 @@ public void PathTests()
{
var repository = new PartialViewRepository(fileSystems);
- var partialView = new PartialView(PartialViewType.PartialView, "test-path-1.cshtml") { Content = "// partialView" };
+ IPartialView partialView = new PartialView(PartialViewType.PartialView, "test-path-1.cshtml") { Content = "// partialView" };
repository.Save(partialView);
Assert.IsTrue(_fileSystem.FileExists("test-path-1.cshtml"));
Assert.AreEqual("test-path-1.cshtml", partialView.Path);
@@ -62,10 +62,10 @@ public void PathTests()
partialView = new PartialView(PartialViewType.PartialView, "path-2/test-path-2.cshtml") { Content = "// partialView" };
repository.Save(partialView);
Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-2.cshtml"));
- Assert.AreEqual("path-2\\test-path-2.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path); // fixed in 7.3 - 7.2.8 does not update the path
+ Assert.AreEqual("path-2\\test-path-2.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path);
Assert.AreEqual("/Views/Partials/path-2/test-path-2.cshtml", partialView.VirtualPath);
- partialView = (PartialView)repository.Get("path-2/test-path-2.cshtml");
+ partialView = repository.Get("path-2/test-path-2.cshtml");
Assert.IsNotNull(partialView);
Assert.AreEqual("path-2\\test-path-2.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path);
Assert.AreEqual("/Views/Partials/path-2/test-path-2.cshtml", partialView.VirtualPath);
@@ -76,26 +76,33 @@ public void PathTests()
Assert.AreEqual("path-2\\test-path-3.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path);
Assert.AreEqual("/Views/Partials/path-2/test-path-3.cshtml", partialView.VirtualPath);
- partialView = (PartialView)repository.Get("path-2/test-path-3.cshtml");
+ partialView = repository.Get("path-2/test-path-3.cshtml");
Assert.IsNotNull(partialView);
Assert.AreEqual("path-2\\test-path-3.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path);
Assert.AreEqual("/Views/Partials/path-2/test-path-3.cshtml", partialView.VirtualPath);
- partialView = (PartialView)repository.Get("path-2\\test-path-3.cshtml");
+ partialView = repository.Get("path-2\\test-path-3.cshtml");
Assert.IsNotNull(partialView);
Assert.AreEqual("path-2\\test-path-3.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path);
Assert.AreEqual("/Views/Partials/path-2/test-path-3.cshtml", partialView.VirtualPath);
- partialView = new PartialView(PartialViewType.PartialView, "\\test-path-4.cshtml") { Content = "// partialView" };
- Assert.Throws(() => // fixed in 7.3 - 7.2.8 used to strip the \
+ partialView = new PartialView(PartialViewType.PartialView, "..\\test-path-4.cshtml") { Content = "// partialView" };
+ Assert.Throws(() =>
repository.Save(partialView));
- partialView = (PartialView)repository.Get("missing.cshtml");
+ partialView = new PartialView(PartialViewType.PartialView, "\\test-path-5.cshtml") { Content = "// partialView" };
+ repository.Save(partialView);
+
+ partialView = repository.Get("\\test-path-5.cshtml");
+ Assert.IsNotNull(partialView);
+ Assert.AreEqual("test-path-5.cshtml", partialView.Path);
+ Assert.AreEqual("/Views/Partials/test-path-5.cshtml", partialView.VirtualPath);
+
+ partialView = repository.Get("missing.cshtml");
Assert.IsNull(partialView);
- // fixed in 7.3 - 7.2.8 used to...
- Assert.Throws(() => partialView = (PartialView)repository.Get("\\test-path-4.cshtml"));
- Assert.Throws(() => partialView = (PartialView)repository.Get("../../packages.config"));
+ Assert.Throws(() => partialView = repository.Get("..\\test-path-4.cshtml"));
+ Assert.Throws(() => partialView = repository.Get("../../packages.config"));
}
}
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs
index 28f9a9eff15e..4721af14e171 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs
@@ -303,15 +303,22 @@ public void PathTests()
Assert.AreEqual("path-2\\test-path-3.js".Replace("\\", $"{Path.DirectorySeparatorChar}"), script.Path);
Assert.AreEqual("/scripts/path-2/test-path-3.js", script.VirtualPath);
- script = new Script("\\test-path-4.js") { Content = "// script" };
- Assert.Throws(() => // fixed in 7.3 - 7.2.8 used to strip the \
+ script = new Script("..\\test-path-4.js") { Content = "// script" };
+ Assert.Throws(() =>
repository.Save(script));
+ script = new Script("\\test-path-5.js") { Content = "// script" };
+ repository.Save(script);
+
+ script = repository.Get("\\test-path-5.js");
+ Assert.IsNotNull(script);
+ Assert.AreEqual("test-path-5.js", script.Path);
+ Assert.AreEqual("/scripts/test-path-5.js", script.VirtualPath);
+
script = repository.Get("missing.js");
Assert.IsNull(script);
- // fixed in 7.3 - 7.2.8 used to...
- Assert.Throws(() => script = repository.Get("\\test-path-4.js"));
+ Assert.Throws(() => script = repository.Get("..\\test-path-4.js"));
Assert.Throws(() => script = repository.Get("../packages.config"));
}
}
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs
index d9bde0bca2ec..a32638ed4dba 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs
@@ -275,7 +275,7 @@ public void PathTests()
repository.Save(stylesheet);
Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-2.css"));
- Assert.AreEqual("path-2\\test-path-2.css".Replace("\\", $"{Path.DirectorySeparatorChar}"), stylesheet.Path); // fixed in 7.3 - 7.2.8 does not update the path
+ Assert.AreEqual("path-2\\test-path-2.css".Replace("\\", $"{Path.DirectorySeparatorChar}"), stylesheet.Path);
Assert.AreEqual("/css/path-2/test-path-2.css", stylesheet.VirtualPath);
stylesheet = repository.Get("path-2/test-path-2.css");
@@ -300,17 +300,24 @@ public void PathTests()
Assert.AreEqual("path-2\\test-path-3.css".Replace("\\", $"{Path.DirectorySeparatorChar}"), stylesheet.Path);
Assert.AreEqual("/css/path-2/test-path-3.css", stylesheet.VirtualPath);
- stylesheet = new Stylesheet("\\test-path-4.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" };
- Assert.Throws(() => // fixed in 7.3 - 7.2.8 used to strip the \
+ stylesheet = new Stylesheet("..\\test-path-4.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" };
+ Assert.Throws(() =>
repository.Save(stylesheet));
- // fixed in 7.3 - 7.2.8 used to throw
+ stylesheet = new Stylesheet("\\test-path-5.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" };
+ repository.Save(stylesheet);
+
+ stylesheet = repository.Get("\\test-path-5.css");
+ Assert.IsNotNull(stylesheet);
+ Assert.AreEqual("test-path-5.css", stylesheet.Path);
+ Assert.AreEqual("/css/test-path-5.css", stylesheet.VirtualPath);
+
stylesheet = repository.Get("missing.css");
Assert.IsNull(stylesheet);
// #7713 changes behaviour to return null when outside the filesystem
// to accomodate changing the CSS path and not flooding the backoffice with errors
- stylesheet = repository.Get("\\test-path-4.css"); // outside the filesystem, does not exist
+ stylesheet = repository.Get("..\\test-path-4.css"); // outside the filesystem, does not exist
Assert.IsNull(stylesheet);
stylesheet = repository.Get("../packages.config"); // outside the filesystem, exists
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs
index e30d7bbf5542..7ea8e65edaea 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeFileSystemsTests.cs
@@ -48,7 +48,7 @@ private void ClearFiles(IIOHelper ioHelper)
[Test]
public void MediaFileManager_does_not_write_to_physical_file_system_when_scoped_if_scope_does_not_complete()
{
- string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPath);
+ string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPhysicalRootPath);
string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaPath);
var physMediaFileSystem = new PhysicalFileSystem(IOHelper, HostingEnvironment, GetRequiredService>(), rootPath, rootUrl);
MediaFileManager mediaFileManager = MediaFileManager;
@@ -77,7 +77,7 @@ public void MediaFileManager_does_not_write_to_physical_file_system_when_scoped_
[Test]
public void MediaFileManager_writes_to_physical_file_system_when_scoped_and_scope_is_completed()
{
- string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPath);
+ string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPhysicalRootPath);
string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaPath);
var physMediaFileSystem = new PhysicalFileSystem(IOHelper, HostingEnvironment, GetRequiredService>(), rootPath, rootUrl);
MediaFileManager mediaFileManager = MediaFileManager;
@@ -108,7 +108,7 @@ public void MediaFileManager_writes_to_physical_file_system_when_scoped_and_scop
[Test]
public void MultiThread()
{
- string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPath);
+ string rootPath = HostingEnvironment.MapPathWebRoot(GlobalSettings.UmbracoMediaPhysicalRootPath);
string rootUrl = HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoMediaPath);
var physMediaFileSystem = new PhysicalFileSystem(IOHelper, HostingEnvironment, GetRequiredService>(), rootPath, rootUrl);
MediaFileManager mediaFileManager = MediaFileManager;
diff --git a/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs b/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs
index 8dee7db12d67..ba89c9775efa 100644
--- a/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs
+++ b/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs
@@ -134,9 +134,9 @@ public static MapperConfigurationStore CreateMaps()
///
public static string MapPathForTestFiles(string relativePath) => s_testHelperInternal.MapPathForTestFiles(relativePath);
- public static void InitializeContentDirectories() => CreateDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPath, Constants.SystemDirectories.AppPlugins });
+ public static void InitializeContentDirectories() => CreateDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPhysicalRootPath, Constants.SystemDirectories.AppPlugins });
- public static void CleanContentDirectories() => CleanDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPath });
+ public static void CleanContentDirectories() => CleanDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPhysicalRootPath });
public static void CreateDirectories(string[] directories)
{