More filesystem DI fixes

This commit is contained in:
Stephan
2018-11-24 15:38:00 +01:00
parent ce47eae85b
commit 392c9ed83b
14 changed files with 138 additions and 132 deletions

View File

@@ -2,6 +2,7 @@
using System.Configuration;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.IO.MediaPathSchemes;
namespace Umbraco.Core.Composing.Composers
{
@@ -13,34 +14,45 @@ namespace Umbraco.Core.Composing.Composers
*
* Create a component and use it to modify the composition by adding something like:
*
* composition.Container.RegisterSingleton<IFileSystem>("media", factory => ...);
* composition.Container.RegisterFileSystem<IMediaFileSystem, MediaFileSystem>(
* factory => new PhysicalFileSystem("~/somewhere"));
*
* where the ... part returns the new underlying filesystem, as an IFileSystem.
* return whatever supporting filesystem you like.
*
*
* HOW TO IMPLEMENT MY OWN FILESYSTEM
* ----------------------------------
*
* Declare your filesystem interface:
*
* public interface IMyFileSystem : IFileSystem
* { }
*
* Create your filesystem class:
*
* [FileSystem("my")]
* public class MyFileSystem : FileSystemWrapper, IFormsFileSystem
* public class MyFileSystem : FileSystemWrapper
* {
* public FormsFileSystem(IFileSystem innerFileSystem)
* public MyFileSystem(IFileSystem innerFileSystem)
* : base(innerFileSystem)
* { }
* }
*
* Register both the underlying filesystem, and your filesystem, in a component:
* Note that the ctor parameter MUST be named innerFileSystem. fixme oh yea?
* The ctor can have more parameters that will be resolved by the container.
*
* composition.Container.RegisterSingleton<IFileSystem>("my", factory => ...);
* composition.Container.RegisterSingleton<IMyFileSystem>(factory =>
* factory.GetInstance<FileSystems>().GetFileSystem<MyFileSystem>();
* Register your filesystem, in a component:
*
* composition.Container.RegisterFileSystem<MyFileSystem>(
* factory => new PhysicalFileSystem("~/my"));
*
* And that's it, you can inject MyFileSystem wherever it's needed.
*
*
* You can also declare a filesystem interface:
*
* public interface IMyFileSystem : IFileSystem
* { }
*
* Make the class implement the interface, then
* register your filesystem, in a component:
*
* composition.Container.RegisterFileSystem<IMyFileSystem, MyFileSystem>(
* factory => new PhysicalFileSystem("~/my"));
*
* And that's it, you can inject IMyFileSystem wherever it's needed.
*
@@ -53,14 +65,6 @@ namespace Umbraco.Core.Composing.Composers
* compared to creating your own physical filesystem, ensures that your filesystem
* would participate into such transactions.
*
* Also note that in order for things to work correctly, all filesystems should
* be instantiated before shadowing - so if registering a new filesystem in a
* component, it's a good idea to initialize it. This would be enough (in the
* component):
*
* public void Initialize(IMyFileSystem fs)
* { }
*
*
*/
@@ -70,34 +74,19 @@ namespace Umbraco.Core.Composing.Composers
// it needs to be registered (not only the interface) because it provides additional
// functionality eg for scoping, and is injected in the scope provider - whereas the
// interface is really for end-users to get access to filesystems.
//container.RegisterSingleton<FileSystems>();
container.RegisterSingleton(factory => factory.CreateInstance<FileSystems>(new { container} ));
// register IFileSystems, which gives access too all filesystems
container.RegisterSingleton<IFileSystems>(factory => factory.GetInstance<FileSystems>());
// register IMediaFileSystem with its actual, underlying filesystem factory
container.RegisterSingleton<IMediaFileSystem>(factory => factory.GetInstance<FileSystems>().GetFileSystem<MediaFileSystem>(MediaInnerFileSystemFactory));
// register the scheme for media paths
container.RegisterSingleton<IMediaPathScheme, TwoGuidsMediaPathScheme>();
// register the IMediaFileSystem implementation with a supporting filesystem
container.RegisterFileSystem<IMediaFileSystem, MediaFileSystem>(
factory => new PhysicalFileSystem("~/media"));
return container;
}
private static IFileSystem MediaInnerFileSystemFactory()
{
// for the time being, we still use the FileSystemProvider config file
// but, detect if ppl are trying to use it to change the "provider"
var virtualRoot = "~/media";
var config = (FileSystemProvidersSection)ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders");
var p = config?.Providers["media"];
if (p == null) return new PhysicalFileSystem(virtualRoot);
if (!string.IsNullOrWhiteSpace(p.Type) && p.Type != "Umbraco.Core.IO.PhysicalFileSystem, Umbraco.Core")
throw new InvalidOperationException("Setting a provider type in FileSystemProviders.config is not supported anymore, see FileSystemsComposer for help.");
virtualRoot = p?.Parameters["virtualRoot"]?.Value ?? "~/media";
return new PhysicalFileSystem(virtualRoot);
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Umbraco.Core.IO;
namespace Umbraco.Core.Composing
{
@@ -167,6 +168,8 @@ namespace Umbraco.Core.Composing
// more expensive to build and cache a dynamic method ctor than to simply invoke the ctor, as we do
// here - this can be discussed
// TODO: we currently try the ctor with most parameters, but we could want to fall back to others
var ctor = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public).OrderByDescending(x => x.GetParameters().Length).FirstOrDefault();
if (ctor == null) throw new InvalidOperationException($"Could not find a public constructor for type {type.FullName}.");
@@ -177,10 +180,50 @@ namespace Umbraco.Core.Composing
{
// no! IsInstanceOfType is not ok here
// ReSharper disable once UseMethodIsInstanceOfType
// fixme so we just ignore the names?
var arg = args?.Values.FirstOrDefault(a => parameter.ParameterType.IsAssignableFrom(a.GetType()));
ctorArgs[i++] = arg ?? container.GetInstance(parameter.ParameterType);
}
return ctor.Invoke(ctorArgs);
}
/// <summary>
/// Registers a filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <typeparam name="TImplementing">The implementing type.</typeparam>
/// <param name="container">The container.</param>
/// <param name="supportingFileSystemFactory">A factory method creating the supporting filesystem.</param>
/// <returns>The container.</returns>
public static IContainer RegisterFileSystem<TFileSystem, TImplementing>(this IContainer container, Func<IContainer, IFileSystem> supportingFileSystemFactory)
where TImplementing : FileSystemWrapper, TFileSystem
{
container.RegisterSingleton<TFileSystem>(factory =>
{
var fileSystems = factory.GetInstance<FileSystems>();
return fileSystems.GetFileSystem<TImplementing>(supportingFileSystemFactory(factory));
});
return container;
}
/// <summary>
/// Registers a filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <param name="container">The container.</param>
/// <param name="supportingFileSystemFactory">A factory method creating the supporting filesystem.</param>
/// <returns>The container.</returns>
public static IContainer RegisterFileSystem<TFileSystem>(this IContainer container, Func<IContainer, IFileSystem> supportingFileSystemFactory)
where TFileSystem : FileSystemWrapper
{
container.RegisterSingleton(factory =>
{
var fileSystems = factory.GetInstance<FileSystems>();
return fileSystems.GetFileSystem<TFileSystem>(supportingFileSystemFactory(factory));
});
return container;
}
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Umbraco.Core.Logging;
using Umbraco.Core.Composing;
@@ -13,7 +12,7 @@ namespace Umbraco.Core.IO
private readonly IContainer _container;
private readonly ILogger _logger;
private readonly ConcurrentDictionary<string, Lazy<IFileSystem>> _filesystems = new ConcurrentDictionary<string, Lazy<IFileSystem>>();
private readonly ConcurrentDictionary<Type, Lazy<IFileSystem>> _filesystems = new ConcurrentDictionary<Type, Lazy<IFileSystem>>();
// wrappers for shadow support
private ShadowWrapper _macroPartialFileSystem;
@@ -170,18 +169,17 @@ namespace Umbraco.Core.IO
/// <para>Note that any filesystem created by this method *after* shadowing begins, will *not* be
/// shadowing (and an exception will be thrown by the ShadowWrapper).</para>
/// </remarks>
public TFileSystem GetFileSystem<TFileSystem>(Func<IFileSystem> innerFileSystemFactory)
public TFileSystem GetFileSystem<TFileSystem>(IFileSystem supporting)
where TFileSystem : FileSystemWrapper
{
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
var name = typeof(TFileSystem).FullName;
if (name == null) throw new Exception("panic!");
return (TFileSystem) _filesystems.GetOrAdd(name, _ => new Lazy<IFileSystem>(() =>
return (TFileSystem) _filesystems.GetOrAdd(typeof(TFileSystem), _ => new Lazy<IFileSystem>(() =>
{
var innerFileSystem = innerFileSystemFactory();
var shadowWrapper = CreateShadowWrapper(innerFileSystem, "typed/" + name);
var name = typeof(TFileSystem).FullName;
if (name == null) throw new Exception("panic!");
var shadowWrapper = CreateShadowWrapper(supporting, "typed/" + name);
return _container.CreateInstance<TFileSystem>(new { innerFileSystem = shadowWrapper });
})).Value;
}
@@ -194,19 +192,6 @@ namespace Umbraco.Core.IO
// shadowing is thread-safe, but entering and exiting shadow mode is not, and there is only one
// global shadow for the entire application, so great care should be taken to ensure that the
// application is *not* doing anything else when using a shadow.
// shadow applies to well-known filesystems *only* - at the moment, any other filesystem that would
// be created directly (via ctor) or via GetFileSystem<T> is *not* shadowed.
// shadow must be enabled in an app event handler before anything else ie before any filesystem
// is actually created and used - after, it is too late - enabling shadow has a negligible perfs
// impact.
// NO! by the time an app event handler is instantiated it is already too late, see note in ctor.
//internal void EnableShadow()
//{
// if (_mvcViewsFileSystem != null) // test one of the fs...
// throw new InvalidOperationException("Cannot enable shadow once filesystems have been created.");
// _shadowEnabled = true;
//}
internal ICompletable Shadow(Guid id)
{

View File

@@ -7,35 +7,27 @@ namespace Umbraco.Core.IO
/// </summary>
public interface IMediaPathScheme
{
// fixme
// to anyone finding this code: YES the Initialize() method is CompletelyBroken™ (temporal whatever)
// but at the moment, the media filesystem wants a scheme which wants a filesystem, and it's all
// convoluted due to how filesystems are managed in FileSystems - clear that part first!
/// <summary>
/// Initialize.
/// </summary>
void Initialize(IFileSystem filesystem);
/// <summary>
/// Gets a media file path.
/// </summary>
/// <param name="fileSystem">The media filesystem.</param>
/// <param name="itemGuid">The (content, media) item unique identifier.</param>
/// <param name="propertyGuid">The property type unique identifier.</param>
/// <param name="filename">The file name.</param>
/// <param name="previous">A previous filename.</param>
/// <returns>The filesystem-relative complete file path.</returns>
string GetFilePath(Guid itemGuid, Guid propertyGuid, string filename, string previous = null);
string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null);
/// <summary>
/// Gets the directory that can be deleted when the file is deleted.
/// </summary>
/// <param name="fileSystem">The media filesystem.</param>
/// <param name="filepath">The filesystem-relative path of the file.</param>
/// <returns>The filesystem-relative path of the directory.</returns>
/// <remarks>
/// <para>The directory, and anything below it, will be deleted.</para>
/// <para>Can return null (or empty) when no directory should be deleted.</para>
/// </remarks>
string GetDeleteDirectory(string filepath);
string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath);
}
}

