Merge remote-tracking branch 'origin/temp8' into temp8-logviewer
# Conflicts: # src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs
This commit is contained in:
@@ -22,11 +22,11 @@
|
||||
not want this to happen as the alpha of the next major is, really, the next major already.
|
||||
-->
|
||||
<dependency id="Microsoft.AspNet.SignalR.Core" version="[2.2.3, 2.999999)" />
|
||||
<dependency id="Umbraco.ModelsBuilder.Ui" version="[8.0.0-alpha.24]" />
|
||||
<dependency id="Umbraco.ModelsBuilder.Ui" version="[8.0.0-alpha.26]" />
|
||||
<dependency id="ImageProcessor.Web" version="[4.9.3.25,4.999999)" />
|
||||
<dependency id="ImageProcessor.Web.Config" version="[2.4.1.19,2.999999)" />
|
||||
<dependency id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="[2.0.0,2.999999)" />
|
||||
<dependency id="Microsoft.Net.Compilers" version="[2.9.0,2.999999)" />
|
||||
<dependency id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="[2.0.1,2.999999)" />
|
||||
<dependency id="Microsoft.Net.Compilers" version="[2.10.0,2.999999)" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
|
||||
@@ -19,4 +19,4 @@ using System.Resources;
|
||||
|
||||
// these are FYI and changed automatically
|
||||
[assembly: AssemblyFileVersion("8.0.0")]
|
||||
[assembly: AssemblyInformationalVersion("8.0.0-alpha.52")]
|
||||
[assembly: AssemblyInformationalVersion("8.0.0-alpha.56")]
|
||||
|
||||
@@ -4,27 +4,10 @@ using System.Web;
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that is exposed by the ApplicationContext for application wide caching purposes
|
||||
/// Represents the application-wide caches.
|
||||
/// </summary>
|
||||
public class CacheHelper
|
||||
{
|
||||
public static CacheHelper NoCache { get; } = new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance));
|
||||
|
||||
/// <summary>
|
||||
/// Creates a cache helper with disabled caches
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Good for unit testing
|
||||
/// </remarks>
|
||||
public static CacheHelper CreateDisabledCacheHelper()
|
||||
{
|
||||
// do *not* return NoCache
|
||||
// NoCache is a special instance that is detected by RepositoryBase and disables all cache policies
|
||||
// CreateDisabledCacheHelper is used in tests to use no cache, *but* keep all cache policies
|
||||
return new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance for use in the web
|
||||
/// </summary>
|
||||
@@ -40,7 +23,6 @@ namespace Umbraco.Core.Cache
|
||||
/// <summary>
|
||||
/// Initializes a new instance for use in the web
|
||||
/// </summary>
|
||||
/// <param name="cache"></param>
|
||||
public CacheHelper(System.Web.Caching.Cache cache)
|
||||
: this(
|
||||
new HttpRuntimeCacheProvider(cache),
|
||||
@@ -50,30 +32,39 @@ namespace Umbraco.Core.Cache
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance based on the provided providers
|
||||
/// </summary>
|
||||
/// <param name="httpCacheProvider"></param>
|
||||
/// <param name="staticCacheProvider"></param>
|
||||
/// <param name="requestCacheProvider"></param>
|
||||
/// <param name="isolatedCacheManager"></param>
|
||||
public CacheHelper(
|
||||
IRuntimeCacheProvider httpCacheProvider,
|
||||
ICacheProvider staticCacheProvider,
|
||||
ICacheProvider requestCacheProvider,
|
||||
IsolatedRuntimeCache isolatedCacheManager)
|
||||
{
|
||||
if (httpCacheProvider == null) throw new ArgumentNullException("httpCacheProvider");
|
||||
if (staticCacheProvider == null) throw new ArgumentNullException("staticCacheProvider");
|
||||
if (requestCacheProvider == null) throw new ArgumentNullException("requestCacheProvider");
|
||||
if (isolatedCacheManager == null) throw new ArgumentNullException("isolatedCacheManager");
|
||||
RuntimeCache = httpCacheProvider;
|
||||
StaticCache = staticCacheProvider;
|
||||
RequestCache = requestCacheProvider;
|
||||
IsolatedRuntimeCache = isolatedCacheManager;
|
||||
RuntimeCache = httpCacheProvider ?? throw new ArgumentNullException(nameof(httpCacheProvider));
|
||||
StaticCache = staticCacheProvider ?? throw new ArgumentNullException(nameof(staticCacheProvider));
|
||||
RequestCache = requestCacheProvider ?? throw new ArgumentNullException(nameof(requestCacheProvider));
|
||||
IsolatedRuntimeCache = isolatedCacheManager ?? throw new ArgumentNullException(nameof(isolatedCacheManager));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the special disabled instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>When used by repositories, all cache policies apply, but the underlying caches do not cache anything.</para>
|
||||
/// <para>Used by tests.</para>
|
||||
/// </remarks>
|
||||
public static CacheHelper Disabled { get; } = new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the special no-cache instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>When used by repositories, all cache policies are bypassed.</para>
|
||||
/// <para>Used by repositories that do no cache.</para>
|
||||
/// </remarks>
|
||||
public static CacheHelper NoCache { get; } = new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance));
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current Request cache
|
||||
/// </summary>
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
public class CacheRefresherCollectionBuilder : LazyCollectionBuilderBase<CacheRefresherCollectionBuilder, CacheRefresherCollection, ICacheRefresher>
|
||||
{
|
||||
public CacheRefresherCollectionBuilder(IServiceContainer container)
|
||||
: base(container)
|
||||
{ }
|
||||
|
||||
protected override CacheRefresherCollectionBuilder This => this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,30 @@ using Umbraco.Core.Services.Implement;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
public sealed class AuditEventsComponent : UmbracoComponentBase, IUmbracoCoreComponent
|
||||
public sealed class AuditEventsComponent : IComponent
|
||||
{
|
||||
private IAuditService _auditService;
|
||||
private IUserService _userService;
|
||||
private IEntityService _entityService;
|
||||
private readonly IAuditService _auditService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IEntityService _entityService;
|
||||
|
||||
public AuditEventsComponent(IAuditService auditService, IUserService userService, IEntityService entityService)
|
||||
{
|
||||
_auditService = auditService;
|
||||
_userService = userService;
|
||||
_entityService = entityService;
|
||||
|
||||
UserService.SavedUserGroup += OnSavedUserGroupWithUsers;
|
||||
|
||||
UserService.SavedUser += OnSavedUser;
|
||||
UserService.DeletedUser += OnDeletedUser;
|
||||
UserService.UserGroupPermissionsAssigned += UserGroupPermissionAssigned;
|
||||
|
||||
MemberService.Saved += OnSavedMember;
|
||||
MemberService.Deleted += OnDeletedMember;
|
||||
MemberService.AssignedRoles += OnAssignedRoles;
|
||||
MemberService.RemovedRoles += OnRemovedRoles;
|
||||
MemberService.Exported += OnMemberExported;
|
||||
}
|
||||
|
||||
private IUser CurrentPerformingUser
|
||||
{
|
||||
@@ -46,25 +65,6 @@ namespace Umbraco.Core.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize(IAuditService auditService, IUserService userService, IEntityService entityService)
|
||||
{
|
||||
_auditService = auditService;
|
||||
_userService = userService;
|
||||
_entityService = entityService;
|
||||
|
||||
UserService.SavedUserGroup += OnSavedUserGroupWithUsers;
|
||||
|
||||
UserService.SavedUser += OnSavedUser;
|
||||
UserService.DeletedUser += OnDeletedUser;
|
||||
UserService.UserGroupPermissionsAssigned += UserGroupPermissionAssigned;
|
||||
|
||||
MemberService.Saved += OnSavedMember;
|
||||
MemberService.Deleted += OnDeletedMember;
|
||||
MemberService.AssignedRoles += OnAssignedRoles;
|
||||
MemberService.RemovedRoles += OnRemovedRoles;
|
||||
MemberService.Exported += OnMemberExported;
|
||||
}
|
||||
|
||||
private string FormatEmail(IMember member)
|
||||
{
|
||||
return member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? "" : $"<{member.Email}>";
|
||||
|
||||
5
src/Umbraco.Core/Components/AuditEventsComposer.cs
Normal file
5
src/Umbraco.Core/Components/AuditEventsComposer.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
public sealed class AuditEventsComposer : ComponentComposer<AuditEventsComponent>, ICoreComposer
|
||||
{ }
|
||||
}
|
||||
@@ -1,368 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Collections;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
// note: this class is NOT thread-safe in any ways
|
||||
|
||||
internal class BootLoader
|
||||
{
|
||||
private readonly IServiceContainer _container;
|
||||
private readonly ProfilingLogger _proflog;
|
||||
private readonly ILogger _logger;
|
||||
private IUmbracoComponent[] _components;
|
||||
private bool _booted;
|
||||
|
||||
private const int LogThresholdMilliseconds = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BootLoader"/> class.
|
||||
/// </summary>
|
||||
/// <param name="container">The application container.</param>
|
||||
public BootLoader(IServiceContainer container)
|
||||
{
|
||||
_container = container ?? throw new ArgumentNullException(nameof(container));
|
||||
_proflog = container.GetInstance<ProfilingLogger>();
|
||||
_logger = container.GetInstance<ILogger>();
|
||||
}
|
||||
|
||||
private class EnableInfo
|
||||
{
|
||||
public bool Enabled;
|
||||
public int Weight = -1;
|
||||
}
|
||||
|
||||
public void Boot(IEnumerable<Type> componentTypes, RuntimeLevel level)
|
||||
{
|
||||
if (_booted) throw new InvalidOperationException("Can not boot, has already booted.");
|
||||
|
||||
var orderedComponentTypes = PrepareComponentTypes(componentTypes, level);
|
||||
|
||||
InstanciateComponents(orderedComponentTypes);
|
||||
ComposeComponents(level);
|
||||
|
||||
using (var scope = _container.GetInstance<IScopeProvider>().CreateScope())
|
||||
{
|
||||
InitializeComponents();
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
// rejoice!
|
||||
_booted = true;
|
||||
}
|
||||
|
||||
private IEnumerable<Type> PrepareComponentTypes(IEnumerable<Type> componentTypes, RuntimeLevel level)
|
||||
{
|
||||
using (_proflog.DebugDuration<BootLoader>("Preparing component types.", "Prepared component types."))
|
||||
{
|
||||
return PrepareComponentTypes2(componentTypes, level);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Type> PrepareComponentTypes2(IEnumerable<Type> componentTypes, RuntimeLevel level)
|
||||
{
|
||||
// create a list, remove those that cannot be enabled due to runtime level
|
||||
var componentTypeList = componentTypes
|
||||
.Where(x =>
|
||||
{
|
||||
// use the min level specified by the attribute if any
|
||||
// otherwise, user components have Run min level, anything else is Unknown (always run)
|
||||
var attr = x.GetCustomAttribute<RuntimeLevelAttribute>();
|
||||
var minLevel = attr?.MinLevel ?? (x.Implements<IUmbracoUserComponent>() ? RuntimeLevel.Run : RuntimeLevel.Unknown);
|
||||
return level >= minLevel;
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// cannot remove that one - ever
|
||||
if (componentTypeList.Contains(typeof(UmbracoCoreComponent)) == false)
|
||||
componentTypeList.Add(typeof(UmbracoCoreComponent));
|
||||
|
||||
// enable or disable components
|
||||
EnableDisableComponents(componentTypeList);
|
||||
|
||||
// sort the components according to their dependencies
|
||||
var requirements = new Dictionary<Type, List<Type>>();
|
||||
foreach (var type in componentTypeList) requirements[type] = null;
|
||||
foreach (var type in componentTypeList)
|
||||
{
|
||||
GatherRequirementsFromRequireAttribute(type, componentTypeList, requirements);
|
||||
GatherRequirementsFromRequiredAttribute(type, componentTypeList, requirements);
|
||||
}
|
||||
|
||||
// only for debugging, this is verbose
|
||||
//_logger.Debug<BootLoader>(GetComponentsReport(requirements));
|
||||
|
||||
// sort components
|
||||
var graph = new TopoGraph<Type, KeyValuePair<Type, List<Type>>>(kvp => kvp.Key, kvp => kvp.Value);
|
||||
graph.AddItems(requirements);
|
||||
List<Type> sortedComponentTypes;
|
||||
try
|
||||
{
|
||||
sortedComponentTypes = graph.GetSortedItems().Select(x => x.Key).ToList();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// in case of an error, force-dump everything to log
|
||||
_logger.Info<BootLoader>("Component Report:\r\n{ComponentReport}", GetComponentsReport(requirements));
|
||||
_logger.Error<BootLoader>(e, "Failed to sort compontents.");
|
||||
throw;
|
||||
}
|
||||
|
||||
// bit verbose but should help for troubleshooting
|
||||
var text = "Ordered Components: " + Environment.NewLine + string.Join(Environment.NewLine, sortedComponentTypes) + Environment.NewLine;
|
||||
Console.WriteLine(text);
|
||||
_logger.Debug<BootLoader>("Ordered Components: {SortedComponentTypes}", sortedComponentTypes);
|
||||
|
||||
return sortedComponentTypes;
|
||||
}
|
||||
|
||||
private static string GetComponentsReport(Dictionary<Type, List<Type>> requirements)
|
||||
{
|
||||
var text = new StringBuilder();
|
||||
text.AppendLine("Components & Dependencies:");
|
||||
text.AppendLine();
|
||||
|
||||
foreach (var kvp in requirements)
|
||||
{
|
||||
var type = kvp.Key;
|
||||
|
||||
text.AppendLine(type.FullName);
|
||||
foreach (var attribute in type.GetCustomAttributes<RequireComponentAttribute>())
|
||||
text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue
|
||||
? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")"))
|
||||
: ""));
|
||||
foreach (var attribute in type.GetCustomAttributes<RequiredComponentAttribute>())
|
||||
text.AppendLine(" -< " + attribute.RequiringType);
|
||||
foreach (var i in type.GetInterfaces())
|
||||
{
|
||||
text.AppendLine(" : " + i.FullName);
|
||||
foreach (var attribute in i.GetCustomAttributes<RequireComponentAttribute>())
|
||||
text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue
|
||||
? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")"))
|
||||
: ""));
|
||||
foreach (var attribute in i.GetCustomAttributes<RequiredComponentAttribute>())
|
||||
text.AppendLine(" -< " + attribute.RequiringType);
|
||||
}
|
||||
if (kvp.Value != null)
|
||||
foreach (var t in kvp.Value)
|
||||
text.AppendLine(" = " + t);
|
||||
text.AppendLine();
|
||||
}
|
||||
text.AppendLine("/");
|
||||
text.AppendLine();
|
||||
return text.ToString();
|
||||
}
|
||||
|
||||
private static void EnableDisableComponents(ICollection<Type> types)
|
||||
{
|
||||
var enabled = new Dictionary<Type, EnableInfo>();
|
||||
|
||||
// process the enable/disable attributes
|
||||
// these two attributes are *not* inherited and apply to *classes* only (not interfaces).
|
||||
// remote declarations (when a component enables/disables *another* component)
|
||||
// have priority over local declarations (when a component disables itself) so that
|
||||
// ppl can enable components that, by default, are disabled.
|
||||
// what happens in case of conflicting remote declarations is unspecified. more
|
||||
// precisely, the last declaration to be processed wins, but the order of the
|
||||
// declarations depends on the type finder and is unspecified.
|
||||
foreach (var componentType in types)
|
||||
{
|
||||
foreach (var attr in componentType.GetCustomAttributes<EnableComponentAttribute>())
|
||||
{
|
||||
var type = attr.EnabledType ?? componentType;
|
||||
if (enabled.TryGetValue(type, out var enableInfo) == false) enableInfo = enabled[type] = new EnableInfo();
|
||||
var weight = type == componentType ? 1 : 2;
|
||||
if (enableInfo.Weight > weight) continue;
|
||||
|
||||
enableInfo.Enabled = true;
|
||||
enableInfo.Weight = weight;
|
||||
}
|
||||
foreach (var attr in componentType.GetCustomAttributes<DisableComponentAttribute>())
|
||||
{
|
||||
var type = attr.DisabledType ?? componentType;
|
||||
if (type == typeof(UmbracoCoreComponent)) throw new InvalidOperationException("Cannot disable UmbracoCoreComponent.");
|
||||
if (enabled.TryGetValue(type, out var enableInfo) == false) enableInfo = enabled[type] = new EnableInfo();
|
||||
var weight = type == componentType ? 1 : 2;
|
||||
if (enableInfo.Weight > weight) continue;
|
||||
|
||||
enableInfo.Enabled = false;
|
||||
enableInfo.Weight = weight;
|
||||
}
|
||||
}
|
||||
|
||||
// remove components that end up being disabled
|
||||
foreach (var kvp in enabled.Where(x => x.Value.Enabled == false))
|
||||
types.Remove(kvp.Key);
|
||||
}
|
||||
|
||||
private static void GatherRequirementsFromRequireAttribute(Type type, ICollection<Type> types, IDictionary<Type, List<Type>> requirements)
|
||||
{
|
||||
// get 'require' attributes
|
||||
// these attributes are *not* inherited because we want to "custom-inherit" for interfaces only
|
||||
var requireAttributes = type
|
||||
.GetInterfaces().SelectMany(x => x.GetCustomAttributes<RequireComponentAttribute>()) // those marking interfaces
|
||||
.Concat(type.GetCustomAttributes<RequireComponentAttribute>()); // those marking the component
|
||||
|
||||
// what happens in case of conflicting attributes (different strong/weak for same type) is not specified.
|
||||
foreach (var attr in requireAttributes)
|
||||
{
|
||||
if (attr.RequiredType == type) continue; // ignore self-requirements (+ exclude in implems, below)
|
||||
|
||||
// requiring an interface = require any enabled component implementing that interface
|
||||
// unless strong, and then require at least one enabled component implementing that interface
|
||||
if (attr.RequiredType.IsInterface)
|
||||
{
|
||||
var implems = types.Where(x => x != type && attr.RequiredType.IsAssignableFrom(x)).ToList();
|
||||
if (implems.Count > 0)
|
||||
{
|
||||
if (requirements[type] == null) requirements[type] = new List<Type>();
|
||||
requirements[type].AddRange(implems);
|
||||
}
|
||||
else if (attr.Weak == false) // if explicitely set to !weak, is strong, else is weak
|
||||
throw new Exception($"Broken component dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
|
||||
}
|
||||
// requiring a class = require that the component is enabled
|
||||
// unless weak, and then requires it if it is enabled
|
||||
else
|
||||
{
|
||||
if (types.Contains(attr.RequiredType))
|
||||
{
|
||||
if (requirements[type] == null) requirements[type] = new List<Type>();
|
||||
requirements[type].Add(attr.RequiredType);
|
||||
}
|
||||
else if (attr.Weak != true) // if not explicitely set to weak, is strong
|
||||
throw new Exception($"Broken component dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void GatherRequirementsFromRequiredAttribute(Type type, ICollection<Type> types, IDictionary<Type, List<Type>> requirements)
|
||||
{
|
||||
// get 'required' attributes
|
||||
// fixme explain
|
||||
var requiredAttributes = type
|
||||
.GetInterfaces().SelectMany(x => x.GetCustomAttributes<RequiredComponentAttribute>())
|
||||
.Concat(type.GetCustomAttributes<RequiredComponentAttribute>());
|
||||
|
||||
foreach (var attr in requiredAttributes)
|
||||
{
|
||||
if (attr.RequiringType == type) continue; // ignore self-requirements (+ exclude in implems, below)
|
||||
|
||||
if (attr.RequiringType.IsInterface)
|
||||
{
|
||||
var implems = types.Where(x => x != type && attr.RequiringType.IsAssignableFrom(x)).ToList();
|
||||
foreach (var implem in implems)
|
||||
{
|
||||
if (requirements[implem] == null) requirements[implem] = new List<Type>();
|
||||
requirements[implem].Add(type);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (types.Contains(attr.RequiringType))
|
||||
{
|
||||
if (requirements[attr.RequiringType] == null) requirements[attr.RequiringType] = new List<Type>();
|
||||
requirements[attr.RequiringType].Add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InstanciateComponents(IEnumerable<Type> types)
|
||||
{
|
||||
using (_proflog.DebugDuration<BootLoader>("Instanciating components.", "Instanciated components."))
|
||||
{
|
||||
_components = types.Select(x => (IUmbracoComponent) Activator.CreateInstance(x)).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private void ComposeComponents(RuntimeLevel level)
|
||||
{
|
||||
using (_proflog.DebugDuration<BootLoader>($"Composing components. (log when >{LogThresholdMilliseconds}ms)", "Composed components."))
|
||||
{
|
||||
var composition = new Composition(_container, level);
|
||||
foreach (var component in _components)
|
||||
{
|
||||
var componentType = component.GetType();
|
||||
using (_proflog.DebugDuration<BootLoader>($"Composing {componentType.FullName}.", $"Composed {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
|
||||
{
|
||||
component.Compose(composition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeComponents()
|
||||
{
|
||||
// use a container scope to ensure that PerScope instances are disposed
|
||||
// components that require instances that should not survive should register them with PerScope lifetime
|
||||
using (_proflog.DebugDuration<BootLoader>($"Initializing components. (log when >{LogThresholdMilliseconds}ms)", "Initialized components."))
|
||||
using (_container.BeginScope())
|
||||
{
|
||||
foreach (var component in _components)
|
||||
{
|
||||
var componentType = component.GetType();
|
||||
var initializers = componentType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Where(x => x.Name == "Initialize" && x.IsGenericMethod == false);
|
||||
using (_proflog.DebugDuration<BootLoader>($"Initializing {componentType.FullName}.", $"Initialised {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
|
||||
{
|
||||
foreach (var initializer in initializers)
|
||||
{
|
||||
var parameters = initializer.GetParameters()
|
||||
.Select(x => GetParameter(componentType, x.ParameterType))
|
||||
.ToArray();
|
||||
initializer.Invoke(component, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object GetParameter(Type componentType, Type parameterType)
|
||||
{
|
||||
object param;
|
||||
|
||||
try
|
||||
{
|
||||
param = _container.TryGetInstance(parameterType);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new BootFailedException($"Could not get parameter of type {parameterType.FullName} for component {componentType.FullName}.", e);
|
||||
}
|
||||
|
||||
if (param == null) throw new BootFailedException($"Could not get parameter of type {parameterType.FullName} for component {componentType.FullName}.");
|
||||
return param;
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
if (_booted == false)
|
||||
{
|
||||
_proflog.Logger.Warn<BootLoader>("Cannot terminate, has not booted.");
|
||||
return;
|
||||
}
|
||||
|
||||
using (_proflog.DebugDuration<BootLoader>($"Terminating. (log components when >{LogThresholdMilliseconds}ms)", "Terminated."))
|
||||
{
|
||||
for (var i = _components.Length - 1; i >= 0; i--) // terminate components in reverse order
|
||||
{
|
||||
var component = _components[i];
|
||||
var componentType = component.GetType();
|
||||
using (_proflog.DebugDuration<BootLoader>($"Terminating {componentType.FullName}.", $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
|
||||
{
|
||||
component.Terminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/Umbraco.Core/Components/ComponentCollection.cs
Normal file
38
src/Umbraco.Core/Components/ComponentCollection.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the collection of <see cref="IComponent"/> implementations.
|
||||
/// </summary>
|
||||
public class ComponentCollection : BuilderCollectionBase<IComponent>
|
||||
{
|
||||
private const int LogThresholdMilliseconds = 100;
|
||||
|
||||
private readonly IProfilingLogger _logger;
|
||||
|
||||
public ComponentCollection(IEnumerable<IComponent> items, IProfilingLogger logger)
|
||||
: base(items)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
using (_logger.DebugDuration<ComponentCollection>($"Terminating. (log components when >{LogThresholdMilliseconds}ms)", "Terminated."))
|
||||
{
|
||||
foreach (var component in this.Reverse()) // terminate components in reverse order
|
||||
{
|
||||
var componentType = component.GetType();
|
||||
using (_logger.DebugDuration<ComponentCollection>($"Terminating {componentType.FullName}.", $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
|
||||
{
|
||||
component.DisposeIfDisposable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/Umbraco.Core/Components/ComponentCollectionBuilder.cs
Normal file
40
src/Umbraco.Core/Components/ComponentCollectionBuilder.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds a <see cref="ComponentCollection"/>.
|
||||
/// </summary>
|
||||
public class ComponentCollectionBuilder : OrderedCollectionBuilderBase<ComponentCollectionBuilder, ComponentCollection, IComponent>
|
||||
{
|
||||
private const int LogThresholdMilliseconds = 100;
|
||||
|
||||
private IProfilingLogger _logger;
|
||||
|
||||
public ComponentCollectionBuilder()
|
||||
{ }
|
||||
|
||||
protected override ComponentCollectionBuilder This => this;
|
||||
|
||||
protected override IEnumerable<IComponent> CreateItems(IFactory factory)
|
||||
{
|
||||
_logger = factory.GetInstance<IProfilingLogger>();
|
||||
|
||||
using (_logger.DebugDuration<ComponentCollectionBuilder>($"Creating components. (log when >{LogThresholdMilliseconds}ms)", "Created."))
|
||||
{
|
||||
return base.CreateItems(factory);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IComponent CreateItem(IFactory factory, Type itemType)
|
||||
{
|
||||
using (_logger.DebugDuration<Composers>($"Creating {itemType.FullName}.", $"Created {itemType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
|
||||
{
|
||||
return base.CreateItem(factory, itemType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/Umbraco.Core/Components/ComponentComposer.cs
Normal file
20
src/Umbraco.Core/Components/ComponentComposer.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class for composers which compose a component.
|
||||
/// </summary>
|
||||
/// <typeparam name="TComponent">The type of the component</typeparam>
|
||||
public abstract class ComponentComposer<TComponent> : IComposer
|
||||
where TComponent : IComponent
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public virtual void Compose(Composition composition)
|
||||
{
|
||||
composition.Components().Append<TComponent>();
|
||||
}
|
||||
|
||||
// note: thanks to this class, a component that does not compose anything can be
|
||||
// registered with one line:
|
||||
// public class MyComponentComposer : ComponentComposer<MyComponent> { }
|
||||
}
|
||||
}
|
||||
59
src/Umbraco.Core/Components/ComposeAfterAttribute.cs
Normal file
59
src/Umbraco.Core/Components/ComposeAfterAttribute.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a composer requires another composer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This attribute is *not* inherited. This means that a composer class inheriting from
|
||||
/// another composer class does *not* inherit its requirements. However, the runtime checks
|
||||
/// the *interfaces* of every composer for their requirements, so requirements declared on
|
||||
/// interfaces are inherited by every composer class implementing the interface.</para>
|
||||
/// <para>When targeting a class, indicates a dependency on the composer which must be enabled,
|
||||
/// unless the requirement has explicitly been declared as weak (and then, only if the composer
|
||||
/// is enabled).</para>
|
||||
/// <para>When targeting an interface, indicates a dependency on enabled composers implementing
|
||||
/// the interface. It could be no composer at all, unless the requirement has explicitly been
|
||||
/// declared as strong (and at least one composer must be enabled).</para>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
|
||||
public sealed class ComposeAfterAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ComposeAfterAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="requiredType">The type of the required composer.</param>
|
||||
public ComposeAfterAttribute(Type requiredType)
|
||||
{
|
||||
if (typeof(IComposer).IsAssignableFrom(requiredType) == false)
|
||||
throw new ArgumentException($"Type {requiredType.FullName} is invalid here because it does not implement {typeof(IComposer).FullName}.");
|
||||
RequiredType = requiredType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ComposeAfterAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="requiredType">The type of the required composer.</param>
|
||||
/// <param name="weak">A value indicating whether the requirement is weak.</param>
|
||||
public ComposeAfterAttribute(Type requiredType, bool weak)
|
||||
: this(requiredType)
|
||||
{
|
||||
Weak = weak;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the required type.
|
||||
/// </summary>
|
||||
public Type RequiredType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the requirement is weak.
|
||||
/// </summary>
|
||||
/// <remarks>Returns <c>true</c> if the requirement is weak (requires the other composer if it
|
||||
/// is enabled), <c>false</c> if the requirement is strong (requires the other composer to be
|
||||
/// enabled), and <c>null</c> if unspecified, in which case it is strong for classes and weak for
|
||||
/// interfaces.</remarks>
|
||||
public bool? Weak { get; }
|
||||
}
|
||||
}
|
||||
40
src/Umbraco.Core/Components/ComposeBeforeAttribute.cs
Normal file
40
src/Umbraco.Core/Components/ComposeBeforeAttribute.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a component is required by another composer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This attribute is *not* inherited. This means that a composer class inheriting from
|
||||
/// another composer class does *not* inherit its requirements. However, the runtime checks
|
||||
/// the *interfaces* of every composer for their requirements, so requirements declared on
|
||||
/// interfaces are inherited by every composer class implementing the interface.</para>
|
||||
/// <para>When targeting a class, indicates a dependency on the composer which must be enabled,
|
||||
/// unless the requirement has explicitly been declared as weak (and then, only if the composer
|
||||
/// is enabled).</para>
|
||||
/// <para>When targeting an interface, indicates a dependency on enabled composers implementing
|
||||
/// the interface. It could be no composer at all, unless the requirement has explicitly been
|
||||
/// declared as strong (and at least one composer must be enabled).</para>
|
||||
/// </remarks>
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
|
||||
public sealed class ComposeBeforeAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ComposeBeforeAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="requiringType">The type of the required composer.</param>
|
||||
public ComposeBeforeAttribute(Type requiringType)
|
||||
{
|
||||
if (typeof(IComposer).IsAssignableFrom(requiringType) == false)
|
||||
throw new ArgumentException($"Type {requiringType.FullName} is invalid here because it does not implement {typeof(IComposer).FullName}.");
|
||||
RequiringType = requiringType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the required type.
|
||||
/// </summary>
|
||||
public Type RequiringType { get; }
|
||||
}
|
||||
}
|
||||
294
src/Umbraco.Core/Components/Composers.cs
Normal file
294
src/Umbraco.Core/Components/Composers.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Umbraco.Core.Collections;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
// note: this class is NOT thread-safe in any ways
|
||||
|
||||
/// <summary>
|
||||
/// Handles the composers.
|
||||
/// </summary>
|
||||
public class Composers
|
||||
{
|
||||
private readonly Composition _composition;
|
||||
private readonly IProfilingLogger _logger;
|
||||
private readonly IEnumerable<Type> _composerTypes;
|
||||
|
||||
private const int LogThresholdMilliseconds = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Composers"/> class.
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
/// <param name="composerTypes">The composer types.</param>
|
||||
/// <param name="logger">A profiling logger.</param>
|
||||
public Composers(Composition composition, IEnumerable<Type> composerTypes, IProfilingLogger logger)
|
||||
{
|
||||
_composition = composition ?? throw new ArgumentNullException(nameof(composition));
|
||||
_composerTypes = composerTypes ?? throw new ArgumentNullException(nameof(composerTypes));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
private class EnableInfo
|
||||
{
|
||||
public bool Enabled;
|
||||
public int Weight = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates and composes the composers.
|
||||
/// </summary>
|
||||
public void Compose()
|
||||
{
|
||||
// make sure it is there
|
||||
_composition.WithCollectionBuilder<ComponentCollectionBuilder>();
|
||||
|
||||
IEnumerable<Type> orderedComposerTypes;
|
||||
|
||||
using (_logger.DebugDuration<Composers>("Preparing composer types.", "Prepared composer types."))
|
||||
{
|
||||
orderedComposerTypes = PrepareComposerTypes();
|
||||
}
|
||||
|
||||
var composers = InstantiateComposers(orderedComposerTypes);
|
||||
|
||||
using (_logger.DebugDuration<Composers>($"Composing composers. (log when >{LogThresholdMilliseconds}ms)", "Composed composers."))
|
||||
{
|
||||
foreach (var composer in composers)
|
||||
{
|
||||
var componentType = composer.GetType();
|
||||
using (_logger.DebugDuration<Composers>($"Composing {componentType.FullName}.", $"Composed {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
|
||||
{
|
||||
composer.Compose(_composition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Type> PrepareComposerTypes()
|
||||
{
|
||||
// create a list, remove those that cannot be enabled due to runtime level
|
||||
var composerTypeList = _composerTypes
|
||||
.Where(x =>
|
||||
{
|
||||
// use the min level specified by the attribute if any
|
||||
// otherwise, user composers have Run min level, anything else is Unknown (always run)
|
||||
var attr = x.GetCustomAttribute<RuntimeLevelAttribute>();
|
||||
var minLevel = attr?.MinLevel ?? (x.Implements<IUserComposer>() ? RuntimeLevel.Run : RuntimeLevel.Unknown);
|
||||
return _composition.RuntimeState.Level >= minLevel;
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// enable or disable composers
|
||||
EnableDisableComposers(composerTypeList);
|
||||
|
||||
// sort the composers according to their dependencies
|
||||
var requirements = new Dictionary<Type, List<Type>>();
|
||||
foreach (var type in composerTypeList) requirements[type] = null;
|
||||
foreach (var type in composerTypeList)
|
||||
{
|
||||
GatherRequirementsFromRequireAttribute(type, composerTypeList, requirements);
|
||||
GatherRequirementsFromRequiredByAttribute(type, composerTypeList, requirements);
|
||||
}
|
||||
|
||||
// only for debugging, this is verbose
|
||||
//_logger.Debug<Composers>(GetComposersReport(requirements));
|
||||
|
||||
// sort composers
|
||||
var graph = new TopoGraph<Type, KeyValuePair<Type, List<Type>>>(kvp => kvp.Key, kvp => kvp.Value);
|
||||
graph.AddItems(requirements);
|
||||
List<Type> sortedComposerTypes;
|
||||
try
|
||||
{
|
||||
sortedComposerTypes = graph.GetSortedItems().Select(x => x.Key).ToList();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// in case of an error, force-dump everything to log
|
||||
_logger.Info<Composers>("Composer Report:\r\n{ComposerReport}", GetComposersReport(requirements));
|
||||
_logger.Error<Composers>(e, "Failed to sort composers.");
|
||||
throw;
|
||||
}
|
||||
|
||||
// bit verbose but should help for troubleshooting
|
||||
//var text = "Ordered Composers: " + Environment.NewLine + string.Join(Environment.NewLine, sortedComposerTypes) + Environment.NewLine;
|
||||
_logger.Debug<Composers>("Ordered Composers: {SortedComposerTypes}", sortedComposerTypes);
|
||||
|
||||
return sortedComposerTypes;
|
||||
}
|
||||
|
||||
private static string GetComposersReport(Dictionary<Type, List<Type>> requirements)
|
||||
{
|
||||
var text = new StringBuilder();
|
||||
text.AppendLine("Composers & Dependencies:");
|
||||
text.AppendLine();
|
||||
|
||||
foreach (var kvp in requirements)
|
||||
{
|
||||
var type = kvp.Key;
|
||||
|
||||
text.AppendLine(type.FullName);
|
||||
foreach (var attribute in type.GetCustomAttributes<ComposeAfterAttribute>())
|
||||
text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue
|
||||
? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")"))
|
||||
: ""));
|
||||
foreach (var attribute in type.GetCustomAttributes<ComposeBeforeAttribute>())
|
||||
text.AppendLine(" -< " + attribute.RequiringType);
|
||||
foreach (var i in type.GetInterfaces())
|
||||
{
|
||||
text.AppendLine(" : " + i.FullName);
|
||||
foreach (var attribute in i.GetCustomAttributes<ComposeAfterAttribute>())
|
||||
text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue
|
||||
? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")"))
|
||||
: ""));
|
||||
foreach (var attribute in i.GetCustomAttributes<ComposeBeforeAttribute>())
|
||||
text.AppendLine(" -< " + attribute.RequiringType);
|
||||
}
|
||||
if (kvp.Value != null)
|
||||
foreach (var t in kvp.Value)
|
||||
text.AppendLine(" = " + t);
|
||||
text.AppendLine();
|
||||
}
|
||||
text.AppendLine("/");
|
||||
text.AppendLine();
|
||||
return text.ToString();
|
||||
}
|
||||
|
||||
private static void EnableDisableComposers(ICollection<Type> types)
|
||||
{
|
||||
var enabled = new Dictionary<Type, EnableInfo>();
|
||||
|
||||
// process the enable/disable attributes
|
||||
// these two attributes are *not* inherited and apply to *classes* only (not interfaces).
|
||||
// remote declarations (when a composer enables/disables *another* composer)
|
||||
// have priority over local declarations (when a composer disables itself) so that
|
||||
// ppl can enable composers that, by default, are disabled.
|
||||
// what happens in case of conflicting remote declarations is unspecified. more
|
||||
// precisely, the last declaration to be processed wins, but the order of the
|
||||
// declarations depends on the type finder and is unspecified.
|
||||
foreach (var composerType in types)
|
||||
{
|
||||
foreach (var attr in composerType.GetCustomAttributes<EnableAttribute>())
|
||||
{
|
||||
var type = attr.EnabledType ?? composerType;
|
||||
if (enabled.TryGetValue(type, out var enableInfo) == false) enableInfo = enabled[type] = new EnableInfo();
|
||||
var weight = type == composerType ? 1 : 2;
|
||||
if (enableInfo.Weight > weight) continue;
|
||||
|
||||
enableInfo.Enabled = true;
|
||||
enableInfo.Weight = weight;
|
||||
}
|
||||
foreach (var attr in composerType.GetCustomAttributes<DisableAttribute>())
|
||||
{
|
||||
var type = attr.DisabledType ?? composerType;
|
||||
if (enabled.TryGetValue(type, out var enableInfo) == false) enableInfo = enabled[type] = new EnableInfo();
|
||||
var weight = type == composerType ? 1 : 2;
|
||||
if (enableInfo.Weight > weight) continue;
|
||||
|
||||
enableInfo.Enabled = false;
|
||||
enableInfo.Weight = weight;
|
||||
}
|
||||
}
|
||||
|
||||
// remove composers that end up being disabled
|
||||
foreach (var kvp in enabled.Where(x => x.Value.Enabled == false))
|
||||
types.Remove(kvp.Key);
|
||||
}
|
||||
|
||||
private static void GatherRequirementsFromRequireAttribute(Type type, ICollection<Type> types, IDictionary<Type, List<Type>> requirements)
|
||||
{
|
||||
// get 'require' attributes
|
||||
// these attributes are *not* inherited because we want to "custom-inherit" for interfaces only
|
||||
var requireAttributes = type
|
||||
.GetInterfaces().SelectMany(x => x.GetCustomAttributes<ComposeAfterAttribute>()) // those marking interfaces
|
||||
.Concat(type.GetCustomAttributes<ComposeAfterAttribute>()); // those marking the composer
|
||||
|
||||
// what happens in case of conflicting attributes (different strong/weak for same type) is not specified.
|
||||
foreach (var attr in requireAttributes)
|
||||
{
|
||||
if (attr.RequiredType == type) continue; // ignore self-requirements (+ exclude in implems, below)
|
||||
|
||||
// requiring an interface = require any enabled composer implementing that interface
|
||||
// unless strong, and then require at least one enabled composer implementing that interface
|
||||
if (attr.RequiredType.IsInterface)
|
||||
{
|
||||
var implems = types.Where(x => x != type && attr.RequiredType.IsAssignableFrom(x)).ToList();
|
||||
if (implems.Count > 0)
|
||||
{
|
||||
if (requirements[type] == null) requirements[type] = new List<Type>();
|
||||
requirements[type].AddRange(implems);
|
||||
}
|
||||
else if (attr.Weak == false) // if explicitly set to !weak, is strong, else is weak
|
||||
throw new Exception($"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
|
||||
}
|
||||
// requiring a class = require that the composer is enabled
|
||||
// unless weak, and then requires it if it is enabled
|
||||
else
|
||||
{
|
||||
if (types.Contains(attr.RequiredType))
|
||||
{
|
||||
if (requirements[type] == null) requirements[type] = new List<Type>();
|
||||
requirements[type].Add(attr.RequiredType);
|
||||
}
|
||||
else if (attr.Weak != true) // if not explicitly set to weak, is strong
|
||||
throw new Exception($"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void GatherRequirementsFromRequiredByAttribute(Type type, ICollection<Type> types, IDictionary<Type, List<Type>> requirements)
|
||||
{
|
||||
// get 'required' attributes
|
||||
// these attributes are *not* inherited because we want to "custom-inherit" for interfaces only
|
||||
var requiredAttributes = type
|
||||
.GetInterfaces().SelectMany(x => x.GetCustomAttributes<ComposeBeforeAttribute>()) // those marking interfaces
|
||||
.Concat(type.GetCustomAttributes<ComposeBeforeAttribute>()); // those marking the composer
|
||||
|
||||
foreach (var attr in requiredAttributes)
|
||||
{
|
||||
if (attr.RequiringType == type) continue; // ignore self-requirements (+ exclude in implems, below)
|
||||
|
||||
// required by an interface = by any enabled composer implementing this that interface
|
||||
if (attr.RequiringType.IsInterface)
|
||||
{
|
||||
var implems = types.Where(x => x != type && attr.RequiringType.IsAssignableFrom(x)).ToList();
|
||||
foreach (var implem in implems)
|
||||
{
|
||||
if (requirements[implem] == null) requirements[implem] = new List<Type>();
|
||||
requirements[implem].Add(type);
|
||||
}
|
||||
}
|
||||
// required by a class
|
||||
else
|
||||
{
|
||||
if (types.Contains(attr.RequiringType))
|
||||
{
|
||||
if (requirements[attr.RequiringType] == null) requirements[attr.RequiringType] = new List<Type>();
|
||||
requirements[attr.RequiringType].Add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<IComposer> InstantiateComposers(IEnumerable<Type> types)
|
||||
{
|
||||
IComposer InstantiateComposer(Type type)
|
||||
{
|
||||
var ctor = type.GetConstructor(Array.Empty<Type>());
|
||||
if (ctor == null)
|
||||
throw new InvalidOperationException($"Composer {type.FullName} does not have a parameter-less constructor.");
|
||||
return (IComposer) ctor.Invoke(Array.Empty<object>());
|
||||
}
|
||||
|
||||
using (_logger.DebugDuration<Composers>("Instantiating composers.", "Instantiated composers."))
|
||||
{
|
||||
return types.Select(InstantiateComposer).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
using LightInject;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
@@ -10,28 +13,190 @@ namespace Umbraco.Core.Components
|
||||
/// avoid accessing the container. This is because everything needs to be properly registered and with
|
||||
/// the proper lifecycle. These methods will take care of it. Directly registering into the container
|
||||
/// may cause issues.</remarks>
|
||||
public class Composition
|
||||
public class Composition : IRegister
|
||||
{
|
||||
private readonly Dictionary<Type, ICollectionBuilder> _builders = new Dictionary<Type, ICollectionBuilder>();
|
||||
private readonly Dictionary<Type, Unique> _uniques = new Dictionary<Type, Unique>();
|
||||
private readonly IRegister _register;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Composition"/> class.
|
||||
/// </summary>
|
||||
/// <param name="container">A container.</param>
|
||||
/// <param name="level">The runtime level.</param>
|
||||
public Composition(IServiceContainer container, RuntimeLevel level)
|
||||
/// <param name="register">A register.</param>
|
||||
/// <param name="typeLoader">A type loader.</param>
|
||||
/// <param name="logger">A logger.</param>
|
||||
/// <param name="runtimeState">The runtime state.</param>
|
||||
public Composition(IRegister register, TypeLoader typeLoader, IProfilingLogger logger, IRuntimeState runtimeState)
|
||||
{
|
||||
Container = container;
|
||||
RuntimeLevel = level;
|
||||
_register = register;
|
||||
TypeLoader = typeLoader;
|
||||
Logger = logger;
|
||||
RuntimeState = runtimeState;
|
||||
}
|
||||
|
||||
#region Services
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger.
|
||||
/// </summary>
|
||||
public IProfilingLogger Logger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type loader.
|
||||
/// </summary>
|
||||
public TypeLoader TypeLoader { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the runtime state.
|
||||
/// </summary>
|
||||
public IRuntimeState RuntimeState { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region IRegister
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Concrete => _register.Concrete;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Register(Type serviceType, Lifetime lifetime = Lifetime.Transient)
|
||||
=> _register.Register(serviceType, lifetime);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Register(Type serviceType, Type implementingType, Lifetime lifetime = Lifetime.Transient)
|
||||
=> _register.Register(serviceType, implementingType, lifetime);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Register<TService>(Func<IFactory, TService> factory, Lifetime lifetime = Lifetime.Transient)
|
||||
=> _register.Register(factory, lifetime);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RegisterInstance(Type serviceType, object instance)
|
||||
=> _register.RegisterInstance(serviceType, instance);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RegisterAuto(Type serviceBaseType)
|
||||
=> _register.RegisterAuto(serviceBaseType);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ConfigureForWeb()
|
||||
=> _register.ConfigureForWeb();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFactory CreateFactory()
|
||||
{
|
||||
foreach (var onCreating in OnCreatingFactory.Values)
|
||||
onCreating();
|
||||
|
||||
foreach (var unique in _uniques.Values)
|
||||
unique.RegisterWith(_register);
|
||||
|
||||
foreach (var builder in _builders.Values)
|
||||
builder.RegisterWith(_register);
|
||||
|
||||
return _register.CreateFactory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the container.
|
||||
/// Gets a dictionary of action to execute when creating the factory.
|
||||
/// </summary>
|
||||
/// <remarks>Use with care!</remarks>
|
||||
public IServiceContainer Container { get; }
|
||||
public Dictionary<string, Action> OnCreatingFactory { get; } = new Dictionary<string, Action>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unique
|
||||
|
||||
/// <summary>
|
||||
/// Gets the runtime level.
|
||||
/// Registers a unique service.
|
||||
/// </summary>
|
||||
public RuntimeLevel RuntimeLevel { get; }
|
||||
/// <remarks>Unique services have one single implementation, and a Singleton lifetime.</remarks>
|
||||
public void RegisterUnique(Type serviceType, Type implementingType)
|
||||
=> _uniques[serviceType] = new Unique(serviceType, implementingType);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a unique service.
|
||||
/// </summary>
|
||||
/// <remarks>Unique services have one single implementation, and a Singleton lifetime.</remarks>
|
||||
public void RegisterUnique(Type serviceType, object instance)
|
||||
=> _uniques[serviceType] = new Unique(serviceType, instance);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a unique service.
|
||||
/// </summary>
|
||||
/// <remarks>Unique services have one single implementation, and a Singleton lifetime.</remarks>
|
||||
public void RegisterUnique<TService>(Func<IFactory, TService> factory)
|
||||
=> _uniques[typeof(TService)] = new Unique<TService>(factory);
|
||||
|
||||
private class Unique
|
||||
{
|
||||
private readonly Type _serviceType;
|
||||
private readonly Type _implementingType;
|
||||
private readonly object _instance;
|
||||
|
||||
protected Unique(Type serviceType)
|
||||
{
|
||||
_serviceType = serviceType;
|
||||
}
|
||||
|
||||
public Unique(Type serviceType, Type implementingType)
|
||||
: this(serviceType)
|
||||
{
|
||||
_implementingType = implementingType;
|
||||
}
|
||||
|
||||
public Unique(Type serviceType, object instance)
|
||||
: this(serviceType)
|
||||
{
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
public virtual void RegisterWith(IRegister register)
|
||||
{
|
||||
if (_implementingType != null)
|
||||
register.Register(_serviceType, _implementingType, Lifetime.Singleton);
|
||||
else if (_instance != null)
|
||||
register.RegisterInstance(_serviceType, _instance);
|
||||
}
|
||||
}
|
||||
|
||||
private class Unique<TService> : Unique
|
||||
{
|
||||
private readonly Func<IFactory, TService> _factory;
|
||||
|
||||
public Unique(Func<IFactory, TService> factory)
|
||||
: base(typeof(TService))
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public override void RegisterWith(IRegister register)
|
||||
{
|
||||
register.Register(_factory, Lifetime.Singleton);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Collection Builders
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection builder (and registers the collection).
|
||||
/// </summary>
|
||||
/// <typeparam name="TBuilder">The type of the collection builder.</typeparam>
|
||||
/// <returns>The collection builder.</returns>
|
||||
public TBuilder WithCollectionBuilder<TBuilder>()
|
||||
where TBuilder: ICollectionBuilder, new()
|
||||
{
|
||||
var typeOfBuilder = typeof(TBuilder);
|
||||
|
||||
if (_builders.TryGetValue(typeOfBuilder, out var o))
|
||||
return (TBuilder) o;
|
||||
|
||||
var builder = new TBuilder();
|
||||
_builders[typeOfBuilder] = builder;
|
||||
return builder;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Migrations;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Persistence.Mappers;
|
||||
@@ -19,6 +18,45 @@ namespace Umbraco.Core.Components
|
||||
/// </summary>
|
||||
public static class CompositionExtensions
|
||||
{
|
||||
#region FileSystems
|
||||
|
||||
/// <summary>
|
||||
/// Registers a filesystem.
|
||||
/// </summary>
|
||||
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
|
||||
/// <typeparam name="TImplementing">The implementing type.</typeparam>
|
||||
/// <param name="composition">The composition.</param>
|
||||
/// <param name="supportingFileSystemFactory">A factory method creating the supporting filesystem.</param>
|
||||
/// <returns>The register.</returns>
|
||||
public static void RegisterFileSystem<TFileSystem, TImplementing>(this Composition composition, Func<IFactory, IFileSystem> supportingFileSystemFactory)
|
||||
where TImplementing : FileSystemWrapper, TFileSystem
|
||||
{
|
||||
composition.RegisterUnique<TFileSystem>(factory =>
|
||||
{
|
||||
var fileSystems = factory.GetInstance<FileSystems>();
|
||||
return fileSystems.GetFileSystem<TImplementing>(supportingFileSystemFactory(factory));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a filesystem.
|
||||
/// </summary>
|
||||
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
|
||||
/// <param name="composition">The composition.</param>
|
||||
/// <param name="supportingFileSystemFactory">A factory method creating the supporting filesystem.</param>
|
||||
/// <returns>The register.</returns>
|
||||
public static void RegisterFileSystem<TFileSystem>(this Composition composition, Func<IFactory, IFileSystem> supportingFileSystemFactory)
|
||||
where TFileSystem : FileSystemWrapper
|
||||
{
|
||||
composition.RegisterUnique(factory =>
|
||||
{
|
||||
var fileSystems = factory.GetInstance<FileSystems>();
|
||||
return fileSystems.GetFileSystem<TFileSystem>(supportingFileSystemFactory(factory));
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Collection Builders
|
||||
|
||||
/// <summary>
|
||||
@@ -26,60 +64,66 @@ namespace Umbraco.Core.Components
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
public static CacheRefresherCollectionBuilder CacheRefreshers(this Composition composition)
|
||||
=> composition.Container.GetInstance<CacheRefresherCollectionBuilder>();
|
||||
=> composition.WithCollectionBuilder<CacheRefresherCollectionBuilder>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mappers collection builder.
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
public static MapperCollectionBuilder Mappers(this Composition composition)
|
||||
=> composition.Container.GetInstance<MapperCollectionBuilder>();
|
||||
=> composition.WithCollectionBuilder<MapperCollectionBuilder>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the package actions collection builder.
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
internal static PackageActionCollectionBuilder PackageActions(this Composition composition)
|
||||
=> composition.Container.GetInstance<PackageActionCollectionBuilder>();
|
||||
=> composition.WithCollectionBuilder<PackageActionCollectionBuilder>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data editor collection builder.
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
public static DataEditorCollectionBuilder DataEditors(this Composition composition)
|
||||
=> composition.Container.GetInstance<DataEditorCollectionBuilder>();
|
||||
=> composition.WithCollectionBuilder<DataEditorCollectionBuilder>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value converters collection builder.
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
public static PropertyValueConverterCollectionBuilder PropertyValueConverters(this Composition composition)
|
||||
=> composition.Container.GetInstance<PropertyValueConverterCollectionBuilder>();
|
||||
=> composition.WithCollectionBuilder<PropertyValueConverterCollectionBuilder>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the url segment providers collection builder.
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this Composition composition)
|
||||
=> composition.Container.GetInstance<UrlSegmentProviderCollectionBuilder>();
|
||||
=> composition.WithCollectionBuilder<UrlSegmentProviderCollectionBuilder>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the validators collection builder.
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
internal static ManifestValueValidatorCollectionBuilder Validators(this Composition composition)
|
||||
=> composition.Container.GetInstance<ManifestValueValidatorCollectionBuilder>();
|
||||
=> composition.WithCollectionBuilder<ManifestValueValidatorCollectionBuilder>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the post-migrations collection builder.
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
internal static PostMigrationCollectionBuilder PostMigrations(this Composition composition)
|
||||
=> composition.Container.GetInstance<PostMigrationCollectionBuilder>();
|
||||
=> composition.WithCollectionBuilder<PostMigrationCollectionBuilder>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the components collection builder.
|
||||
/// </summary>
|
||||
public static ComponentCollectionBuilder Components(this Composition composition)
|
||||
=> composition.WithCollectionBuilder<ComponentCollectionBuilder>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Singleton
|
||||
#region Uniques
|
||||
|
||||
/// <summary>
|
||||
/// Sets the culture dictionary factory.
|
||||
@@ -89,7 +133,7 @@ namespace Umbraco.Core.Components
|
||||
public static void SetCultureDictionaryFactory<T>(this Composition composition)
|
||||
where T : ICultureDictionaryFactory
|
||||
{
|
||||
composition.Container.RegisterSingleton<ICultureDictionaryFactory, T>();
|
||||
composition.RegisterUnique<ICultureDictionaryFactory, T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -97,9 +141,9 @@ namespace Umbraco.Core.Components
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
/// <param name="factory">A function creating a culture dictionary factory.</param>
|
||||
public static void SetCultureDictionaryFactory(this Composition composition, Func<IServiceFactory, ICultureDictionaryFactory> factory)
|
||||
public static void SetCultureDictionaryFactory(this Composition composition, Func<IFactory, ICultureDictionaryFactory> factory)
|
||||
{
|
||||
composition.Container.RegisterSingleton(factory);
|
||||
composition.RegisterUnique(factory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -109,7 +153,7 @@ namespace Umbraco.Core.Components
|
||||
/// <param name="factory">A factory.</param>
|
||||
public static void SetCultureDictionaryFactory(this Composition composition, ICultureDictionaryFactory factory)
|
||||
{
|
||||
composition.Container.RegisterSingleton(_ => factory);
|
||||
composition.RegisterUnique(_ => factory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -120,7 +164,7 @@ namespace Umbraco.Core.Components
|
||||
public static void SetPublishedContentModelFactory<T>(this Composition composition)
|
||||
where T : IPublishedModelFactory
|
||||
{
|
||||
composition.Container.RegisterSingleton<IPublishedModelFactory, T>();
|
||||
composition.RegisterUnique<IPublishedModelFactory, T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -128,9 +172,9 @@ namespace Umbraco.Core.Components
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
/// <param name="factory">A function creating a published content model factory.</param>
|
||||
public static void SetPublishedContentModelFactory(this Composition composition, Func<IServiceFactory, IPublishedModelFactory> factory)
|
||||
public static void SetPublishedContentModelFactory(this Composition composition, Func<IFactory, IPublishedModelFactory> factory)
|
||||
{
|
||||
composition.Container.RegisterSingleton(factory);
|
||||
composition.RegisterUnique(factory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -140,7 +184,7 @@ namespace Umbraco.Core.Components
|
||||
/// <param name="factory">A published content model factory.</param>
|
||||
public static void SetPublishedContentModelFactory(this Composition composition, IPublishedModelFactory factory)
|
||||
{
|
||||
composition.Container.RegisterSingleton(_ => factory);
|
||||
composition.RegisterUnique(_ => factory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -151,7 +195,7 @@ namespace Umbraco.Core.Components
|
||||
public static void SetServerRegistrar<T>(this Composition composition)
|
||||
where T : IServerRegistrar
|
||||
{
|
||||
composition.Container.RegisterSingleton<IServerRegistrar, T>();
|
||||
composition.RegisterUnique<IServerRegistrar, T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -159,9 +203,9 @@ namespace Umbraco.Core.Components
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
/// <param name="factory">A function creating a server registar.</param>
|
||||
public static void SetServerRegistrar(this Composition composition, Func<IServiceFactory, IServerRegistrar> factory)
|
||||
public static void SetServerRegistrar(this Composition composition, Func<IFactory, IServerRegistrar> factory)
|
||||
{
|
||||
composition.Container.RegisterSingleton(factory);
|
||||
composition.RegisterUnique(factory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -171,7 +215,7 @@ namespace Umbraco.Core.Components
|
||||
/// <param name="registrar">A server registrar.</param>
|
||||
public static void SetServerRegistrar(this Composition composition, IServerRegistrar registrar)
|
||||
{
|
||||
composition.Container.RegisterSingleton(_ => registrar);
|
||||
composition.RegisterUnique(_ => registrar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -182,7 +226,7 @@ namespace Umbraco.Core.Components
|
||||
public static void SetServerMessenger<T>(this Composition composition)
|
||||
where T : IServerMessenger
|
||||
{
|
||||
composition.Container.RegisterSingleton<IServerMessenger, T>();
|
||||
composition.RegisterUnique<IServerMessenger, T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -190,9 +234,9 @@ namespace Umbraco.Core.Components
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
/// <param name="factory">A function creating a server messenger.</param>
|
||||
public static void SetServerMessenger(this Composition composition, Func<IServiceFactory, IServerMessenger> factory)
|
||||
public static void SetServerMessenger(this Composition composition, Func<IFactory, IServerMessenger> factory)
|
||||
{
|
||||
composition.Container.RegisterSingleton(factory);
|
||||
composition.RegisterUnique(factory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -202,7 +246,7 @@ namespace Umbraco.Core.Components
|
||||
/// <param name="registrar">A server messenger.</param>
|
||||
public static void SetServerMessenger(this Composition composition, IServerMessenger registrar)
|
||||
{
|
||||
composition.Container.RegisterSingleton(_ => registrar);
|
||||
composition.RegisterUnique(_ => registrar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -213,7 +257,7 @@ namespace Umbraco.Core.Components
|
||||
public static void SetShortStringHelper<T>(this Composition composition)
|
||||
where T : IShortStringHelper
|
||||
{
|
||||
composition.Container.RegisterSingleton<IShortStringHelper, T>();
|
||||
composition.RegisterUnique<IShortStringHelper, T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -221,9 +265,9 @@ namespace Umbraco.Core.Components
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
/// <param name="factory">A function creating a short string helper.</param>
|
||||
public static void SetShortStringHelper(this Composition composition, Func<IServiceFactory, IShortStringHelper> factory)
|
||||
public static void SetShortStringHelper(this Composition composition, Func<IFactory, IShortStringHelper> factory)
|
||||
{
|
||||
composition.Container.RegisterSingleton(factory);
|
||||
composition.RegisterUnique(factory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -233,7 +277,7 @@ namespace Umbraco.Core.Components
|
||||
/// <param name="helper">A short string helper.</param>
|
||||
public static void SetShortStringHelper(this Composition composition, IShortStringHelper helper)
|
||||
{
|
||||
composition.Container.RegisterSingleton(_ => helper);
|
||||
composition.RegisterUnique(_ => helper);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
38
src/Umbraco.Core/Components/DisableAttribute.cs
Normal file
38
src/Umbraco.Core/Components/DisableAttribute.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a composer should be disabled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If a type is specified, disables the composer of that type, else disables the composer marked with the attribute.</para>
|
||||
/// <para>This attribute is *not* inherited.</para>
|
||||
/// <para>This attribute applies to classes only, it is not possible to enable/disable interfaces.</para>
|
||||
/// <para>If a composer ends up being both enabled and disabled: attributes marking the composer itself have lower priority
|
||||
/// than attributes on *other* composers, eg if a composer declares itself as disabled it is possible to enable it from
|
||||
/// another composer. Anything else is unspecified.</para>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public class DisableAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DisableAttribute"/> class.
|
||||
/// </summary>
|
||||
public DisableAttribute()
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DisableAttribute"/> class.
|
||||
/// </summary>
|
||||
public DisableAttribute(Type disabledType)
|
||||
{
|
||||
DisabledType = disabledType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the disabled type, or null if it is the composer marked with the attribute.
|
||||
/// </summary>
|
||||
public Type DisabledType { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a component should be disabled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If a type is specified, disables the component of that type, else disables the component marked with the attribute.</para>
|
||||
/// <para>This attribute is *not* inherited.</para>
|
||||
/// <para>This attribute applies to classes only, it is not possible to enable/disable interfaces.</para>
|
||||
/// <para>If a component ends up being both enabled and disabled: attributes marking the component itself have lower priority
|
||||
/// than attributes on *other* components, eg if a component declares itself as disabled it is possible to enable it from
|
||||
/// another component. Anything else is unspecified.</para>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public class DisableComponentAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DisableComponentAttribute"/> class.
|
||||
/// </summary>
|
||||
public DisableComponentAttribute()
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DisableComponentAttribute"/> class.
|
||||
/// </summary>
|
||||
public DisableComponentAttribute(Type disabledType)
|
||||
{
|
||||
DisabledType = disabledType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the disabled type, or null if it is the component marked with the attribute.
|
||||
/// </summary>
|
||||
public Type DisabledType { get; }
|
||||
}
|
||||
}
|
||||
38
src/Umbraco.Core/Components/EnableAttribute.cs
Normal file
38
src/Umbraco.Core/Components/EnableAttribute.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a composer should be enabled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If a type is specified, enables the composer of that type, else enables the composer marked with the attribute.</para>
|
||||
/// <para>This attribute is *not* inherited.</para>
|
||||
/// <para>This attribute applies to classes only, it is not possible to enable/disable interfaces.</para>
|
||||
/// <para>If a composer ends up being both enabled and disabled: attributes marking the composer itself have lower priority
|
||||
/// than attributes on *other* composers, eg if a composer declares itself as disabled it is possible to enable it from
|
||||
/// another composer. Anything else is unspecified.</para>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public class EnableAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EnableAttribute"/> class.
|
||||
/// </summary>
|
||||
public EnableAttribute()
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EnableAttribute"/> class.
|
||||
/// </summary>
|
||||
public EnableAttribute(Type enabledType)
|
||||
{
|
||||
EnabledType = enabledType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the enabled type, or null if it is the composer marked with the attribute.
|
||||
/// </summary>
|
||||
public Type EnabledType { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a component should be enabled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If a type is specified, enables the component of that type, else enables the component marked with the attribute.</para>
|
||||
/// <para>This attribute is *not* inherited.</para>
|
||||
/// <para>This attribute applies to classes only, it is not possible to enable/disable interfaces.</para>
|
||||
/// <para>If a component ends up being both enabled and disabled: attributes marking the component itself have lower priority
|
||||
/// than attributes on *other* components, eg if a component declares itself as disabled it is possible to enable it from
|
||||
/// another component. Anything else is unspecified.</para>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public class EnableComponentAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EnableComponentAttribute"/> class.
|
||||
/// </summary>
|
||||
public EnableComponentAttribute()
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EnableComponentAttribute"/> class.
|
||||
/// </summary>
|
||||
public EnableComponentAttribute(Type enabledType)
|
||||
{
|
||||
EnabledType = enabledType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the enabled type, or null if it is the component marked with the attribute.
|
||||
/// </summary>
|
||||
public Type EnabledType { get; }
|
||||
}
|
||||
}
|
||||
15
src/Umbraco.Core/Components/IComponent.cs
Normal file
15
src/Umbraco.Core/Components/IComponent.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a component.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Components are created by DI and therefore must have a public constructor.</para>
|
||||
/// <para>All components which are also disposable, will be disposed in reverse
|
||||
/// order, when Umbraco terminates.</para>
|
||||
/// <para>The Dispose method may be invoked more than once, and components
|
||||
/// should ensure they support this.</para>
|
||||
/// </remarks>
|
||||
public interface IComponent
|
||||
{ }
|
||||
}
|
||||
16
src/Umbraco.Core/Components/IComposer.cs
Normal file
16
src/Umbraco.Core/Components/IComposer.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a composer.
|
||||
/// </summary>
|
||||
public interface IComposer : IDiscoverable
|
||||
{
|
||||
/// <summary>
|
||||
/// Compose.
|
||||
/// </summary>
|
||||
/// <param name="composition"></param>
|
||||
void Compose(Composition composition);
|
||||
}
|
||||
}
|
||||
13
src/Umbraco.Core/Components/ICoreComposer.cs
Normal file
13
src/Umbraco.Core/Components/ICoreComposer.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a core <see cref="IComposer"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>All core composers are required by (compose before) all user composers,
|
||||
/// and require (compose after) all runtime composers.</para>
|
||||
/// </remarks>
|
||||
[ComposeAfter(typeof(IRuntimeComposer))]
|
||||
public interface ICoreComposer : IComposer
|
||||
{ }
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
public interface IRuntimeComponent : IUmbracoComponent
|
||||
{ }
|
||||
}
|
||||
11
src/Umbraco.Core/Components/IRuntimeComposer.cs
Normal file
11
src/Umbraco.Core/Components/IRuntimeComposer.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a runtime <see cref="IComposer"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>All runtime composers are required by (compose before) all core composers</para>
|
||||
/// </remarks>
|
||||
public interface IRuntimeComposer : IComposer
|
||||
{ }
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an Umbraco component.
|
||||
/// </summary>
|
||||
public interface IUmbracoComponent : IDiscoverable
|
||||
{
|
||||
/// <summary>
|
||||
/// Composes the component.
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition.</param>
|
||||
void Compose(Composition composition);
|
||||
|
||||
/// <summary>
|
||||
/// Terminates the component.
|
||||
/// </summary>
|
||||
void Terminate();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
[RequireComponent(typeof(IRuntimeComponent))]
|
||||
public interface IUmbracoCoreComponent : IUmbracoComponent
|
||||
{ }
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
[RequireComponent(typeof(UmbracoCoreComponent))]
|
||||
public interface IUmbracoUserComponent : IUmbracoComponent
|
||||
{ }
|
||||
}
|
||||
12
src/Umbraco.Core/Components/IUserComposer.cs
Normal file
12
src/Umbraco.Core/Components/IUserComposer.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a user <see cref="IComposer"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>All user composers require (compose after) all core composers.</para>
|
||||
/// </remarks>
|
||||
[ComposeAfter(typeof(ICoreComposer))]
|
||||
public interface IUserComposer : IComposer
|
||||
{ }
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Manifest;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
public class ManifestWatcherComponent : UmbracoComponentBase, IUmbracoCoreComponent
|
||||
public sealed class ManifestWatcherComponent : IComponent, IDisposable
|
||||
{
|
||||
// if configured and in debug mode, a ManifestWatcher watches App_Plugins folders for
|
||||
// package.manifest chances and restarts the application on any change
|
||||
private ManifestWatcher _mw;
|
||||
|
||||
public void Initialize(IRuntimeState runtime, ILogger logger)
|
||||
public ManifestWatcherComponent(IRuntimeState runtimeState, ILogger logger)
|
||||
{
|
||||
if (runtime.Debug == false) return;
|
||||
if (runtimeState.Debug == false) return;
|
||||
|
||||
//if (ApplicationContext.Current.IsConfigured == false || GlobalSettings.DebugMode == false)
|
||||
// return;
|
||||
@@ -26,9 +26,11 @@ namespace Umbraco.Core.Components
|
||||
_mw.Start(Directory.GetDirectories(appPlugins));
|
||||
}
|
||||
|
||||
public override void Terminate()
|
||||
public void Dispose()
|
||||
{
|
||||
_mw?.Dispose();
|
||||
if (_mw == null) return;
|
||||
|
||||
_mw.Dispose();
|
||||
_mw = null;
|
||||
}
|
||||
}
|
||||
|
||||
6
src/Umbraco.Core/Components/ManifestWatcherComposer.cs
Normal file
6
src/Umbraco.Core/Components/ManifestWatcherComposer.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
public class ManifestWatcherComposer : ComponentComposer<ManifestWatcherComponent>, ICoreComposer
|
||||
{ }
|
||||
}
|
||||
@@ -6,10 +6,9 @@ using Umbraco.Core.Services.Implement;
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
//TODO: This should just exist in the content service/repo!
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
public sealed class RelateOnCopyComponent : UmbracoComponentBase, IUmbracoCoreComponent
|
||||
public sealed class RelateOnCopyComponent : IComponent
|
||||
{
|
||||
public void Initialize()
|
||||
public RelateOnCopyComponent()
|
||||
{
|
||||
ContentService.Copied += ContentServiceCopied;
|
||||
}
|
||||
|
||||
6
src/Umbraco.Core/Components/RelateOnCopyComposer.cs
Normal file
6
src/Umbraco.Core/Components/RelateOnCopyComposer.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
public sealed class RelateOnCopyComposer : ComponentComposer<RelateOnCopyComponent>, ICoreComposer
|
||||
{ }
|
||||
}
|
||||
@@ -7,10 +7,9 @@ using Umbraco.Core.Services.Implement;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
public sealed class RelateOnTrashComponent : UmbracoComponentBase, IUmbracoCoreComponent
|
||||
public sealed class RelateOnTrashComponent : IComponent
|
||||
{
|
||||
public void Initialize()
|
||||
public RelateOnTrashComponent()
|
||||
{
|
||||
ContentService.Moved += ContentService_Moved;
|
||||
ContentService.Trashed += ContentService_Trashed;
|
||||
|
||||
6
src/Umbraco.Core/Components/RelateOnTrashComposer.cs
Normal file
6
src/Umbraco.Core/Components/RelateOnTrashComposer.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
public sealed class RelateOnTrashComposer : ComponentComposer<RelateOnTrashComponent>, ICoreComposer
|
||||
{ }
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a component requires another component.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This attribute is *not* inherited. This means that a component class inheriting from
|
||||
/// another component class does *not* inherit its requirements. However, the bootloader checks
|
||||
/// the *interfaces* of every component for their requirements, so requirements declared on
|
||||
/// interfaces are inherited by every component class implementing the interface.</para>
|
||||
/// <para>When targetting a class, indicates a dependency on the component which must be enabled,
|
||||
/// unless the requirement has explicitely been declared as weak (and then, only if the component
|
||||
/// is enabled).</para>
|
||||
/// <para>When targetting an interface, indicates a dependency on enabled components implementing
|
||||
/// the interface. It could be no component at all, unless the requirement has explicitely been
|
||||
/// declared as strong (and at least one component must be enabled).</para>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
|
||||
public class RequireComponentAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequireComponentAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="requiredType">The type of the required component.</param>
|
||||
public RequireComponentAttribute(Type requiredType)
|
||||
{
|
||||
if (typeof(IUmbracoComponent).IsAssignableFrom(requiredType) == false)
|
||||
throw new ArgumentException($"Type {requiredType.FullName} is invalid here because it does not implement {typeof(IUmbracoComponent).FullName}.");
|
||||
RequiredType = requiredType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequireComponentAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="requiredType">The type of the required component.</param>
|
||||
/// <param name="weak">A value indicating whether the requirement is weak.</param>
|
||||
public RequireComponentAttribute(Type requiredType, bool weak)
|
||||
: this(requiredType)
|
||||
{
|
||||
Weak = weak;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the required type.
|
||||
/// </summary>
|
||||
public Type RequiredType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the requirement is weak.
|
||||
/// </summary>
|
||||
/// <remarks>Returns <c>true</c> if the requirement is weak (requires the other component if it
|
||||
/// is enabled), <c>false</c> if the requirement is strong (requires the other component to be
|
||||
/// enabled), and <c>null</c> if unspecified, in which case it is strong for classes and weak for
|
||||
/// interfaces.</remarks>
|
||||
public bool? Weak { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a component is required by another component.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// fixme
|
||||
/// <para>This attribute is *not* inherited. This means that a component class inheriting from
|
||||
/// another component class does *not* inherit its requirements. However, the bootloader checks
|
||||
/// the *interfaces* of every component for their requirements, so requirements declared on
|
||||
/// interfaces are inherited by every component class implementing the interface.</para>
|
||||
/// <para>When targetting a class, indicates a dependency on the component which must be enabled,
|
||||
/// unless the requirement has explicitely been declared as weak (and then, only if the component
|
||||
/// is enabled).</para>
|
||||
/// <para>When targetting an interface, indicates a dependency on enabled components implementing
|
||||
/// the interface. It could be no component at all, unless the requirement has explicitely been
|
||||
/// declared as strong (and at least one component must be enabled).</para>
|
||||
/// </remarks>
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
|
||||
public class RequiredComponentAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequiredComponentAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="requiringType">The type of the required component.</param>
|
||||
public RequiredComponentAttribute(Type requiringType)
|
||||
{
|
||||
if (typeof(IUmbracoComponent).IsAssignableFrom(requiringType) == false)
|
||||
throw new ArgumentException($"Type {requiringType.FullName} is invalid here because it does not implement {typeof(IUmbracoComponent).FullName}.");
|
||||
RequiringType = requiringType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the required type.
|
||||
/// </summary>
|
||||
public Type RequiringType { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class for <see cref="IUmbracoComponent"/> implementations.
|
||||
/// </summary>
|
||||
public abstract class UmbracoComponentBase : IUmbracoComponent
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public virtual void Compose(Composition composition)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void Terminate()
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
// the UmbracoCoreComponent requires all IUmbracoCoreComponent
|
||||
// all user-level components should require the UmbracoCoreComponent
|
||||
|
||||
[RequireComponent(typeof(IUmbracoCoreComponent))]
|
||||
public class UmbracoCoreComponent : UmbracoComponentBase
|
||||
{ }
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using LightInject;
|
||||
|
||||
namespace Umbraco.Core.Composing
|
||||
{
|
||||
@@ -18,65 +16,30 @@ namespace Umbraco.Core.Composing
|
||||
{
|
||||
private readonly List<Type> _types = new List<Type>();
|
||||
private readonly object _locker = new object();
|
||||
private Func<IEnumerable<TItem>, TCollection> _collectionCtor;
|
||||
private ServiceRegistration[] _registrations;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CollectionBuilderBase{TBuilder, TCollection,TItem}"/>
|
||||
/// class with a service container.
|
||||
/// </summary>
|
||||
/// <param name="container">A service container.</param>
|
||||
protected CollectionBuilderBase(IServiceContainer container)
|
||||
{
|
||||
Container = container;
|
||||
// ReSharper disable once DoNotCallOverridableMethodsInConstructor
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the service container.
|
||||
/// </summary>
|
||||
protected IServiceContainer Container { get; }
|
||||
private Type[] _registeredTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the internal list of types as an IEnumerable (immutable).
|
||||
/// </summary>
|
||||
public IEnumerable<Type> GetTypes() => _types;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the builder.
|
||||
/// </summary>
|
||||
/// <remarks>This is called by the constructor and, by default, registers the
|
||||
/// collection automatically.</remarks>
|
||||
protected virtual void Initialize()
|
||||
/// <inheritdoc />
|
||||
public virtual void RegisterWith(IRegister register)
|
||||
{
|
||||
// compile the auto-collection constructor
|
||||
var argType = typeof(IEnumerable<TItem>);
|
||||
var ctorArgTypes = new[] { argType };
|
||||
var constructor = typeof(TCollection).GetConstructor(ctorArgTypes);
|
||||
if (constructor != null)
|
||||
{
|
||||
var exprArg = Expression.Parameter(argType, "items");
|
||||
var exprNew = Expression.New(constructor, exprArg);
|
||||
var expr = Expression.Lambda<Func<IEnumerable<TItem>, TCollection>>(exprNew, exprArg);
|
||||
_collectionCtor = expr.Compile();
|
||||
}
|
||||
// else _collectionCtor remains null, assuming CreateCollection has been overriden
|
||||
|
||||
// we just don't want to support re-registering collections here
|
||||
var registration = Container.GetAvailableService<TCollection>();
|
||||
if (registration != null)
|
||||
throw new InvalidOperationException("Collection builders cannot be registered once the collection itself has been registered.");
|
||||
if (_registeredTypes != null)
|
||||
throw new InvalidOperationException("This builder has already been registered.");
|
||||
|
||||
// register the collection
|
||||
Container.Register(_ => CreateCollection(), CollectionLifetime);
|
||||
register.Register(CreateCollection, CollectionLifetime);
|
||||
|
||||
// register the types
|
||||
RegisterTypes(register);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection lifetime.
|
||||
/// </summary>
|
||||
/// <remarks>Return null for transient collections.</remarks>
|
||||
protected virtual ILifetime CollectionLifetime => new PerContainerLifetime();
|
||||
protected virtual Lifetime CollectionLifetime => Lifetime.Singleton;
|
||||
|
||||
/// <summary>
|
||||
/// Configures the internal list of types.
|
||||
@@ -87,8 +50,8 @@ namespace Umbraco.Core.Composing
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
if (_registrations != null)
|
||||
throw new InvalidOperationException("Cannot configure a collection builder after its types have been resolved.");
|
||||
if (_registeredTypes != null)
|
||||
throw new InvalidOperationException("Cannot configure a collection builder after it has been registered.");
|
||||
action(_types);
|
||||
}
|
||||
}
|
||||
@@ -104,55 +67,54 @@ namespace Umbraco.Core.Composing
|
||||
return types;
|
||||
}
|
||||
|
||||
private void RegisterTypes()
|
||||
private void RegisterTypes(IRegister register)
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
if (_registrations != null) return;
|
||||
if (_registeredTypes != null) return;
|
||||
|
||||
var types = GetRegisteringTypes(_types).ToArray();
|
||||
|
||||
// ensure they are safe
|
||||
foreach (var type in types)
|
||||
EnsureType(type, "register");
|
||||
|
||||
var prefix = GetType().FullName + "_";
|
||||
var i = 0;
|
||||
// register them
|
||||
foreach (var type in types)
|
||||
{
|
||||
var name = $"{prefix}{i++:00000}";
|
||||
Container.Register(typeof(TItem), type, name);
|
||||
}
|
||||
register.Register(type);
|
||||
|
||||
_registrations = Container.AvailableServices
|
||||
.Where(x => x.ServiceName.StartsWith(prefix))
|
||||
.OrderBy(x => x.ServiceName)
|
||||
.ToArray();
|
||||
_registeredTypes = types;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the collection items.
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments.</param>
|
||||
/// <returns>The collection items.</returns>
|
||||
protected virtual IEnumerable<TItem> CreateItems(params object[] args)
|
||||
protected virtual IEnumerable<TItem> CreateItems(IFactory factory)
|
||||
{
|
||||
RegisterTypes(); // will do it only once
|
||||
if (_registeredTypes == null)
|
||||
throw new InvalidOperationException("Cannot create items before the collection builder has been registered.");
|
||||
|
||||
var type = typeof (TItem);
|
||||
return _registrations
|
||||
.Select(x => (TItem) Container.GetInstanceOrThrow(type, x.ServiceName, x.ImplementingType, args))
|
||||
return _registeredTypes // respect order
|
||||
.Select(x => CreateItem(factory, x))
|
||||
.ToArray(); // safe
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a collection item.
|
||||
/// </summary>
|
||||
protected virtual TItem CreateItem(IFactory factory, Type itemType)
|
||||
=> (TItem) factory.GetInstance(itemType);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a collection.
|
||||
/// </summary>
|
||||
/// <returns>A collection.</returns>
|
||||
/// <remarks>Creates a new collection each time it is invoked.</remarks>
|
||||
public virtual TCollection CreateCollection()
|
||||
public virtual TCollection CreateCollection(IFactory factory)
|
||||
{
|
||||
if (_collectionCtor == null) throw new InvalidOperationException("Collection auto-creation is not possible.");
|
||||
return _collectionCtor(CreateItems());
|
||||
return factory.CreateInstance<TCollection>(CreateItems(factory));
|
||||
}
|
||||
|
||||
protected Type EnsureType(Type type, string action)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using Umbraco.Core.Components;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
|
||||
namespace Umbraco.Core.Composing.Composers
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets up IoC container for Umbraco configuration classes
|
||||
/// </summary>
|
||||
public static class ConfigurationComposer
|
||||
{
|
||||
public static Composition ComposeConfiguration(this Composition composition)
|
||||
{
|
||||
composition.RegisterUnique<UmbracoConfig>();
|
||||
composition.RegisterUnique(factory => factory.GetInstance<UmbracoConfig>().Umbraco());
|
||||
composition.RegisterUnique(factory => factory.GetInstance<IUmbracoSettingsSection>().Content);
|
||||
composition.RegisterUnique(factory => factory.GetInstance<IUmbracoSettingsSection>().Templates);
|
||||
composition.RegisterUnique(factory => factory.GetInstance<IUmbracoSettingsSection>().RequestHandler);
|
||||
composition.RegisterUnique(factory => factory.GetInstance<IUmbracoSettingsSection>().Security);
|
||||
composition.RegisterUnique(factory => factory.GetInstance<UmbracoConfig>().Global());
|
||||
composition.RegisterUnique(factory => factory.GetInstance<UmbracoConfig>().Dashboards());
|
||||
composition.RegisterUnique(factory => factory.GetInstance<UmbracoConfig>().HealthChecks());
|
||||
composition.RegisterUnique(factory => factory.GetInstance<UmbracoConfig>().Grids());
|
||||
|
||||
// fixme - other sections we need to add?
|
||||
|
||||
return composition;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Components;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
|
||||
namespace Umbraco.Core.Composing.Composers
|
||||
|
||||
{
|
||||
public static class CoreMappingProfilesComposer
|
||||
{
|
||||
public static Composition ComposeCoreMappingProfiles(this Composition composition)
|
||||
{
|
||||
composition.Register<Profile, IdentityMapperProfile>();
|
||||
return composition;
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs
Normal file
89
src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using Umbraco.Core.Components;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.IO.MediaPathSchemes;
|
||||
|
||||
namespace Umbraco.Core.Composing.Composers
|
||||
{
|
||||
public static class FileSystemsComposer
|
||||
{
|
||||
/*
|
||||
* HOW TO REPLACE THE MEDIA UNDERLYING FILESYSTEM
|
||||
* ----------------------------------------------
|
||||
*
|
||||
* Create a component and use it to modify the composition by adding something like:
|
||||
*
|
||||
* composition.Container.RegisterFileSystem<IMediaFileSystem, MediaFileSystem>(
|
||||
* factory => new PhysicalFileSystem("~/somewhere"));
|
||||
*
|
||||
* return whatever supporting filesystem you like.
|
||||
*
|
||||
*
|
||||
* HOW TO IMPLEMENT MY OWN FILESYSTEM
|
||||
* ----------------------------------
|
||||
*
|
||||
* Create your filesystem class:
|
||||
*
|
||||
* public class MyFileSystem : FileSystemWrapper
|
||||
* {
|
||||
* public MyFileSystem(IFileSystem innerFileSystem)
|
||||
* : base(innerFileSystem)
|
||||
* { }
|
||||
* }
|
||||
*
|
||||
* The ctor can have more parameters that will be resolved by the container.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* WHAT IS SHADOWING
|
||||
* -----------------
|
||||
*
|
||||
* Shadowing is the technology used for Deploy to implement some sort of
|
||||
* transaction-management on top of filesystems. The plumbing explained above,
|
||||
* compared to creating your own physical filesystem, ensures that your filesystem
|
||||
* would participate into such transactions.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
public static Composition ComposeFileSystems(this Composition composition)
|
||||
{
|
||||
// register FileSystems, which manages all filesystems
|
||||
// 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.
|
||||
composition.RegisterUnique(factory => factory.CreateInstance<FileSystems>(factory));
|
||||
|
||||
// register IFileSystems, which gives access too all filesystems
|
||||
composition.RegisterUnique<IFileSystems>(factory => factory.GetInstance<FileSystems>());
|
||||
|
||||
// register the scheme for media paths
|
||||
composition.RegisterUnique<IMediaPathScheme, TwoGuidsMediaPathScheme>();
|
||||
|
||||
// register the IMediaFileSystem implementation with a supporting filesystem
|
||||
composition.RegisterFileSystem<IMediaFileSystem, MediaFileSystem>(
|
||||
factory => new PhysicalFileSystem("~/media"));
|
||||
|
||||
return composition;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs
Normal file
54
src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Umbraco.Core.Components;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
|
||||
namespace Umbraco.Core.Composing.Composers
|
||||
{
|
||||
/// <summary>
|
||||
/// Composes repositories.
|
||||
/// </summary>
|
||||
public static class RepositoriesComposer
|
||||
{
|
||||
public static Composition ComposeRepositories(this Composition composition)
|
||||
{
|
||||
// repositories
|
||||
composition.RegisterUnique<IAuditRepository, AuditRepository>();
|
||||
composition.RegisterUnique<IAuditEntryRepository, AuditEntryRepository>();
|
||||
composition.RegisterUnique<IContentTypeRepository, ContentTypeRepository>();
|
||||
composition.RegisterUnique<IDataTypeContainerRepository, DataTypeContainerRepository>();
|
||||
composition.RegisterUnique<IDataTypeRepository, DataTypeRepository>();
|
||||
composition.RegisterUnique<IDictionaryRepository, DictionaryRepository>();
|
||||
composition.RegisterUnique<IDocumentBlueprintRepository, DocumentBlueprintRepository>();
|
||||
composition.RegisterUnique<IDocumentRepository, DocumentRepository>();
|
||||
composition.RegisterUnique<IDocumentTypeContainerRepository, DocumentTypeContainerRepository>();
|
||||
composition.RegisterUnique<IDomainRepository, DomainRepository>();
|
||||
composition.RegisterUnique<IEntityRepository, EntityRepository>();
|
||||
composition.RegisterUnique<IExternalLoginRepository, ExternalLoginRepository>();
|
||||
composition.RegisterUnique<ILanguageRepository, LanguageRepository>();
|
||||
composition.RegisterUnique<IMacroRepository, MacroRepository>();
|
||||
composition.RegisterUnique<IMediaRepository, MediaRepository>();
|
||||
composition.RegisterUnique<IMediaTypeContainerRepository, MediaTypeContainerRepository>();
|
||||
composition.RegisterUnique<IMediaTypeRepository, MediaTypeRepository>();
|
||||
composition.RegisterUnique<IMemberGroupRepository, MemberGroupRepository>();
|
||||
composition.RegisterUnique<IMemberRepository, MemberRepository>();
|
||||
composition.RegisterUnique<IMemberTypeRepository, MemberTypeRepository>();
|
||||
composition.RegisterUnique<INotificationsRepository, NotificationsRepository>();
|
||||
composition.RegisterUnique<IPublicAccessRepository, PublicAccessRepository>();
|
||||
composition.RegisterUnique<IRedirectUrlRepository, RedirectUrlRepository>();
|
||||
composition.RegisterUnique<IRelationRepository, RelationRepository>();
|
||||
composition.RegisterUnique<IRelationTypeRepository, RelationTypeRepository>();
|
||||
composition.RegisterUnique<IServerRegistrationRepository, ServerRegistrationRepository>();
|
||||
composition.RegisterUnique<ITagRepository, TagRepository>();
|
||||
composition.RegisterUnique<ITemplateRepository, TemplateRepository>();
|
||||
composition.RegisterUnique<IUserGroupRepository, UserGroupRepository>();
|
||||
composition.RegisterUnique<IUserRepository, UserRepository>();
|
||||
composition.RegisterUnique<IConsentRepository, ConsentRepository>();
|
||||
composition.RegisterUnique<IPartialViewMacroRepository, PartialViewMacroRepository>();
|
||||
composition.RegisterUnique<IPartialViewRepository, PartialViewRepository>();
|
||||
composition.RegisterUnique<IScriptRepository, ScriptRepository>();
|
||||
composition.RegisterUnique<IStylesheetRepository, StylesheetRepository>();
|
||||
|
||||
return composition;
|
||||
}
|
||||
}
|
||||
}
|
||||
96
src/Umbraco.Core/Composing/Composers/ServicesComposer.cs
Normal file
96
src/Umbraco.Core/Composing/Composers/ServicesComposer.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Components;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
|
||||
namespace Umbraco.Core.Composing.Composers
|
||||
{
|
||||
public static class ServicesComposer
|
||||
{
|
||||
public static Composition ComposeServices(this Composition composition)
|
||||
{
|
||||
// register a transient messages factory, which will be replaced by the web
|
||||
// boot manager when running in a web context
|
||||
composition.RegisterUnique<IEventMessagesFactory, TransientEventMessagesFactory>();
|
||||
|
||||
// register the service context
|
||||
composition.RegisterUnique<ServiceContext>();
|
||||
|
||||
// register the special idk map
|
||||
composition.RegisterUnique<IdkMap>();
|
||||
|
||||
// register the services
|
||||
composition.RegisterUnique<IKeyValueService, KeyValueService>();
|
||||
composition.RegisterUnique<IPublicAccessService, PublicAccessService>();
|
||||
composition.RegisterUnique<IDomainService, DomainService>();
|
||||
composition.RegisterUnique<IAuditService, AuditService>();
|
||||
composition.RegisterUnique<ITagService, TagService>();
|
||||
composition.RegisterUnique<IContentService, ContentService>();
|
||||
composition.RegisterUnique<IUserService, UserService>();
|
||||
composition.RegisterUnique<IMemberService, MemberService>();
|
||||
composition.RegisterUnique<IMediaService, MediaService>();
|
||||
composition.RegisterUnique<IContentTypeService, ContentTypeService>();
|
||||
composition.RegisterUnique<IMediaTypeService, MediaTypeService>();
|
||||
composition.RegisterUnique<IDataTypeService, DataTypeService>();
|
||||
composition.RegisterUnique<IFileService, FileService>();
|
||||
composition.RegisterUnique<ILocalizationService, LocalizationService>();
|
||||
composition.RegisterUnique<IPackagingService, PackagingService>();
|
||||
composition.RegisterUnique<IServerRegistrationService, ServerRegistrationService>();
|
||||
composition.RegisterUnique<IEntityService, EntityService>();
|
||||
composition.RegisterUnique<IRelationService, RelationService>();
|
||||
composition.RegisterUnique<IMacroService, MacroService>();
|
||||
composition.RegisterUnique<IMemberTypeService, MemberTypeService>();
|
||||
composition.RegisterUnique<IMemberGroupService, MemberGroupService>();
|
||||
composition.RegisterUnique<INotificationService, NotificationService>();
|
||||
composition.RegisterUnique<IExternalLoginService, ExternalLoginService>();
|
||||
composition.RegisterUnique<IRedirectUrlService, RedirectUrlService>();
|
||||
composition.RegisterUnique<IConsentService, ConsentService>();
|
||||
composition.Register<LocalizedTextServiceFileSources>(SourcesFactory);
|
||||
composition.RegisterUnique<ILocalizedTextService>(factory => new LocalizedTextService(
|
||||
factory.GetInstance<Lazy<LocalizedTextServiceFileSources>>(),
|
||||
factory.GetInstance<ILogger>()));
|
||||
|
||||
//TODO: These are replaced in the web project - we need to declare them so that
|
||||
// something is wired up, just not sure this is very nice but will work for now.
|
||||
composition.RegisterUnique<IApplicationTreeService, EmptyApplicationTreeService>();
|
||||
composition.RegisterUnique<ISectionService, EmptySectionService>();
|
||||
|
||||
return composition;
|
||||
}
|
||||
|
||||
private static LocalizedTextServiceFileSources SourcesFactory(IFactory container)
|
||||
{
|
||||
var mainLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Umbraco + "/config/lang/"));
|
||||
var appPlugins = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins));
|
||||
var configLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config + "/lang/"));
|
||||
|
||||
var pluginLangFolders = appPlugins.Exists == false
|
||||
? Enumerable.Empty<LocalizedTextServiceSupplementaryFileSource>()
|
||||
: appPlugins.GetDirectories()
|
||||
.SelectMany(x => x.GetDirectories("Lang"))
|
||||
.SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly))
|
||||
.Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 5)
|
||||
.Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false));
|
||||
|
||||
//user defined langs that overwrite the default, these should not be used by plugin creators
|
||||
var userLangFolders = configLangFolder.Exists == false
|
||||
? Enumerable.Empty<LocalizedTextServiceSupplementaryFileSource>()
|
||||
: configLangFolder
|
||||
.GetFiles("*.user.xml", SearchOption.TopDirectoryOnly)
|
||||
.Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 10)
|
||||
.Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true));
|
||||
|
||||
return new LocalizedTextServiceFileSources(
|
||||
container.GetInstance<ILogger>(),
|
||||
container.GetInstance<CacheHelper>().RuntimeCache,
|
||||
mainLangFolder,
|
||||
pluginLangFolders.Concat(userLangFolders));
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/Umbraco.Core/Composing/CompositionExtensions.cs
Normal file
62
src/Umbraco.Core/Composing/CompositionExtensions.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Components;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
|
||||
namespace Umbraco.Core.Composing
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods to the <see cref="Composition"/> class.
|
||||
/// </summary>
|
||||
public static class CompositionExtensions
|
||||
{
|
||||
#region Essentials
|
||||
|
||||
/// <summary>
|
||||
/// Registers essential services.
|
||||
/// </summary>
|
||||
public static void RegisterEssentials(this Composition composition,
|
||||
ILogger logger, IProfiler profiler, IProfilingLogger profilingLogger,
|
||||
IMainDom mainDom,
|
||||
CacheHelper appCaches,
|
||||
IUmbracoDatabaseFactory databaseFactory,
|
||||
TypeLoader typeLoader,
|
||||
IRuntimeState state)
|
||||
{
|
||||
composition.RegisterUnique(logger);
|
||||
composition.RegisterUnique(profiler);
|
||||
composition.RegisterUnique(profilingLogger);
|
||||
composition.RegisterUnique(mainDom);
|
||||
composition.RegisterUnique(appCaches);
|
||||
composition.RegisterUnique(factory => factory.GetInstance<CacheHelper>().RuntimeCache);
|
||||
composition.RegisterUnique(databaseFactory);
|
||||
composition.RegisterUnique(factory => factory.GetInstance<IUmbracoDatabaseFactory>().SqlContext);
|
||||
composition.RegisterUnique(typeLoader);
|
||||
composition.RegisterUnique(state);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unique
|
||||
|
||||
/// <summary>
|
||||
/// Registers a unique service as its own implementation.
|
||||
/// </summary>
|
||||
public static void RegisterUnique<TService>(this Composition composition)
|
||||
=> composition.RegisterUnique(typeof(TService), typeof(TService));
|
||||
|
||||
/// <summary>
|
||||
/// Registers a unique service with an implementation type.
|
||||
/// </summary>
|
||||
public static void RegisterUnique<TService, TImplementing>(this Composition composition)
|
||||
=> composition.RegisterUnique(typeof(TService), typeof(TImplementing));
|
||||
|
||||
/// <summary>
|
||||
/// Registers a unique service with an implementing instance.
|
||||
/// </summary>
|
||||
public static void RegisterUnique<TService>(this Composition composition, TService instance)
|
||||
=> composition.RegisterUnique(typeof(TService), instance);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using LightInject;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
|
||||
namespace Umbraco.Core.Composing.CompositionRoots
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets up IoC container for Umbraco configuration classes
|
||||
/// </summary>
|
||||
public sealed class ConfigurationCompositionRoot : ICompositionRoot
|
||||
{
|
||||
public void Compose(IServiceRegistry container)
|
||||
{
|
||||
container.Register(factory => UmbracoConfig.For.UmbracoSettings());
|
||||
container.Register(factory => factory.GetInstance<IUmbracoSettingsSection>().Content);
|
||||
container.Register(factory => factory.GetInstance<IUmbracoSettingsSection>().Templates);
|
||||
container.Register(factory => factory.GetInstance<IUmbracoSettingsSection>().RequestHandler);
|
||||
container.Register(factory => UmbracoConfig.For.GlobalSettings());
|
||||
container.Register(factory => UmbracoConfig.For.DashboardSettings());
|
||||
|
||||
// fixme - other sections we need to add?
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using LightInject;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
|
||||
namespace Umbraco.Core.Composing.CompositionRoots
|
||||
{
|
||||
public sealed class CoreMappingProfilesCompositionRoot : ICompositionRoot
|
||||
{
|
||||
public void Compose(IServiceRegistry container)
|
||||
{
|
||||
container.Register<IdentityMapperProfile>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
using System;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
|
||||
namespace Umbraco.Core.Composing.CompositionRoots
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the IoC container for the umbraco data layer/repositories/sql/database/etc...
|
||||
/// </summary>
|
||||
public sealed class RepositoryCompositionRoot : ICompositionRoot
|
||||
{
|
||||
public const string DisabledCache = "DisabledCache";
|
||||
|
||||
public void Compose(IServiceRegistry container)
|
||||
{
|
||||
// register cache helpers
|
||||
// the main cache helper is registered by CoreBootManager and is used by most repositories
|
||||
// the disabled one is used by those repositories that have an annotated ctor parameter
|
||||
container.RegisterSingleton(factory => CacheHelper.CreateDisabledCacheHelper(), DisabledCache);
|
||||
|
||||
// resolve ctor dependency from GetInstance() runtimeArguments, if possible - 'factory' is
|
||||
// the container, 'info' describes the ctor argument, and 'args' contains the args that
|
||||
// were passed to GetInstance() - use first arg if it is the right type,
|
||||
//
|
||||
// for ...
|
||||
//container.RegisterConstructorDependency((factory, info, args) =>
|
||||
//{
|
||||
// if (info.Member.DeclaringType != typeof(EntityContainerRepository)) return default;
|
||||
// return args.Length > 0 && args[0] is Guid guid ? guid : default;
|
||||
//});
|
||||
|
||||
// register repositories
|
||||
// repos depend on various things,
|
||||
// some repositories have an annotated ctor parameter to pick the right cache helper
|
||||
|
||||
// repositories
|
||||
container.RegisterSingleton<IAuditRepository, AuditRepository>();
|
||||
container.RegisterSingleton<IAuditEntryRepository, AuditEntryRepository>();
|
||||
container.RegisterSingleton<IContentTypeRepository, ContentTypeRepository>();
|
||||
container.RegisterSingleton<IDataTypeContainerRepository, DataTypeContainerRepository>();
|
||||
container.RegisterSingleton<IDataTypeRepository, DataTypeRepository>();
|
||||
container.RegisterSingleton<IDictionaryRepository, DictionaryRepository>();
|
||||
container.RegisterSingleton<IDocumentBlueprintRepository, DocumentBlueprintRepository>();
|
||||
container.RegisterSingleton<IDocumentRepository, DocumentRepository>();
|
||||
container.RegisterSingleton<IDocumentTypeContainerRepository, DocumentTypeContainerRepository>();
|
||||
container.RegisterSingleton<IDomainRepository, DomainRepository>();
|
||||
container.RegisterSingleton<IEntityRepository, EntityRepository>();
|
||||
container.RegisterSingleton<IExternalLoginRepository, ExternalLoginRepository>();
|
||||
container.RegisterSingleton<ILanguageRepository, LanguageRepository>();
|
||||
container.RegisterSingleton<IMacroRepository, MacroRepository>();
|
||||
container.RegisterSingleton<IMediaRepository, MediaRepository>();
|
||||
container.RegisterSingleton<IMediaTypeContainerRepository, MediaTypeContainerRepository>();
|
||||
container.RegisterSingleton<IMediaTypeRepository, MediaTypeRepository>();
|
||||
container.RegisterSingleton<IMemberGroupRepository, MemberGroupRepository>();
|
||||
container.RegisterSingleton<IMemberRepository, MemberRepository>();
|
||||
container.RegisterSingleton<IMemberTypeRepository, MemberTypeRepository>();
|
||||
container.RegisterSingleton<INotificationsRepository, NotificationsRepository>();
|
||||
container.RegisterSingleton<IPublicAccessRepository, PublicAccessRepository>();
|
||||
container.RegisterSingleton<IRedirectUrlRepository, RedirectUrlRepository>();
|
||||
container.RegisterSingleton<IRelationRepository, RelationRepository>();
|
||||
container.RegisterSingleton<IRelationTypeRepository, RelationTypeRepository>();
|
||||
container.RegisterSingleton<IServerRegistrationRepository, ServerRegistrationRepository>();
|
||||
container.RegisterSingleton<ITagRepository, TagRepository>();
|
||||
container.RegisterSingleton<ITemplateRepository, TemplateRepository>();
|
||||
container.RegisterSingleton<IUserGroupRepository, UserGroupRepository>();
|
||||
container.RegisterSingleton<IUserRepository, UserRepository>();
|
||||
container.RegisterSingleton<IConsentRepository, ConsentRepository>();
|
||||
|
||||
// repositories that depend on a filesystem
|
||||
// these have an annotated ctor parameter to pick the right file system
|
||||
container.RegisterSingleton<IPartialViewMacroRepository, PartialViewMacroRepository>();
|
||||
container.RegisterSingleton<IPartialViewRepository, PartialViewRepository>();
|
||||
container.RegisterSingleton<IScriptRepository, ScriptRepository>();
|
||||
container.RegisterSingleton<IStylesheetRepository, StylesheetRepository>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
|
||||
namespace Umbraco.Core.Composing.CompositionRoots
|
||||
{
|
||||
public sealed class ServicesCompositionRoot : ICompositionRoot
|
||||
{
|
||||
public void Compose(IServiceRegistry container)
|
||||
{
|
||||
// register a transient messages factory, which will be replaced by the web
|
||||
// boot manager when running in a web context
|
||||
container.RegisterSingleton<IEventMessagesFactory, TransientEventMessagesFactory>();
|
||||
|
||||
// register the service context
|
||||
container.RegisterSingleton<ServiceContext>();
|
||||
|
||||
// register the special idk map
|
||||
container.RegisterSingleton<IdkMap>();
|
||||
|
||||
// register the services
|
||||
container.RegisterSingleton<IKeyValueService, KeyValueService>();
|
||||
container.RegisterSingleton<IPublicAccessService, PublicAccessService>();
|
||||
container.RegisterSingleton<IDomainService, DomainService>();
|
||||
container.RegisterSingleton<IAuditService, AuditService>();
|
||||
container.RegisterSingleton<ITagService, TagService>();
|
||||
container.RegisterSingleton<IContentService, ContentService>();
|
||||
container.RegisterSingleton<IUserService, UserService>();
|
||||
container.RegisterSingleton<IMemberService, MemberService>();
|
||||
container.RegisterSingleton<IMediaService, MediaService>();
|
||||
container.RegisterSingleton<IContentTypeService, ContentTypeService>();
|
||||
container.RegisterSingleton<IMediaTypeService, MediaTypeService>();
|
||||
container.RegisterSingleton<IDataTypeService, DataTypeService>();
|
||||
container.RegisterSingleton<IFileService, FileService>();
|
||||
container.RegisterSingleton<ILocalizationService, LocalizationService>();
|
||||
container.RegisterSingleton<IPackagingService, PackagingService>();
|
||||
container.RegisterSingleton<IServerRegistrationService, ServerRegistrationService>();
|
||||
container.RegisterSingleton<IEntityService, EntityService>();
|
||||
container.RegisterSingleton<IRelationService, RelationService>();
|
||||
container.RegisterSingleton<IMacroService, MacroService>();
|
||||
container.RegisterSingleton<IMemberTypeService, MemberTypeService>();
|
||||
container.RegisterSingleton<IMemberGroupService, MemberGroupService>();
|
||||
container.RegisterSingleton<INotificationService, NotificationService>();
|
||||
container.RegisterSingleton<IExternalLoginService, ExternalLoginService>();
|
||||
container.RegisterSingleton<IRedirectUrlService, RedirectUrlService>();
|
||||
container.RegisterSingleton<IConsentService, ConsentService>();
|
||||
container.Register<LocalizedTextServiceFileSources>(factory =>
|
||||
{
|
||||
var mainLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Umbraco + "/config/lang/"));
|
||||
var appPlugins = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins));
|
||||
var configLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config + "/lang/"));
|
||||
|
||||
var pluginLangFolders = appPlugins.Exists == false
|
||||
? Enumerable.Empty<LocalizedTextServiceSupplementaryFileSource>()
|
||||
: appPlugins.GetDirectories()
|
||||
.SelectMany(x => x.GetDirectories("Lang"))
|
||||
.SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly))
|
||||
.Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 5)
|
||||
.Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false));
|
||||
|
||||
//user defined langs that overwrite the default, these should not be used by plugin creators
|
||||
var userLangFolders = configLangFolder.Exists == false
|
||||
? Enumerable.Empty<LocalizedTextServiceSupplementaryFileSource>()
|
||||
: configLangFolder
|
||||
.GetFiles("*.user.xml", SearchOption.TopDirectoryOnly)
|
||||
.Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 10)
|
||||
.Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true));
|
||||
|
||||
return new LocalizedTextServiceFileSources(
|
||||
factory.GetInstance<ILogger>(),
|
||||
factory.GetInstance<CacheHelper>().RuntimeCache,
|
||||
mainLangFolder,
|
||||
pluginLangFolders.Concat(userLangFolders));
|
||||
});
|
||||
container.RegisterSingleton<ILocalizedTextService>(factory => new LocalizedTextService(
|
||||
factory.GetInstance<Lazy<LocalizedTextServiceFileSources>>(),
|
||||
factory.GetInstance<ILogger>()));
|
||||
|
||||
//TODO: These are replaced in the web project - we need to declare them so that
|
||||
// something is wired up, just not sure this is very nice but will work for now.
|
||||
container.RegisterSingleton<IApplicationTreeService, EmptyApplicationTreeService>();
|
||||
container.RegisterSingleton<ISectionService, EmptySectionService>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Dictionary;
|
||||
@@ -20,46 +19,53 @@ namespace Umbraco.Core.Composing
|
||||
/// Provides a static service locator for most singletons.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This class is initialized with the container via LightInjectExtensions.ConfigureUmbracoCore,
|
||||
/// <para>This class is initialized with the container in UmbracoApplicationBase,
|
||||
/// right after the container is created in UmbracoApplicationBase.HandleApplicationStart.</para>
|
||||
/// <para>Obviously, this is a service locator, which some may consider an anti-pattern. And yet,
|
||||
/// practically, it works.</para>
|
||||
/// </remarks>
|
||||
public static class Current
|
||||
{
|
||||
private static IServiceContainer _container;
|
||||
private static IFactory _factory;
|
||||
|
||||
// fixme - refactor
|
||||
// we don't want Umbraco tests to die because the container has not been properly initialized,
|
||||
// for some too-important things such as IShortStringHelper or loggers, so if it's not
|
||||
// registered we setup a default one. We should really refactor our tests so that it does
|
||||
// not happen.
|
||||
|
||||
private static IShortStringHelper _shortStringHelper;
|
||||
private static ILogger _logger;
|
||||
private static IProfiler _profiler;
|
||||
private static ProfilingLogger _profilingLogger;
|
||||
private static IProfilingLogger _profilingLogger;
|
||||
private static IPublishedValueFallback _publishedValueFallback;
|
||||
private static UmbracoConfig _config;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the DI container.
|
||||
/// Gets or sets the factory.
|
||||
/// </summary>
|
||||
public static IServiceContainer Container
|
||||
public static IFactory Factory
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_container == null) throw new Exception("No container has been set.");
|
||||
return _container;
|
||||
if (_factory == null) throw new Exception("No factory has been set.");
|
||||
return _factory;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_container != null) throw new Exception("A container has already been set.");
|
||||
_container = value;
|
||||
if (_factory != null) throw new Exception("A factory has already been set.");
|
||||
_factory = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool HasContainer => _container != null;
|
||||
internal static bool HasContainer => _factory != null;
|
||||
|
||||
// for UNIT TESTS exclusively!
|
||||
// resets *everything* that is 'current'
|
||||
internal static void Reset()
|
||||
{
|
||||
_container?.Dispose();
|
||||
_container = null;
|
||||
_factory.DisposeIfDisposable();
|
||||
_factory = null;
|
||||
|
||||
_shortStringHelper = null;
|
||||
_logger = null;
|
||||
@@ -74,93 +80,94 @@ namespace Umbraco.Core.Composing
|
||||
|
||||
#region Getters
|
||||
|
||||
// fixme - refactor
|
||||
// we don't want Umbraco to die because the container has not been properly initialized,
|
||||
// for some too-important things such as IShortStringHelper or loggers, so if it's not
|
||||
// registered we setup a default one. We should really refactor our tests so that it does
|
||||
// not happen. Will do when we get rid of IShortStringHelper.
|
||||
|
||||
public static IShortStringHelper ShortStringHelper
|
||||
=> _shortStringHelper ?? (_shortStringHelper = _container?.TryGetInstance<IShortStringHelper>()
|
||||
?? new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(UmbracoConfig.For.UmbracoSettings())));
|
||||
=> _shortStringHelper ?? (_shortStringHelper = _factory?.TryGetInstance<IShortStringHelper>()
|
||||
?? new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(Config.Umbraco())));
|
||||
|
||||
public static ILogger Logger
|
||||
=> _logger ?? (_logger = _container?.TryGetInstance<ILogger>()
|
||||
=> _logger ?? (_logger = _factory?.TryGetInstance<ILogger>()
|
||||
?? new DebugDiagnosticsLogger());
|
||||
|
||||
public static IProfiler Profiler
|
||||
=> _profiler ?? (_profiler = _container?.TryGetInstance<IProfiler>()
|
||||
=> _profiler ?? (_profiler = _factory?.TryGetInstance<IProfiler>()
|
||||
?? new LogProfiler(Logger));
|
||||
|
||||
public static ProfilingLogger ProfilingLogger
|
||||
=> _profilingLogger ?? (_profilingLogger = _container?.TryGetInstance<ProfilingLogger>())
|
||||
public static IProfilingLogger ProfilingLogger
|
||||
=> _profilingLogger ?? (_profilingLogger = _factory?.TryGetInstance<IProfilingLogger>())
|
||||
?? new ProfilingLogger(Logger, Profiler);
|
||||
|
||||
public static IRuntimeState RuntimeState
|
||||
=> Container.GetInstance<IRuntimeState>();
|
||||
=> Factory.GetInstance<IRuntimeState>();
|
||||
|
||||
public static TypeLoader TypeLoader
|
||||
=> Container.GetInstance<TypeLoader>();
|
||||
=> Factory.GetInstance<TypeLoader>();
|
||||
|
||||
public static FileSystems FileSystems
|
||||
=> Container.GetInstance<FileSystems>();
|
||||
public static UmbracoConfig Config
|
||||
=> _config ?? (_config = _factory?.TryGetInstance<UmbracoConfig>()
|
||||
?? new UmbracoConfig(Logger, _factory?.TryGetInstance<IRuntimeCacheProvider>(), _factory?.TryGetInstance<IRuntimeState>()));
|
||||
|
||||
public static IFileSystems FileSystems
|
||||
=> Factory.GetInstance<IFileSystems>();
|
||||
|
||||
public static IMediaFileSystem MediaFileSystem
|
||||
=> Factory.GetInstance<IMediaFileSystem>();
|
||||
|
||||
public static UrlSegmentProviderCollection UrlSegmentProviders
|
||||
=> Container.GetInstance<UrlSegmentProviderCollection>();
|
||||
=> Factory.GetInstance<UrlSegmentProviderCollection>();
|
||||
|
||||
public static CacheRefresherCollection CacheRefreshers
|
||||
=> Container.GetInstance<CacheRefresherCollection>();
|
||||
=> Factory.GetInstance<CacheRefresherCollection>();
|
||||
|
||||
public static DataEditorCollection DataEditors
|
||||
=> Container.GetInstance<DataEditorCollection>();
|
||||
=> Factory.GetInstance<DataEditorCollection>();
|
||||
|
||||
public static PropertyEditorCollection PropertyEditors
|
||||
=> Container.GetInstance<PropertyEditorCollection>();
|
||||
=> Factory.GetInstance<PropertyEditorCollection>();
|
||||
|
||||
public static ParameterEditorCollection ParameterEditors
|
||||
=> Container.GetInstance<ParameterEditorCollection>();
|
||||
=> Factory.GetInstance<ParameterEditorCollection>();
|
||||
|
||||
internal static ManifestValueValidatorCollection ManifestValidators
|
||||
=> Container.GetInstance<ManifestValueValidatorCollection>();
|
||||
=> Factory.GetInstance<ManifestValueValidatorCollection>();
|
||||
|
||||
internal static PackageActionCollection PackageActions
|
||||
=> Container.GetInstance<PackageActionCollection>();
|
||||
=> Factory.GetInstance<PackageActionCollection>();
|
||||
|
||||
internal static PropertyValueConverterCollection PropertyValueConverters
|
||||
=> Container.GetInstance<PropertyValueConverterCollection>();
|
||||
=> Factory.GetInstance<PropertyValueConverterCollection>();
|
||||
|
||||
internal static IPublishedModelFactory PublishedModelFactory
|
||||
=> Container.GetInstance<IPublishedModelFactory>();
|
||||
=> Factory.GetInstance<IPublishedModelFactory>();
|
||||
|
||||
public static IServerMessenger ServerMessenger
|
||||
=> Container.GetInstance<IServerMessenger>();
|
||||
=> Factory.GetInstance<IServerMessenger>();
|
||||
|
||||
public static IServerRegistrar ServerRegistrar
|
||||
=> Container.GetInstance<IServerRegistrar>();
|
||||
=> Factory.GetInstance<IServerRegistrar>();
|
||||
|
||||
public static ICultureDictionaryFactory CultureDictionaryFactory
|
||||
=> Container.GetInstance<ICultureDictionaryFactory>();
|
||||
=> Factory.GetInstance<ICultureDictionaryFactory>();
|
||||
|
||||
public static CacheHelper ApplicationCache
|
||||
=> Container.GetInstance<CacheHelper>();
|
||||
=> Factory.GetInstance<CacheHelper>();
|
||||
|
||||
public static ServiceContext Services
|
||||
=> Container.GetInstance<ServiceContext>();
|
||||
=> Factory.GetInstance<ServiceContext>();
|
||||
|
||||
public static IScopeProvider ScopeProvider
|
||||
=> Container.GetInstance<IScopeProvider>();
|
||||
=> Factory.GetInstance<IScopeProvider>();
|
||||
|
||||
public static ISqlContext SqlContext
|
||||
=> Container.GetInstance<ISqlContext>();
|
||||
=> Factory.GetInstance<ISqlContext>();
|
||||
|
||||
public static IPublishedContentTypeFactory PublishedContentTypeFactory
|
||||
=> Container.GetInstance<IPublishedContentTypeFactory>();
|
||||
=> Factory.GetInstance<IPublishedContentTypeFactory>();
|
||||
|
||||
public static IPublishedValueFallback PublishedValueFallback
|
||||
=> _publishedValueFallback ?? Container.GetInstance<IPublishedValueFallback>() ?? new NoopPublishedValueFallback();
|
||||
=> _publishedValueFallback ?? Factory.GetInstance<IPublishedValueFallback>() ?? new NoopPublishedValueFallback();
|
||||
|
||||
public static IVariationContextAccessor VariationContextAccessor
|
||||
=> Container.GetInstance<IVariationContextAccessor>();
|
||||
=> Factory.GetInstance<IVariationContextAccessor>();
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
87
src/Umbraco.Core/Composing/FactoryExtensions.cs
Normal file
87
src/Umbraco.Core/Composing/FactoryExtensions.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Umbraco.Core.Composing
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods to the <see cref="IFactory"/> class.
|
||||
/// </summary>
|
||||
public static class FactoryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an instance of a service.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the service.</typeparam>
|
||||
/// <param name="factory">The factory.</param>
|
||||
/// <returns>An instance of the specified type.</returns>
|
||||
/// <remarks>Throws an exception if the factory failed to get an instance of the specified type.</remarks>
|
||||
public static T GetInstance<T>(this IFactory factory)
|
||||
=> (T)factory.GetInstance(typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get an instance of a service.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the service.</typeparam>
|
||||
/// <returns>An instance of the specified type, or null.</returns>
|
||||
/// <remarks>Returns null if the factory does not know how to get an instance
|
||||
/// of the specified type. Throws an exception if the factory does know how
|
||||
/// to get an instance of the specified type, but failed to do so.</remarks>
|
||||
public static T TryGetInstance<T>(this IFactory factory)
|
||||
=> (T)factory.TryGetInstance(typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance with arguments.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the instance.</typeparam>
|
||||
/// <param name="factory">The factory.</param>
|
||||
/// <param name="args">Arguments.</param>
|
||||
/// <returns>An instance of the specified type.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Throws an exception if the factory failed to get an instance of the specified type.</para>
|
||||
/// <para>The arguments are used as dependencies by the factory.</para>
|
||||
/// </remarks>
|
||||
public static T CreateInstance<T>(this IFactory factory, params object[] args)
|
||||
=> (T)factory.CreateInstance(typeof(T), args);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of a service, with arguments.
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="type">The type of the instance.</param>
|
||||
/// <param name="args">Named arguments.</param>
|
||||
/// <returns>An instance of the specified type.</returns>
|
||||
/// <remarks>
|
||||
/// <para>The instance type does not need to be registered into the factory.</para>
|
||||
/// <para>The arguments are used as dependencies by the factory. Other dependencies
|
||||
/// are retrieved from the factory.</para>
|
||||
/// </remarks>
|
||||
public static object CreateInstance(this IFactory factory, Type type, params object[] args)
|
||||
{
|
||||
// LightInject has this, but then it requires RegisterConstructorDependency etc and has various oddities
|
||||
// including the most annoying one, which is that it does not work on singletons (hard to fix)
|
||||
//return factory.GetInstance(type, args);
|
||||
|
||||
// this method is essentially used to build singleton instances, so it is assumed that it would be
|
||||
// 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}.");
|
||||
|
||||
var ctorParameters = ctor.GetParameters();
|
||||
var ctorArgs = new object[ctorParameters.Length];
|
||||
var i = 0;
|
||||
foreach (var parameter in ctorParameters)
|
||||
{
|
||||
// no! IsInstanceOfType is not ok here
|
||||
// ReSharper disable once UseMethodIsInstanceOfType
|
||||
var arg = args?.FirstOrDefault(a => parameter.ParameterType.IsAssignableFrom(a.GetType()));
|
||||
ctorArgs[i++] = arg ?? factory.GetInstance(parameter.ParameterType);
|
||||
}
|
||||
return ctor.Invoke(ctorArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,23 @@
|
||||
namespace Umbraco.Core.Composing
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a collection builder.
|
||||
/// </summary>
|
||||
public interface ICollectionBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers the builder so it can build the collection, by
|
||||
/// registering the collection and the types.
|
||||
/// </summary>
|
||||
void RegisterWith(IRegister register);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a collection builder.
|
||||
/// </summary>
|
||||
/// <typeparam name="TCollection">The type of the collection.</typeparam>
|
||||
/// <typeparam name="TItem">The type of the items.</typeparam>
|
||||
public interface ICollectionBuilder<out TCollection, TItem>
|
||||
public interface ICollectionBuilder<out TCollection, TItem> : ICollectionBuilder
|
||||
where TCollection : IBuilderCollection<TItem>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -13,6 +25,6 @@
|
||||
/// </summary>
|
||||
/// <returns>A collection.</returns>
|
||||
/// <remarks>Creates a new collection each time it is invoked.</remarks>
|
||||
TCollection CreateCollection();
|
||||
TCollection CreateCollection(IFactory factory);
|
||||
}
|
||||
}
|
||||
|
||||
81
src/Umbraco.Core/Composing/IFactory.cs
Normal file
81
src/Umbraco.Core/Composing/IFactory.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Composing
|
||||
{
|
||||
// Implementing:
|
||||
//
|
||||
// The factory
|
||||
// - always picks the constructor with the most parameters
|
||||
// - supports Lazy parameters (and prefers them over non-Lazy) in constructors
|
||||
// - what happens with 'releasing' is unclear
|
||||
|
||||
/// <summary>
|
||||
/// Defines a service factory for Umbraco.
|
||||
/// </summary>
|
||||
public interface IFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the concrete factory.
|
||||
/// </summary>
|
||||
object Concrete { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance of a service.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the service.</param>
|
||||
/// <returns>An instance of the specified type.</returns>
|
||||
/// <remarks>Throws an exception if the container failed to get an instance of the specified type.</remarks>
|
||||
object GetInstance(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get an instance of a service.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the service.</param>
|
||||
/// <returns>An instance of the specified type, or null.</returns>
|
||||
/// <remarks>Returns null if the container does not know how to get an instance
|
||||
/// of the specified type. Throws an exception if the container does know how
|
||||
/// to get an instance of the specified type, but failed to do so.</remarks>
|
||||
object TryGetInstance(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all instances of a service.
|
||||
/// </summary>
|
||||
/// <param name="serviceType">The type of the service.</param>
|
||||
IEnumerable<object> GetAllInstances(Type serviceType);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all instances of a service.
|
||||
/// </summary>
|
||||
/// <typeparam name="TService">The type of the service.</typeparam>
|
||||
IEnumerable<TService> GetAllInstances<TService>();
|
||||
|
||||
/// <summary>
|
||||
/// Releases an instance.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <remarks>
|
||||
/// See https://stackoverflow.com/questions/14072208 and http://kozmic.net/2010/08/27/must-i-release-everything-when-using-windsor/,
|
||||
/// you only need to release instances you specifically resolved, and even then, if done right, that might never be needed. For
|
||||
/// instance, LightInject does not require this and does not support it - should work with scopes.
|
||||
/// </remarks>
|
||||
void Release(object instance);
|
||||
|
||||
/// <summary>
|
||||
/// Begins a scope.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>When the scope is disposed, scoped instances that have been created during the scope are disposed.</para>
|
||||
/// <para>Scopes can be nested. Each instance is disposed individually.</para>
|
||||
/// </remarks>
|
||||
IDisposable BeginScope();
|
||||
|
||||
/// <summary>
|
||||
/// Enables per-request scope.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Ties scopes to web requests.</para>
|
||||
/// </remarks>
|
||||
void EnablePerWebRequestScope();
|
||||
}
|
||||
}
|
||||
76
src/Umbraco.Core/Composing/IRegister.cs
Normal file
76
src/Umbraco.Core/Composing/IRegister.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Composing
|
||||
{
|
||||
// Implementing:
|
||||
//
|
||||
// The register
|
||||
// - supports registering a service, even after some instances of other services have been created
|
||||
// - supports re-registering a service, as long as no instance of that service has been created
|
||||
// - throws when re-registering a service, and an instance of that service has been created
|
||||
//
|
||||
// - registers only one implementation of a nameless service, re-registering replaces the previous
|
||||
// registration - names are required to register multiple implementations - and getting an
|
||||
// IEnumerable of the service, nameless, returns them all
|
||||
|
||||
/// <summary>
|
||||
/// Defines a service register for Umbraco.
|
||||
/// </summary>
|
||||
public interface IRegister
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the concrete container.
|
||||
/// </summary>
|
||||
object Concrete { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Registers a service as its own implementation.
|
||||
/// </summary>
|
||||
void Register(Type serviceType, Lifetime lifetime = Lifetime.Transient);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a service with an implementation type.
|
||||
/// </summary>
|
||||
void Register(Type serviceType, Type implementingType, Lifetime lifetime = Lifetime.Transient);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a service with an implementation factory.
|
||||
/// </summary>
|
||||
void Register<TService>(Func<IFactory, TService> factory, Lifetime lifetime = Lifetime.Transient);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a service with an implementing instance.
|
||||
/// </summary>
|
||||
void RegisterInstance(Type serviceType, object instance);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a base type for auto-registration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Auto-registration means that anytime the container is asked to create an instance
|
||||
/// of a type deriving from <paramref name="serviceBaseType"/>, it will first register that
|
||||
/// type automatically.</para>
|
||||
/// <para>This can be used for instance for views or controllers. Then, one just needs to
|
||||
/// register a common base class or interface, and the container knows how to create instances.</para>
|
||||
/// </remarks>
|
||||
void RegisterAuto(Type serviceBaseType);
|
||||
|
||||
#region Control
|
||||
|
||||
/// <summary>
|
||||
/// Configures the container for web support.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Enables support for MVC, WebAPI, but *not* per-request scope. This is used early in the boot
|
||||
/// process, where anything "scoped" should not be linked to a web request.</para>
|
||||
/// </remarks>
|
||||
void ConfigureForWeb();
|
||||
|
||||
/// <summary>
|
||||
/// Creates the factory.
|
||||
/// </summary>
|
||||
IFactory CreateFactory();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LightInject;
|
||||
|
||||
namespace Umbraco.Core.Composing
|
||||
{
|
||||
@@ -15,31 +14,22 @@ namespace Umbraco.Core.Composing
|
||||
where TBuilder : LazyCollectionBuilderBase<TBuilder, TCollection, TItem>
|
||||
where TCollection : IBuilderCollection<TItem>
|
||||
{
|
||||
private readonly List<Func<IEnumerable<Type>>> _producers1 = new List<Func<IEnumerable<Type>>>();
|
||||
private readonly List<Func<IServiceFactory, IEnumerable<Type>>> _producers2 = new List<Func<IServiceFactory, IEnumerable<Type>>>();
|
||||
private readonly List<Func<IEnumerable<Type>>> _producers = new List<Func<IEnumerable<Type>>>();
|
||||
private readonly List<Type> _excluded = new List<Type>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LazyCollectionBuilderBase{TBuilder,TCollection,TItem}"/> class.
|
||||
/// </summary>
|
||||
protected LazyCollectionBuilderBase(IServiceContainer container)
|
||||
: base(container)
|
||||
{ }
|
||||
|
||||
protected abstract TBuilder This { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears all types in the collection.
|
||||
/// </summary>
|
||||
/// <returns>The buidler.</returns>
|
||||
/// <returns>The builder.</returns>
|
||||
public TBuilder Clear()
|
||||
{
|
||||
Configure(types =>
|
||||
{
|
||||
types.Clear();
|
||||
_producers1.Clear();
|
||||
_producers2.Clear();
|
||||
_excluded.Clear();
|
||||
_producers.Clear();
|
||||
_excluded.Clear();
|
||||
});
|
||||
return This;
|
||||
}
|
||||
@@ -84,21 +74,7 @@ namespace Umbraco.Core.Composing
|
||||
{
|
||||
Configure(types =>
|
||||
{
|
||||
_producers1.Add(producer);
|
||||
});
|
||||
return This;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a types producer to the collection.
|
||||
/// </summary>
|
||||
/// <param name="producer">The types producer.</param>
|
||||
/// <returns>The builder.</returns>
|
||||
public TBuilder Add(Func<IServiceFactory, IEnumerable<Type>> producer)
|
||||
{
|
||||
Configure(types =>
|
||||
{
|
||||
_producers2.Add(producer);
|
||||
_producers.Add(producer);
|
||||
});
|
||||
return This;
|
||||
}
|
||||
@@ -137,8 +113,7 @@ namespace Umbraco.Core.Composing
|
||||
protected override IEnumerable<Type> GetRegisteringTypes(IEnumerable<Type> types)
|
||||
{
|
||||
return types
|
||||
.Union(_producers1.SelectMany(x => x()))
|
||||
.Union(_producers2.SelectMany(x => x(Container)))
|
||||
.Union(_producers.SelectMany(x => x()))
|
||||
.Distinct()
|
||||
.Select(x => EnsureType(x, "register"))
|
||||
.Except(_excluded);
|
||||
|
||||
49
src/Umbraco.Core/Composing/Lifetime.cs
Normal file
49
src/Umbraco.Core/Composing/Lifetime.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
namespace Umbraco.Core.Composing
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the lifetime of a registered instance.
|
||||
/// </summary>
|
||||
public enum Lifetime
|
||||
{
|
||||
/// <summary>
|
||||
/// Always get a new instance.
|
||||
/// </summary>
|
||||
/// <remarks>Corresponds to Transient in LightInject, Castle Windsor
|
||||
/// or MS.DI, PerDependency in Autofac.</remarks>
|
||||
Transient,
|
||||
|
||||
/// <summary>
|
||||
/// One unique instance per request.
|
||||
/// </summary>
|
||||
// fixme - not what you think!
|
||||
// currently, corresponds to 'Request' in LightInject which is 'Transient + disposed by Scope'
|
||||
// but NOT (in LightInject) a per-web-request lifetime, more a TransientScoped
|
||||
//
|
||||
// we use it for controllers, httpContextBase and umbracoContext
|
||||
// - so that they are automatically disposed at the end of the scope (ie request)
|
||||
// - not sure they should not be simply 'scoped'?
|
||||
//
|
||||
// Castle has an extra PerWebRequest something, and others use scope
|
||||
// what about Request before first request ie during application startup?
|
||||
// see http://blog.ploeh.dk/2009/11/17/UsingCastleWindsor'sPerWebRequestlifestylewithASP.NETMVConIIS7/
|
||||
// Castle ends up requiring a special scope manager too
|
||||
// see https://groups.google.com/forum/#!topic/castle-project-users/1E2W9LVIYR4
|
||||
//
|
||||
// but maybe also - why are we requiring scoped services at startup?
|
||||
Request,
|
||||
|
||||
/// <summary>
|
||||
/// One unique instance per container scope.
|
||||
/// </summary>
|
||||
/// <remarks>Corresponds to Scope in LightInject, Scoped in MS.DI
|
||||
/// or Castle Windsor, PerLifetimeScope in Autofac.</remarks>
|
||||
Scope,
|
||||
|
||||
/// <summary>
|
||||
/// One unique instance per container.
|
||||
/// </summary>
|
||||
/// <remarks>Corresponds to Singleton in LightInject, Castle Windsor
|
||||
/// or MS.DI and to SingleInstance in Autofac.</remarks>
|
||||
Singleton
|
||||
}
|
||||
}
|
||||
279
src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs
Normal file
279
src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs
Normal file
@@ -0,0 +1,279 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using LightInject;
|
||||
|
||||
namespace Umbraco.Core.Composing.LightInject
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements DI with LightInject.
|
||||
/// </summary>
|
||||
public class LightInjectContainer : IRegister, IFactory, IDisposable
|
||||
{
|
||||
private int _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LightInjectContainer"/> with a LightInject container.
|
||||
/// </summary>
|
||||
protected LightInjectContainer(ServiceContainer container)
|
||||
{
|
||||
Container = container;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="LightInjectContainer"/> class.
|
||||
/// </summary>
|
||||
public static LightInjectContainer Create()
|
||||
=> new LightInjectContainer(CreateServiceContainer());
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the LightInject service container.
|
||||
/// </summary>
|
||||
protected static ServiceContainer CreateServiceContainer()
|
||||
{
|
||||
var container = new ServiceContainer(new ContainerOptions { EnablePropertyInjection = false });
|
||||
|
||||
// note: the block below is disabled, as it is too LightInject-specific
|
||||
//
|
||||
// supports annotated constructor injections
|
||||
// eg to specify the service name on some services
|
||||
//container.EnableAnnotatedConstructorInjection();
|
||||
|
||||
// note: the block below is disabled, we do not allow property injection at all anymore
|
||||
// (see options in CreateServiceContainer)
|
||||
//
|
||||
// from the docs: "LightInject considers all read/write properties a dependency, but implements
|
||||
// a loose strategy around property dependencies, meaning that it will NOT throw an exception
|
||||
// in the case of an unresolved property dependency."
|
||||
//
|
||||
// in Umbraco we do NOT want to do property injection by default, so we have to disable it.
|
||||
// from the docs, the following line will cause the container to "now only try to inject
|
||||
// dependencies for properties that is annotated with the InjectAttribute."
|
||||
//
|
||||
// could not find it documented, but tests & code review shows that LightInject considers a
|
||||
// property to be "injectable" when its setter exists and is not static, nor private, nor
|
||||
// it is an index property. which means that eg protected or internal setters are OK.
|
||||
//Container.EnableAnnotatedPropertyInjection();
|
||||
|
||||
// ensure that we do *not* scan assemblies
|
||||
// we explicitly RegisterFrom our own composition roots and don't want them scanned
|
||||
container.AssemblyScanner = new AssemblyScanner(/*container.AssemblyScanner*/);
|
||||
|
||||
// see notes in MixedLightInjectScopeManagerProvider
|
||||
container.ScopeManagerProvider = new MixedLightInjectScopeManagerProvider();
|
||||
|
||||
// note: the block below is disabled, because it does not work, because collection builders
|
||||
// are singletons, and constructor dependencies don't work on singletons, see
|
||||
// https://github.com/seesharper/LightInject/issues/294
|
||||
//
|
||||
// if looking for a IContainer, and one was passed in args, use it
|
||||
// this is for collection builders which require the IContainer
|
||||
//container.RegisterConstructorDependency((c, i, a) => a.OfType<IContainer>().FirstOrDefault());
|
||||
//
|
||||
// and, the block below is also disabled, because it is ugly
|
||||
//
|
||||
//// which means that the only way to inject the container into builders is to register it
|
||||
//container.RegisterInstance<IContainer>(this);
|
||||
//
|
||||
// instead, we use an explicit GetInstance with arguments implementation
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the LightInject container.
|
||||
/// </summary>
|
||||
protected ServiceContainer Container { get; }
|
||||
|
||||
/// <inheritdoc cref="IRegister"/>
|
||||
/// <inheritdoc cref="IFactory"/>
|
||||
public object Concrete => Container;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _disposed, 1) == 1)
|
||||
return;
|
||||
|
||||
Container.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFactory CreateFactory() => this;
|
||||
|
||||
#region Factory
|
||||
|
||||
/// <inheritdoc />
|
||||
public object GetInstance(Type type)
|
||||
=> Container.GetInstance(type);
|
||||
|
||||
/// <inheritdoc />
|
||||
public object TryGetInstance(Type type)
|
||||
=> Container.TryGetInstance(type);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<T> GetAllInstances<T>()
|
||||
=> Container.GetAllInstances<T>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<object> GetAllInstances(Type type)
|
||||
=> Container.GetAllInstances(type);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Release(object instance)
|
||||
{
|
||||
// nothing to release with LightInject
|
||||
}
|
||||
|
||||
// notes:
|
||||
// we may want to look into MS code, eg:
|
||||
// TypeActivatorCache in MVC at https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.Core/Internal/TypeActivatorCache.cs
|
||||
// which relies onto
|
||||
// ActivatorUtilities at https://github.com/aspnet/DependencyInjection/blob/master/shared/Microsoft.Extensions.ActivatorUtilities.Sources/ActivatorUtilities.cs
|
||||
|
||||
#endregion
|
||||
|
||||
#region Registry
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Register(Type serviceType, Lifetime lifetime = Lifetime.Transient)
|
||||
{
|
||||
switch (lifetime)
|
||||
{
|
||||
case Lifetime.Transient:
|
||||
Container.Register(serviceType);
|
||||
break;
|
||||
case Lifetime.Request:
|
||||
case Lifetime.Scope:
|
||||
case Lifetime.Singleton:
|
||||
Container.Register(serviceType, GetLifetime(lifetime));
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"Lifetime {lifetime} is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Register(Type serviceType, Type implementingType, Lifetime lifetime = Lifetime.Transient)
|
||||
{
|
||||
switch (lifetime)
|
||||
{
|
||||
case Lifetime.Transient:
|
||||
Container.Register(serviceType, implementingType, implementingType.Name);
|
||||
break;
|
||||
case Lifetime.Request:
|
||||
case Lifetime.Scope:
|
||||
case Lifetime.Singleton:
|
||||
Container.Register(serviceType, implementingType, GetLifetime(lifetime));
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"Lifetime {lifetime} is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Register<TService>(Func<IFactory, TService> factory, Lifetime lifetime = Lifetime.Transient)
|
||||
{
|
||||
switch (lifetime)
|
||||
{
|
||||
case Lifetime.Transient:
|
||||
Container.Register(f => factory(this));
|
||||
break;
|
||||
case Lifetime.Request:
|
||||
case Lifetime.Scope:
|
||||
case Lifetime.Singleton:
|
||||
Container.Register(f => factory(this), GetLifetime(lifetime));
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"Lifetime {lifetime} is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
private ILifetime GetLifetime(Lifetime lifetime)
|
||||
{
|
||||
switch (lifetime)
|
||||
{
|
||||
case Lifetime.Transient:
|
||||
return null;
|
||||
case Lifetime.Request:
|
||||
return new PerRequestLifeTime();
|
||||
case Lifetime.Scope:
|
||||
return new PerScopeLifetime();
|
||||
case Lifetime.Singleton:
|
||||
return new PerContainerLifetime();
|
||||
default:
|
||||
throw new NotSupportedException($"Lifetime {lifetime} is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RegisterInstance(Type serviceType, object instance)
|
||||
=> Container.RegisterInstance(serviceType, instance);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RegisterAuto(Type serviceBaseType)
|
||||
{
|
||||
Container.RegisterFallback((serviceType, serviceName) =>
|
||||
{
|
||||
// https://github.com/seesharper/LightInject/issues/173
|
||||
if (serviceBaseType.IsAssignableFromGtd(serviceType))
|
||||
Container.Register(serviceType);
|
||||
return false;
|
||||
}, null);
|
||||
}
|
||||
|
||||
// was the Light-Inject specific way of dealing with args, but we've replaced it with our own
|
||||
// beware! does NOT work on singletons, see https://github.com/seesharper/LightInject/issues/294
|
||||
//
|
||||
///// <inheritdoc />
|
||||
//public void RegisterConstructorDependency<TDependency>(Func<IContainer, ParameterInfo, TDependency> factory)
|
||||
// => Container.RegisterConstructorDependency((f, x) => factory(this, x));
|
||||
//
|
||||
///// <inheritdoc />
|
||||
//public void RegisterConstructorDependency<TDependency>(Func<IContainer, ParameterInfo, object[], TDependency> factory)
|
||||
// => Container.RegisterConstructorDependency((f, x, a) => factory(this, x, a));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Control
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable BeginScope()
|
||||
=> Container.BeginScope();
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void ConfigureForWeb()
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void EnablePerWebRequestScope()
|
||||
{
|
||||
if (!(Container.ScopeManagerProvider is MixedLightInjectScopeManagerProvider smp))
|
||||
throw new Exception("Container.ScopeManagerProvider is not MixedLightInjectScopeManagerProvider.");
|
||||
smp.EnablePerWebRequestScope();
|
||||
}
|
||||
|
||||
private class AssemblyScanner : IAssemblyScanner
|
||||
{
|
||||
//private readonly IAssemblyScanner _scanner;
|
||||
|
||||
//public AssemblyScanner(IAssemblyScanner scanner)
|
||||
//{
|
||||
// _scanner = scanner;
|
||||
//}
|
||||
|
||||
public void Scan(Assembly assembly, IServiceRegistry serviceRegistry, Func<ILifetime> lifetime, Func<Type, Type, bool> shouldRegister, Func<Type, Type, string> serviceNameProvider)
|
||||
{
|
||||
// nothing - we *could* scan non-Umbraco assemblies, though
|
||||
}
|
||||
|
||||
public void Scan(Assembly assembly, IServiceRegistry serviceRegistry)
|
||||
{
|
||||
// nothing - we *could* scan non-Umbraco assemblies, though
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Umbraco.Core.Exceptions
|
||||
namespace Umbraco.Core.Composing.LightInject
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents errors that occur due to LightInject.
|
||||
@@ -1,7 +1,7 @@
|
||||
using LightInject;
|
||||
using LightInject.Web;
|
||||
|
||||
namespace Umbraco.Core.Composing
|
||||
namespace Umbraco.Core.Composing.LightInject
|
||||
{
|
||||
// by default, the container's scope manager provider is PerThreadScopeManagerProvider,
|
||||
// and then container.EnablePerWebRequestScope() replaces it with PerWebRequestScopeManagerProvider,
|
||||
@@ -1,392 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Exceptions;
|
||||
|
||||
namespace Umbraco.Core.Composing
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extensions to LightInject.
|
||||
/// </summary>
|
||||
public static class LightInjectExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configure the container for Umbraco Core usage and assign to Current.
|
||||
/// </summary>
|
||||
/// <param name="container">The container.</param>
|
||||
/// <remarks>The container is now the unique application container and is now accessible via Current.Container.</remarks>
|
||||
internal static void ConfigureUmbracoCore(this ServiceContainer container)
|
||||
{
|
||||
// supports annotated constructor injections
|
||||
// eg to specify the service name on some services
|
||||
container.EnableAnnotatedConstructorInjection();
|
||||
|
||||
// from the docs: "LightInject considers all read/write properties a dependency, but implements
|
||||
// a loose strategy around property dependencies, meaning that it will NOT throw an exception
|
||||
// in the case of an unresolved property dependency."
|
||||
//
|
||||
// in Umbraco we do NOT want to do property injection by default, so we have to disable it.
|
||||
// from the docs, the following line will cause the container to "now only try to inject
|
||||
// dependencies for properties that is annotated with the InjectAttribute."
|
||||
//
|
||||
// could not find it documented, but tests & code review shows that LightInject considers a
|
||||
// property to be "injectable" when its setter exists and is not static, nor private, nor
|
||||
// it is an index property. which means that eg protected or internal setters are OK.
|
||||
container.EnableAnnotatedPropertyInjection();
|
||||
|
||||
// ensure that we do *not* scan assemblies
|
||||
// we explicitely RegisterFrom our own composition roots and don't want them scanned
|
||||
container.AssemblyScanner = new AssemblyScanner(/*container.AssemblyScanner*/);
|
||||
|
||||
// see notes in MixedLightInjectScopeManagerProvider
|
||||
container.ScopeManagerProvider = new MixedLightInjectScopeManagerProvider();
|
||||
|
||||
// self-register
|
||||
container.Register<IServiceContainer>(_ => container);
|
||||
|
||||
// configure the current container
|
||||
Current.Container = container;
|
||||
}
|
||||
|
||||
private class AssemblyScanner : IAssemblyScanner
|
||||
{
|
||||
//private readonly IAssemblyScanner _scanner;
|
||||
|
||||
//public AssemblyScanner(IAssemblyScanner scanner)
|
||||
//{
|
||||
// _scanner = scanner;
|
||||
//}
|
||||
|
||||
public void Scan(Assembly assembly, IServiceRegistry serviceRegistry, Func<ILifetime> lifetime, Func<Type, Type, bool> shouldRegister, Func<Type, Type, string> serviceNameProvider)
|
||||
{
|
||||
// nothing - we *could* scan non-Umbraco assemblies, though
|
||||
}
|
||||
|
||||
public void Scan(Assembly assembly, IServiceRegistry serviceRegistry)
|
||||
{
|
||||
// nothing - we *could* scan non-Umbraco assemblies, though
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a service implementation with a specified lifetime.
|
||||
/// </summary>
|
||||
/// <typeparam name="TService">The type of the service.</typeparam>
|
||||
/// <typeparam name="TImplementation">The type of the implementation.</typeparam>
|
||||
/// <typeparam name="TLifetime">The type of the lifetime.</typeparam>
|
||||
/// <param name="container">The container.</param>
|
||||
public static void Register<TService, TImplementation, TLifetime>(this IServiceContainer container)
|
||||
where TImplementation : TService
|
||||
where TLifetime : ILifetime, new()
|
||||
{
|
||||
container.Register<TService, TImplementation>(new TLifetime());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a service implementation with a specified lifetime.
|
||||
/// </summary>
|
||||
/// <typeparam name="TService">The type of the service.</typeparam>
|
||||
/// <typeparam name="TLifetime">The type of the lifetime.</typeparam>
|
||||
/// <param name="container">The container.</param>
|
||||
/// <param name="factory">A factory.</param>
|
||||
public static void Register<TService, TLifetime>(this IServiceContainer container, Func<IServiceFactory, TService> factory)
|
||||
where TLifetime : ILifetime, new()
|
||||
{
|
||||
container.Register(factory, new TLifetime());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers several service implementations with a specified lifetime.
|
||||
/// </summary>
|
||||
/// <typeparam name="TService">The type of the service.</typeparam>
|
||||
/// <typeparam name="TLifeTime">The type of the lifetime.</typeparam>
|
||||
/// <param name="container">The container.</param>
|
||||
/// <param name="implementations">The types of the implementations.</param>
|
||||
public static void RegisterMany<TService, TLifeTime>(this IServiceContainer container, IEnumerable<Type> implementations)
|
||||
where TLifeTime : ILifetime, new()
|
||||
{
|
||||
foreach (var implementation in implementations)
|
||||
{
|
||||
// if "typeof (TService)" is there then "implementation.FullName" MUST be there too
|
||||
container.Register(typeof(TService), implementation, implementation.FullName, new TLifeTime());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the TService with the factory that describes the dependencies of the service, as a singleton.
|
||||
/// </summary>
|
||||
public static void RegisterSingleton<TService>(this IServiceRegistry container, Func<IServiceFactory, TService> factory, string serviceName)
|
||||
{
|
||||
var registration = container.GetAvailableService<TService>(serviceName);
|
||||
if (registration == null)
|
||||
{
|
||||
container.Register(factory, serviceName, new PerContainerLifetime());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (registration.Lifetime is PerContainerLifetime == false)
|
||||
throw new InvalidOperationException("Existing registration lifetime is not PerContainer.");
|
||||
UpdateRegistration(registration, null, factory);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the TService with the TImplementation as a singleton.
|
||||
/// </summary>
|
||||
public static void RegisterSingleton<TService, TImplementation>(this IServiceRegistry container)
|
||||
where TImplementation : TService
|
||||
{
|
||||
var registration = container.GetAvailableService<TService>();
|
||||
|
||||
if (registration == null)
|
||||
{
|
||||
container.Register<TService, TImplementation>(new PerContainerLifetime());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (registration.Lifetime is PerContainerLifetime == false)
|
||||
throw new InvalidOperationException("Existing registration lifetime is not PerContainer.");
|
||||
UpdateRegistration(registration, typeof(TImplementation), null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a concrete type as a singleton service.
|
||||
/// </summary>
|
||||
public static void RegisterSingleton<TImplementation>(this IServiceRegistry container)
|
||||
{
|
||||
var registration = container.GetAvailableService<TImplementation>();
|
||||
if (registration == null)
|
||||
{
|
||||
container.Register<TImplementation>(new PerContainerLifetime());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (registration.Lifetime is PerContainerLifetime == false)
|
||||
throw new InvalidOperationException("Existing registration lifetime is not PerContainer.");
|
||||
UpdateRegistration(registration, typeof(TImplementation), null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the TService with the factory that describes the dependencies of the service, as a singleton.
|
||||
/// </summary>
|
||||
/// <typeparam name="TService"></typeparam>
|
||||
/// <param name="container"></param>
|
||||
/// <param name="factory"></param>
|
||||
public static void RegisterSingleton<TService>(this IServiceRegistry container, Func<IServiceFactory, TService> factory)
|
||||
{
|
||||
var registration = container.GetAvailableService<TService>();
|
||||
if (registration == null)
|
||||
container.Register(factory, new PerContainerLifetime());
|
||||
else
|
||||
UpdateRegistration(registration, null, factory);
|
||||
}
|
||||
|
||||
// note - what's below ALSO applies to non-singleton ie transient services
|
||||
//
|
||||
// see https://github.com/seesharper/LightInject/issues/133
|
||||
//
|
||||
// note: we *could* use tracking lifetimes for singletons to ensure they have not been resolved
|
||||
// already but that would not work for transient as the transient lifetime is null (and that is
|
||||
// optimized in LightInject)
|
||||
//
|
||||
// also, RegisterSingleton above is dangerous because ppl could still use Register with a
|
||||
// PerContainerLifetime and it will not work + the default Register would not work either for other
|
||||
// lifetimes
|
||||
//
|
||||
// all in all, not sure we want to let ppl have direct access to the container
|
||||
// we might instead want to expose some methods in UmbracoComponentBase or whatever?
|
||||
|
||||
/// <summary>
|
||||
/// Updates a registration.
|
||||
/// </summary>
|
||||
private static void UpdateRegistration(Registration registration, Type implementingType, Delegate factoryExpression)
|
||||
{
|
||||
// if the container has compiled already then the registrations have been captured,
|
||||
// and re-registering - although updating available services - does not modify the
|
||||
// output of GetInstance
|
||||
//
|
||||
// so we have to rely on different methods
|
||||
//
|
||||
// assuming the service has NOT been resolved, both methods below work, but the first
|
||||
// one looks simpler. it would be good to check whether the service HAS been resolved
|
||||
// but I am not sure how to do it right now, depending on the lifetime
|
||||
//
|
||||
// if the service HAS been resolved then updating is probably a bad idea
|
||||
|
||||
// not sure which is best? that one works, though, and looks simpler
|
||||
registration.ImplementingType = implementingType;
|
||||
registration.FactoryExpression = factoryExpression;
|
||||
|
||||
//container.Override(
|
||||
// r => r.ServiceType == typeof (TService) && (registration.ServiceName == null || r.ServiceName == registration.ServiceName),
|
||||
// (f, r) =>
|
||||
// {
|
||||
// r.ImplementingType = implementingType;
|
||||
// r.FactoryExpression = factoryExpression;
|
||||
// return r;
|
||||
// });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available service registrations for a service type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TService">The service type.</typeparam>
|
||||
/// <param name="container">The container.</param>
|
||||
/// <returns>The service registrations for the service type.</returns>
|
||||
public static IEnumerable<ServiceRegistration> GetAvailableServices<TService>(this IServiceRegistry container)
|
||||
{
|
||||
var typeofTService = typeof(TService);
|
||||
return container.AvailableServices.Where(x => x.ServiceType == typeofTService);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique available service registration for a service type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TService">The service type.</typeparam>
|
||||
/// <param name="container">The container.</param>
|
||||
/// <returns>The unique service registration for the service type.</returns>
|
||||
/// <remarks>Can return <c>null</c>, but throws if more than one registration exist for the service type.</remarks>
|
||||
public static ServiceRegistration GetAvailableService<TService>(this IServiceRegistry container)
|
||||
{
|
||||
var typeofTService = typeof(TService);
|
||||
return container.AvailableServices.SingleOrDefault(x => x.ServiceType == typeofTService);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique available service registration for a service type and a name.
|
||||
/// </summary>
|
||||
/// <typeparam name="TService">The service type.</typeparam>
|
||||
/// <param name="container">The container.</param>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns>The unique service registration for the service type and the name.</returns>
|
||||
/// <remarks>Can return <c>null</c>, but throws if more than one registration exist for the service type and the name.</remarks>
|
||||
public static ServiceRegistration GetAvailableService<TService>(this IServiceRegistry container, string name)
|
||||
{
|
||||
var typeofTService = typeof(TService);
|
||||
return container.AvailableServices.SingleOrDefault(x => x.ServiceType == typeofTService && x.ServiceName == name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance of a TService or throws a meaningful exception.
|
||||
/// </summary>
|
||||
/// <typeparam name="TService">The service type.</typeparam>
|
||||
/// <param name="factory">The container.</param>
|
||||
/// <returns>The instance.</returns>
|
||||
public static TService GetInstanceOrThrow<TService>(this IServiceFactory factory)
|
||||
{
|
||||
if (factory == null)
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
|
||||
try
|
||||
{
|
||||
return factory.GetInstance<TService>();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LightInjectException.TryThrow(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance of a TService or throws a meaningful exception.
|
||||
/// </summary>
|
||||
/// <param name="factory">The container.</param>
|
||||
/// <param name="tService">The type of the service.</param>
|
||||
/// <param name="serviceName">The name of the service.</param>
|
||||
/// <param name="implementingType">The implementing type.</param>
|
||||
/// <param name="args">Arguments.</param>
|
||||
/// <returns>The instance.</returns>
|
||||
internal static object GetInstanceOrThrow(this IServiceFactory factory, Type tService, string serviceName, Type implementingType, object[] args)
|
||||
{
|
||||
if (factory == null)
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
|
||||
// fixme temp - STOP doing this, it confuses LightInject and then we get ugly exception traces
|
||||
// we HAVE to let LightInject throw - and catch at THE OUTERMOST if InvalidOperationException in LightInject.Anything!
|
||||
|
||||
return factory.GetInstance(tService, serviceName, args);
|
||||
//try
|
||||
//{
|
||||
// return factory.GetInstance(tService, serviceName, args);
|
||||
//}
|
||||
//catch (Exception e)
|
||||
//{
|
||||
// LightInjectException.TryThrow(e, implementingType);
|
||||
// throw;
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a base type for auto-registration.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The base type.</typeparam>
|
||||
/// <param name="container">The container.</param>
|
||||
/// <remarks>
|
||||
/// <para>Any type that inherits/implements the base type will be auto-registered on-demand.</para>
|
||||
/// <para>This methods works with actual types. Use the other overload for eg generic definitions.</para>
|
||||
/// </remarks>
|
||||
public static void RegisterAuto<T>(this IServiceContainer container)
|
||||
{
|
||||
container.RegisterFallback((serviceType, serviceName) =>
|
||||
{
|
||||
//Current.Logger.Debug(typeof(LightInjectExtensions), $"Fallback for type {serviceType.FullName}.");
|
||||
// https://github.com/seesharper/LightInject/issues/173
|
||||
|
||||
if (typeof(T).IsAssignableFrom(serviceType))
|
||||
container.Register(serviceType);
|
||||
return false;
|
||||
}, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a base type for auto-registration.
|
||||
/// </summary>
|
||||
/// <param name="container">The container.</param>
|
||||
/// <param name="type">The base type.</param>
|
||||
/// <remarks>
|
||||
/// <para>Any type that inherits/implements the base type will be auto-registered on-demand.</para>
|
||||
/// <para>This methods works with actual types, as well as generic definitions eg <c>typeof(MyBase{})</c>.</para>
|
||||
/// </remarks>
|
||||
public static void RegisterAuto(this IServiceContainer container, Type type)
|
||||
{
|
||||
container.RegisterFallback((serviceType, serviceName) =>
|
||||
{
|
||||
//Current.Logger.Debug(typeof(LightInjectExtensions), $"Fallback for type {serviceType.FullName}.");
|
||||
// https://github.com/seesharper/LightInject/issues/173
|
||||
|
||||
if (type.IsAssignableFromGtd(serviceType))
|
||||
container.Register(serviceType);
|
||||
return false;
|
||||
}, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers and instanciates a collection builder.
|
||||
/// </summary>
|
||||
/// <typeparam name="TBuilder">The type of the collection builder.</typeparam>
|
||||
/// <param name="container">The container.</param>
|
||||
/// <returns>The collection builder.</returns>
|
||||
public static TBuilder RegisterCollectionBuilder<TBuilder>(this IServiceContainer container)
|
||||
{
|
||||
// make sure it's not already registered
|
||||
// we just don't want to support re-registering collection builders
|
||||
var registration = container.GetAvailableService<TBuilder>();
|
||||
if (registration != null)
|
||||
throw new InvalidOperationException("Collection builders should be registered only once.");
|
||||
|
||||
// register the builder - per container
|
||||
var builderLifetime = new PerContainerLifetime();
|
||||
container.Register<TBuilder>(builderLifetime);
|
||||
|
||||
// return the builder
|
||||
// (also initializes the builder)
|
||||
return container.GetInstance<TBuilder>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using LightInject;
|
||||
|
||||
namespace Umbraco.Core.Composing
|
||||
{
|
||||
@@ -14,20 +13,12 @@ namespace Umbraco.Core.Composing
|
||||
where TBuilder : OrderedCollectionBuilderBase<TBuilder, TCollection, TItem>
|
||||
where TCollection : IBuilderCollection<TItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OrderedCollectionBuilderBase{TBuilder,TCollection,TItem}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="container"></param>
|
||||
protected OrderedCollectionBuilderBase(IServiceContainer container)
|
||||
: base (container)
|
||||
{ }
|
||||
|
||||
protected abstract TBuilder This { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears all types in the collection.
|
||||
/// </summary>
|
||||
/// <returns>The buidler.</returns>
|
||||
/// <returns>The builder.</returns>
|
||||
public TBuilder Clear()
|
||||
{
|
||||
Configure(types => types.Clear());
|
||||
@@ -87,26 +78,6 @@ namespace Umbraco.Core.Composing
|
||||
return This;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends types to the collections.
|
||||
/// </summary>
|
||||
/// <param name="types">The types to append.</param>
|
||||
/// <returns>The builder.</returns>
|
||||
public TBuilder Append(Func<IServiceFactory, IEnumerable<Type>> types)
|
||||
{
|
||||
Configure(list =>
|
||||
{
|
||||
foreach (var type in types(Container))
|
||||
{
|
||||
// would be detected by CollectionBuilderBase when registering, anyways, but let's fail fast
|
||||
EnsureType(type, "register");
|
||||
if (list.Contains(type)) list.Remove(type);
|
||||
list.Add(type);
|
||||
}
|
||||
});
|
||||
return This;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a type after another type.
|
||||
/// </summary>
|
||||
|
||||
32
src/Umbraco.Core/Composing/RegisterExtensions.cs
Normal file
32
src/Umbraco.Core/Composing/RegisterExtensions.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace Umbraco.Core.Composing
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods to the <see cref="IRegister"/> class.
|
||||
/// </summary>
|
||||
public static class RegisterExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a service with an implementation type.
|
||||
/// </summary>
|
||||
public static void Register<TService, TImplementing>(this IRegister register, Lifetime lifetime = Lifetime.Transient)
|
||||
=> register.Register(typeof(TService), typeof(TImplementing), lifetime);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a service as its own implementation.
|
||||
/// </summary>
|
||||
public static void Register<TService>(this IRegister register, Lifetime lifetime = Lifetime.Transient)
|
||||
=> register.Register(typeof(TService), lifetime);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a service with an implementing instance.
|
||||
/// </summary>
|
||||
public static void RegisterInstance<TService>(this IRegister register, TService instance)
|
||||
=> register.RegisterInstance(typeof(TService), instance);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a base type for auto-registration.
|
||||
/// </summary>
|
||||
public static void RegisterAuto<TServiceBase>(this IRegister register)
|
||||
=> register.RegisterAuto(typeof(TServiceBase));
|
||||
}
|
||||
}
|
||||
56
src/Umbraco.Core/Composing/RegisterFactory.cs
Normal file
56
src/Umbraco.Core/Composing/RegisterFactory.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Umbraco.Core.Composing
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the container.
|
||||
/// </summary>
|
||||
public static class RegisterFactory
|
||||
{
|
||||
// cannot use typeof().AssemblyQualifiedName on the web container - we don't reference it
|
||||
// a normal Umbraco site should run on the web container, but an app may run on the core one
|
||||
private const string CoreLightInjectContainerTypeName = "Umbraco.Core.Composing.LightInject.LightInjectContainer,Umbraco.Core";
|
||||
private const string WebLightInjectContainerTypeName = "Umbraco.Web.Composing.LightInject.LightInjectContainer,Umbraco.Web";
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the configured container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To override the default LightInjectContainer, add an appSetting named umbracoContainerType with
|
||||
/// a fully qualified type name to a class with a static method "Create" returning an IRegister.
|
||||
/// </remarks>
|
||||
public static IRegister Create()
|
||||
{
|
||||
Type type;
|
||||
|
||||
var configuredTypeName = ConfigurationManager.AppSettings["umbracoRegisterType"];
|
||||
if (configuredTypeName.IsNullOrWhiteSpace())
|
||||
{
|
||||
// try to get the web LightInject container type,
|
||||
// else the core LightInject container type
|
||||
type = Type.GetType(configuredTypeName = WebLightInjectContainerTypeName) ??
|
||||
Type.GetType(configuredTypeName = CoreLightInjectContainerTypeName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// try to get the configured type
|
||||
type = Type.GetType(configuredTypeName);
|
||||
}
|
||||
|
||||
if (type == null)
|
||||
throw new Exception($"Cannot find register factory class '{configuredTypeName}'.");
|
||||
|
||||
var factoryMethod = type.GetMethod("Create", BindingFlags.Public | BindingFlags.Static);
|
||||
if (factoryMethod == null)
|
||||
throw new Exception($"Register factory class '{configuredTypeName}' does not have a public static method named Create.");
|
||||
|
||||
var container = factoryMethod.Invoke(null, Array.Empty<object>()) as IRegister;
|
||||
if (container == null)
|
||||
throw new Exception($"Register factory '{configuredTypeName}' did not return an IRegister implementation.");
|
||||
|
||||
return container;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,11 +30,10 @@ namespace Umbraco.Core.Composing
|
||||
private const string CacheKey = "umbraco-types.list";
|
||||
|
||||
private readonly IRuntimeCacheProvider _runtimeCache;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly ProfilingLogger _logger;
|
||||
private readonly IProfilingLogger _logger;
|
||||
|
||||
private readonly Dictionary<CompositeTypeTypeKey, TypeList> _types = new Dictionary<CompositeTypeTypeKey, TypeList>();
|
||||
private readonly object _typesLock = new object();
|
||||
private readonly object _locko = new object();
|
||||
private readonly object _timerLock = new object();
|
||||
|
||||
private Timer _timer;
|
||||
@@ -43,31 +42,30 @@ namespace Umbraco.Core.Composing
|
||||
private string _currentAssembliesHash;
|
||||
private IEnumerable<Assembly> _assemblies;
|
||||
private bool _reportedChange;
|
||||
private static LocalTempStorage _localTempStorage = LocalTempStorage.Unknown;
|
||||
private static LocalTempStorage _localTempStorage;
|
||||
private static string _fileBasePath;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TypeLoader"/> class.
|
||||
/// </summary>
|
||||
/// <param name="runtimeCache">The application runtime cache.</param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="localTempStorage">Files storage mode.</param>
|
||||
/// <param name="logger">A profiling logger.</param>
|
||||
/// <remarks>Used by LightInject.</remarks>
|
||||
public TypeLoader(IRuntimeCacheProvider runtimeCache, IGlobalSettings globalSettings, ProfilingLogger logger)
|
||||
: this(runtimeCache, globalSettings, logger, true)
|
||||
public TypeLoader(IRuntimeCacheProvider runtimeCache, LocalTempStorage localTempStorage, IProfilingLogger logger)
|
||||
: this(runtimeCache, localTempStorage, logger, true)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TypeLoader"/> class.
|
||||
/// </summary>
|
||||
/// <param name="runtimeCache">The application runtime cache.</param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="localTempStorage">Files storage mode.</param>
|
||||
/// <param name="logger">A profiling logger.</param>
|
||||
/// <param name="detectChanges">Whether to detect changes using hashes.</param>
|
||||
internal TypeLoader(IRuntimeCacheProvider runtimeCache, IGlobalSettings globalSettings, ProfilingLogger logger, bool detectChanges)
|
||||
internal TypeLoader(IRuntimeCacheProvider runtimeCache, LocalTempStorage localTempStorage, IProfilingLogger logger, bool detectChanges)
|
||||
{
|
||||
_runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache));
|
||||
_globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings));
|
||||
_localTempStorage = localTempStorage == LocalTempStorage.Unknown ? LocalTempStorage.Default : localTempStorage;
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
if (detectChanges)
|
||||
@@ -100,6 +98,13 @@ namespace Umbraco.Core.Composing
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new, test/blank, instance of the <see cref="TypeLoader"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>The initialized instance cannot get types.</remarks>
|
||||
internal TypeLoader()
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the set of assemblies to scan.
|
||||
/// </summary>
|
||||
@@ -206,7 +211,7 @@ namespace Umbraco.Core.Composing
|
||||
/// <returns>The hash.</returns>
|
||||
/// <remarks>Each file is a tuple containing the FileInfo object and a boolean which indicates whether to hash the
|
||||
/// file properties (false) or the file contents (true).</remarks>
|
||||
private static string GetFileHash(IEnumerable<Tuple<FileSystemInfo, bool>> filesAndFolders, ProfilingLogger logger)
|
||||
private static string GetFileHash(IEnumerable<Tuple<FileSystemInfo, bool>> filesAndFolders, IProfilingLogger logger)
|
||||
{
|
||||
using (logger.TraceDuration<TypeLoader>("Determining hash of code files on disk", "Hash determined"))
|
||||
{
|
||||
@@ -264,7 +269,7 @@ namespace Umbraco.Core.Composing
|
||||
/// <param name="logger">A profiling logger.</param>
|
||||
/// <returns>The hash.</returns>
|
||||
// internal for tests
|
||||
internal static string GetFileHash(IEnumerable<FileSystemInfo> filesAndFolders, ProfilingLogger logger)
|
||||
internal static string GetFileHash(IEnumerable<FileSystemInfo> filesAndFolders, IProfilingLogger logger)
|
||||
{
|
||||
using (logger.TraceDuration<TypeLoader>("Determining hash of code files on disk", "Hash determined"))
|
||||
{
|
||||
@@ -380,14 +385,15 @@ namespace Umbraco.Core.Composing
|
||||
|
||||
private string GetFileBasePath()
|
||||
{
|
||||
var localTempStorage = _globalSettings.LocalTempStorageLocation;
|
||||
if (_localTempStorage != localTempStorage)
|
||||
lock (_locko)
|
||||
{
|
||||
string path;
|
||||
switch (_globalSettings.LocalTempStorageLocation)
|
||||
if (_fileBasePath != null)
|
||||
return _fileBasePath;
|
||||
|
||||
switch (_localTempStorage)
|
||||
{
|
||||
case LocalTempStorage.AspNetTemp:
|
||||
path = Path.Combine(HttpRuntime.CodegenDir, "UmbracoData", "umbraco-types");
|
||||
_fileBasePath = Path.Combine(HttpRuntime.CodegenDir, "UmbracoData", "umbraco-types");
|
||||
break;
|
||||
case LocalTempStorage.EnvironmentTemp:
|
||||
// include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back
|
||||
@@ -395,33 +401,30 @@ namespace Umbraco.Core.Composing
|
||||
// utilizing an old path - assuming we cannot have SHA1 collisions on AppDomainAppId
|
||||
var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1();
|
||||
var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", appDomainHash);
|
||||
path = Path.Combine(cachePath, "umbraco-types");
|
||||
_fileBasePath = Path.Combine(cachePath, "umbraco-types");
|
||||
break;
|
||||
case LocalTempStorage.Default:
|
||||
default:
|
||||
var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/TypesCache");
|
||||
path = Path.Combine(tempFolder, "umbraco-types." + NetworkHelper.FileSafeMachineName);
|
||||
_fileBasePath = Path.Combine(tempFolder, "umbraco-types." + NetworkHelper.FileSafeMachineName);
|
||||
break;
|
||||
}
|
||||
|
||||
_fileBasePath = path;
|
||||
_localTempStorage = localTempStorage;
|
||||
// ensure that the folder exists
|
||||
var directory = Path.GetDirectoryName(_fileBasePath);
|
||||
if (directory == null)
|
||||
throw new InvalidOperationException($"Could not determine folder for path \"{_fileBasePath}\".");
|
||||
if (Directory.Exists(directory) == false)
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
return _fileBasePath;
|
||||
}
|
||||
|
||||
// ensure that the folder exists
|
||||
var directory = Path.GetDirectoryName(_fileBasePath);
|
||||
if (directory == null)
|
||||
throw new InvalidOperationException($"Could not determine folder for path \"{_fileBasePath}\".");
|
||||
if (Directory.Exists(directory) == false)
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
return _fileBasePath;
|
||||
}
|
||||
|
||||
// internal for tests
|
||||
internal void WriteCache()
|
||||
{
|
||||
_logger.Logger.Debug<TypeLoader>("Writing cache file.");
|
||||
_logger.Debug<TypeLoader>("Writing cache file.");
|
||||
var typesListFilePath = GetTypesListFilePath();
|
||||
using (var stream = GetFileStream(typesListFilePath, FileMode.Create, FileAccess.Write, FileShare.None, ListFileOpenWriteTimeout))
|
||||
using (var writer = new StreamWriter(stream))
|
||||
@@ -493,7 +496,7 @@ namespace Umbraco.Core.Composing
|
||||
if (--attempts == 0)
|
||||
throw;
|
||||
|
||||
_logger.Logger.Debug<TypeLoader>("Attempted to get filestream for file {Path} failed, {NumberOfAttempts} attempts left, pausing for {PauseMilliseconds} milliseconds", path, attempts, pauseMilliseconds);
|
||||
_logger.Debug<TypeLoader>("Attempted to get filestream for file {Path} failed, {NumberOfAttempts} attempts left, pausing for {PauseMilliseconds} milliseconds", path, attempts, pauseMilliseconds);
|
||||
Thread.Sleep(pauseMilliseconds);
|
||||
}
|
||||
}
|
||||
@@ -514,7 +517,7 @@ namespace Umbraco.Core.Composing
|
||||
if (--attempts == 0)
|
||||
throw;
|
||||
|
||||
_logger.Logger.Debug<TypeLoader>("Attempted to delete file {Path} failed, {NumberOfAttempts} attempts left, pausing for {PauseMilliseconds} milliseconds", path, attempts, pauseMilliseconds);
|
||||
_logger.Debug<TypeLoader>("Attempted to delete file {Path} failed, {NumberOfAttempts} attempts left, pausing for {PauseMilliseconds} milliseconds", path, attempts, pauseMilliseconds);
|
||||
Thread.Sleep(pauseMilliseconds);
|
||||
}
|
||||
}
|
||||
@@ -534,6 +537,9 @@ namespace Umbraco.Core.Composing
|
||||
/// <remarks>Caching is disabled when using specific assemblies.</remarks>
|
||||
public IEnumerable<Type> GetTypes<T>(bool cache = true, IEnumerable<Assembly> specificAssemblies = null)
|
||||
{
|
||||
if (_logger == null)
|
||||
throw new InvalidOperationException("Cannot get types from a test/blank type loader.");
|
||||
|
||||
// do not cache anything from specific assemblies
|
||||
cache &= specificAssemblies == null;
|
||||
|
||||
@@ -541,7 +547,7 @@ namespace Umbraco.Core.Composing
|
||||
if (!typeof(IDiscoverable).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
// warn
|
||||
_logger.Logger.Debug<TypeLoader>("Running a full, " + (cache ? "" : "non-") + "cached, scan for non-discoverable type {TypeName} (slow).", typeof(T).FullName);
|
||||
_logger.Debug<TypeLoader>("Running a full, " + (cache ? "" : "non-") + "cached, scan for non-discoverable type {TypeName} (slow).", typeof(T).FullName);
|
||||
|
||||
return GetTypesInternal(
|
||||
typeof(T), null,
|
||||
@@ -559,7 +565,7 @@ namespace Umbraco.Core.Composing
|
||||
|
||||
// warn
|
||||
if (!cache)
|
||||
_logger.Logger.Debug<TypeLoader>("Running a non-cached, filter for discoverable type {TypeName} (slowish).", typeof(T).FullName);
|
||||
_logger.Debug<TypeLoader>("Running a non-cached, filter for discoverable type {TypeName} (slowish).", typeof(T).FullName);
|
||||
|
||||
// filter the cached discovered types (and maybe cache the result)
|
||||
return GetTypesInternal(
|
||||
@@ -582,13 +588,16 @@ namespace Umbraco.Core.Composing
|
||||
public IEnumerable<Type> GetTypesWithAttribute<T, TAttribute>(bool cache = true, IEnumerable<Assembly> specificAssemblies = null)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
if (_logger == null)
|
||||
throw new InvalidOperationException("Cannot get types from a test/blank type loader.");
|
||||
|
||||
// do not cache anything from specific assemblies
|
||||
cache &= specificAssemblies == null;
|
||||
|
||||
// if not IDiscoverable, directly get types
|
||||
if (!typeof(IDiscoverable).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
_logger.Logger.Debug<TypeLoader>("Running a full, " + (cache ? "" : "non-") + "cached, scan for non-discoverable type {TypeName} / attribute {AttributeName} (slow).", typeof(T).FullName, typeof(TAttribute).FullName);
|
||||
_logger.Debug<TypeLoader>("Running a full, " + (cache ? "" : "non-") + "cached, scan for non-discoverable type {TypeName} / attribute {AttributeName} (slow).", typeof(T).FullName, typeof(TAttribute).FullName);
|
||||
|
||||
return GetTypesInternal(
|
||||
typeof(T), typeof(TAttribute),
|
||||
@@ -606,7 +615,7 @@ namespace Umbraco.Core.Composing
|
||||
|
||||
// warn
|
||||
if (!cache)
|
||||
_logger.Logger.Debug<TypeLoader>("Running a non-cached, filter for discoverable type {TypeName} / attribute {AttributeName} (slowish).", typeof(T).FullName, typeof(TAttribute).FullName);
|
||||
_logger.Debug<TypeLoader>("Running a non-cached, filter for discoverable type {TypeName} / attribute {AttributeName} (slowish).", typeof(T).FullName, typeof(TAttribute).FullName);
|
||||
|
||||
// filter the cached discovered types (and maybe cache the result)
|
||||
return GetTypesInternal(
|
||||
@@ -629,11 +638,14 @@ namespace Umbraco.Core.Composing
|
||||
public IEnumerable<Type> GetAttributedTypes<TAttribute>(bool cache = true, IEnumerable<Assembly> specificAssemblies = null)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
if (_logger == null)
|
||||
throw new InvalidOperationException("Cannot get types from a test/blank type loader.");
|
||||
|
||||
// do not cache anything from specific assemblies
|
||||
cache &= specificAssemblies == null;
|
||||
|
||||
if (!cache)
|
||||
_logger.Logger.Debug<TypeLoader>("Running a full, non-cached, scan for types / attribute {AttributeName} (slow).", typeof(TAttribute).FullName);
|
||||
_logger.Debug<TypeLoader>("Running a full, non-cached, scan for types / attribute {AttributeName} (slow).", typeof(TAttribute).FullName);
|
||||
|
||||
return GetTypesInternal(
|
||||
typeof (object), typeof (TAttribute),
|
||||
@@ -655,7 +667,7 @@ namespace Umbraco.Core.Composing
|
||||
|
||||
var name = GetName(baseType, attributeType);
|
||||
|
||||
lock (_typesLock)
|
||||
lock (_locko)
|
||||
using (_logger.TraceDuration<TypeLoader>(
|
||||
"Getting " + name,
|
||||
"Got " + name)) // cannot contain typesFound.Count as it's evaluated before the find
|
||||
@@ -689,7 +701,7 @@ namespace Umbraco.Core.Composing
|
||||
if (typeList != null)
|
||||
{
|
||||
// need to put some logging here to try to figure out why this is happening: http://issues.umbraco.org/issue/U4-3505
|
||||
_logger.Logger.Debug<TypeLoader>("Getting {TypeName}: found a cached type list.", GetName(baseType, attributeType));
|
||||
_logger.Debug<TypeLoader>("Getting {TypeName}: found a cached type list.", GetName(baseType, attributeType));
|
||||
return typeList.Types;
|
||||
}
|
||||
|
||||
@@ -705,7 +717,7 @@ namespace Umbraco.Core.Composing
|
||||
// report (only once) and scan and update the cache file
|
||||
if (_reportedChange == false)
|
||||
{
|
||||
_logger.Logger.Debug<TypeLoader>("Assemblies changes detected, need to rescan everything.");
|
||||
_logger.Debug<TypeLoader>("Assemblies changes detected, need to rescan everything.");
|
||||
_reportedChange = true;
|
||||
}
|
||||
}
|
||||
@@ -720,7 +732,7 @@ namespace Umbraco.Core.Composing
|
||||
// so in this instance there will never be a result.
|
||||
if (cacheResult.Exception is CachedTypeNotFoundInFileException || cacheResult.Success == false)
|
||||
{
|
||||
_logger.Logger.Debug<TypeLoader>("Getting {TypeName}: failed to load from cache file, must scan assemblies.", GetName(baseType, attributeType));
|
||||
_logger.Debug<TypeLoader>("Getting {TypeName}: failed to load from cache file, must scan assemblies.", GetName(baseType, attributeType));
|
||||
scan = true;
|
||||
}
|
||||
else
|
||||
@@ -739,7 +751,7 @@ namespace Umbraco.Core.Composing
|
||||
catch (Exception ex)
|
||||
{
|
||||
// in case of any exception, we have to exit, and revert to scanning
|
||||
_logger.Logger.Error<TypeLoader>(ex, "Getting {TypeName}: failed to load cache file type {CacheType}, reverting to scanning assemblies.", GetName(baseType, attributeType), type);
|
||||
_logger.Error<TypeLoader>(ex, "Getting {TypeName}: failed to load cache file type {CacheType}, reverting to scanning assemblies.", GetName(baseType, attributeType), type);
|
||||
scan = true;
|
||||
break;
|
||||
}
|
||||
@@ -747,7 +759,7 @@ namespace Umbraco.Core.Composing
|
||||
|
||||
if (scan == false)
|
||||
{
|
||||
_logger.Logger.Debug<TypeLoader>("Getting {TypeName}: loaded types from cache file.", GetName(baseType, attributeType));
|
||||
_logger.Debug<TypeLoader>("Getting {TypeName}: loaded types from cache file.", GetName(baseType, attributeType));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -755,7 +767,7 @@ namespace Umbraco.Core.Composing
|
||||
if (scan)
|
||||
{
|
||||
// either we had to scan, or we could not get the types from the cache file - scan now
|
||||
_logger.Logger.Debug<TypeLoader>("Getting {TypeName}: " + action + ".", GetName(baseType, attributeType));
|
||||
_logger.Debug<TypeLoader>("Getting {TypeName}: " + action + ".", GetName(baseType, attributeType));
|
||||
|
||||
foreach (var t in finder())
|
||||
typeList.Add(t);
|
||||
@@ -773,11 +785,11 @@ namespace Umbraco.Core.Composing
|
||||
UpdateCache();
|
||||
}
|
||||
|
||||
_logger.Logger.Debug<TypeLoader>("Got {TypeName}, caching ({CacheType}).", GetName(baseType, attributeType), added.ToString().ToLowerInvariant());
|
||||
_logger.Debug<TypeLoader>("Got {TypeName}, caching ({CacheType}).", GetName(baseType, attributeType), added.ToString().ToLowerInvariant());
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Logger.Debug<TypeLoader>("Got {TypeName}.", GetName(baseType, attributeType));
|
||||
_logger.Debug<TypeLoader>("Got {TypeName}.", GetName(baseType, attributeType));
|
||||
}
|
||||
|
||||
return typeList.Types;
|
||||
|
||||
@@ -42,13 +42,5 @@ namespace Umbraco.Core.Composing
|
||||
{
|
||||
return mgr.GetTypesWithAttribute<BaseMapper, MapperForAttribute>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all classes implementing ISqlSyntaxProvider and marked with the SqlSyntaxProviderAttribute.
|
||||
/// </summary>
|
||||
public static IEnumerable<Type> GetSqlSyntaxProviders(this TypeLoader mgr)
|
||||
{
|
||||
return mgr.GetTypesWithAttribute<ISqlSyntaxProvider, SqlSyntaxProviderAttribute>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LightInject;
|
||||
|
||||
namespace Umbraco.Core.Composing
|
||||
{
|
||||
@@ -15,20 +14,12 @@ namespace Umbraco.Core.Composing
|
||||
where TBuilder : WeightedCollectionBuilderBase<TBuilder, TCollection, TItem>
|
||||
where TCollection : IBuilderCollection<TItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WeightedCollectionBuilderBase{TBuilder,TCollection,TItem}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="container"></param>
|
||||
protected WeightedCollectionBuilderBase(IServiceContainer container)
|
||||
: base(container)
|
||||
{ }
|
||||
|
||||
protected abstract TBuilder This { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears all types in the collection.
|
||||
/// </summary>
|
||||
/// <returns>The buidler.</returns>
|
||||
/// <returns>The builder.</returns>
|
||||
public TBuilder Clear()
|
||||
{
|
||||
Configure(types => types.Clear());
|
||||
|
||||
@@ -7,190 +7,132 @@ using Umbraco.Core.Configuration.Grid;
|
||||
using Umbraco.Core.Configuration.HealthChecks;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// The gateway to all umbraco configuration
|
||||
/// The gateway to all umbraco configuration.
|
||||
/// </summary>
|
||||
/// <remarks>This should be registered as a unique service in the container.</remarks>
|
||||
public class UmbracoConfig
|
||||
{
|
||||
#region Singleton
|
||||
|
||||
private static readonly Lazy<UmbracoConfig> Lazy = new Lazy<UmbracoConfig>(() => new UmbracoConfig());
|
||||
|
||||
public static UmbracoConfig For => Lazy.Value;
|
||||
|
||||
#endregion
|
||||
private IGlobalSettings _global;
|
||||
private Lazy<IUmbracoSettingsSection> _umbraco;
|
||||
private Lazy<IHealthChecks> _healthChecks;
|
||||
private Lazy<IDashboardSection> _dashboards;
|
||||
private Lazy<IGridConfig> _grids;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// Initializes a new instance of the <see cref="UmbracoConfig"/> class.
|
||||
/// </summary>
|
||||
private UmbracoConfig()
|
||||
public UmbracoConfig(ILogger logger, IRuntimeCacheProvider runtimeCache, IRuntimeState runtimeState)
|
||||
{
|
||||
_global = new GlobalSettings();
|
||||
|
||||
var appPluginsDir = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins));
|
||||
var configDir = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config));
|
||||
|
||||
_umbraco = new Lazy<IUmbracoSettingsSection>(() => GetConfig<IUmbracoSettingsSection>("umbracoConfiguration/settings"));
|
||||
_dashboards = new Lazy<IDashboardSection>(() =>GetConfig<IDashboardSection>("umbracoConfiguration/dashBoard"));
|
||||
_healthChecks = new Lazy<IHealthChecks>(() => GetConfig<IHealthChecks>("umbracoConfiguration/HealthChecks"));
|
||||
_grids = new Lazy<IGridConfig>(() => new GridConfig(logger, runtimeCache, appPluginsDir, configDir, runtimeState.Debug));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a typed and named config section.
|
||||
/// </summary>
|
||||
/// <typeparam name="TConfig">The type of the configuration section.</typeparam>
|
||||
/// <param name="sectionName">The name of the configuration section.</param>
|
||||
/// <returns>The configuration section.</returns>
|
||||
public static TConfig GetConfig<TConfig>(string sectionName)
|
||||
where TConfig : class
|
||||
{
|
||||
// note: need to use SafeCallContext here because ConfigurationManager.GetSection ends up getting AppDomain.Evidence
|
||||
// which will want to serialize the call context including anything that is in there - what a mess!
|
||||
|
||||
if (_umbracoSettings == null)
|
||||
using (new SafeCallContext())
|
||||
{
|
||||
IUmbracoSettingsSection umbracoSettings;
|
||||
using (new SafeCallContext())
|
||||
{
|
||||
umbracoSettings = ConfigurationManager.GetSection("umbracoConfiguration/settings") as IUmbracoSettingsSection;
|
||||
}
|
||||
SetUmbracoSettings(umbracoSettings);
|
||||
}
|
||||
|
||||
if (_dashboardSection == null)
|
||||
{
|
||||
IDashboardSection dashboardConfig;
|
||||
using (new SafeCallContext())
|
||||
{
|
||||
dashboardConfig = ConfigurationManager.GetSection("umbracoConfiguration/dashBoard") as IDashboardSection;
|
||||
}
|
||||
SetDashboardSettings(dashboardConfig);
|
||||
}
|
||||
|
||||
if (_healthChecks == null)
|
||||
{
|
||||
var healthCheckConfig = ConfigurationManager.GetSection("umbracoConfiguration/HealthChecks") as IHealthChecks;
|
||||
SetHealthCheckSettings(healthCheckConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor - can be used for testing
|
||||
/// </summary>
|
||||
/// <param name="umbracoSettings"></param>
|
||||
/// <param name="dashboardSettings"></param>
|
||||
/// <param name="healthChecks"></param>
|
||||
/// <param name="globalSettings"></param>
|
||||
public UmbracoConfig(IUmbracoSettingsSection umbracoSettings, IDashboardSection dashboardSettings, IHealthChecks healthChecks, IGlobalSettings globalSettings)
|
||||
{
|
||||
SetHealthCheckSettings(healthChecks);
|
||||
SetUmbracoSettings(umbracoSettings);
|
||||
SetDashboardSettings(dashboardSettings);
|
||||
SetGlobalConfig(globalSettings);
|
||||
}
|
||||
|
||||
private IHealthChecks _healthChecks;
|
||||
private IDashboardSection _dashboardSection;
|
||||
private IUmbracoSettingsSection _umbracoSettings;
|
||||
private IGridConfig _gridConfig;
|
||||
private IGlobalSettings _globalSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IHealthCheck config
|
||||
/// </summary>
|
||||
public IHealthChecks HealthCheck()
|
||||
{
|
||||
if (_healthChecks == null)
|
||||
{
|
||||
var ex = new ConfigurationErrorsException("Could not load the " + typeof(IHealthChecks) + " from config file, ensure the web.config and healthchecks.config files are formatted correctly");
|
||||
if ((ConfigurationManager.GetSection(sectionName) is TConfig config))
|
||||
return config;
|
||||
var ex = new ConfigurationErrorsException($"Could not get configuration section \"{sectionName}\" from config files.");
|
||||
Current.Logger.Error<UmbracoConfig>(ex, "Config error");
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return _healthChecks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IDashboardSection
|
||||
/// Gets the global configuration.
|
||||
/// </summary>
|
||||
public IDashboardSection DashboardSettings()
|
||||
{
|
||||
if (_dashboardSection == null)
|
||||
{
|
||||
var ex = new ConfigurationErrorsException("Could not load the " + typeof(IDashboardSection) + " from config file, ensure the web.config and Dashboard.config files are formatted correctly");
|
||||
Current.Logger.Error<UmbracoConfig>(ex, "Config error");
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return _dashboardSection;
|
||||
}
|
||||
public IGlobalSettings Global()
|
||||
=> _global;
|
||||
|
||||
/// <summary>
|
||||
/// Only for testing
|
||||
/// Gets the Umbraco configuration.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public void SetDashboardSettings(IDashboardSection value)
|
||||
{
|
||||
_dashboardSection = value;
|
||||
}
|
||||
public IUmbracoSettingsSection Umbraco()
|
||||
=> _umbraco.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Only for testing
|
||||
/// Gets the dashboards configuration.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public void SetHealthCheckSettings(IHealthChecks value)
|
||||
{
|
||||
_healthChecks = value;
|
||||
}
|
||||
public IDashboardSection Dashboards()
|
||||
=> _dashboards.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Only for testing
|
||||
/// Gets the health checks configuration.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public void SetUmbracoSettings(IUmbracoSettingsSection value)
|
||||
{
|
||||
_umbracoSettings = value;
|
||||
}
|
||||
public IHealthChecks HealthChecks()
|
||||
=> _healthChecks.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Only for testing
|
||||
/// Gets the grids configuration.
|
||||
/// </summary>
|
||||
public IGridConfig Grids()
|
||||
=> _grids.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the global configuration, for tests only.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public void SetGlobalConfig(IGlobalSettings value)
|
||||
{
|
||||
_globalSettings = value;
|
||||
_global = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IGlobalSettings
|
||||
/// Sets the Umbraco configuration, for tests only.
|
||||
/// </summary>
|
||||
public IGlobalSettings GlobalSettings()
|
||||
public void SetUmbracoConfig(IUmbracoSettingsSection value)
|
||||
{
|
||||
return _globalSettings ?? (_globalSettings = new GlobalSettings());
|
||||
_umbraco = new Lazy<IUmbracoSettingsSection>(() => value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IUmbracoSettings
|
||||
/// </summary>
|
||||
public IUmbracoSettingsSection UmbracoSettings()
|
||||
{
|
||||
if (_umbracoSettings == null)
|
||||
{
|
||||
var ex = new ConfigurationErrorsException("Could not load the " + typeof (IUmbracoSettingsSection) + " from config file, ensure the web.config and umbracoSettings.config files are formatted correctly");
|
||||
Current.Logger.Error<UmbracoConfig>(ex, "Config error");
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return _umbracoSettings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only for testing
|
||||
/// Sets the dashboards configuration, for tests only.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public void SetGridConfig(IGridConfig value)
|
||||
public void SetDashboardsConfig(IDashboardSection value)
|
||||
{
|
||||
_gridConfig = value;
|
||||
_dashboards = new Lazy<IDashboardSection>(() => value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IGridConfig
|
||||
/// Sets the health checks configuration, for tests only.
|
||||
/// </summary>
|
||||
public IGridConfig GridConfig(ILogger logger, IRuntimeCacheProvider runtimeCache, DirectoryInfo appPlugins, DirectoryInfo configFolder, bool isDebug)
|
||||
public void SetHealthChecksConfig(IHealthChecks value)
|
||||
{
|
||||
if (_gridConfig == null)
|
||||
{
|
||||
_gridConfig = new GridConfig(logger, runtimeCache, appPlugins, configFolder, isDebug);
|
||||
}
|
||||
_healthChecks = new Lazy<IHealthChecks>(() => value);
|
||||
}
|
||||
|
||||
return _gridConfig;
|
||||
/// <summary>
|
||||
/// Sets the grids configuration, for tests only.
|
||||
/// </summary>
|
||||
public void SetGridsConfig(IGridConfig value)
|
||||
{
|
||||
_grids = new Lazy<IGridConfig>(() => value);
|
||||
}
|
||||
|
||||
//TODO: Add other configurations here !
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,20 +9,6 @@
|
||||
/// Defines constants for composition.
|
||||
/// </summary>
|
||||
public static class Composing
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines file system names.
|
||||
/// </summary>
|
||||
public static class FileSystems
|
||||
{
|
||||
public const string ScriptFileSystem = "ScriptFileSystem";
|
||||
public const string PartialViewFileSystem = "PartialViewFileSystem";
|
||||
public const string PartialViewMacroFileSystem = "PartialViewMacroFileSystem";
|
||||
public const string StylesheetFileSystem = "StylesheetFileSystem";
|
||||
public const string MasterpageFileSystem = "MasterpageFileSystem";
|
||||
public const string ViewFileSystem = "ViewFileSystem";
|
||||
public const string JavascriptLibraryFileSystem = "JavascriptLibraryFileSystem";
|
||||
}
|
||||
}
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Xml.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -18,8 +20,8 @@ namespace Umbraco.Core
|
||||
public static class ContentExtensions
|
||||
{
|
||||
// this ain't pretty
|
||||
private static MediaFileSystem _mediaFileSystem;
|
||||
private static MediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.FileSystems.MediaFileSystem);
|
||||
private static IMediaFileSystem _mediaFileSystem;
|
||||
private static IMediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.MediaFileSystem);
|
||||
|
||||
#region IContent
|
||||
|
||||
@@ -189,7 +191,21 @@ namespace Umbraco.Core
|
||||
private static void SetUploadFile(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null)
|
||||
{
|
||||
var property = GetProperty(content, propertyTypeAlias);
|
||||
var oldpath = property.GetValue(culture, segment) is string svalue ? MediaFileSystem.GetRelativePath(svalue) : null;
|
||||
|
||||
// Fixes https://github.com/umbraco/Umbraco-CMS/issues/3937 - Assigning a new file to an
|
||||
// existing IMedia with extension SetValue causes exception 'Illegal characters in path'
|
||||
string oldpath = null;
|
||||
if (property.GetValue(culture, segment) is string svalue)
|
||||
{
|
||||
if (svalue.DetectIsJson())
|
||||
{
|
||||
// the property value is a JSON serialized image crop data set - grab the "src" property as the file source
|
||||
var jObject = JsonConvert.DeserializeObject<JObject>(svalue);
|
||||
svalue = jObject != null ? jObject.GetValueAsString("src") : svalue;
|
||||
}
|
||||
oldpath = MediaFileSystem.GetRelativePath(svalue);
|
||||
}
|
||||
|
||||
var filepath = MediaFileSystem.StoreFile(content, property.PropertyType, filename, filestream, oldpath);
|
||||
property.SetValue(MediaFileSystem.GetUrl(filepath), culture, segment);
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ namespace Umbraco.Core.Events
|
||||
}
|
||||
}
|
||||
|
||||
private MediaFileSystem _mediaFileSystem;
|
||||
private IMediaFileSystem _mediaFileSystem;
|
||||
|
||||
// fixme inject
|
||||
private MediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.FileSystems.MediaFileSystem);
|
||||
private IMediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.MediaFileSystem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Umbraco.Core.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception that is thrown if the Umbraco application cannnot boot.
|
||||
/// An exception that is thrown if the Umbraco application cannot boot.
|
||||
/// </summary>
|
||||
public class BootFailedException : Exception
|
||||
{
|
||||
@@ -29,5 +30,31 @@ namespace Umbraco.Core.Exceptions
|
||||
public BootFailedException(string message, Exception inner)
|
||||
: base(message, inner)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Rethrows a captured <see cref="BootFailedException"/>.
|
||||
/// </summary>
|
||||
/// <remarks>The exception can be null, in which case a default message is used.</remarks>
|
||||
public static void Rethrow(BootFailedException bootFailedException)
|
||||
{
|
||||
if (bootFailedException == null)
|
||||
throw new BootFailedException(DefaultMessage);
|
||||
|
||||
// see https://stackoverflow.com/questions/57383
|
||||
// would that be the correct way to do it?
|
||||
//ExceptionDispatchInfo.Capture(bootFailedException).Throw();
|
||||
|
||||
Exception e = bootFailedException;
|
||||
var m = new StringBuilder();
|
||||
m.Append(DefaultMessage);
|
||||
while (e != null)
|
||||
{
|
||||
m.Append($"\n\n-> {e.GetType().FullName}: {e.Message}");
|
||||
if (string.IsNullOrWhiteSpace(e.StackTrace) == false)
|
||||
m.Append($"\n{e.StackTrace}");
|
||||
e = e.InnerException;
|
||||
}
|
||||
throw new BootFailedException(m.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
38
src/Umbraco.Core/IMainDom.cs
Normal file
38
src/Umbraco.Core/IMainDom.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the main AppDomain running for a given application.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>There can be only one "main" AppDomain running for a given application at a time.</para>
|
||||
/// <para>It is possible to register against the MainDom and be notified when it is released.</para>
|
||||
/// </remarks>
|
||||
public interface IMainDom
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current domain is the main domain.
|
||||
/// </summary>
|
||||
bool IsMainDom { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Registers a resource that requires the current AppDomain to be the main domain to function.
|
||||
/// </summary>
|
||||
/// <param name="release">An action to execute before the AppDomain releases the main domain status.</param>
|
||||
/// <param name="weight">An optional weight (lower goes first).</param>
|
||||
/// <returns>A value indicating whether it was possible to register.</returns>
|
||||
bool Register(Action release, int weight = 100);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a resource that requires the current AppDomain to be the main domain to function.
|
||||
/// </summary>
|
||||
/// <param name="install">An action to execute when registering.</param>
|
||||
/// <param name="release">An action to execute before the AppDomain releases the main domain status.</param>
|
||||
/// <param name="weight">An optional weight (lower goes first).</param>
|
||||
/// <returns>A value indicating whether it was possible to register.</returns>
|
||||
/// <remarks>If registering is successful, then the <paramref name="install"/> action
|
||||
/// is guaranteed to execute before the AppDomain releases the main domain status.</remarks>
|
||||
bool Register(Action install, Action release, int weight = 100);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Umbraco.Core.CodeAnnotations;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class FileSystemProviderAttribute : Attribute
|
||||
{
|
||||
public string Alias { get; private set; }
|
||||
|
||||
public FileSystemProviderAttribute(string alias)
|
||||
{
|
||||
Alias = alias;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,103 +16,103 @@ namespace Umbraco.Core.IO
|
||||
/// </remarks>
|
||||
public abstract class FileSystemWrapper : IFileSystem
|
||||
{
|
||||
protected FileSystemWrapper(IFileSystem wrapped)
|
||||
protected FileSystemWrapper(IFileSystem innerFileSystem)
|
||||
{
|
||||
Wrapped = wrapped;
|
||||
InnerFileSystem = innerFileSystem;
|
||||
}
|
||||
|
||||
internal IFileSystem Wrapped { get; set; }
|
||||
public IFileSystem InnerFileSystem { get; internal set; }
|
||||
|
||||
public IEnumerable<string> GetDirectories(string path)
|
||||
{
|
||||
return Wrapped.GetDirectories(path);
|
||||
return InnerFileSystem.GetDirectories(path);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
Wrapped.DeleteDirectory(path);
|
||||
InnerFileSystem.DeleteDirectory(path);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path, bool recursive)
|
||||
{
|
||||
Wrapped.DeleteDirectory(path, recursive);
|
||||
InnerFileSystem.DeleteDirectory(path, recursive);
|
||||
}
|
||||
|
||||
public bool DirectoryExists(string path)
|
||||
{
|
||||
return Wrapped.DirectoryExists(path);
|
||||
return InnerFileSystem.DirectoryExists(path);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream)
|
||||
{
|
||||
Wrapped.AddFile(path, stream);
|
||||
InnerFileSystem.AddFile(path, stream);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream, bool overrideExisting)
|
||||
{
|
||||
Wrapped.AddFile(path, stream, overrideExisting);
|
||||
InnerFileSystem.AddFile(path, stream, overrideExisting);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFiles(string path)
|
||||
{
|
||||
return Wrapped.GetFiles(path);
|
||||
return InnerFileSystem.GetFiles(path);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFiles(string path, string filter)
|
||||
{
|
||||
return Wrapped.GetFiles(path, filter);
|
||||
return InnerFileSystem.GetFiles(path, filter);
|
||||
}
|
||||
|
||||
public Stream OpenFile(string path)
|
||||
{
|
||||
return Wrapped.OpenFile(path);
|
||||
return InnerFileSystem.OpenFile(path);
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
Wrapped.DeleteFile(path);
|
||||
InnerFileSystem.DeleteFile(path);
|
||||
}
|
||||
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
return Wrapped.FileExists(path);
|
||||
return InnerFileSystem.FileExists(path);
|
||||
}
|
||||
|
||||
public string GetRelativePath(string fullPathOrUrl)
|
||||
{
|
||||
return Wrapped.GetRelativePath(fullPathOrUrl);
|
||||
return InnerFileSystem.GetRelativePath(fullPathOrUrl);
|
||||
}
|
||||
|
||||
public string GetFullPath(string path)
|
||||
{
|
||||
return Wrapped.GetFullPath(path);
|
||||
return InnerFileSystem.GetFullPath(path);
|
||||
}
|
||||
|
||||
public string GetUrl(string path)
|
||||
{
|
||||
return Wrapped.GetUrl(path);
|
||||
return InnerFileSystem.GetUrl(path);
|
||||
}
|
||||
|
||||
public DateTimeOffset GetLastModified(string path)
|
||||
{
|
||||
return Wrapped.GetLastModified(path);
|
||||
return InnerFileSystem.GetLastModified(path);
|
||||
}
|
||||
|
||||
public DateTimeOffset GetCreated(string path)
|
||||
{
|
||||
return Wrapped.GetCreated(path);
|
||||
return InnerFileSystem.GetCreated(path);
|
||||
}
|
||||
|
||||
public long GetSize(string path)
|
||||
{
|
||||
return Wrapped.GetSize(path);
|
||||
return InnerFileSystem.GetSize(path);
|
||||
}
|
||||
|
||||
public bool CanAddPhysical => Wrapped.CanAddPhysical;
|
||||
public bool CanAddPhysical => InnerFileSystem.CanAddPhysical;
|
||||
|
||||
public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false)
|
||||
{
|
||||
Wrapped.AddFile(path, physicalPath, overrideIfExists, copy);
|
||||
InnerFileSystem.AddFile(path, physicalPath, overrideIfExists, copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
public class FileSystems
|
||||
public class FileSystems : IFileSystems
|
||||
{
|
||||
private readonly IFileSystemProvidersSection _config;
|
||||
private readonly ConcurrentSet<ShadowWrapper> _wrappers = new ConcurrentSet<ShadowWrapper>();
|
||||
private readonly IFactory _container;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly ConcurrentDictionary<string, ProviderConstructionInfo> _providerLookup = new ConcurrentDictionary<string, ProviderConstructionInfo>();
|
||||
private readonly ConcurrentDictionary<string, IFileSystem> _filesystems = new ConcurrentDictionary<string, IFileSystem>();
|
||||
private readonly ConcurrentDictionary<Type, Lazy<IFileSystem>> _filesystems = new ConcurrentDictionary<Type, Lazy<IFileSystem>>();
|
||||
|
||||
// wrappers for shadow support
|
||||
private ShadowWrapper _macroPartialFileSystem;
|
||||
@@ -28,40 +21,48 @@ namespace Umbraco.Core.IO
|
||||
private ShadowWrapper _scriptsFileSystem;
|
||||
private ShadowWrapper _masterPagesFileSystem;
|
||||
private ShadowWrapper _mvcViewsFileSystem;
|
||||
|
||||
|
||||
// well-known file systems lazy initialization
|
||||
private object _wkfsLock = new object();
|
||||
private bool _wkfsInitialized;
|
||||
private object _wkfsObject;
|
||||
private object _wkfsObject; // unused
|
||||
|
||||
private MediaFileSystem _mediaFileSystem;
|
||||
|
||||
// shadow support
|
||||
private readonly List<ShadowWrapper> _shadowWrappers = new List<ShadowWrapper>();
|
||||
private readonly object _shadowLocker = new object();
|
||||
private static Guid _shadowCurrentId = Guid.Empty; // static - unique!!
|
||||
#region Constructor
|
||||
|
||||
// DI wants a public ctor
|
||||
// but IScopeProviderInternal is not public
|
||||
public FileSystems(ILogger logger)
|
||||
public FileSystems(IFactory container, ILogger logger)
|
||||
{
|
||||
// fixme inject config section => can be used by tests
|
||||
_config = (FileSystemProvidersSection) ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders");
|
||||
_container = container;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
// for tests only, totally unsafe
|
||||
internal void Reset()
|
||||
{
|
||||
_wrappers.Clear();
|
||||
_providerLookup.Clear();
|
||||
_shadowWrappers.Clear();
|
||||
_filesystems.Clear();
|
||||
Volatile.Write(ref _wkfsInitialized, false);
|
||||
_shadowCurrentId = Guid.Empty;
|
||||
}
|
||||
|
||||
// for tests only, totally unsafe
|
||||
internal static void ResetShadowId()
|
||||
{
|
||||
_shadowCurrentId = Guid.Empty;
|
||||
}
|
||||
|
||||
// set by the scope provider when taking control of filesystems
|
||||
internal Func<bool> IsScoped { get; set; } = () => false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Well-Known FileSystems
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFileSystem MacroPartialsFileSystem
|
||||
{
|
||||
get
|
||||
@@ -71,6 +72,7 @@ namespace Umbraco.Core.IO
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFileSystem PartialViewsFileSystem
|
||||
{
|
||||
get
|
||||
@@ -80,6 +82,7 @@ namespace Umbraco.Core.IO
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFileSystem StylesheetsFileSystem
|
||||
{
|
||||
get
|
||||
@@ -89,6 +92,7 @@ namespace Umbraco.Core.IO
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFileSystem ScriptsFileSystem
|
||||
{
|
||||
get
|
||||
@@ -97,16 +101,18 @@ namespace Umbraco.Core.IO
|
||||
return _scriptsFileSystem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFileSystem MasterPagesFileSystem
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
|
||||
return _masterPagesFileSystem;// fixme - see 7.6?!
|
||||
return _masterPagesFileSystem;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFileSystem MvcViewsFileSystem
|
||||
{
|
||||
get
|
||||
@@ -116,15 +122,6 @@ namespace Umbraco.Core.IO
|
||||
}
|
||||
}
|
||||
|
||||
public MediaFileSystem MediaFileSystem
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
|
||||
return _mediaFileSystem;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureWellKnownFileSystems()
|
||||
{
|
||||
LazyInitializer.EnsureInitialized(ref _wkfsObject, ref _wkfsInitialized, ref _wkfsLock, CreateWellKnownFileSystems);
|
||||
@@ -141,15 +138,20 @@ namespace Umbraco.Core.IO
|
||||
var masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages);
|
||||
var mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews);
|
||||
|
||||
_macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", () => IsScoped());
|
||||
_partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", () => IsScoped());
|
||||
_stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, "css", () => IsScoped());
|
||||
_scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, "scripts", () => IsScoped());
|
||||
_masterPagesFileSystem = new ShadowWrapper(masterPagesFileSystem, "masterpages", () => IsScoped());
|
||||
_mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", () => IsScoped());
|
||||
_macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", IsScoped);
|
||||
_partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", IsScoped);
|
||||
_stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, "css", IsScoped);
|
||||
_scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, "scripts", IsScoped);
|
||||
_masterPagesFileSystem = new ShadowWrapper(masterPagesFileSystem, "masterpages", IsScoped);
|
||||
_mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", IsScoped);
|
||||
|
||||
// filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again
|
||||
_mediaFileSystem = GetFileSystemProvider<MediaFileSystem>();
|
||||
// fixme locking?
|
||||
_shadowWrappers.Add(_macroPartialFileSystem);
|
||||
_shadowWrappers.Add(_partialViewsFileSystem);
|
||||
_shadowWrappers.Add(_stylesheetsFileSystem);
|
||||
_shadowWrappers.Add(_scriptsFileSystem);
|
||||
_shadowWrappers.Add(_masterPagesFileSystem);
|
||||
_shadowWrappers.Add(_mvcViewsFileSystem);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -158,156 +160,28 @@ namespace Umbraco.Core.IO
|
||||
|
||||
#region Providers
|
||||
|
||||
/// <summary>
|
||||
/// used to cache the lookup of how to construct this object so we don't have to reflect each time.
|
||||
/// </summary>
|
||||
private class ProviderConstructionInfo
|
||||
{
|
||||
public object[] Parameters { get; set; }
|
||||
public ConstructorInfo Constructor { get; set; }
|
||||
//public string ProviderAlias { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem.
|
||||
/// </summary>
|
||||
/// <param name="alias">The alias of the strongly-typed filesystem.</param>
|
||||
/// <returns>The non-typed filesystem supporting the strongly-typed filesystem with the specified alias.</returns>
|
||||
/// <remarks>This method should not be used directly, used <see cref="GetFileSystemProvider{TFileSystem}()"/> instead.</remarks>
|
||||
public IFileSystem GetUnderlyingFileSystemProvider(string alias)
|
||||
{
|
||||
return GetUnderlyingFileSystemProvider(alias, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem.
|
||||
/// </summary>
|
||||
/// <param name="alias">The alias of the strongly-typed filesystem.</param>
|
||||
/// <param name="fallback">A fallback creator for the filesystem.</param>
|
||||
/// <returns>The non-typed filesystem supporting the strongly-typed filesystem with the specified alias.</returns>
|
||||
/// <remarks>This method should not be used directly, used <see cref="GetFileSystem{TFileSystem}"/> instead.</remarks>
|
||||
internal IFileSystem GetUnderlyingFileSystemProvider(string alias, Func<IFileSystem> fallback)
|
||||
{
|
||||
// either get the constructor info from cache or create it and add to cache
|
||||
var ctorInfo = _providerLookup.GetOrAdd(alias, _ => GetUnderlyingFileSystemCtor(alias, fallback));
|
||||
return ctorInfo == null ? fallback() : (IFileSystem) ctorInfo.Constructor.Invoke(ctorInfo.Parameters);
|
||||
}
|
||||
|
||||
private IFileSystem GetUnderlyingFileSystemNoCache(string alias, Func<IFileSystem> fallback)
|
||||
{
|
||||
var ctorInfo = GetUnderlyingFileSystemCtor(alias, fallback);
|
||||
return ctorInfo == null ? fallback() : (IFileSystem) ctorInfo.Constructor.Invoke(ctorInfo.Parameters);
|
||||
}
|
||||
|
||||
private ProviderConstructionInfo GetUnderlyingFileSystemCtor(string alias, Func<IFileSystem> fallback)
|
||||
{
|
||||
// get config
|
||||
if (_config.Providers.TryGetValue(alias, out var providerConfig) == false)
|
||||
{
|
||||
if (fallback != null) return null;
|
||||
throw new ArgumentException($"No provider found with alias {alias}.");
|
||||
}
|
||||
|
||||
// get the filesystem type
|
||||
var providerType = Type.GetType(providerConfig.Type);
|
||||
if (providerType == null)
|
||||
throw new InvalidOperationException($"Could not find type {providerConfig.Type}.");
|
||||
|
||||
// ensure it implements IFileSystem
|
||||
if (providerType.IsAssignableFrom(typeof (IFileSystem)))
|
||||
throw new InvalidOperationException($"Type {providerType.FullName} does not implement IFileSystem.");
|
||||
|
||||
// find a ctor matching the config parameters
|
||||
var paramCount = providerConfig.Parameters?.Count ?? 0;
|
||||
var constructor = providerType.GetConstructors().SingleOrDefault(x
|
||||
=> x.GetParameters().Length == paramCount && x.GetParameters().All(y => providerConfig.Parameters.Keys.Contains(y.Name)));
|
||||
if (constructor == null)
|
||||
throw new InvalidOperationException($"Type {providerType.FullName} has no ctor matching the {paramCount} configuration parameter(s).");
|
||||
|
||||
var parameters = new object[paramCount];
|
||||
if (providerConfig.Parameters != null) // keeps ReSharper happy
|
||||
{
|
||||
var allKeys = providerConfig.Parameters.Keys.ToArray();
|
||||
for (var i = 0; i < paramCount; i++)
|
||||
parameters[i] = providerConfig.Parameters[allKeys[i]];
|
||||
}
|
||||
|
||||
return new ProviderConstructionInfo
|
||||
{
|
||||
Constructor = constructor,
|
||||
Parameters = parameters,
|
||||
//ProviderAlias = s
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a strongly-typed filesystem.
|
||||
/// </summary>
|
||||
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
|
||||
/// <returns>A strongly-typed filesystem of the specified type.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Ideally, this should cache the instances, but that would break backward compatibility, so we
|
||||
/// only do it for our own MediaFileSystem - for everything else, it's the responsibility of the caller
|
||||
/// to ensure that they maintain singletons. This is important for singletons, as each filesystem maintains
|
||||
/// its own shadow and having multiple instances would lead to inconsistencies.</para>
|
||||
/// <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>
|
||||
// fixme - should it change for v8?
|
||||
public TFileSystem GetFileSystemProvider<TFileSystem>()
|
||||
public TFileSystem GetFileSystem<TFileSystem>(IFileSystem supporting)
|
||||
where TFileSystem : FileSystemWrapper
|
||||
{
|
||||
return GetFileSystemProvider<TFileSystem>(null);
|
||||
}
|
||||
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a strongly-typed filesystem.
|
||||
/// </summary>
|
||||
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
|
||||
/// <param name="fallback">A fallback creator for the inner filesystem.</param>
|
||||
/// <returns>A strongly-typed filesystem of the specified type.</returns>
|
||||
/// <remarks>
|
||||
/// <para>The fallback creator is used only if nothing is configured.</para>
|
||||
/// <para>Ideally, this should cache the instances, but that would break backward compatibility, so we
|
||||
/// only do it for our own MediaFileSystem - for everything else, it's the responsibility of the caller
|
||||
/// to ensure that they maintain singletons. This is important for singletons, as each filesystem maintains
|
||||
/// its own shadow and having multiple instances would lead to inconsistencies.</para>
|
||||
/// <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 GetFileSystemProvider<TFileSystem>(Func<IFileSystem> fallback)
|
||||
where TFileSystem : FileSystemWrapper
|
||||
{
|
||||
var alias = GetFileSystemAlias<TFileSystem>();
|
||||
return (TFileSystem)_filesystems.GetOrAdd(alias, _ =>
|
||||
return (TFileSystem) _filesystems.GetOrAdd(typeof(TFileSystem), _ => new Lazy<IFileSystem>(() =>
|
||||
{
|
||||
// gets the inner fs, create the strongly-typed fs wrapping the inner fs, register & return
|
||||
// so we are double-wrapping here
|
||||
// could be optimized by having FileSystemWrapper inherit from ShadowWrapper, maybe
|
||||
var innerFs = GetUnderlyingFileSystemNoCache(alias, fallback);
|
||||
var shadowWrapper = new ShadowWrapper(innerFs, "typed/" + alias, () => IsScoped());
|
||||
var fs = (IFileSystem) Activator.CreateInstance(typeof(TFileSystem), shadowWrapper);
|
||||
_wrappers.Add(shadowWrapper); // keeping a reference to the wrapper
|
||||
return fs;
|
||||
});
|
||||
}
|
||||
var name = typeof(TFileSystem).FullName;
|
||||
if (name == null) throw new Exception("panic!");
|
||||
|
||||
private string GetFileSystemAlias<TFileSystem>()
|
||||
{
|
||||
var fsType = typeof(TFileSystem);
|
||||
|
||||
// validate the ctor
|
||||
var constructor = fsType.GetConstructors().SingleOrDefault(x
|
||||
=> x.GetParameters().Length == 1 && TypeHelper.IsTypeAssignableFrom<IFileSystem>(x.GetParameters().Single().ParameterType));
|
||||
if (constructor == null)
|
||||
throw new InvalidOperationException("Type " + fsType.FullName + " must inherit from FileSystemWrapper and have a constructor that accepts one parameter of type " + typeof(IFileSystem).FullName + ".");
|
||||
|
||||
// find the attribute and get the alias
|
||||
var attr = (FileSystemProviderAttribute)fsType.GetCustomAttributes(typeof(FileSystemProviderAttribute), false).SingleOrDefault();
|
||||
if (attr == null)
|
||||
throw new InvalidOperationException("Type " + fsType.FullName + "is missing the required FileSystemProviderAttribute.");
|
||||
|
||||
return attr.Alias;
|
||||
var shadowWrapper = CreateShadowWrapper(supporting, "typed/" + name);
|
||||
return _container.CreateInstance<TFileSystem>(shadowWrapper);
|
||||
})).Value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -318,68 +192,75 @@ 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 neglictible perfs
|
||||
// impact.
|
||||
// NO! by the time an app event handler is instanciated 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)
|
||||
{
|
||||
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
|
||||
|
||||
var typed = _wrappers.ToArray();
|
||||
var wrappers = new ShadowWrapper[typed.Length + 6];
|
||||
var i = 0;
|
||||
while (i < typed.Length) wrappers[i] = typed[i++];
|
||||
wrappers[i++] = _macroPartialFileSystem;
|
||||
wrappers[i++] = _partialViewsFileSystem;
|
||||
wrappers[i++] = _stylesheetsFileSystem;
|
||||
wrappers[i++] = _scriptsFileSystem;
|
||||
wrappers[i++] = _masterPagesFileSystem;
|
||||
wrappers[i] = _mvcViewsFileSystem;
|
||||
return new ShadowFileSystems(this, id); // will invoke BeginShadow and EndShadow
|
||||
}
|
||||
|
||||
return new ShadowFileSystems(id, wrappers, _logger);
|
||||
internal void BeginShadow(Guid id)
|
||||
{
|
||||
lock (_shadowLocker)
|
||||
{
|
||||
// if we throw here, it means that something very wrong happened.
|
||||
if (_shadowCurrentId != Guid.Empty)
|
||||
throw new InvalidOperationException("Already shadowing.");
|
||||
_shadowCurrentId = id;
|
||||
|
||||
_logger.Debug<ShadowFileSystems>("Shadow '{ShadowId}'", id);
|
||||
|
||||
foreach (var wrapper in _shadowWrappers)
|
||||
wrapper.Shadow(id);
|
||||
}
|
||||
}
|
||||
|
||||
internal void EndShadow(Guid id, bool completed)
|
||||
{
|
||||
lock (_shadowLocker)
|
||||
{
|
||||
// if we throw here, it means that something very wrong happened.
|
||||
if (_shadowCurrentId == Guid.Empty)
|
||||
throw new InvalidOperationException("Not shadowing.");
|
||||
if (id != _shadowCurrentId)
|
||||
throw new InvalidOperationException("Not the current shadow.");
|
||||
|
||||
_logger.Debug<ShadowFileSystems>("UnShadow '{ShadowId}' {Status}", id, completed ? "complete" : "abort");
|
||||
|
||||
var exceptions = new List<Exception>();
|
||||
foreach (var wrapper in _shadowWrappers)
|
||||
{
|
||||
try
|
||||
{
|
||||
// this may throw an AggregateException if some of the changes could not be applied
|
||||
wrapper.UnShadow(completed);
|
||||
}
|
||||
catch (AggregateException ae)
|
||||
{
|
||||
exceptions.Add(ae);
|
||||
}
|
||||
}
|
||||
|
||||
_shadowCurrentId = Guid.Empty;
|
||||
|
||||
if (exceptions.Count > 0)
|
||||
throw new AggregateException(completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
private ShadowWrapper CreateShadowWrapper(IFileSystem filesystem, string shadowPath)
|
||||
{
|
||||
lock (_shadowLocker)
|
||||
{
|
||||
var wrapper = new ShadowWrapper(filesystem, shadowPath, IsScoped);
|
||||
if (_shadowCurrentId != Guid.Empty)
|
||||
wrapper.Shadow(_shadowCurrentId);
|
||||
_shadowWrappers.Add(wrapper);
|
||||
return wrapper;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private class ConcurrentSet<T>
|
||||
where T : class
|
||||
{
|
||||
private readonly HashSet<T> _set = new HashSet<T>();
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
lock (_set)
|
||||
{
|
||||
_set.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (_set)
|
||||
{
|
||||
_set.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public T[] ToArray()
|
||||
{
|
||||
lock (_set)
|
||||
{
|
||||
return _set.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.IO;
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods allowing the manipulation of files within an Umbraco application.
|
||||
/// Provides methods allowing the manipulation of files.
|
||||
/// </summary>
|
||||
public interface IFileSystem
|
||||
{
|
||||
|
||||
38
src/Umbraco.Core/IO/IFileSystems.cs
Normal file
38
src/Umbraco.Core/IO/IFileSystems.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the system filesystems.
|
||||
/// </summary>
|
||||
public interface IFileSystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the macro partials filesystem.
|
||||
/// </summary>
|
||||
IFileSystem MacroPartialsFileSystem { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the partial views filesystem.
|
||||
/// </summary>
|
||||
IFileSystem PartialViewsFileSystem { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stylesheets filesystem.
|
||||
/// </summary>
|
||||
IFileSystem StylesheetsFileSystem { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the scripts filesystem.
|
||||
/// </summary>
|
||||
IFileSystem ScriptsFileSystem { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the masterpages filesystem.
|
||||
/// </summary>
|
||||
IFileSystem MasterPagesFileSystem { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the MVC views filesystem.
|
||||
/// </summary>
|
||||
IFileSystem MvcViewsFileSystem { get; }
|
||||
}
|
||||
}
|
||||
66
src/Umbraco.Core/IO/IMediaFileSystem.cs
Normal file
66
src/Umbraco.Core/IO/IMediaFileSystem.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods allowing the manipulation of media files.
|
||||
/// </summary>
|
||||
public interface IMediaFileSystem : IFileSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Delete media files.
|
||||
/// </summary>
|
||||
/// <param name="files">Files to delete (filesystem-relative paths).</param>
|
||||
void DeleteMediaFiles(IEnumerable<string> files);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file path of a media file.
|
||||
/// </summary>
|
||||
/// <param name="filename">The file name.</param>
|
||||
/// <param name="cuid">The unique identifier of the content/media owning the file.</param>
|
||||
/// <param name="puid">The unique identifier of the property type owning the file.</param>
|
||||
/// <returns>The filesystem-relative path to the media file.</returns>
|
||||
/// <remarks>With the old media path scheme, this CREATES a new media path each time it is invoked.</remarks>
|
||||
string GetMediaPath(string filename, Guid cuid, Guid puid);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file path of a media file.
|
||||
/// </summary>
|
||||
/// <param name="filename">The file name.</param>
|
||||
/// <param name="prevpath">A previous file path.</param>
|
||||
/// <param name="cuid">The unique identifier of the content/media owning the file.</param>
|
||||
/// <param name="puid">The unique identifier of the property type owning the file.</param>
|
||||
/// <returns>The filesystem-relative path to the media file.</returns>
|
||||
/// <remarks>In the old, legacy, number-based scheme, we try to re-use the media folder
|
||||
/// specified by <paramref name="prevpath"/>. Else, we CREATE a new one. Each time we are invoked.</remarks>
|
||||
string GetMediaPath(string filename, string prevpath, Guid cuid, Guid puid);
|
||||
|
||||
/// <summary>
|
||||
/// Stores a media file associated to a property of a content item.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item owning the media file.</param>
|
||||
/// <param name="propertyType">The property type owning the media file.</param>
|
||||
/// <param name="filename">The media file name.</param>
|
||||
/// <param name="filestream">A stream containing the media bytes.</param>
|
||||
/// <param name="oldpath">An optional filesystem-relative filepath to the previous media file.</param>
|
||||
/// <returns>The filesystem-relative filepath to the media file.</returns>
|
||||
/// <remarks>
|
||||
/// <para>The file is considered "owned" by the content/propertyType.</para>
|
||||
/// <para>If an <paramref name="oldpath"/> is provided then that file (and associated thumbnails if any) is deleted
|
||||
/// before the new file is saved, and depending on the media path scheme, the folder may be reused for the new file.</para>
|
||||
/// </remarks>
|
||||
string StoreFile(IContentBase content, PropertyType propertyType, string filename, Stream filestream, string oldpath);
|
||||
|
||||
/// <summary>
|
||||
/// Copies a media file as a new media file, associated to a property of a content item.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item owning the copy of the media file.</param>
|
||||
/// <param name="propertyType">The property type owning the copy of the media file.</param>
|
||||
/// <param name="sourcepath">The filesystem-relative path to the source media file.</param>
|
||||
/// <returns>The filesystem-relative path to the copy of the media file.</returns>
|
||||
string CopyFile(IContentBase content, PropertyType propertyType, string sourcepath);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Media;
|
||||
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
@@ -17,78 +17,32 @@ namespace Umbraco.Core.IO
|
||||
/// <summary>
|
||||
/// A custom file system provider for media
|
||||
/// </summary>
|
||||
[FileSystemProvider("media")]
|
||||
public class MediaFileSystem : FileSystemWrapper
|
||||
public class MediaFileSystem : FileSystemWrapper, IMediaFileSystem
|
||||
{
|
||||
public MediaFileSystem(IFileSystem wrapped)
|
||||
: base(wrapped)
|
||||
{
|
||||
// due to how FileSystems is written at the moment, the ctor cannot be used to inject
|
||||
// dependencies, so we have to rely on property injection for anything we might need
|
||||
Current.Container.InjectProperties(this);
|
||||
MediaPathScheme.Initialize(this);
|
||||
}
|
||||
private readonly IMediaPathScheme _mediaPathScheme;
|
||||
private readonly IContentSection _contentConfig;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
[Inject]
|
||||
internal IMediaPathScheme MediaPathScheme { get; set; }
|
||||
|
||||
[Inject]
|
||||
internal IContentSection ContentConfig { get; set; }
|
||||
|
||||
[Inject]
|
||||
internal ILogger Logger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all files passed in.
|
||||
/// Initializes a new instance of the <see cref="MediaFileSystem"/> class.
|
||||
/// </summary>
|
||||
/// <param name="files"></param>
|
||||
/// <param name="onError"></param>
|
||||
/// <returns></returns>
|
||||
internal bool DeleteFiles(IEnumerable<string> files, Action<string, Exception> onError = null)
|
||||
public MediaFileSystem(IFileSystem innerFileSystem, IContentSection contentConfig, IMediaPathScheme mediaPathScheme, ILogger logger)
|
||||
: base(innerFileSystem)
|
||||
{
|
||||
//ensure duplicates are removed
|
||||
files = files.Distinct();
|
||||
|
||||
var allsuccess = true;
|
||||
var rootRelativePath = GetRelativePath("/");
|
||||
|
||||
Parallel.ForEach(files, file =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (file.IsNullOrWhiteSpace()) return;
|
||||
|
||||
var relativeFilePath = GetRelativePath(file);
|
||||
if (FileExists(relativeFilePath) == false) return;
|
||||
|
||||
var parentDirectory = Path.GetDirectoryName(relativeFilePath);
|
||||
|
||||
// don't want to delete the media folder if not using directories.
|
||||
if (ContentConfig.UploadAllowDirectories && parentDirectory != rootRelativePath)
|
||||
{
|
||||
//issue U4-771: if there is a parent directory the recursive parameter should be true
|
||||
DeleteDirectory(parentDirectory, string.IsNullOrEmpty(parentDirectory) == false);
|
||||
}
|
||||
else
|
||||
{
|
||||
DeleteFile(file);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
onError?.Invoke(file, e);
|
||||
allsuccess = false;
|
||||
}
|
||||
});
|
||||
|
||||
return allsuccess;
|
||||
_contentConfig = contentConfig;
|
||||
_mediaPathScheme = mediaPathScheme;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritoc />
|
||||
public void DeleteMediaFiles(IEnumerable<string> files)
|
||||
{
|
||||
files = files.Distinct();
|
||||
|
||||
Parallel.ForEach(files, file =>
|
||||
// kinda try to keep things under control
|
||||
var options = new ParallelOptions { MaxDegreeOfParallelism = 20 };
|
||||
|
||||
Parallel.ForEach(files, options, file =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -96,73 +50,44 @@ 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 attached file '{File}'", file);
|
||||
_logger.Error<MediaFileSystem>(e, "Failed to delete media file '{File}'.", file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#region Media Path
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file path of a media file.
|
||||
/// </summary>
|
||||
/// <param name="filename">The file name.</param>
|
||||
/// <param name="cuid">The unique identifier of the content/media owning the file.</param>
|
||||
/// <param name="puid">The unique identifier of the property type owning the file.</param>
|
||||
/// <returns>The filesystem-relative path to the media file.</returns>
|
||||
/// <remarks>With the old media path scheme, this CREATES a new media path each time it is invoked.</remarks>
|
||||
/// <inheritoc />
|
||||
public string GetMediaPath(string filename, Guid cuid, Guid puid)
|
||||
{
|
||||
filename = Path.GetFileName(filename);
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file path of a media file.
|
||||
/// </summary>
|
||||
/// <param name="filename">The file name.</param>
|
||||
/// <param name="prevpath">A previous file path.</param>
|
||||
/// <param name="cuid">The unique identifier of the content/media owning the file.</param>
|
||||
/// <param name="puid">The unique identifier of the property type owning the file.</param>
|
||||
/// <returns>The filesystem-relative path to the media file.</returns>
|
||||
/// <remarks>In the old, legacy, number-based scheme, we try to re-use the media folder
|
||||
/// specified by <paramref name="prevpath"/>. Else, we CREATE a new one. Each time we are invoked.</remarks>
|
||||
/// <inheritoc />
|
||||
public string GetMediaPath(string filename, string prevpath, Guid cuid, Guid puid)
|
||||
{
|
||||
filename = Path.GetFileName(filename);
|
||||
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
|
||||
|
||||
#region Associated Media Files
|
||||
|
||||
/// <summary>
|
||||
/// Stores a media file associated to a property of a content item.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item owning the media file.</param>
|
||||
/// <param name="propertyType">The property type owning the media file.</param>
|
||||
/// <param name="filename">The media file name.</param>
|
||||
/// <param name="filestream">A stream containing the media bytes.</param>
|
||||
/// <param name="oldpath">An optional filesystem-relative filepath to the previous media file.</param>
|
||||
/// <returns>The filesystem-relative filepath to the media file.</returns>
|
||||
/// <remarks>
|
||||
/// <para>The file is considered "owned" by the content/propertyType.</para>
|
||||
/// <para>If an <paramref name="oldpath"/> is provided then that file (and associated thumbnails if any) is deleted
|
||||
/// before the new file is saved, and depending on the media path scheme, the folder may be reused for the new file.</para>
|
||||
/// </remarks>
|
||||
/// <inheritoc />
|
||||
public string StoreFile(IContentBase content, PropertyType propertyType, string filename, Stream filestream, string oldpath)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException(nameof(content));
|
||||
@@ -181,13 +106,7 @@ namespace Umbraco.Core.IO
|
||||
return filepath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a media file as a new media file, associated to a property of a content item.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item owning the copy of the media file.</param>
|
||||
/// <param name="propertyType">The property type owning the copy of the media file.</param>
|
||||
/// <param name="sourcepath">The filesystem-relative path to the source media file.</param>
|
||||
/// <returns>The filesystem-relative path to the copy of the media file.</returns>
|
||||
/// <inheritoc />
|
||||
public string CopyFile(IContentBase content, PropertyType propertyType, string sourcepath)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException(nameof(content));
|
||||
@@ -204,9 +123,6 @@ namespace Umbraco.Core.IO
|
||||
return filepath;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,6 +21,6 @@ namespace Umbraco.Core.IO.MediaPathSchemes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetDeleteDirectory(string filepath) => Path.GetDirectoryName(filepath);
|
||||
public string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath) => Path.GetDirectoryName(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration;
|
||||
|
||||
namespace Umbraco.Core.IO.MediaPathSchemes
|
||||
@@ -17,18 +18,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)
|
||||
@@ -37,45 +31,45 @@ namespace Umbraco.Core.IO.MediaPathSchemes
|
||||
// prevpath should be "<int>/<filename>" OR "<int>-<filename>"
|
||||
// and we want to reuse the "<int>" part, so try to find it
|
||||
|
||||
var sep = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories ? "/" : "-";
|
||||
var sep = Current.Config.Umbraco().Content.UploadAllowDirectories ? "/" : "-";
|
||||
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)
|
||||
throw new InvalidOperationException("Cannot use a null directory.");
|
||||
|
||||
return UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories
|
||||
return Current.Config.Umbraco().Content.UploadAllowDirectories
|
||||
? Path.Combine(directory, filename).Replace('\\', '/')
|
||||
: directory + "-" + filename;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,158 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
// shadow filesystems is definitively ... too convoluted
|
||||
|
||||
internal class ShadowFileSystems : ICompletable
|
||||
{
|
||||
// note: taking a reference to the _manager instead of using manager.Current
|
||||
// to avoid using Current everywhere but really, we support only 1 scope at
|
||||
// a time, not multiple scopes in case of multiple managers (not supported)
|
||||
|
||||
// fixme - why are we managing logical call context here? should be bound
|
||||
// to the current scope, always => REFACTOR! but there should be something in
|
||||
// place (static?) to ensure we only have one concurrent shadow FS?
|
||||
//
|
||||
// => yes, that's _currentId - need to cleanup this entirely
|
||||
// and, we probably need a way to stop shadowing entirely without cycling the app
|
||||
|
||||
private const string ItemKey = "Umbraco.Core.IO.ShadowFileSystems";
|
||||
|
||||
private static readonly object Locker = new object();
|
||||
private static Guid _currentId = Guid.Empty;
|
||||
|
||||
private readonly Guid _id;
|
||||
private readonly ShadowWrapper[] _wrappers;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly FileSystems _fileSystems;
|
||||
private bool _completed;
|
||||
|
||||
//static ShadowFileSystems()
|
||||
//{
|
||||
// SafeCallContext.Register(
|
||||
// () =>
|
||||
// {
|
||||
// var scope = CallContext.LogicalGetData(ItemKey);
|
||||
// CallContext.FreeNamedDataSlot(ItemKey);
|
||||
// return scope;
|
||||
// },
|
||||
// o =>
|
||||
// {
|
||||
// if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException();
|
||||
// if (o != null) CallContext.LogicalSetData(ItemKey, o);
|
||||
// });
|
||||
//}
|
||||
|
||||
public ShadowFileSystems(Guid id, ShadowWrapper[] wrappers, ILogger logger)
|
||||
// invoked by the filesystems when shadowing
|
||||
public ShadowFileSystems(FileSystems fileSystems, Guid id)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
if (_currentId != Guid.Empty)
|
||||
throw new InvalidOperationException("Already shadowing.");
|
||||
_currentId = id;
|
||||
}
|
||||
_fileSystems = fileSystems;
|
||||
Id = id;
|
||||
|
||||
_logger = logger;
|
||||
_logger.Debug<ShadowFileSystems>("Shadow '{ShadowId}'", id);
|
||||
_id = id;
|
||||
|
||||
_wrappers = wrappers;
|
||||
foreach (var wrapper in _wrappers)
|
||||
wrapper.Shadow(id);
|
||||
}
|
||||
|
||||
// fixme - remove
|
||||
//// internal for tests + FileSystems
|
||||
//// do NOT use otherwise
|
||||
//internal static ShadowFileSystems CreateScope(Guid id, ShadowWrapper[] wrappers, ILogger logger)
|
||||
//{
|
||||
// lock (Locker)
|
||||
// {
|
||||
// if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException("Already shadowing.");
|
||||
// CallContext.LogicalSetData(ItemKey, ItemKey); // value does not matter
|
||||
// }
|
||||
// return new ShadowFileSystems(id, wrappers, logger);
|
||||
//}
|
||||
|
||||
//internal static bool InScope => NoScope == false;
|
||||
|
||||
//internal static bool NoScope => CallContext.LogicalGetData(ItemKey) == null;
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
_completed = true;
|
||||
//lock (Locker)
|
||||
//{
|
||||
// _logger.Debug<ShadowFileSystems>("UnShadow " + _id + " (complete).");
|
||||
|
||||
// var exceptions = new List<Exception>();
|
||||
// foreach (var wrapper in _wrappers)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// // this may throw an AggregateException if some of the changes could not be applied
|
||||
// wrapper.UnShadow(true);
|
||||
// }
|
||||
// catch (AggregateException ae)
|
||||
// {
|
||||
// exceptions.Add(ae);
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (exceptions.Count > 0)
|
||||
// throw new AggregateException("Failed to apply all changes (see exceptions).", exceptions);
|
||||
|
||||
// // last, & *only* if successful (otherwise we'll unshadow & cleanup as best as we can)
|
||||
// CallContext.FreeNamedDataSlot(ItemKey);
|
||||
//}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
_logger.Debug<ShadowFileSystems>("UnShadow '{ShadowId}' {Status}", _id, _completed ? "complete" : "abort");
|
||||
|
||||
var exceptions = new List<Exception>();
|
||||
foreach (var wrapper in _wrappers)
|
||||
{
|
||||
try
|
||||
{
|
||||
// this may throw an AggregateException if some of the changes could not be applied
|
||||
wrapper.UnShadow(_completed);
|
||||
}
|
||||
catch (AggregateException ae)
|
||||
{
|
||||
exceptions.Add(ae);
|
||||
}
|
||||
}
|
||||
|
||||
_currentId = Guid.Empty;
|
||||
|
||||
if (exceptions.Count > 0)
|
||||
throw new AggregateException(_completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions);
|
||||
|
||||
//if (CallContext.LogicalGetData(ItemKey) == null) return;
|
||||
|
||||
//try
|
||||
//{
|
||||
// _logger.Debug<ShadowFileSystems>("UnShadow " + _id + " (abort)");
|
||||
// foreach (var wrapper in _wrappers)
|
||||
// wrapper.UnShadow(false); // should not throw
|
||||
//}
|
||||
//finally
|
||||
//{
|
||||
// // last, & always
|
||||
// CallContext.FreeNamedDataSlot(ItemKey);
|
||||
//}
|
||||
}
|
||||
_fileSystems.BeginShadow(id);
|
||||
}
|
||||
|
||||
// for tests
|
||||
internal static void ResetId()
|
||||
public Guid Id { get; }
|
||||
|
||||
// invoked by the scope when exiting, if completed
|
||||
public void Complete()
|
||||
{
|
||||
_currentId = Guid.Empty;
|
||||
_completed = true;
|
||||
}
|
||||
|
||||
// invoked by the scope when exiting
|
||||
public void Dispose()
|
||||
{
|
||||
_fileSystems.EndShadow(Id, _completed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using LightInject;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
@@ -10,8 +10,14 @@ namespace Umbraco.Core
|
||||
/// <summary>
|
||||
/// Boots the runtime.
|
||||
/// </summary>
|
||||
/// <param name="container">The application service container.</param>
|
||||
void Boot(ServiceContainer container);
|
||||
/// <param name="register">The application register.</param>
|
||||
/// <returns>The application factory.</returns>
|
||||
IFactory Boot(IRegister register);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the runtime state.
|
||||
/// </summary>
|
||||
IRuntimeState State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Terminates the runtime.
|
||||
|
||||
40
src/Umbraco.Core/Logging/IProfilingLogger.cs
Normal file
40
src/Umbraco.Core/Logging/IProfilingLogger.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the profiling logging service.
|
||||
/// </summary>
|
||||
public interface IProfilingLogger : ILogger
|
||||
{
|
||||
/// <summary>
|
||||
/// Profiles an action and log as information messages.
|
||||
/// </summary>
|
||||
DisposableTimer TraceDuration<T>(string startMessage);
|
||||
|
||||
/// <summary>
|
||||
/// Profiles an action and log as information messages.
|
||||
/// </summary>
|
||||
DisposableTimer TraceDuration<T>(string startMessage, string completeMessage, string failMessage = null);
|
||||
|
||||
/// <summary>
|
||||
/// Profiles an action and log as information messages.
|
||||
/// </summary>
|
||||
DisposableTimer TraceDuration(Type loggerType, string startMessage, string completeMessage, string failMessage = null);
|
||||
|
||||
/// <summary>
|
||||
/// Profiles an action and log as debug messages.
|
||||
/// </summary>
|
||||
DisposableTimer DebugDuration<T>(string startMessage);
|
||||
|
||||
/// <summary>
|
||||
/// Profiles an action and log as debug messages.
|
||||
/// </summary>
|
||||
DisposableTimer DebugDuration<T>(string startMessage, string completeMessage, string failMessage = null, int thresholdMilliseconds = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Profiles an action and log as debug messages.
|
||||
/// </summary>
|
||||
DisposableTimer DebugDuration(Type loggerType, string startMessage, string completeMessage, string failMessage = null, int thresholdMilliseconds = 0);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Serilog.Parsing;
|
||||
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
@@ -29,7 +33,23 @@ namespace Umbraco.Core.Logging
|
||||
if (!bound)
|
||||
throw new FormatException($"Could not format message \"{messageTemplate}\" with {args.Length} args.");
|
||||
|
||||
return parsedTemplate.Render(boundProperties.ToDictionary(x => x.Name, x => x.Value));
|
||||
var values = boundProperties.ToDictionary(x => x.Name, x => x.Value);
|
||||
|
||||
// this ends up putting every string parameter between quotes
|
||||
//return parsedTemplate.Render(values);
|
||||
|
||||
// this does not
|
||||
var tw = new StringWriter();
|
||||
foreach (var t in parsedTemplate.Tokens)
|
||||
{
|
||||
if (t is PropertyToken pt &&
|
||||
values.TryGetValue(pt.PropertyName, out var propVal) &&
|
||||
(propVal as ScalarValue)?.Value is string s)
|
||||
tw.Write(s);
|
||||
else
|
||||
t.Render(values, tw);
|
||||
}
|
||||
return tw.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,23 @@
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides debug or trace logging with duration management.
|
||||
/// Provides logging and profiling services.
|
||||
/// </summary>
|
||||
public sealed class ProfilingLogger
|
||||
public sealed class ProfilingLogger : IProfilingLogger
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the underlying <see cref="ILogger"/> implementation.
|
||||
/// </summary>
|
||||
public ILogger Logger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying <see cref="IProfiler"/> implementation.
|
||||
/// </summary>
|
||||
public IProfiler Profiler { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ProfilingLogger"/> class.
|
||||
/// </summary>
|
||||
public ProfilingLogger(ILogger logger, IProfiler profiler)
|
||||
{
|
||||
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
@@ -52,5 +61,72 @@ namespace Umbraco.Core.Logging
|
||||
? new DisposableTimer(Logger, LogLevel.Debug, Profiler, loggerType, startMessage, completeMessage, failMessage, thresholdMilliseconds)
|
||||
: null;
|
||||
}
|
||||
|
||||
#region ILogger
|
||||
|
||||
public bool IsEnabled(Type reporting, LogLevel level)
|
||||
=> Logger.IsEnabled(reporting, level);
|
||||
|
||||
public void Fatal(Type reporting, Exception exception, string message)
|
||||
=> Logger.Fatal(reporting, exception, message);
|
||||
|
||||
public void Fatal(Type reporting, Exception exception)
|
||||
=> Logger.Fatal(reporting, exception);
|
||||
|
||||
public void Fatal(Type reporting, string message)
|
||||
=> Logger.Fatal(reporting, message);
|
||||
|
||||
public void Fatal(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
|
||||
=> Logger.Fatal(reporting, exception, messageTemplate, propertyValues);
|
||||
|
||||
public void Fatal(Type reporting, string messageTemplate, params object[] propertyValues)
|
||||
=> Logger.Fatal(reporting, messageTemplate, propertyValues);
|
||||
|
||||
public void Error(Type reporting, Exception exception, string message)
|
||||
=> Logger.Error(reporting, exception, message);
|
||||
|
||||
public void Error(Type reporting, Exception exception)
|
||||
=> Logger.Error(reporting, exception);
|
||||
|
||||
public void Error(Type reporting, string message)
|
||||
=> Logger.Error(reporting, message);
|
||||
|
||||
public void Error(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
|
||||
=> Logger.Error(reporting, exception, messageTemplate, propertyValues);
|
||||
|
||||
public void Error(Type reporting, string messageTemplate, params object[] propertyValues)
|
||||
=> Logger.Error(reporting, messageTemplate, propertyValues);
|
||||
|
||||
public void Warn(Type reporting, string message)
|
||||
=> Logger.Warn(reporting, message);
|
||||
|
||||
public void Warn(Type reporting, string messageTemplate, params object[] propertyValues)
|
||||
=> Logger.Warn(reporting, messageTemplate, propertyValues);
|
||||
|
||||
public void Warn(Type reporting, Exception exception, string message)
|
||||
=> Logger.Warn(reporting, exception, message);
|
||||
|
||||
public void Warn(Type reporting, Exception exception, string messageTemplate, params object[] propertyValues)
|
||||
=> Logger.Warn(reporting, exception, messageTemplate, propertyValues);
|
||||
|
||||
public void Info(Type reporting, string message)
|
||||
=> Logger.Info(reporting, message);
|
||||
|
||||
public void Info(Type reporting, string messageTemplate, params object[] propertyValues)
|
||||
=> Logger.Info(reporting, messageTemplate, propertyValues);
|
||||
|
||||
public void Debug(Type reporting, string message)
|
||||
=> Logger.Debug(reporting, message);
|
||||
|
||||
public void Debug(Type reporting, string messageTemplate, params object[] propertyValues)
|
||||
=> Logger.Debug(reporting, messageTemplate, propertyValues);
|
||||
|
||||
public void Verbose(Type reporting, string message)
|
||||
=> Logger.Verbose(reporting, message);
|
||||
|
||||
public void Verbose(Type reporting, string messageTemplate, params object[] propertyValues)
|
||||
=> Logger.Verbose(reporting, messageTemplate, propertyValues);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Reflection;
|
||||
using System.Threading;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Diagnostics;
|
||||
|
||||
@@ -165,7 +166,7 @@ namespace Umbraco.Core.Logging.Serilog
|
||||
messageTemplate += "\r\nThe thread has been aborted, because the request has timed out.";
|
||||
|
||||
// dump if configured, or if stacktrace contains Monitor.ReliableEnter
|
||||
dump = UmbracoConfig.For.CoreDebug().DumpOnTimeoutThreadAbort || IsMonitorEnterThreadAbortException(exception);
|
||||
dump = Current.Config.CoreDebug().DumpOnTimeoutThreadAbort || IsMonitorEnterThreadAbortException(exception);
|
||||
|
||||
// dump if it is ok to dump (might have a cap on number of dump...)
|
||||
dump &= MiniDump.OkToDump();
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
using Umbraco.Core.Components;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
public class LogViewerComponent : UmbracoComponentBase, IUmbracoCoreComponent
|
||||
{
|
||||
public override void Compose(Composition composition)
|
||||
{
|
||||
composition.Container.RegisterSingleton<ILogViewer, JsonLogViewer>();
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs
Normal file
14
src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Umbraco.Core.Components;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Core.Logging.Viewer
|
||||
{
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
public class LogViewerComposer : ICoreComposer
|
||||
{
|
||||
public void Compose(Composition composition)
|
||||
{
|
||||
composition.RegisterUnique<ILogViewer, JsonLogViewer>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,13 @@ using Umbraco.Core.Logging;
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the main AppDomain running for a given application.
|
||||
/// Provides the full implementation of <see cref="IMainDom"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>There can be only one "main" AppDomain running for a given application at a time.</para>
|
||||
/// <para>When an AppDomain starts, it tries to acquire the main domain status.</para>
|
||||
/// <para>When an AppDomain stops (eg the application is restarting) it should release the main domain status.</para>
|
||||
/// <para>It is possible to register against the MainDom and be notified when it is released.</para>
|
||||
/// </remarks>
|
||||
internal class MainDom : IRegisteredObject
|
||||
internal class MainDom : IMainDom, IRegisteredObject
|
||||
{
|
||||
#region Vars
|
||||
|
||||
@@ -84,9 +82,7 @@ namespace Umbraco.Core
|
||||
/// <param name="weight">An optional weight (lower goes first).</param>
|
||||
/// <returns>A value indicating whether it was possible to register.</returns>
|
||||
public bool Register(Action release, int weight = 100)
|
||||
{
|
||||
return Register(null, release, weight);
|
||||
}
|
||||
=> Register(null, release, weight);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a resource that requires the current AppDomain to be the main domain to function.
|
||||
@@ -195,7 +191,9 @@ namespace Umbraco.Core
|
||||
}
|
||||
}
|
||||
|
||||
// gets a value indicating whether we are the main domain
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current domain is the main domain.
|
||||
/// </summary>
|
||||
public bool IsMainDom => _isMainDom;
|
||||
|
||||
// IRegisteredObject
|
||||
|
||||
@@ -120,7 +120,7 @@ namespace Umbraco.Core.Migrations.Install
|
||||
|
||||
#region Configure Connection String
|
||||
|
||||
private const string EmbeddedDatabaseConnectionString = @"Data Source=|DataDirectory|\Umbraco.sdf;Flush Interval=1;";
|
||||
public const string EmbeddedDatabaseConnectionString = @"Data Source=|DataDirectory|\Umbraco.sdf;Flush Interval=1;";
|
||||
|
||||
/// <summary>
|
||||
/// Configures a connection string for the embedded database.
|
||||
@@ -369,52 +369,6 @@ namespace Umbraco.Core.Migrations.Install
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utils
|
||||
|
||||
internal static void GiveLegacyAChance(IUmbracoDatabaseFactory factory, ILogger logger)
|
||||
{
|
||||
// look for the legacy appSettings key
|
||||
var legacyConnString = ConfigurationManager.AppSettings[Constants.System.UmbracoConnectionName];
|
||||
if (string.IsNullOrWhiteSpace(legacyConnString)) return;
|
||||
|
||||
var test = legacyConnString.ToLowerInvariant();
|
||||
if (test.Contains("sqlce4umbraco"))
|
||||
{
|
||||
// sql ce
|
||||
ConfigureEmbeddedDatabaseConnection(factory, logger);
|
||||
}
|
||||
else if (test.Contains("tcp:"))
|
||||
{
|
||||
// sql azure
|
||||
SaveConnectionString(legacyConnString, Constants.DbProviderNames.SqlServer, logger);
|
||||
factory.Configure(legacyConnString, Constants.DbProviderNames.SqlServer);
|
||||
}
|
||||
else if (test.Contains("datalayer=mysql"))
|
||||
{
|
||||
// mysql
|
||||
|
||||
// strip the datalayer part off
|
||||
var connectionStringWithoutDatalayer = string.Empty;
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var variable in legacyConnString.Split(';').Where(x => x.ToLowerInvariant().StartsWith("datalayer") == false))
|
||||
connectionStringWithoutDatalayer = $"{connectionStringWithoutDatalayer}{variable};";
|
||||
|
||||
SaveConnectionString(connectionStringWithoutDatalayer, Constants.DbProviderNames.MySql, logger);
|
||||
factory.Configure(connectionStringWithoutDatalayer, Constants.DbProviderNames.MySql);
|
||||
}
|
||||
else
|
||||
{
|
||||
// sql server
|
||||
SaveConnectionString(legacyConnString, Constants.DbProviderNames.SqlServer, logger);
|
||||
factory.Configure(legacyConnString, Constants.DbProviderNames.SqlServer);
|
||||
}
|
||||
|
||||
// remove the legacy connection string, so we don't end up in a loop if something goes wrong
|
||||
GlobalSettings.RemoveSetting(Constants.System.UmbracoConnectionName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Database Schema
|
||||
|
||||
/// <summary>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,36 +1,20 @@
|
||||
using System;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Core.Migrations
|
||||
{
|
||||
public class MigrationBuilder : IMigrationBuilder
|
||||
{
|
||||
private readonly IServiceContainer _container;
|
||||
private readonly IFactory _container;
|
||||
|
||||
public MigrationBuilder(IServiceContainer container)
|
||||
public MigrationBuilder(IFactory container)
|
||||
{
|
||||
_container = container;
|
||||
|
||||
// because the builder should be "per container" this ctor should run only once per container.
|
||||
//
|
||||
// note: constructor dependencies do NOT work with lifetimes other than transient
|
||||
// see https://github.com/seesharper/LightInject/issues/294
|
||||
//
|
||||
// resolve ctor dependency from GetInstance() runtimeArguments, if possible - 'factory' is
|
||||
// the container, 'info' describes the ctor argument, and 'args' contains the args that
|
||||
// were passed to GetInstance() - use first arg if it is the right type.
|
||||
//
|
||||
// for IMigrationContext
|
||||
container.RegisterConstructorDependency((factory, info, args) => args.Length > 0 ? args[0] as IMigrationContext : null);
|
||||
}
|
||||
|
||||
public IMigration Build(Type migrationType, IMigrationContext context)
|
||||
{
|
||||
// LightInject .Create() is a shortcut for .Register() + .GetInstance()
|
||||
// but it does not support parameters, so we do it ourselves here
|
||||
|
||||
_container.Register(migrationType);
|
||||
return (IMigration) _container.GetInstance(migrationType, new object[] { context });
|
||||
return (IMigration) _container.CreateInstance(migrationType, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
using LightInject;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Core.Migrations
|
||||
{
|
||||
public class PostMigrationCollectionBuilder : LazyCollectionBuilderBase<PostMigrationCollectionBuilder, PostMigrationCollection, IPostMigration>
|
||||
{
|
||||
public PostMigrationCollectionBuilder(IServiceContainer container)
|
||||
: base(container)
|
||||
{ }
|
||||
|
||||
protected override PostMigrationCollectionBuilder This => this;
|
||||
|
||||
protected override ILifetime CollectionLifetime => null; // transient
|
||||
protected override Lifetime CollectionLifetime => Lifetime.Transient;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,9 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
To<TagsMigrationFix>("{EE429F1B-9B26-43CA-89F8-A86017C809A3}");
|
||||
To<DropTemplateDesignColumn>("{08919C4B-B431-449C-90EC-2B8445B5C6B1}");
|
||||
To<TablesForScheduledPublishing>("{7EB0254C-CB8B-4C75-B15B-D48C55B449EB}");
|
||||
To<DropTaskTables>("{648A2D5F-7467-48F8-B309-E99CEEE00E2A}"); // fixed version
|
||||
To<MakeTagsVariant>("{C39BF2A7-1454-4047-BBFE-89E40F66ED63}");
|
||||
To<MakeRedirectUrlVariant>("{64EBCE53-E1F0-463A-B40B-E98EFCCA8AE2}");
|
||||
|
||||
//FINAL
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
if (TableExists("cmsTaskType"))
|
||||
Delete.Table("cmsTaskType");
|
||||
if (TableExists("cmsTask"))
|
||||
Delete.Table("cmsTask");
|
||||
Delete.Table("cmsTask").Do();
|
||||
if (TableExists("cmsTaskType"))
|
||||
Delete.Table("cmsTaskType").Do();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
public class MakeRedirectUrlVariant : MigrationBase
|
||||
{
|
||||
public MakeRedirectUrlVariant(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
AddColumn<RedirectUrlDto>("culture");
|
||||
|
||||
Delete.Index("IX_umbracoRedirectUrl").OnTable(Constants.DatabaseSchema.Tables.RedirectUrl).Do();
|
||||
Create.Index("IX_umbracoRedirectUrl").OnTable(Constants.DatabaseSchema.Tables.RedirectUrl)
|
||||
.OnColumn("urlHash")
|
||||
.Ascending()
|
||||
.OnColumn("contentKey")
|
||||
.Ascending()
|
||||
.OnColumn("culture")
|
||||
.Ascending()
|
||||
.OnColumn("createDateUtc")
|
||||
.Ascending()
|
||||
.WithOptions().Unique()
|
||||
.Do();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
Delete.Column("edited").FromTable(Constants.DatabaseSchema.Tables.ContentVersionCultureVariation).Do();
|
||||
if (ColumnExists(Constants.DatabaseSchema.Tables.ContentVersionCultureVariation, "edited"))
|
||||
Delete.Column("edited").FromTable(Constants.DatabaseSchema.Tables.ContentVersionCultureVariation).Do();
|
||||
|
||||
|
||||
// add available column
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user