View File

@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
@@ -17,24 +19,21 @@ namespace Umbraco.Core.IO
/// </summary>
public class MediaFileSystem : FileSystemWrapper, IMediaFileSystem
{
private readonly IMediaPathScheme _mediaPathScheme;
private readonly IContentSection _contentConfig;
private readonly ILogger _logger;
/// <summary>
/// Initializes a new instance of the <see cref="MediaFileSystem"/> class.
/// </summary>
public MediaFileSystem(IFileSystem innerFileSystem)
public MediaFileSystem(IFileSystem innerFileSystem, IContentSection contentConfig, IMediaPathScheme mediaPathScheme, ILogger logger)
: base(innerFileSystem)
{
ContentConfig = Current.Container.GetInstance<IContentSection>();
Logger = Current.Container.GetInstance<ILogger>();
MediaPathScheme = Current.Container.GetInstance<IMediaPathScheme>();
MediaPathScheme.Initialize(this);
_contentConfig = contentConfig;
_mediaPathScheme = mediaPathScheme;
_logger = logger;
}
private IMediaPathScheme MediaPathScheme { get; }
private IContentSection ContentConfig { get; }
private ILogger Logger { get; }
/// <inheritoc />
public void DeleteMediaFiles(IEnumerable<string> files)
{
@@ -51,13 +50,13 @@ namespace Umbraco.Core.IO
if (FileExists(file) == false) return;
DeleteFile(file);
var directory = MediaPathScheme.GetDeleteDirectory(file);
var directory = _mediaPathScheme.GetDeleteDirectory(this, file);
if (!directory.IsNullOrWhiteSpace())
DeleteDirectory(directory, true);
}
catch (Exception e)
{
Logger.Error<MediaFileSystem>(e, "Failed to delete media file '{File}'.", file);
_logger.Error<MediaFileSystem>(e, "Failed to delete media file '{File}'.", file);
}
});
}
@@ -71,7 +70,7 @@ namespace Umbraco.Core.IO
if (filename == null) throw new ArgumentException("Cannot become a safe filename.", nameof(filename));
filename = IOHelper.SafeFileName(filename.ToLowerInvariant());
return MediaPathScheme.GetFilePath(cuid, puid, filename);
return _mediaPathScheme.GetFilePath(this, cuid, puid, filename);
}
/// <inheritoc />
@@ -81,7 +80,7 @@ namespace Umbraco.Core.IO
if (filename == null) throw new ArgumentException("Cannot become a safe filename.", nameof(filename));
filename = IOHelper.SafeFileName(filename.ToLowerInvariant());
return MediaPathScheme.GetFilePath(cuid, puid, filename, prevpath);
return _mediaPathScheme.GetFilePath(this, cuid, puid, filename, prevpath);
}
#endregion
@@ -124,6 +123,6 @@ namespace Umbraco.Core.IO
return filepath;
}
#endregion
#endregion
}
}

View File

@@ -12,11 +12,7 @@ namespace Umbraco.Core.IO.MediaPathSchemes
public class CombinedGuidsMediaPathScheme : IMediaPathScheme
{
/// <inheritdoc />
public void Initialize(IFileSystem filesystem)
{ }
/// <inheritdoc />
public string GetFilePath(Guid itemGuid, Guid propertyGuid, string filename, string previous = null)
public string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null)
{
// assumes that cuid and puid keys can be trusted - and that a single property type
// for a single content cannot store two different files with the same name
@@ -25,7 +21,7 @@ namespace Umbraco.Core.IO.MediaPathSchemes
}
/// <inheritdoc />
public string GetDeleteDirectory(string filepath)
public string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath)
{
return Path.GetDirectoryName(filepath);
}

View File

@@ -17,18 +17,11 @@ namespace Umbraco.Core.IO.MediaPathSchemes
public class OriginalMediaPathScheme : IMediaPathScheme
{
private readonly object _folderCounterLock = new object();
private IFileSystem _filesystem;
private long _folderCounter;
private bool _folderCounterInitialized;
/// <inheritdoc />
public void Initialize(IFileSystem filesystem)
{
_filesystem = filesystem;
}
/// <inheritdoc />
public string GetFilePath(Guid itemGuid, Guid propertyGuid, string filename, string previous = null)
public string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null)
{
string directory;
if (previous != null)
@@ -41,11 +34,11 @@ namespace Umbraco.Core.IO.MediaPathSchemes
var pos = previous.IndexOf(sep, StringComparison.Ordinal);
var s = pos > 0 ? previous.Substring(0, pos) : null;
directory = pos > 0 && int.TryParse(s, out _) ? s : GetNextDirectory();
directory = pos > 0 && int.TryParse(s, out _) ? s : GetNextDirectory(fileSystem);
}
else
{
directory = GetNextDirectory();
directory = GetNextDirectory(fileSystem);
}
if (directory == null)
@@ -57,25 +50,25 @@ namespace Umbraco.Core.IO.MediaPathSchemes
}
/// <inheritdoc />
public string GetDeleteDirectory(string filepath)
public string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath)
{
return Path.GetDirectoryName(filepath);
}
private string GetNextDirectory()
private string GetNextDirectory(IFileSystem fileSystem)
{
EnsureFolderCounterIsInitialized();
EnsureFolderCounterIsInitialized(fileSystem);
return Interlocked.Increment(ref _folderCounter).ToString(CultureInfo.InvariantCulture);
}
private void EnsureFolderCounterIsInitialized()
private void EnsureFolderCounterIsInitialized(IFileSystem fileSystem)
{
lock (_folderCounterLock)
{
if (_folderCounterInitialized) return;
_folderCounter = 1000; // seed
var directories = _filesystem.GetDirectories("");
var directories = fileSystem.GetDirectories("");
foreach (var directory in directories)
{
if (long.TryParse(directory, out var folderNumber) && folderNumber > _folderCounter)

View File

@@ -12,17 +12,13 @@ namespace Umbraco.Core.IO.MediaPathSchemes
public class TwoGuidsMediaPathScheme : IMediaPathScheme
{
/// <inheritdoc />
public void Initialize(IFileSystem filesystem)
{ }
/// <inheritdoc />
public string GetFilePath(Guid itemGuid, Guid propertyGuid, string filename, string previous = null)
public string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null)
{
return Path.Combine(itemGuid.ToString("N"), propertyGuid.ToString("N"), filename).Replace('\\', '/');
}
/// <inheritdoc />
public string GetDeleteDirectory(string filepath)
public string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath)
{
return Path.GetDirectoryName(filepath);
}

View File

@@ -107,8 +107,6 @@ namespace Umbraco.Core.Runtime
// by default, register a noop factory
composition.Container.RegisterSingleton<IPublishedModelFactory, NoopPublishedModelFactory>();
composition.Container.RegisterSingleton<IMediaPathScheme, TwoGuidsMediaPathScheme>();
}
internal void Initialize(IEnumerable<Profile> mapperProfiles)

View File

@@ -398,7 +398,7 @@ namespace Umbraco.Tests.IO
var container = Mock.Of<IContainer>();
var fileSystems = new FileSystems(container, logger) { IsScoped = () => scopedFileSystems };
var fs = fileSystems.GetFileSystem<FS>(() => phy);
var fs = fileSystems.GetFileSystem<FS>(phy);
var sw = (ShadowWrapper) fs.InnerFileSystem;
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
@@ -491,7 +491,7 @@ namespace Umbraco.Tests.IO
var container = Mock.Of<IContainer>();
var fileSystems = new FileSystems(container, logger) { IsScoped = () => scopedFileSystems };
var fs = fileSystems.GetFileSystem<FS>(() => phy);
var fs = fileSystems.GetFileSystem<FS>( phy);
var sw = (ShadowWrapper) fs.InnerFileSystem;
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
@@ -544,7 +544,7 @@ namespace Umbraco.Tests.IO
var container = Mock.Of<IContainer>();
var fileSystems = new FileSystems(container, logger) { IsScoped = () => scopedFileSystems };
var fs = fileSystems.GetFileSystem<FS>(() => phy);
var fs = fileSystems.GetFileSystem<FS>( phy);
var sw = (ShadowWrapper)fs.InnerFileSystem;
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))

View File

@@ -29,7 +29,12 @@ namespace Umbraco.Tests.Models
// reference, so static ctor runs, so event handlers register
// and then, this will reset the width, height... because the file does not exist, of course ;-(
var ignored = new FileUploadPropertyEditor(Mock.Of<ILogger>(), new MediaFileSystem(Mock.Of<IFileSystem>()), Mock.Of<IContentSection>());
var logger = Mock.Of<ILogger>();
var scheme = Mock.Of<IMediaPathScheme>();
var config = Mock.Of<IContentSection>();
var mediaFileSystem = new MediaFileSystem(Mock.Of<IFileSystem>(), config, scheme, logger);
var ignored = new FileUploadPropertyEditor(Mock.Of<ILogger>(), mediaFileSystem, config);
var media = MockedMedia.CreateMediaImage(mediaType, -1);
media.WriterId = -1; // else it's zero and that's not a user and it breaks the tests

View File

@@ -70,10 +70,11 @@ namespace Umbraco.Tests.PropertyEditors
container.RegisterCollectionBuilder<PropertyValueConverterCollectionBuilder>();
Current.Container.RegisterSingleton<ILogger>(f => Mock.Of<ILogger>());
Current.Container.RegisterSingleton<IContentSection>(f => Mock.Of<IContentSection>());
Current.Container.RegisterSingleton<IMediaPathScheme>(f => Mock.Of<IMediaPathScheme>());
var mediaFileSystem = new MediaFileSystem(Mock.Of<IFileSystem>());
var logger = Mock.Of<ILogger>();
var scheme = Mock.Of<IMediaPathScheme>();
var config = Mock.Of<IContentSection>();
var mediaFileSystem = new MediaFileSystem(Mock.Of<IFileSystem>(), config, scheme, logger);
var dataTypeService = new TestObjects.TestDataTypeService(
new DataType(new ImageCropperPropertyEditor(Mock.Of<ILogger>(), mediaFileSystem, Mock.Of<IContentSection>(), Mock.Of<IDataTypeService>())) { Id = 1 });

View File

@@ -117,7 +117,10 @@ namespace Umbraco.Tests.TestHelpers
if (logger == null) throw new ArgumentNullException(nameof(logger));
if (eventMessagesFactory == null) throw new ArgumentNullException(nameof(eventMessagesFactory));
var mediaFileSystem = new MediaFileSystem(Mock.Of<IFileSystem>());
var scheme = Mock.Of<IMediaPathScheme>();
var config = Mock.Of<IContentSection>();
var mediaFileSystem = new MediaFileSystem(Mock.Of<IFileSystem>(), config, scheme, logger);
var externalLoginService = GetLazyService<IExternalLoginService>(container, c => new ExternalLoginService(scopeProvider, logger, eventMessagesFactory, GetRepo<IExternalLoginRepository>(c)));
var publicAccessService = GetLazyService<IPublicAccessService>(container, c => new PublicAccessService(scopeProvider, logger, eventMessagesFactory, GetRepo<IPublicAccessRepository>(c)));

View File

@@ -295,7 +295,13 @@ namespace Umbraco.Tests.Testing
// register filesystems
Container.RegisterSingleton(factory => TestObjects.GetFileSystemsMock());
Container.RegisterSingleton<IMediaFileSystem>(factory => new MediaFileSystem(Mock.Of<IFileSystem>()));
var logger = Mock.Of<ILogger>();
var scheme = Mock.Of<IMediaPathScheme>();
var config = Mock.Of<IContentSection>();
var mediaFileSystem = new MediaFileSystem(Mock.Of<IFileSystem>(), config, scheme, logger);
Container.RegisterSingleton<IMediaFileSystem>(factory => mediaFileSystem);
// no factory (noop)
Container.RegisterSingleton<IPublishedModelFactory, NoopPublishedModelFactory>();