diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 434fe812ef..55703d1509 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -22,11 +22,11 @@ not want this to happen as the alpha of the next major is, really, the next major already. --> - + - - + + diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index b5af335791..68d3cb4b6f 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -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")] diff --git a/src/Umbraco.Core/Cache/CacheHelper.cs b/src/Umbraco.Core/Cache/CacheHelper.cs index f99b1e847b..cd2225ae9d 100644 --- a/src/Umbraco.Core/Cache/CacheHelper.cs +++ b/src/Umbraco.Core/Cache/CacheHelper.cs @@ -4,27 +4,10 @@ using System.Web; namespace Umbraco.Core.Cache { /// - /// Class that is exposed by the ApplicationContext for application wide caching purposes + /// Represents the application-wide caches. /// public class CacheHelper { - public static CacheHelper NoCache { get; } = new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance)); - - /// - /// Creates a cache helper with disabled caches - /// - /// - /// - /// Good for unit testing - /// - 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)); - } - /// /// Initializes a new instance for use in the web /// @@ -40,7 +23,6 @@ namespace Umbraco.Core.Cache /// /// Initializes a new instance for use in the web /// - /// public CacheHelper(System.Web.Caching.Cache cache) : this( new HttpRuntimeCacheProvider(cache), @@ -50,30 +32,39 @@ namespace Umbraco.Core.Cache { } - /// /// Initializes a new instance based on the provided providers /// - /// - /// - /// - /// 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)); } + /// + /// Gets the special disabled instance. + /// + /// + /// When used by repositories, all cache policies apply, but the underlying caches do not cache anything. + /// Used by tests. + /// + public static CacheHelper Disabled { get; } = new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance)); + + /// + /// Gets the special no-cache instance. + /// + /// + /// When used by repositories, all cache policies are bypassed. + /// Used by repositories that do no cache. + /// + public static CacheHelper NoCache { get; } = new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance)); + /// /// Returns the current Request cache /// diff --git a/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs b/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs index 11ac05844b..8bae755149 100644 --- a/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs +++ b/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs @@ -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 { - public CacheRefresherCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override CacheRefresherCollectionBuilder This => this; } } diff --git a/src/Umbraco.Core/Components/AuditEventsComponent.cs b/src/Umbraco.Core/Components/AuditEventsComponent.cs index 134aa18414..f592613e0d 100644 --- a/src/Umbraco.Core/Components/AuditEventsComponent.cs +++ b/src/Umbraco.Core/Components/AuditEventsComponent.cs @@ -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}>"; diff --git a/src/Umbraco.Core/Components/AuditEventsComposer.cs b/src/Umbraco.Core/Components/AuditEventsComposer.cs new file mode 100644 index 0000000000..692cb6c6dd --- /dev/null +++ b/src/Umbraco.Core/Components/AuditEventsComposer.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Core.Components +{ + public sealed class AuditEventsComposer : ComponentComposer, ICoreComposer + { } +} diff --git a/src/Umbraco.Core/Components/BootLoader.cs b/src/Umbraco.Core/Components/BootLoader.cs deleted file mode 100644 index fd292990c8..0000000000 --- a/src/Umbraco.Core/Components/BootLoader.cs +++ /dev/null @@ -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; - - /// - /// Initializes a new instance of the class. - /// - /// The application container. - public BootLoader(IServiceContainer container) - { - _container = container ?? throw new ArgumentNullException(nameof(container)); - _proflog = container.GetInstance(); - _logger = container.GetInstance(); - } - - private class EnableInfo - { - public bool Enabled; - public int Weight = -1; - } - - public void Boot(IEnumerable 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().CreateScope()) - { - InitializeComponents(); - scope.Complete(); - } - - // rejoice! - _booted = true; - } - - private IEnumerable PrepareComponentTypes(IEnumerable componentTypes, RuntimeLevel level) - { - using (_proflog.DebugDuration("Preparing component types.", "Prepared component types.")) - { - return PrepareComponentTypes2(componentTypes, level); - } - } - - private IEnumerable PrepareComponentTypes2(IEnumerable 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(); - var minLevel = attr?.MinLevel ?? (x.Implements() ? 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>(); - 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(GetComponentsReport(requirements)); - - // sort components - var graph = new TopoGraph>>(kvp => kvp.Key, kvp => kvp.Value); - graph.AddItems(requirements); - List 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("Component Report:\r\n{ComponentReport}", GetComponentsReport(requirements)); - _logger.Error(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("Ordered Components: {SortedComponentTypes}", sortedComponentTypes); - - return sortedComponentTypes; - } - - private static string GetComponentsReport(Dictionary> 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()) - text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue - ? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")")) - : "")); - foreach (var attribute in type.GetCustomAttributes()) - text.AppendLine(" -< " + attribute.RequiringType); - foreach (var i in type.GetInterfaces()) - { - text.AppendLine(" : " + i.FullName); - foreach (var attribute in i.GetCustomAttributes()) - text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue - ? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")")) - : "")); - foreach (var attribute in i.GetCustomAttributes()) - 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 types) - { - var enabled = new Dictionary(); - - // 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()) - { - 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()) - { - 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 types, IDictionary> 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()) // those marking interfaces - .Concat(type.GetCustomAttributes()); // 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(); - 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(); - 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 types, IDictionary> requirements) - { - // get 'required' attributes - // fixme explain - var requiredAttributes = type - .GetInterfaces().SelectMany(x => x.GetCustomAttributes()) - .Concat(type.GetCustomAttributes()); - - 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(); - requirements[implem].Add(type); - } - } - else - { - if (types.Contains(attr.RequiringType)) - { - if (requirements[attr.RequiringType] == null) requirements[attr.RequiringType] = new List(); - requirements[attr.RequiringType].Add(type); - } - } - } - } - - private void InstanciateComponents(IEnumerable types) - { - using (_proflog.DebugDuration("Instanciating components.", "Instanciated components.")) - { - _components = types.Select(x => (IUmbracoComponent) Activator.CreateInstance(x)).ToArray(); - } - } - - private void ComposeComponents(RuntimeLevel level) - { - using (_proflog.DebugDuration($"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($"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($"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($"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("Cannot terminate, has not booted."); - return; - } - - using (_proflog.DebugDuration($"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($"Terminating {componentType.FullName}.", $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) - { - component.Terminate(); - } - } - } - } - } -} diff --git a/src/Umbraco.Core/Components/ComponentCollection.cs b/src/Umbraco.Core/Components/ComponentCollection.cs new file mode 100644 index 0000000000..abe493b524 --- /dev/null +++ b/src/Umbraco.Core/Components/ComponentCollection.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Components +{ + /// + /// Represents the collection of implementations. + /// + public class ComponentCollection : BuilderCollectionBase + { + private const int LogThresholdMilliseconds = 100; + + private readonly IProfilingLogger _logger; + + public ComponentCollection(IEnumerable items, IProfilingLogger logger) + : base(items) + { + _logger = logger; + } + + public void Terminate() + { + using (_logger.DebugDuration($"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($"Terminating {componentType.FullName}.", $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + { + component.DisposeIfDisposable(); + } + } + } + } + } +} diff --git a/src/Umbraco.Core/Components/ComponentCollectionBuilder.cs b/src/Umbraco.Core/Components/ComponentCollectionBuilder.cs new file mode 100644 index 0000000000..584de7a8f2 --- /dev/null +++ b/src/Umbraco.Core/Components/ComponentCollectionBuilder.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Components +{ + /// + /// Builds a . + /// + public class ComponentCollectionBuilder : OrderedCollectionBuilderBase + { + private const int LogThresholdMilliseconds = 100; + + private IProfilingLogger _logger; + + public ComponentCollectionBuilder() + { } + + protected override ComponentCollectionBuilder This => this; + + protected override IEnumerable CreateItems(IFactory factory) + { + _logger = factory.GetInstance(); + + using (_logger.DebugDuration($"Creating components. (log when >{LogThresholdMilliseconds}ms)", "Created.")) + { + return base.CreateItems(factory); + } + } + + protected override IComponent CreateItem(IFactory factory, Type itemType) + { + using (_logger.DebugDuration($"Creating {itemType.FullName}.", $"Created {itemType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + { + return base.CreateItem(factory, itemType); + } + } + } +} diff --git a/src/Umbraco.Core/Components/ComponentComposer.cs b/src/Umbraco.Core/Components/ComponentComposer.cs new file mode 100644 index 0000000000..792790c42f --- /dev/null +++ b/src/Umbraco.Core/Components/ComponentComposer.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Core.Components +{ + /// + /// Provides a base class for composers which compose a component. + /// + /// The type of the component + public abstract class ComponentComposer : IComposer + where TComponent : IComponent + { + /// + public virtual void Compose(Composition composition) + { + composition.Components().Append(); + } + + // note: thanks to this class, a component that does not compose anything can be + // registered with one line: + // public class MyComponentComposer : ComponentComposer { } + } +} diff --git a/src/Umbraco.Core/Components/ComposeAfterAttribute.cs b/src/Umbraco.Core/Components/ComposeAfterAttribute.cs new file mode 100644 index 0000000000..a8fdfaa92b --- /dev/null +++ b/src/Umbraco.Core/Components/ComposeAfterAttribute.cs @@ -0,0 +1,59 @@ +using System; + +namespace Umbraco.Core.Components +{ + /// + /// Indicates that a composer requires another composer. + /// + /// + /// 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. + /// 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). + /// 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). + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)] + public sealed class ComposeAfterAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The type of the required composer. + 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; + } + + /// + /// Initializes a new instance of the class. + /// + /// The type of the required composer. + /// A value indicating whether the requirement is weak. + public ComposeAfterAttribute(Type requiredType, bool weak) + : this(requiredType) + { + Weak = weak; + } + + /// + /// Gets the required type. + /// + public Type RequiredType { get; } + + /// + /// Gets a value indicating whether the requirement is weak. + /// + /// Returns true if the requirement is weak (requires the other composer if it + /// is enabled), false if the requirement is strong (requires the other composer to be + /// enabled), and null if unspecified, in which case it is strong for classes and weak for + /// interfaces. + public bool? Weak { get; } + } +} diff --git a/src/Umbraco.Core/Components/ComposeBeforeAttribute.cs b/src/Umbraco.Core/Components/ComposeBeforeAttribute.cs new file mode 100644 index 0000000000..17065d1676 --- /dev/null +++ b/src/Umbraco.Core/Components/ComposeBeforeAttribute.cs @@ -0,0 +1,40 @@ +using System; + +namespace Umbraco.Core.Components +{ + /// + /// Indicates that a component is required by another composer. + /// + /// + /// 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. + /// 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). + /// 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). + /// + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)] + public sealed class ComposeBeforeAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The type of the required composer. + 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; + } + + /// + /// Gets the required type. + /// + public Type RequiringType { get; } + } +} diff --git a/src/Umbraco.Core/Components/Composers.cs b/src/Umbraco.Core/Components/Composers.cs new file mode 100644 index 0000000000..1c836e9e5c --- /dev/null +++ b/src/Umbraco.Core/Components/Composers.cs @@ -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 + + /// + /// Handles the composers. + /// + public class Composers + { + private readonly Composition _composition; + private readonly IProfilingLogger _logger; + private readonly IEnumerable _composerTypes; + + private const int LogThresholdMilliseconds = 100; + + /// + /// Initializes a new instance of the class. + /// + /// The composition. + /// The composer types. + /// A profiling logger. + public Composers(Composition composition, IEnumerable 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; + } + + /// + /// Instantiates and composes the composers. + /// + public void Compose() + { + // make sure it is there + _composition.WithCollectionBuilder(); + + IEnumerable orderedComposerTypes; + + using (_logger.DebugDuration("Preparing composer types.", "Prepared composer types.")) + { + orderedComposerTypes = PrepareComposerTypes(); + } + + var composers = InstantiateComposers(orderedComposerTypes); + + using (_logger.DebugDuration($"Composing composers. (log when >{LogThresholdMilliseconds}ms)", "Composed composers.")) + { + foreach (var composer in composers) + { + var componentType = composer.GetType(); + using (_logger.DebugDuration($"Composing {componentType.FullName}.", $"Composed {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + { + composer.Compose(_composition); + } + } + } + } + + private IEnumerable 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(); + var minLevel = attr?.MinLevel ?? (x.Implements() ? 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>(); + 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(GetComposersReport(requirements)); + + // sort composers + var graph = new TopoGraph>>(kvp => kvp.Key, kvp => kvp.Value); + graph.AddItems(requirements); + List 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("Composer Report:\r\n{ComposerReport}", GetComposersReport(requirements)); + _logger.Error(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("Ordered Composers: {SortedComposerTypes}", sortedComposerTypes); + + return sortedComposerTypes; + } + + private static string GetComposersReport(Dictionary> 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()) + text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue + ? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")")) + : "")); + foreach (var attribute in type.GetCustomAttributes()) + text.AppendLine(" -< " + attribute.RequiringType); + foreach (var i in type.GetInterfaces()) + { + text.AppendLine(" : " + i.FullName); + foreach (var attribute in i.GetCustomAttributes()) + text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue + ? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")")) + : "")); + foreach (var attribute in i.GetCustomAttributes()) + 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 types) + { + var enabled = new Dictionary(); + + // 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()) + { + 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()) + { + 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 types, IDictionary> 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()) // those marking interfaces + .Concat(type.GetCustomAttributes()); // 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(); + 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(); + 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 types, IDictionary> 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()) // those marking interfaces + .Concat(type.GetCustomAttributes()); // 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(); + requirements[implem].Add(type); + } + } + // required by a class + else + { + if (types.Contains(attr.RequiringType)) + { + if (requirements[attr.RequiringType] == null) requirements[attr.RequiringType] = new List(); + requirements[attr.RequiringType].Add(type); + } + } + } + } + + private IEnumerable InstantiateComposers(IEnumerable types) + { + IComposer InstantiateComposer(Type type) + { + var ctor = type.GetConstructor(Array.Empty()); + if (ctor == null) + throw new InvalidOperationException($"Composer {type.FullName} does not have a parameter-less constructor."); + return (IComposer) ctor.Invoke(Array.Empty()); + } + + using (_logger.DebugDuration("Instantiating composers.", "Instantiated composers.")) + { + return types.Select(InstantiateComposer).ToArray(); + } + } + } +} diff --git a/src/Umbraco.Core/Components/Composition.cs b/src/Umbraco.Core/Components/Composition.cs index 671469c73a..53acbdb132 100644 --- a/src/Umbraco.Core/Components/Composition.cs +++ b/src/Umbraco.Core/Components/Composition.cs @@ -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. - public class Composition + public class Composition : IRegister { + private readonly Dictionary _builders = new Dictionary(); + private readonly Dictionary _uniques = new Dictionary(); + private readonly IRegister _register; + /// /// Initializes a new instance of the class. /// - /// A container. - /// The runtime level. - public Composition(IServiceContainer container, RuntimeLevel level) + /// A register. + /// A type loader. + /// A logger. + /// The runtime state. + public Composition(IRegister register, TypeLoader typeLoader, IProfilingLogger logger, IRuntimeState runtimeState) { - Container = container; - RuntimeLevel = level; + _register = register; + TypeLoader = typeLoader; + Logger = logger; + RuntimeState = runtimeState; + } + + #region Services + + /// + /// Gets the logger. + /// + public IProfilingLogger Logger { get; } + + /// + /// Gets the type loader. + /// + public TypeLoader TypeLoader { get; } + + /// + /// Gets the runtime state. + /// + public IRuntimeState RuntimeState { get; } + + #endregion + + #region IRegister + + /// + public object Concrete => _register.Concrete; + + /// + public void Register(Type serviceType, Lifetime lifetime = Lifetime.Transient) + => _register.Register(serviceType, lifetime); + + /// + public void Register(Type serviceType, Type implementingType, Lifetime lifetime = Lifetime.Transient) + => _register.Register(serviceType, implementingType, lifetime); + + /// + public void Register(Func factory, Lifetime lifetime = Lifetime.Transient) + => _register.Register(factory, lifetime); + + /// + public void RegisterInstance(Type serviceType, object instance) + => _register.RegisterInstance(serviceType, instance); + + /// + public void RegisterAuto(Type serviceBaseType) + => _register.RegisterAuto(serviceBaseType); + + /// + public void ConfigureForWeb() + => _register.ConfigureForWeb(); + + /// + 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(); } /// - /// Gets the container. + /// Gets a dictionary of action to execute when creating the factory. /// - /// Use with care! - public IServiceContainer Container { get; } + public Dictionary OnCreatingFactory { get; } = new Dictionary(); + + #endregion + + #region Unique /// - /// Gets the runtime level. + /// Registers a unique service. /// - public RuntimeLevel RuntimeLevel { get; } + /// Unique services have one single implementation, and a Singleton lifetime. + public void RegisterUnique(Type serviceType, Type implementingType) + => _uniques[serviceType] = new Unique(serviceType, implementingType); + + /// + /// Registers a unique service. + /// + /// Unique services have one single implementation, and a Singleton lifetime. + public void RegisterUnique(Type serviceType, object instance) + => _uniques[serviceType] = new Unique(serviceType, instance); + + /// + /// Registers a unique service. + /// + /// Unique services have one single implementation, and a Singleton lifetime. + public void RegisterUnique(Func factory) + => _uniques[typeof(TService)] = new Unique(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 : Unique + { + private readonly Func _factory; + + public Unique(Func factory) + : base(typeof(TService)) + { + _factory = factory; + } + + public override void RegisterWith(IRegister register) + { + register.Register(_factory, Lifetime.Singleton); + } + } + + #endregion + + #region Collection Builders + + /// + /// Gets a collection builder (and registers the collection). + /// + /// The type of the collection builder. + /// The collection builder. + public TBuilder WithCollectionBuilder() + 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 } } diff --git a/src/Umbraco.Core/Components/CompositionExtensions.cs b/src/Umbraco.Core/Components/CompositionExtensions.cs index 7e94e4dc2b..93d190d17e 100644 --- a/src/Umbraco.Core/Components/CompositionExtensions.cs +++ b/src/Umbraco.Core/Components/CompositionExtensions.cs @@ -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 /// public static class CompositionExtensions { + #region FileSystems + + /// + /// Registers a filesystem. + /// + /// The type of the filesystem. + /// The implementing type. + /// The composition. + /// A factory method creating the supporting filesystem. + /// The register. + public static void RegisterFileSystem(this Composition composition, Func supportingFileSystemFactory) + where TImplementing : FileSystemWrapper, TFileSystem + { + composition.RegisterUnique(factory => + { + var fileSystems = factory.GetInstance(); + return fileSystems.GetFileSystem(supportingFileSystemFactory(factory)); + }); + } + + /// + /// Registers a filesystem. + /// + /// The type of the filesystem. + /// The composition. + /// A factory method creating the supporting filesystem. + /// The register. + public static void RegisterFileSystem(this Composition composition, Func supportingFileSystemFactory) + where TFileSystem : FileSystemWrapper + { + composition.RegisterUnique(factory => + { + var fileSystems = factory.GetInstance(); + return fileSystems.GetFileSystem(supportingFileSystemFactory(factory)); + }); + } + + #endregion + #region Collection Builders /// @@ -26,60 +64,66 @@ namespace Umbraco.Core.Components /// /// The composition. public static CacheRefresherCollectionBuilder CacheRefreshers(this Composition composition) - => composition.Container.GetInstance(); + => composition.WithCollectionBuilder(); /// /// Gets the mappers collection builder. /// /// The composition. public static MapperCollectionBuilder Mappers(this Composition composition) - => composition.Container.GetInstance(); + => composition.WithCollectionBuilder(); /// /// Gets the package actions collection builder. /// /// The composition. internal static PackageActionCollectionBuilder PackageActions(this Composition composition) - => composition.Container.GetInstance(); + => composition.WithCollectionBuilder(); /// /// Gets the data editor collection builder. /// /// The composition. public static DataEditorCollectionBuilder DataEditors(this Composition composition) - => composition.Container.GetInstance(); + => composition.WithCollectionBuilder(); /// /// Gets the property value converters collection builder. /// /// The composition. public static PropertyValueConverterCollectionBuilder PropertyValueConverters(this Composition composition) - => composition.Container.GetInstance(); + => composition.WithCollectionBuilder(); /// /// Gets the url segment providers collection builder. /// /// The composition. public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this Composition composition) - => composition.Container.GetInstance(); + => composition.WithCollectionBuilder(); /// /// Gets the validators collection builder. /// /// The composition. internal static ManifestValueValidatorCollectionBuilder Validators(this Composition composition) - => composition.Container.GetInstance(); + => composition.WithCollectionBuilder(); /// /// Gets the post-migrations collection builder. /// /// The composition. internal static PostMigrationCollectionBuilder PostMigrations(this Composition composition) - => composition.Container.GetInstance(); + => composition.WithCollectionBuilder(); + + /// + /// Gets the components collection builder. + /// + public static ComponentCollectionBuilder Components(this Composition composition) + => composition.WithCollectionBuilder(); #endregion - #region Singleton + #region Uniques /// /// Sets the culture dictionary factory. @@ -89,7 +133,7 @@ namespace Umbraco.Core.Components public static void SetCultureDictionaryFactory(this Composition composition) where T : ICultureDictionaryFactory { - composition.Container.RegisterSingleton(); + composition.RegisterUnique(); } /// @@ -97,9 +141,9 @@ namespace Umbraco.Core.Components /// /// The composition. /// A function creating a culture dictionary factory. - public static void SetCultureDictionaryFactory(this Composition composition, Func factory) + public static void SetCultureDictionaryFactory(this Composition composition, Func factory) { - composition.Container.RegisterSingleton(factory); + composition.RegisterUnique(factory); } /// @@ -109,7 +153,7 @@ namespace Umbraco.Core.Components /// A factory. public static void SetCultureDictionaryFactory(this Composition composition, ICultureDictionaryFactory factory) { - composition.Container.RegisterSingleton(_ => factory); + composition.RegisterUnique(_ => factory); } /// @@ -120,7 +164,7 @@ namespace Umbraco.Core.Components public static void SetPublishedContentModelFactory(this Composition composition) where T : IPublishedModelFactory { - composition.Container.RegisterSingleton(); + composition.RegisterUnique(); } /// @@ -128,9 +172,9 @@ namespace Umbraco.Core.Components /// /// The composition. /// A function creating a published content model factory. - public static void SetPublishedContentModelFactory(this Composition composition, Func factory) + public static void SetPublishedContentModelFactory(this Composition composition, Func factory) { - composition.Container.RegisterSingleton(factory); + composition.RegisterUnique(factory); } /// @@ -140,7 +184,7 @@ namespace Umbraco.Core.Components /// A published content model factory. public static void SetPublishedContentModelFactory(this Composition composition, IPublishedModelFactory factory) { - composition.Container.RegisterSingleton(_ => factory); + composition.RegisterUnique(_ => factory); } /// @@ -151,7 +195,7 @@ namespace Umbraco.Core.Components public static void SetServerRegistrar(this Composition composition) where T : IServerRegistrar { - composition.Container.RegisterSingleton(); + composition.RegisterUnique(); } /// @@ -159,9 +203,9 @@ namespace Umbraco.Core.Components /// /// The composition. /// A function creating a server registar. - public static void SetServerRegistrar(this Composition composition, Func factory) + public static void SetServerRegistrar(this Composition composition, Func factory) { - composition.Container.RegisterSingleton(factory); + composition.RegisterUnique(factory); } /// @@ -171,7 +215,7 @@ namespace Umbraco.Core.Components /// A server registrar. public static void SetServerRegistrar(this Composition composition, IServerRegistrar registrar) { - composition.Container.RegisterSingleton(_ => registrar); + composition.RegisterUnique(_ => registrar); } /// @@ -182,7 +226,7 @@ namespace Umbraco.Core.Components public static void SetServerMessenger(this Composition composition) where T : IServerMessenger { - composition.Container.RegisterSingleton(); + composition.RegisterUnique(); } /// @@ -190,9 +234,9 @@ namespace Umbraco.Core.Components /// /// The composition. /// A function creating a server messenger. - public static void SetServerMessenger(this Composition composition, Func factory) + public static void SetServerMessenger(this Composition composition, Func factory) { - composition.Container.RegisterSingleton(factory); + composition.RegisterUnique(factory); } /// @@ -202,7 +246,7 @@ namespace Umbraco.Core.Components /// A server messenger. public static void SetServerMessenger(this Composition composition, IServerMessenger registrar) { - composition.Container.RegisterSingleton(_ => registrar); + composition.RegisterUnique(_ => registrar); } /// @@ -213,7 +257,7 @@ namespace Umbraco.Core.Components public static void SetShortStringHelper(this Composition composition) where T : IShortStringHelper { - composition.Container.RegisterSingleton(); + composition.RegisterUnique(); } /// @@ -221,9 +265,9 @@ namespace Umbraco.Core.Components /// /// The composition. /// A function creating a short string helper. - public static void SetShortStringHelper(this Composition composition, Func factory) + public static void SetShortStringHelper(this Composition composition, Func factory) { - composition.Container.RegisterSingleton(factory); + composition.RegisterUnique(factory); } /// @@ -233,7 +277,7 @@ namespace Umbraco.Core.Components /// A short string helper. public static void SetShortStringHelper(this Composition composition, IShortStringHelper helper) { - composition.Container.RegisterSingleton(_ => helper); + composition.RegisterUnique(_ => helper); } #endregion diff --git a/src/Umbraco.Core/Components/DisableAttribute.cs b/src/Umbraco.Core/Components/DisableAttribute.cs new file mode 100644 index 0000000000..f9a7249b89 --- /dev/null +++ b/src/Umbraco.Core/Components/DisableAttribute.cs @@ -0,0 +1,38 @@ +using System; + +namespace Umbraco.Core.Components +{ + /// + /// Indicates that a composer should be disabled. + /// + /// + /// If a type is specified, disables the composer of that type, else disables the composer marked with the attribute. + /// This attribute is *not* inherited. + /// This attribute applies to classes only, it is not possible to enable/disable interfaces. + /// 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. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class DisableAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + public DisableAttribute() + { } + + /// + /// Initializes a new instance of the class. + /// + public DisableAttribute(Type disabledType) + { + DisabledType = disabledType; + } + + /// + /// Gets the disabled type, or null if it is the composer marked with the attribute. + /// + public Type DisabledType { get; } + } +} diff --git a/src/Umbraco.Core/Components/DisableComponentAttribute.cs b/src/Umbraco.Core/Components/DisableComponentAttribute.cs deleted file mode 100644 index f7ff71e119..0000000000 --- a/src/Umbraco.Core/Components/DisableComponentAttribute.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; - -namespace Umbraco.Core.Components -{ - /// - /// Indicates that a component should be disabled. - /// - /// - /// If a type is specified, disables the component of that type, else disables the component marked with the attribute. - /// This attribute is *not* inherited. - /// This attribute applies to classes only, it is not possible to enable/disable interfaces. - /// 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. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - public class DisableComponentAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - public DisableComponentAttribute() - { } - - /// - /// Initializes a new instance of the class. - /// - public DisableComponentAttribute(Type disabledType) - { - DisabledType = disabledType; - } - - /// - /// Gets the disabled type, or null if it is the component marked with the attribute. - /// - public Type DisabledType { get; } - } -} diff --git a/src/Umbraco.Core/Components/EnableAttribute.cs b/src/Umbraco.Core/Components/EnableAttribute.cs new file mode 100644 index 0000000000..edf3cbdc2e --- /dev/null +++ b/src/Umbraco.Core/Components/EnableAttribute.cs @@ -0,0 +1,38 @@ +using System; + +namespace Umbraco.Core.Components +{ + /// + /// Indicates that a composer should be enabled. + /// + /// + /// If a type is specified, enables the composer of that type, else enables the composer marked with the attribute. + /// This attribute is *not* inherited. + /// This attribute applies to classes only, it is not possible to enable/disable interfaces. + /// 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. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class EnableAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + public EnableAttribute() + { } + + /// + /// Initializes a new instance of the class. + /// + public EnableAttribute(Type enabledType) + { + EnabledType = enabledType; + } + + /// + /// Gets the enabled type, or null if it is the composer marked with the attribute. + /// + public Type EnabledType { get; } + } +} diff --git a/src/Umbraco.Core/Components/EnableComponentAttribute.cs b/src/Umbraco.Core/Components/EnableComponentAttribute.cs deleted file mode 100644 index fa76dc2404..0000000000 --- a/src/Umbraco.Core/Components/EnableComponentAttribute.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; - -namespace Umbraco.Core.Components -{ - /// - /// Indicates that a component should be enabled. - /// - /// - /// If a type is specified, enables the component of that type, else enables the component marked with the attribute. - /// This attribute is *not* inherited. - /// This attribute applies to classes only, it is not possible to enable/disable interfaces. - /// 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. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - public class EnableComponentAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - public EnableComponentAttribute() - { } - - /// - /// Initializes a new instance of the class. - /// - public EnableComponentAttribute(Type enabledType) - { - EnabledType = enabledType; - } - - /// - /// Gets the enabled type, or null if it is the component marked with the attribute. - /// - public Type EnabledType { get; } - } -} diff --git a/src/Umbraco.Core/Components/IComponent.cs b/src/Umbraco.Core/Components/IComponent.cs new file mode 100644 index 0000000000..28ca539387 --- /dev/null +++ b/src/Umbraco.Core/Components/IComponent.cs @@ -0,0 +1,15 @@ +namespace Umbraco.Core.Components +{ + /// + /// Represents a component. + /// + /// + /// Components are created by DI and therefore must have a public constructor. + /// All components which are also disposable, will be disposed in reverse + /// order, when Umbraco terminates. + /// The Dispose method may be invoked more than once, and components + /// should ensure they support this. + /// + public interface IComponent + { } +} diff --git a/src/Umbraco.Core/Components/IComposer.cs b/src/Umbraco.Core/Components/IComposer.cs new file mode 100644 index 0000000000..ce02aa4f13 --- /dev/null +++ b/src/Umbraco.Core/Components/IComposer.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Components +{ + /// + /// Represents a composer. + /// + public interface IComposer : IDiscoverable + { + /// + /// Compose. + /// + /// + void Compose(Composition composition); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Components/ICoreComposer.cs b/src/Umbraco.Core/Components/ICoreComposer.cs new file mode 100644 index 0000000000..94aa9953a9 --- /dev/null +++ b/src/Umbraco.Core/Components/ICoreComposer.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Core.Components +{ + /// + /// Represents a core . + /// + /// + /// All core composers are required by (compose before) all user composers, + /// and require (compose after) all runtime composers. + /// + [ComposeAfter(typeof(IRuntimeComposer))] + public interface ICoreComposer : IComposer + { } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Components/IRuntimeComponent.cs b/src/Umbraco.Core/Components/IRuntimeComponent.cs deleted file mode 100644 index 7f89d21e87..0000000000 --- a/src/Umbraco.Core/Components/IRuntimeComponent.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Umbraco.Core.Components -{ - public interface IRuntimeComponent : IUmbracoComponent - { } -} diff --git a/src/Umbraco.Core/Components/IRuntimeComposer.cs b/src/Umbraco.Core/Components/IRuntimeComposer.cs new file mode 100644 index 0000000000..4b8253ee6c --- /dev/null +++ b/src/Umbraco.Core/Components/IRuntimeComposer.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Core.Components +{ + /// + /// Represents a runtime . + /// + /// + /// All runtime composers are required by (compose before) all core composers + /// + public interface IRuntimeComposer : IComposer + { } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Components/IUmbracoComponent.cs b/src/Umbraco.Core/Components/IUmbracoComponent.cs deleted file mode 100644 index d25b97c270..0000000000 --- a/src/Umbraco.Core/Components/IUmbracoComponent.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Umbraco.Core.Composing; - -namespace Umbraco.Core.Components -{ - /// - /// Represents an Umbraco component. - /// - public interface IUmbracoComponent : IDiscoverable - { - /// - /// Composes the component. - /// - /// The composition. - void Compose(Composition composition); - - /// - /// Terminates the component. - /// - void Terminate(); - } -} diff --git a/src/Umbraco.Core/Components/IUmbracoCoreComponent.cs b/src/Umbraco.Core/Components/IUmbracoCoreComponent.cs deleted file mode 100644 index 28ff286da3..0000000000 --- a/src/Umbraco.Core/Components/IUmbracoCoreComponent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Umbraco.Core.Components -{ - [RequireComponent(typeof(IRuntimeComponent))] - public interface IUmbracoCoreComponent : IUmbracoComponent - { } -} diff --git a/src/Umbraco.Core/Components/IUmbracoUserComponent.cs b/src/Umbraco.Core/Components/IUmbracoUserComponent.cs deleted file mode 100644 index 61a07cfd48..0000000000 --- a/src/Umbraco.Core/Components/IUmbracoUserComponent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Umbraco.Core.Components -{ - [RequireComponent(typeof(UmbracoCoreComponent))] - public interface IUmbracoUserComponent : IUmbracoComponent - { } -} diff --git a/src/Umbraco.Core/Components/IUserComposer.cs b/src/Umbraco.Core/Components/IUserComposer.cs new file mode 100644 index 0000000000..59e0023635 --- /dev/null +++ b/src/Umbraco.Core/Components/IUserComposer.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core.Components +{ + /// + /// Represents a user . + /// + /// + /// All user composers require (compose after) all core composers. + /// + [ComposeAfter(typeof(ICoreComposer))] + public interface IUserComposer : IComposer + { } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Components/ManifestWatcherComponent.cs b/src/Umbraco.Core/Components/ManifestWatcherComponent.cs index 0a8d9ccd52..a96dd516e7 100644 --- a/src/Umbraco.Core/Components/ManifestWatcherComponent.cs +++ b/src/Umbraco.Core/Components/ManifestWatcherComponent.cs @@ -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; } } diff --git a/src/Umbraco.Core/Components/ManifestWatcherComposer.cs b/src/Umbraco.Core/Components/ManifestWatcherComposer.cs new file mode 100644 index 0000000000..b08680156b --- /dev/null +++ b/src/Umbraco.Core/Components/ManifestWatcherComposer.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Core.Components +{ + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public class ManifestWatcherComposer : ComponentComposer, ICoreComposer + { } +} diff --git a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs index bc66dccd31..41ae3c70dc 100644 --- a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs +++ b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs @@ -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; } diff --git a/src/Umbraco.Core/Components/RelateOnCopyComposer.cs b/src/Umbraco.Core/Components/RelateOnCopyComposer.cs new file mode 100644 index 0000000000..f5e9423edd --- /dev/null +++ b/src/Umbraco.Core/Components/RelateOnCopyComposer.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Core.Components +{ + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public sealed class RelateOnCopyComposer : ComponentComposer, ICoreComposer + { } +} diff --git a/src/Umbraco.Core/Components/RelateOnTrashComponent.cs b/src/Umbraco.Core/Components/RelateOnTrashComponent.cs index 8bcce50c68..93803d4fae 100644 --- a/src/Umbraco.Core/Components/RelateOnTrashComponent.cs +++ b/src/Umbraco.Core/Components/RelateOnTrashComponent.cs @@ -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; diff --git a/src/Umbraco.Core/Components/RelateOnTrashComposer.cs b/src/Umbraco.Core/Components/RelateOnTrashComposer.cs new file mode 100644 index 0000000000..5d89bc0e37 --- /dev/null +++ b/src/Umbraco.Core/Components/RelateOnTrashComposer.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Core.Components +{ + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public sealed class RelateOnTrashComposer : ComponentComposer, ICoreComposer + { } +} diff --git a/src/Umbraco.Core/Components/RequireComponentAttribute.cs b/src/Umbraco.Core/Components/RequireComponentAttribute.cs deleted file mode 100644 index 2d53413f99..0000000000 --- a/src/Umbraco.Core/Components/RequireComponentAttribute.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; - -namespace Umbraco.Core.Components -{ - /// - /// Indicates that a component requires another component. - /// - /// - /// 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. - /// 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). - /// 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). - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)] - public class RequireComponentAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// The type of the required component. - 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; - } - - /// - /// Initializes a new instance of the class. - /// - /// The type of the required component. - /// A value indicating whether the requirement is weak. - public RequireComponentAttribute(Type requiredType, bool weak) - : this(requiredType) - { - Weak = weak; - } - - /// - /// Gets the required type. - /// - public Type RequiredType { get; } - - /// - /// Gets a value indicating whether the requirement is weak. - /// - /// Returns true if the requirement is weak (requires the other component if it - /// is enabled), false if the requirement is strong (requires the other component to be - /// enabled), and null if unspecified, in which case it is strong for classes and weak for - /// interfaces. - public bool? Weak { get; } - } -} diff --git a/src/Umbraco.Core/Components/RequiredComponentAttribute.cs b/src/Umbraco.Core/Components/RequiredComponentAttribute.cs deleted file mode 100644 index 7895445179..0000000000 --- a/src/Umbraco.Core/Components/RequiredComponentAttribute.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; - -namespace Umbraco.Core.Components -{ - /// - /// Indicates that a component is required by another component. - /// - /// - /// fixme - /// 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. - /// 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). - /// 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). - /// - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)] - public class RequiredComponentAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - /// The type of the required component. - 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; - } - - /// - /// Gets the required type. - /// - public Type RequiringType { get; } - } -} diff --git a/src/Umbraco.Core/Components/UmbracoComponentBase.cs b/src/Umbraco.Core/Components/UmbracoComponentBase.cs deleted file mode 100644 index 476c4c6d59..0000000000 --- a/src/Umbraco.Core/Components/UmbracoComponentBase.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Umbraco.Core.Components -{ - /// - /// Provides a base class for implementations. - /// - public abstract class UmbracoComponentBase : IUmbracoComponent - { - /// - public virtual void Compose(Composition composition) - { } - - /// - public virtual void Terminate() - { } - } -} diff --git a/src/Umbraco.Core/Components/UmbracoCoreComponent.cs b/src/Umbraco.Core/Components/UmbracoCoreComponent.cs deleted file mode 100644 index 9f6709c494..0000000000 --- a/src/Umbraco.Core/Components/UmbracoCoreComponent.cs +++ /dev/null @@ -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 - { } -} diff --git a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs index 3fac2d3255..7633f6b001 100644 --- a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs @@ -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 _types = new List(); private readonly object _locker = new object(); - private Func, TCollection> _collectionCtor; - private ServiceRegistration[] _registrations; - - /// - /// Initializes a new instance of the - /// class with a service container. - /// - /// A service container. - protected CollectionBuilderBase(IServiceContainer container) - { - Container = container; - // ReSharper disable once DoNotCallOverridableMethodsInConstructor - Initialize(); - } - - /// - /// Gets the service container. - /// - protected IServiceContainer Container { get; } + private Type[] _registeredTypes; /// /// Gets the internal list of types as an IEnumerable (immutable). /// public IEnumerable GetTypes() => _types; - /// - /// Initializes a new instance of the builder. - /// - /// This is called by the constructor and, by default, registers the - /// collection automatically. - protected virtual void Initialize() + /// + public virtual void RegisterWith(IRegister register) { - // compile the auto-collection constructor - var argType = typeof(IEnumerable); - 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, 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(); - 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); } /// /// Gets the collection lifetime. /// - /// Return null for transient collections. - protected virtual ILifetime CollectionLifetime => new PerContainerLifetime(); + protected virtual Lifetime CollectionLifetime => Lifetime.Singleton; /// /// 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; } } /// /// Creates the collection items. /// - /// The arguments. /// The collection items. - protected virtual IEnumerable CreateItems(params object[] args) + protected virtual IEnumerable 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 } + /// + /// Creates a collection item. + /// + protected virtual TItem CreateItem(IFactory factory, Type itemType) + => (TItem) factory.GetInstance(itemType); + /// /// Creates a collection. /// /// A collection. /// Creates a new collection each time it is invoked. - 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(CreateItems(factory)); } protected Type EnsureType(Type type, string action) diff --git a/src/Umbraco.Core/Composing/Composers/ConfigurationComposer.cs b/src/Umbraco.Core/Composing/Composers/ConfigurationComposer.cs new file mode 100644 index 0000000000..7217f551e9 --- /dev/null +++ b/src/Umbraco.Core/Composing/Composers/ConfigurationComposer.cs @@ -0,0 +1,30 @@ +using Umbraco.Core.Components; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Core.Composing.Composers +{ + /// + /// Sets up IoC container for Umbraco configuration classes + /// + public static class ConfigurationComposer + { + public static Composition ComposeConfiguration(this Composition composition) + { + composition.RegisterUnique(); + composition.RegisterUnique(factory => factory.GetInstance().Umbraco()); + composition.RegisterUnique(factory => factory.GetInstance().Content); + composition.RegisterUnique(factory => factory.GetInstance().Templates); + composition.RegisterUnique(factory => factory.GetInstance().RequestHandler); + composition.RegisterUnique(factory => factory.GetInstance().Security); + composition.RegisterUnique(factory => factory.GetInstance().Global()); + composition.RegisterUnique(factory => factory.GetInstance().Dashboards()); + composition.RegisterUnique(factory => factory.GetInstance().HealthChecks()); + composition.RegisterUnique(factory => factory.GetInstance().Grids()); + + // fixme - other sections we need to add? + + return composition; + } + } +} diff --git a/src/Umbraco.Core/Composing/Composers/CoreMappingProfilesComposer.cs b/src/Umbraco.Core/Composing/Composers/CoreMappingProfilesComposer.cs new file mode 100644 index 0000000000..0274b8f1a9 --- /dev/null +++ b/src/Umbraco.Core/Composing/Composers/CoreMappingProfilesComposer.cs @@ -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(); + return composition; + } + } +} diff --git a/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs b/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs new file mode 100644 index 0000000000..f1fb095406 --- /dev/null +++ b/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs @@ -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( + * 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( + * 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( + * 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(factory)); + + // register IFileSystems, which gives access too all filesystems + composition.RegisterUnique(factory => factory.GetInstance()); + + // register the scheme for media paths + composition.RegisterUnique(); + + // register the IMediaFileSystem implementation with a supporting filesystem + composition.RegisterFileSystem( + factory => new PhysicalFileSystem("~/media")); + + return composition; + } + } +} diff --git a/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs b/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs new file mode 100644 index 0000000000..62b92081c1 --- /dev/null +++ b/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs @@ -0,0 +1,54 @@ +using Umbraco.Core.Components; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.Repositories.Implement; + +namespace Umbraco.Core.Composing.Composers +{ + /// + /// Composes repositories. + /// + public static class RepositoriesComposer + { + public static Composition ComposeRepositories(this Composition composition) + { + // repositories + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + + return composition; + } + } +} diff --git a/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs b/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs new file mode 100644 index 0000000000..1b77aaa7d6 --- /dev/null +++ b/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs @@ -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(); + + // register the service context + composition.RegisterUnique(); + + // register the special idk map + composition.RegisterUnique(); + + // register the services + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.RegisterUnique(); + composition.Register(SourcesFactory); + composition.RegisterUnique(factory => new LocalizedTextService( + factory.GetInstance>(), + factory.GetInstance())); + + //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(); + composition.RegisterUnique(); + + 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() + : 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() + : 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(), + container.GetInstance().RuntimeCache, + mainLangFolder, + pluginLangFolders.Concat(userLangFolders)); + } + } +} diff --git a/src/Umbraco.Core/Composing/CompositionExtensions.cs b/src/Umbraco.Core/Composing/CompositionExtensions.cs new file mode 100644 index 0000000000..cfc465b59d --- /dev/null +++ b/src/Umbraco.Core/Composing/CompositionExtensions.cs @@ -0,0 +1,62 @@ +using Umbraco.Core.Cache; +using Umbraco.Core.Components; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; + +namespace Umbraco.Core.Composing +{ + /// + /// Provides extension methods to the class. + /// + public static class CompositionExtensions + { + #region Essentials + + /// + /// Registers essential services. + /// + 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().RuntimeCache); + composition.RegisterUnique(databaseFactory); + composition.RegisterUnique(factory => factory.GetInstance().SqlContext); + composition.RegisterUnique(typeLoader); + composition.RegisterUnique(state); + } + + #endregion + + #region Unique + + /// + /// Registers a unique service as its own implementation. + /// + public static void RegisterUnique(this Composition composition) + => composition.RegisterUnique(typeof(TService), typeof(TService)); + + /// + /// Registers a unique service with an implementation type. + /// + public static void RegisterUnique(this Composition composition) + => composition.RegisterUnique(typeof(TService), typeof(TImplementing)); + + /// + /// Registers a unique service with an implementing instance. + /// + public static void RegisterUnique(this Composition composition, TService instance) + => composition.RegisterUnique(typeof(TService), instance); + + #endregion + } +} diff --git a/src/Umbraco.Core/Composing/CompositionRoots/ConfigurationCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/ConfigurationCompositionRoot.cs deleted file mode 100644 index 80de6d1c35..0000000000 --- a/src/Umbraco.Core/Composing/CompositionRoots/ConfigurationCompositionRoot.cs +++ /dev/null @@ -1,24 +0,0 @@ -using LightInject; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; - -namespace Umbraco.Core.Composing.CompositionRoots -{ - /// - /// Sets up IoC container for Umbraco configuration classes - /// - public sealed class ConfigurationCompositionRoot : ICompositionRoot - { - public void Compose(IServiceRegistry container) - { - container.Register(factory => UmbracoConfig.For.UmbracoSettings()); - container.Register(factory => factory.GetInstance().Content); - container.Register(factory => factory.GetInstance().Templates); - container.Register(factory => factory.GetInstance().RequestHandler); - container.Register(factory => UmbracoConfig.For.GlobalSettings()); - container.Register(factory => UmbracoConfig.For.DashboardSettings()); - - // fixme - other sections we need to add? - } - } -} diff --git a/src/Umbraco.Core/Composing/CompositionRoots/CoreMappingProfilesCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/CoreMappingProfilesCompositionRoot.cs deleted file mode 100644 index 6b55a4af7e..0000000000 --- a/src/Umbraco.Core/Composing/CompositionRoots/CoreMappingProfilesCompositionRoot.cs +++ /dev/null @@ -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(); - } - } -} diff --git a/src/Umbraco.Core/Composing/CompositionRoots/RepositoryCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/RepositoryCompositionRoot.cs deleted file mode 100644 index 9c36bf5cec..0000000000 --- a/src/Umbraco.Core/Composing/CompositionRoots/RepositoryCompositionRoot.cs +++ /dev/null @@ -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 -{ - /// - /// Sets the IoC container for the umbraco data layer/repositories/sql/database/etc... - /// - 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(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - - // repositories that depend on a filesystem - // these have an annotated ctor parameter to pick the right file system - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - } - } -} diff --git a/src/Umbraco.Core/Composing/CompositionRoots/ServicesCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/ServicesCompositionRoot.cs deleted file mode 100644 index 92b8139a04..0000000000 --- a/src/Umbraco.Core/Composing/CompositionRoots/ServicesCompositionRoot.cs +++ /dev/null @@ -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(); - - // register the service context - container.RegisterSingleton(); - - // register the special idk map - container.RegisterSingleton(); - - // register the services - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.Register(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() - : 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() - : 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(), - factory.GetInstance().RuntimeCache, - mainLangFolder, - pluginLangFolders.Concat(userLangFolders)); - }); - container.RegisterSingleton(factory => new LocalizedTextService( - factory.GetInstance>(), - factory.GetInstance())); - - //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(); - container.RegisterSingleton(); - } - } -} diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs index 9515398236..6c093dfc2c 100644 --- a/src/Umbraco.Core/Composing/Current.cs +++ b/src/Umbraco.Core/Composing/Current.cs @@ -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. /// /// - /// This class is initialized with the container via LightInjectExtensions.ConfigureUmbracoCore, + /// This class is initialized with the container in UmbracoApplicationBase, /// right after the container is created in UmbracoApplicationBase.HandleApplicationStart. /// Obviously, this is a service locator, which some may consider an anti-pattern. And yet, /// practically, it works. /// 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; /// - /// Gets or sets the DI container. + /// Gets or sets the factory. /// - 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() - ?? new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(UmbracoConfig.For.UmbracoSettings()))); + => _shortStringHelper ?? (_shortStringHelper = _factory?.TryGetInstance() + ?? new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(Config.Umbraco()))); public static ILogger Logger - => _logger ?? (_logger = _container?.TryGetInstance() + => _logger ?? (_logger = _factory?.TryGetInstance() ?? new DebugDiagnosticsLogger()); public static IProfiler Profiler - => _profiler ?? (_profiler = _container?.TryGetInstance() + => _profiler ?? (_profiler = _factory?.TryGetInstance() ?? new LogProfiler(Logger)); - public static ProfilingLogger ProfilingLogger - => _profilingLogger ?? (_profilingLogger = _container?.TryGetInstance()) + public static IProfilingLogger ProfilingLogger + => _profilingLogger ?? (_profilingLogger = _factory?.TryGetInstance()) ?? new ProfilingLogger(Logger, Profiler); public static IRuntimeState RuntimeState - => Container.GetInstance(); + => Factory.GetInstance(); public static TypeLoader TypeLoader - => Container.GetInstance(); + => Factory.GetInstance(); - public static FileSystems FileSystems - => Container.GetInstance(); + public static UmbracoConfig Config + => _config ?? (_config = _factory?.TryGetInstance() + ?? new UmbracoConfig(Logger, _factory?.TryGetInstance(), _factory?.TryGetInstance())); + + public static IFileSystems FileSystems + => Factory.GetInstance(); + + public static IMediaFileSystem MediaFileSystem + => Factory.GetInstance(); public static UrlSegmentProviderCollection UrlSegmentProviders - => Container.GetInstance(); + => Factory.GetInstance(); public static CacheRefresherCollection CacheRefreshers - => Container.GetInstance(); + => Factory.GetInstance(); public static DataEditorCollection DataEditors - => Container.GetInstance(); + => Factory.GetInstance(); public static PropertyEditorCollection PropertyEditors - => Container.GetInstance(); + => Factory.GetInstance(); public static ParameterEditorCollection ParameterEditors - => Container.GetInstance(); + => Factory.GetInstance(); internal static ManifestValueValidatorCollection ManifestValidators - => Container.GetInstance(); + => Factory.GetInstance(); internal static PackageActionCollection PackageActions - => Container.GetInstance(); + => Factory.GetInstance(); internal static PropertyValueConverterCollection PropertyValueConverters - => Container.GetInstance(); + => Factory.GetInstance(); internal static IPublishedModelFactory PublishedModelFactory - => Container.GetInstance(); + => Factory.GetInstance(); public static IServerMessenger ServerMessenger - => Container.GetInstance(); + => Factory.GetInstance(); public static IServerRegistrar ServerRegistrar - => Container.GetInstance(); + => Factory.GetInstance(); public static ICultureDictionaryFactory CultureDictionaryFactory - => Container.GetInstance(); + => Factory.GetInstance(); public static CacheHelper ApplicationCache - => Container.GetInstance(); + => Factory.GetInstance(); public static ServiceContext Services - => Container.GetInstance(); + => Factory.GetInstance(); public static IScopeProvider ScopeProvider - => Container.GetInstance(); + => Factory.GetInstance(); public static ISqlContext SqlContext - => Container.GetInstance(); + => Factory.GetInstance(); public static IPublishedContentTypeFactory PublishedContentTypeFactory - => Container.GetInstance(); + => Factory.GetInstance(); public static IPublishedValueFallback PublishedValueFallback - => _publishedValueFallback ?? Container.GetInstance() ?? new NoopPublishedValueFallback(); + => _publishedValueFallback ?? Factory.GetInstance() ?? new NoopPublishedValueFallback(); public static IVariationContextAccessor VariationContextAccessor - => Container.GetInstance(); + => Factory.GetInstance(); #endregion } diff --git a/src/Umbraco.Core/Composing/FactoryExtensions.cs b/src/Umbraco.Core/Composing/FactoryExtensions.cs new file mode 100644 index 0000000000..2640b7f7e6 --- /dev/null +++ b/src/Umbraco.Core/Composing/FactoryExtensions.cs @@ -0,0 +1,87 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + /// + /// Provides extension methods to the class. + /// + public static class FactoryExtensions + { + /// + /// Gets an instance of a service. + /// + /// The type of the service. + /// The factory. + /// An instance of the specified type. + /// Throws an exception if the factory failed to get an instance of the specified type. + public static T GetInstance(this IFactory factory) + => (T)factory.GetInstance(typeof(T)); + + /// + /// Tries to get an instance of a service. + /// + /// The type of the service. + /// An instance of the specified type, or null. + /// 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. + public static T TryGetInstance(this IFactory factory) + => (T)factory.TryGetInstance(typeof(T)); + + /// + /// Creates an instance with arguments. + /// + /// The type of the instance. + /// The factory. + /// Arguments. + /// An instance of the specified type. + /// + /// Throws an exception if the factory failed to get an instance of the specified type. + /// The arguments are used as dependencies by the factory. + /// + public static T CreateInstance(this IFactory factory, params object[] args) + => (T)factory.CreateInstance(typeof(T), args); + + /// + /// Creates an instance of a service, with arguments. + /// + /// + /// The type of the instance. + /// Named arguments. + /// An instance of the specified type. + /// + /// The instance type does not need to be registered into the factory. + /// The arguments are used as dependencies by the factory. Other dependencies + /// are retrieved from the factory. + /// + 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); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Composing/ICollectionBuilder.cs b/src/Umbraco.Core/Composing/ICollectionBuilder.cs index 5efc03c9ac..84ff3ba747 100644 --- a/src/Umbraco.Core/Composing/ICollectionBuilder.cs +++ b/src/Umbraco.Core/Composing/ICollectionBuilder.cs @@ -1,11 +1,23 @@ namespace Umbraco.Core.Composing { + /// + /// Represents a collection builder. + /// + public interface ICollectionBuilder + { + /// + /// Registers the builder so it can build the collection, by + /// registering the collection and the types. + /// + void RegisterWith(IRegister register); + } + /// /// Represents a collection builder. /// /// The type of the collection. /// The type of the items. - public interface ICollectionBuilder + public interface ICollectionBuilder : ICollectionBuilder where TCollection : IBuilderCollection { /// @@ -13,6 +25,6 @@ /// /// A collection. /// Creates a new collection each time it is invoked. - TCollection CreateCollection(); + TCollection CreateCollection(IFactory factory); } } diff --git a/src/Umbraco.Core/Composing/IFactory.cs b/src/Umbraco.Core/Composing/IFactory.cs new file mode 100644 index 0000000000..9a59b1c052 --- /dev/null +++ b/src/Umbraco.Core/Composing/IFactory.cs @@ -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 + + /// + /// Defines a service factory for Umbraco. + /// + public interface IFactory + { + /// + /// Gets the concrete factory. + /// + object Concrete { get; } + + /// + /// Gets an instance of a service. + /// + /// The type of the service. + /// An instance of the specified type. + /// Throws an exception if the container failed to get an instance of the specified type. + object GetInstance(Type type); + + /// + /// Tries to get an instance of a service. + /// + /// The type of the service. + /// An instance of the specified type, or null. + /// 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. + object TryGetInstance(Type type); + + /// + /// Gets all instances of a service. + /// + /// The type of the service. + IEnumerable GetAllInstances(Type serviceType); + + /// + /// Gets all instances of a service. + /// + /// The type of the service. + IEnumerable GetAllInstances(); + + /// + /// Releases an instance. + /// + /// The instance. + /// + /// 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. + /// + void Release(object instance); + + /// + /// Begins a scope. + /// + /// + /// When the scope is disposed, scoped instances that have been created during the scope are disposed. + /// Scopes can be nested. Each instance is disposed individually. + /// + IDisposable BeginScope(); + + /// + /// Enables per-request scope. + /// + /// + /// Ties scopes to web requests. + /// + void EnablePerWebRequestScope(); + } +} diff --git a/src/Umbraco.Core/Composing/IRegister.cs b/src/Umbraco.Core/Composing/IRegister.cs new file mode 100644 index 0000000000..8ad3db5409 --- /dev/null +++ b/src/Umbraco.Core/Composing/IRegister.cs @@ -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 + + /// + /// Defines a service register for Umbraco. + /// + public interface IRegister + { + /// + /// Gets the concrete container. + /// + object Concrete { get; } + + /// + /// Registers a service as its own implementation. + /// + void Register(Type serviceType, Lifetime lifetime = Lifetime.Transient); + + /// + /// Registers a service with an implementation type. + /// + void Register(Type serviceType, Type implementingType, Lifetime lifetime = Lifetime.Transient); + + /// + /// Registers a service with an implementation factory. + /// + void Register(Func factory, Lifetime lifetime = Lifetime.Transient); + + /// + /// Registers a service with an implementing instance. + /// + void RegisterInstance(Type serviceType, object instance); + + /// + /// Registers a base type for auto-registration. + /// + /// + /// Auto-registration means that anytime the container is asked to create an instance + /// of a type deriving from , it will first register that + /// type automatically. + /// 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. + /// + void RegisterAuto(Type serviceBaseType); + + #region Control + + /// + /// Configures the container for web support. + /// + /// + /// 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. + /// + void ConfigureForWeb(); + + /// + /// Creates the factory. + /// + IFactory CreateFactory(); + + #endregion + } +} diff --git a/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs index ee263f458f..a1a06621e9 100644 --- a/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs @@ -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 where TCollection : IBuilderCollection { - private readonly List>> _producers1 = new List>>(); - private readonly List>> _producers2 = new List>>(); + private readonly List>> _producers = new List>>(); private readonly List _excluded = new List(); - /// - /// Initializes a new instance of the class. - /// - protected LazyCollectionBuilderBase(IServiceContainer container) - : base(container) - { } - protected abstract TBuilder This { get; } /// /// Clears all types in the collection. /// - /// The buidler. + /// The builder. 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; - } - - /// - /// Adds a types producer to the collection. - /// - /// The types producer. - /// The builder. - public TBuilder Add(Func> producer) - { - Configure(types => - { - _producers2.Add(producer); + _producers.Add(producer); }); return This; } @@ -137,8 +113,7 @@ namespace Umbraco.Core.Composing protected override IEnumerable GetRegisteringTypes(IEnumerable 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); diff --git a/src/Umbraco.Core/Composing/Lifetime.cs b/src/Umbraco.Core/Composing/Lifetime.cs new file mode 100644 index 0000000000..e1b9950c39 --- /dev/null +++ b/src/Umbraco.Core/Composing/Lifetime.cs @@ -0,0 +1,49 @@ +namespace Umbraco.Core.Composing +{ + /// + /// Specifies the lifetime of a registered instance. + /// + public enum Lifetime + { + /// + /// Always get a new instance. + /// + /// Corresponds to Transient in LightInject, Castle Windsor + /// or MS.DI, PerDependency in Autofac. + Transient, + + /// + /// One unique instance per request. + /// + // 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, + + /// + /// One unique instance per container scope. + /// + /// Corresponds to Scope in LightInject, Scoped in MS.DI + /// or Castle Windsor, PerLifetimeScope in Autofac. + Scope, + + /// + /// One unique instance per container. + /// + /// Corresponds to Singleton in LightInject, Castle Windsor + /// or MS.DI and to SingleInstance in Autofac. + Singleton + } +} diff --git a/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs b/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs new file mode 100644 index 0000000000..b39622f66a --- /dev/null +++ b/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using LightInject; + +namespace Umbraco.Core.Composing.LightInject +{ + /// + /// Implements DI with LightInject. + /// + public class LightInjectContainer : IRegister, IFactory, IDisposable + { + private int _disposed; + + /// + /// Initializes a new instance of the with a LightInject container. + /// + protected LightInjectContainer(ServiceContainer container) + { + Container = container; + } + + /// + /// Creates a new instance of the class. + /// + public static LightInjectContainer Create() + => new LightInjectContainer(CreateServiceContainer()); + + /// + /// Creates a new instance of the LightInject service container. + /// + 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().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(this); + // + // instead, we use an explicit GetInstance with arguments implementation + + return container; + } + + /// + /// Gets the LightInject container. + /// + protected ServiceContainer Container { get; } + + /// + /// + public object Concrete => Container; + + /// + public void Dispose() + { + if (Interlocked.Exchange(ref _disposed, 1) == 1) + return; + + Container.Dispose(); + } + + /// + public IFactory CreateFactory() => this; + + #region Factory + + /// + public object GetInstance(Type type) + => Container.GetInstance(type); + + /// + public object TryGetInstance(Type type) + => Container.TryGetInstance(type); + + /// + public IEnumerable GetAllInstances() + => Container.GetAllInstances(); + + /// + public IEnumerable GetAllInstances(Type type) + => Container.GetAllInstances(type); + + /// + 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 + + /// + 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."); + } + } + + /// + 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."); + } + } + + /// + public void Register(Func 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."); + } + } + + /// + public void RegisterInstance(Type serviceType, object instance) + => Container.RegisterInstance(serviceType, instance); + + /// + 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 + // + ///// + //public void RegisterConstructorDependency(Func factory) + // => Container.RegisterConstructorDependency((f, x) => factory(this, x)); + // + ///// + //public void RegisterConstructorDependency(Func factory) + // => Container.RegisterConstructorDependency((f, x, a) => factory(this, x, a)); + + #endregion + + #region Control + + /// + public IDisposable BeginScope() + => Container.BeginScope(); + + /// + public virtual void ConfigureForWeb() + { } + + /// + 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 lifetime, Func shouldRegister, Func 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 + } +} diff --git a/src/Umbraco.Core/Exceptions/LightInjectException.cs b/src/Umbraco.Core/Composing/LightInject/LightInjectException.cs similarity index 96% rename from src/Umbraco.Core/Exceptions/LightInjectException.cs rename to src/Umbraco.Core/Composing/LightInject/LightInjectException.cs index 03fd9f2f9f..fa0aed21ca 100644 --- a/src/Umbraco.Core/Exceptions/LightInjectException.cs +++ b/src/Umbraco.Core/Composing/LightInject/LightInjectException.cs @@ -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 { /// /// Represents errors that occur due to LightInject. diff --git a/src/Umbraco.Core/Composing/MixedLightInjectScopeManagerProvider.cs b/src/Umbraco.Core/Composing/LightInject/MixedLightInjectScopeManagerProvider.cs similarity index 97% rename from src/Umbraco.Core/Composing/MixedLightInjectScopeManagerProvider.cs rename to src/Umbraco.Core/Composing/LightInject/MixedLightInjectScopeManagerProvider.cs index 05bdbc446d..470079c6c0 100644 --- a/src/Umbraco.Core/Composing/MixedLightInjectScopeManagerProvider.cs +++ b/src/Umbraco.Core/Composing/LightInject/MixedLightInjectScopeManagerProvider.cs @@ -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, diff --git a/src/Umbraco.Core/Composing/LightInjectExtensions.cs b/src/Umbraco.Core/Composing/LightInjectExtensions.cs deleted file mode 100644 index 68ba48c803..0000000000 --- a/src/Umbraco.Core/Composing/LightInjectExtensions.cs +++ /dev/null @@ -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 -{ - /// - /// Provides extensions to LightInject. - /// - public static class LightInjectExtensions - { - /// - /// Configure the container for Umbraco Core usage and assign to Current. - /// - /// The container. - /// The container is now the unique application container and is now accessible via Current.Container. - 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(_ => 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 lifetime, Func shouldRegister, Func serviceNameProvider) - { - // nothing - we *could* scan non-Umbraco assemblies, though - } - - public void Scan(Assembly assembly, IServiceRegistry serviceRegistry) - { - // nothing - we *could* scan non-Umbraco assemblies, though - } - } - - /// - /// Registers a service implementation with a specified lifetime. - /// - /// The type of the service. - /// The type of the implementation. - /// The type of the lifetime. - /// The container. - public static void Register(this IServiceContainer container) - where TImplementation : TService - where TLifetime : ILifetime, new() - { - container.Register(new TLifetime()); - } - - /// - /// Registers a service implementation with a specified lifetime. - /// - /// The type of the service. - /// The type of the lifetime. - /// The container. - /// A factory. - public static void Register(this IServiceContainer container, Func factory) - where TLifetime : ILifetime, new() - { - container.Register(factory, new TLifetime()); - } - - /// - /// Registers several service implementations with a specified lifetime. - /// - /// The type of the service. - /// The type of the lifetime. - /// The container. - /// The types of the implementations. - public static void RegisterMany(this IServiceContainer container, IEnumerable 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()); - } - } - - /// - /// Registers the TService with the factory that describes the dependencies of the service, as a singleton. - /// - public static void RegisterSingleton(this IServiceRegistry container, Func factory, string serviceName) - { - var registration = container.GetAvailableService(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); - } - } - - /// - /// Registers the TService with the TImplementation as a singleton. - /// - public static void RegisterSingleton(this IServiceRegistry container) - where TImplementation : TService - { - var registration = container.GetAvailableService(); - - if (registration == null) - { - container.Register(new PerContainerLifetime()); - } - else - { - if (registration.Lifetime is PerContainerLifetime == false) - throw new InvalidOperationException("Existing registration lifetime is not PerContainer."); - UpdateRegistration(registration, typeof(TImplementation), null); - } - } - - /// - /// Registers a concrete type as a singleton service. - /// - public static void RegisterSingleton(this IServiceRegistry container) - { - var registration = container.GetAvailableService(); - if (registration == null) - { - container.Register(new PerContainerLifetime()); - } - else - { - if (registration.Lifetime is PerContainerLifetime == false) - throw new InvalidOperationException("Existing registration lifetime is not PerContainer."); - UpdateRegistration(registration, typeof(TImplementation), null); - } - - } - - /// - /// Registers the TService with the factory that describes the dependencies of the service, as a singleton. - /// - /// - /// - /// - public static void RegisterSingleton(this IServiceRegistry container, Func factory) - { - var registration = container.GetAvailableService(); - 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? - - /// - /// Updates a registration. - /// - 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; - // }); - } - - /// - /// Gets the available service registrations for a service type. - /// - /// The service type. - /// The container. - /// The service registrations for the service type. - public static IEnumerable GetAvailableServices(this IServiceRegistry container) - { - var typeofTService = typeof(TService); - return container.AvailableServices.Where(x => x.ServiceType == typeofTService); - } - - /// - /// Gets the unique available service registration for a service type. - /// - /// The service type. - /// The container. - /// The unique service registration for the service type. - /// Can return null, but throws if more than one registration exist for the service type. - public static ServiceRegistration GetAvailableService(this IServiceRegistry container) - { - var typeofTService = typeof(TService); - return container.AvailableServices.SingleOrDefault(x => x.ServiceType == typeofTService); - } - - /// - /// Gets the unique available service registration for a service type and a name. - /// - /// The service type. - /// The container. - /// The name. - /// The unique service registration for the service type and the name. - /// Can return null, but throws if more than one registration exist for the service type and the name. - public static ServiceRegistration GetAvailableService(this IServiceRegistry container, string name) - { - var typeofTService = typeof(TService); - return container.AvailableServices.SingleOrDefault(x => x.ServiceType == typeofTService && x.ServiceName == name); - } - - /// - /// Gets an instance of a TService or throws a meaningful exception. - /// - /// The service type. - /// The container. - /// The instance. - public static TService GetInstanceOrThrow(this IServiceFactory factory) - { - if (factory == null) - throw new ArgumentNullException(nameof(factory)); - - try - { - return factory.GetInstance(); - } - catch (Exception e) - { - LightInjectException.TryThrow(e); - throw; - } - } - - /// - /// Gets an instance of a TService or throws a meaningful exception. - /// - /// The container. - /// The type of the service. - /// The name of the service. - /// The implementing type. - /// Arguments. - /// The instance. - 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; - //} - } - - /// - /// Registers a base type for auto-registration. - /// - /// The base type. - /// The container. - /// - /// Any type that inherits/implements the base type will be auto-registered on-demand. - /// This methods works with actual types. Use the other overload for eg generic definitions. - /// - public static void RegisterAuto(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); - } - - /// - /// Registers a base type for auto-registration. - /// - /// The container. - /// The base type. - /// - /// Any type that inherits/implements the base type will be auto-registered on-demand. - /// This methods works with actual types, as well as generic definitions eg typeof(MyBase{}). - /// - 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); - } - - /// - /// Registers and instanciates a collection builder. - /// - /// The type of the collection builder. - /// The container. - /// The collection builder. - public static TBuilder RegisterCollectionBuilder(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(); - if (registration != null) - throw new InvalidOperationException("Collection builders should be registered only once."); - - // register the builder - per container - var builderLifetime = new PerContainerLifetime(); - container.Register(builderLifetime); - - // return the builder - // (also initializes the builder) - return container.GetInstance(); - } - } -} diff --git a/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs index 4811551cd2..bde1bf96c5 100644 --- a/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs @@ -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 where TCollection : IBuilderCollection { - /// - /// Initializes a new instance of the class. - /// - /// - protected OrderedCollectionBuilderBase(IServiceContainer container) - : base (container) - { } - protected abstract TBuilder This { get; } /// /// Clears all types in the collection. /// - /// The buidler. + /// The builder. public TBuilder Clear() { Configure(types => types.Clear()); @@ -87,26 +78,6 @@ namespace Umbraco.Core.Composing return This; } - /// - /// Appends types to the collections. - /// - /// The types to append. - /// The builder. - public TBuilder Append(Func> 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; - } - /// /// Appends a type after another type. /// diff --git a/src/Umbraco.Core/Composing/RegisterExtensions.cs b/src/Umbraco.Core/Composing/RegisterExtensions.cs new file mode 100644 index 0000000000..4db1a2e9e4 --- /dev/null +++ b/src/Umbraco.Core/Composing/RegisterExtensions.cs @@ -0,0 +1,32 @@ +namespace Umbraco.Core.Composing +{ + /// + /// Provides extension methods to the class. + /// + public static class RegisterExtensions + { + /// + /// Registers a service with an implementation type. + /// + public static void Register(this IRegister register, Lifetime lifetime = Lifetime.Transient) + => register.Register(typeof(TService), typeof(TImplementing), lifetime); + + /// + /// Registers a service as its own implementation. + /// + public static void Register(this IRegister register, Lifetime lifetime = Lifetime.Transient) + => register.Register(typeof(TService), lifetime); + + /// + /// Registers a service with an implementing instance. + /// + public static void RegisterInstance(this IRegister register, TService instance) + => register.RegisterInstance(typeof(TService), instance); + + /// + /// Registers a base type for auto-registration. + /// + public static void RegisterAuto(this IRegister register) + => register.RegisterAuto(typeof(TServiceBase)); + } +} diff --git a/src/Umbraco.Core/Composing/RegisterFactory.cs b/src/Umbraco.Core/Composing/RegisterFactory.cs new file mode 100644 index 0000000000..8ee6e5a94c --- /dev/null +++ b/src/Umbraco.Core/Composing/RegisterFactory.cs @@ -0,0 +1,56 @@ +using System; +using System.Configuration; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + /// + /// Creates the container. + /// + 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"; + + /// + /// Creates a new instance of the configured container. + /// + /// + /// 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. + /// + 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()) as IRegister; + if (container == null) + throw new Exception($"Register factory '{configuredTypeName}' did not return an IRegister implementation."); + + return container; + } + } +} diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index ef58671e91..3121e869c3 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -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 _types = new Dictionary(); - 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 _assemblies; private bool _reportedChange; - private static LocalTempStorage _localTempStorage = LocalTempStorage.Unknown; + private static LocalTempStorage _localTempStorage; private static string _fileBasePath; /// /// Initializes a new instance of the class. /// /// The application runtime cache. - /// + /// Files storage mode. /// A profiling logger. - /// Used by LightInject. - 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) { } /// /// Initializes a new instance of the class. /// /// The application runtime cache. - /// + /// Files storage mode. /// A profiling logger. /// Whether to detect changes using hashes. - 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 } } + /// + /// Initializes a new, test/blank, instance of the class. + /// + /// The initialized instance cannot get types. + internal TypeLoader() + { } + /// /// Gets or sets the set of assemblies to scan. /// @@ -206,7 +211,7 @@ namespace Umbraco.Core.Composing /// The hash. /// 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). - private static string GetFileHash(IEnumerable> filesAndFolders, ProfilingLogger logger) + private static string GetFileHash(IEnumerable> filesAndFolders, IProfilingLogger logger) { using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined")) { @@ -264,7 +269,7 @@ namespace Umbraco.Core.Composing /// A profiling logger. /// The hash. // internal for tests - internal static string GetFileHash(IEnumerable filesAndFolders, ProfilingLogger logger) + internal static string GetFileHash(IEnumerable filesAndFolders, IProfilingLogger logger) { using (logger.TraceDuration("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("Writing cache file."); + _logger.Debug("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("Attempted to get filestream for file {Path} failed, {NumberOfAttempts} attempts left, pausing for {PauseMilliseconds} milliseconds", path, attempts, pauseMilliseconds); + _logger.Debug("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("Attempted to delete file {Path} failed, {NumberOfAttempts} attempts left, pausing for {PauseMilliseconds} milliseconds", path, attempts, pauseMilliseconds); + _logger.Debug("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 /// Caching is disabled when using specific assemblies. public IEnumerable GetTypes(bool cache = true, IEnumerable 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("Running a full, " + (cache ? "" : "non-") + "cached, scan for non-discoverable type {TypeName} (slow).", typeof(T).FullName); + _logger.Debug("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("Running a non-cached, filter for discoverable type {TypeName} (slowish).", typeof(T).FullName); + _logger.Debug("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 GetTypesWithAttribute(bool cache = true, IEnumerable 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("Running a full, " + (cache ? "" : "non-") + "cached, scan for non-discoverable type {TypeName} / attribute {AttributeName} (slow).", typeof(T).FullName, typeof(TAttribute).FullName); + _logger.Debug("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("Running a non-cached, filter for discoverable type {TypeName} / attribute {AttributeName} (slowish).", typeof(T).FullName, typeof(TAttribute).FullName); + _logger.Debug("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 GetAttributedTypes(bool cache = true, IEnumerable 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("Running a full, non-cached, scan for types / attribute {AttributeName} (slow).", typeof(TAttribute).FullName); + _logger.Debug("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( "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("Getting {TypeName}: found a cached type list.", GetName(baseType, attributeType)); + _logger.Debug("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("Assemblies changes detected, need to rescan everything."); + _logger.Debug("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("Getting {TypeName}: failed to load from cache file, must scan assemblies.", GetName(baseType, attributeType)); + _logger.Debug("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(ex, "Getting {TypeName}: failed to load cache file type {CacheType}, reverting to scanning assemblies.", GetName(baseType, attributeType), type); + _logger.Error(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("Getting {TypeName}: loaded types from cache file.", GetName(baseType, attributeType)); + _logger.Debug("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("Getting {TypeName}: " + action + ".", GetName(baseType, attributeType)); + _logger.Debug("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("Got {TypeName}, caching ({CacheType}).", GetName(baseType, attributeType), added.ToString().ToLowerInvariant()); + _logger.Debug("Got {TypeName}, caching ({CacheType}).", GetName(baseType, attributeType), added.ToString().ToLowerInvariant()); } else { - _logger.Logger.Debug("Got {TypeName}.", GetName(baseType, attributeType)); + _logger.Debug("Got {TypeName}.", GetName(baseType, attributeType)); } return typeList.Types; diff --git a/src/Umbraco.Core/Composing/TypeLoaderExtensions.cs b/src/Umbraco.Core/Composing/TypeLoaderExtensions.cs index 6177151a00..ba57243071 100644 --- a/src/Umbraco.Core/Composing/TypeLoaderExtensions.cs +++ b/src/Umbraco.Core/Composing/TypeLoaderExtensions.cs @@ -42,13 +42,5 @@ namespace Umbraco.Core.Composing { return mgr.GetTypesWithAttribute(); } - - /// - /// Gets all classes implementing ISqlSyntaxProvider and marked with the SqlSyntaxProviderAttribute. - /// - public static IEnumerable GetSqlSyntaxProviders(this TypeLoader mgr) - { - return mgr.GetTypesWithAttribute(); - } } } diff --git a/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs index 99fa2d3eb9..da47c53bf8 100644 --- a/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs @@ -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 where TCollection : IBuilderCollection { - /// - /// Initializes a new instance of the class. - /// - /// - protected WeightedCollectionBuilderBase(IServiceContainer container) - : base(container) - { } - protected abstract TBuilder This { get; } /// /// Clears all types in the collection. /// - /// The buidler. + /// The builder. public TBuilder Clear() { Configure(types => types.Clear()); diff --git a/src/Umbraco.Core/Configuration/UmbracoConfig.cs b/src/Umbraco.Core/Configuration/UmbracoConfig.cs index 6a1203313e..0ace23dba4 100644 --- a/src/Umbraco.Core/Configuration/UmbracoConfig.cs +++ b/src/Umbraco.Core/Configuration/UmbracoConfig.cs @@ -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 { /// - /// The gateway to all umbraco configuration + /// The gateway to all umbraco configuration. /// + /// This should be registered as a unique service in the container. public class UmbracoConfig { - #region Singleton - - private static readonly Lazy Lazy = new Lazy(() => new UmbracoConfig()); - - public static UmbracoConfig For => Lazy.Value; - - #endregion + private IGlobalSettings _global; + private Lazy _umbraco; + private Lazy _healthChecks; + private Lazy _dashboards; + private Lazy _grids; /// - /// Default constructor + /// Initializes a new instance of the class. /// - 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(() => GetConfig("umbracoConfiguration/settings")); + _dashboards = new Lazy(() =>GetConfig("umbracoConfiguration/dashBoard")); + _healthChecks = new Lazy(() => GetConfig("umbracoConfiguration/HealthChecks")); + _grids = new Lazy(() => new GridConfig(logger, runtimeCache, appPluginsDir, configDir, runtimeState.Debug)); + } + + /// + /// Gets a typed and named config section. + /// + /// The type of the configuration section. + /// The name of the configuration section. + /// The configuration section. + public static TConfig GetConfig(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); - } - } - - /// - /// Constructor - can be used for testing - /// - /// - /// - /// - /// - 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; - - /// - /// Gets the IHealthCheck config - /// - 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(ex, "Config error"); throw ex; } - - return _healthChecks; } /// - /// Gets the IDashboardSection + /// Gets the global configuration. /// - 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(ex, "Config error"); - throw ex; - } - - return _dashboardSection; - } + public IGlobalSettings Global() + => _global; /// - /// Only for testing + /// Gets the Umbraco configuration. /// - /// - public void SetDashboardSettings(IDashboardSection value) - { - _dashboardSection = value; - } + public IUmbracoSettingsSection Umbraco() + => _umbraco.Value; /// - /// Only for testing + /// Gets the dashboards configuration. /// - /// - public void SetHealthCheckSettings(IHealthChecks value) - { - _healthChecks = value; - } + public IDashboardSection Dashboards() + => _dashboards.Value; /// - /// Only for testing + /// Gets the health checks configuration. /// - /// - public void SetUmbracoSettings(IUmbracoSettingsSection value) - { - _umbracoSettings = value; - } + public IHealthChecks HealthChecks() + => _healthChecks.Value; /// - /// Only for testing + /// Gets the grids configuration. + /// + public IGridConfig Grids() + => _grids.Value; + + /// + /// Sets the global configuration, for tests only. /// - /// public void SetGlobalConfig(IGlobalSettings value) { - _globalSettings = value; + _global = value; } /// - /// Gets the IGlobalSettings + /// Sets the Umbraco configuration, for tests only. /// - public IGlobalSettings GlobalSettings() + public void SetUmbracoConfig(IUmbracoSettingsSection value) { - return _globalSettings ?? (_globalSettings = new GlobalSettings()); + _umbraco = new Lazy(() => value); } /// - /// Gets the IUmbracoSettings - /// - 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(ex, "Config error"); - throw ex; - } - - return _umbracoSettings; - } - - /// - /// Only for testing + /// Sets the dashboards configuration, for tests only. /// /// - public void SetGridConfig(IGridConfig value) + public void SetDashboardsConfig(IDashboardSection value) { - _gridConfig = value; + _dashboards = new Lazy(() => value); } /// - /// Gets the IGridConfig + /// Sets the health checks configuration, for tests only. /// - 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(() => value); + } - return _gridConfig; + /// + /// Sets the grids configuration, for tests only. + /// + public void SetGridsConfig(IGridConfig value) + { + _grids = new Lazy(() => value); } //TODO: Add other configurations here ! } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Constants-Composing.cs b/src/Umbraco.Core/Constants-Composing.cs index 1e8c9886d2..e65629a278 100644 --- a/src/Umbraco.Core/Constants-Composing.cs +++ b/src/Umbraco.Core/Constants-Composing.cs @@ -9,20 +9,6 @@ /// Defines constants for composition. /// public static class Composing - { - /// - /// Defines file system names. - /// - 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"; - } - } + { } } } diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index b15e371e87..5e2d44c90d 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -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(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); } diff --git a/src/Umbraco.Core/Events/QueuingEventDispatcher.cs b/src/Umbraco.Core/Events/QueuingEventDispatcher.cs index e0d3847c17..b31b64e435 100644 --- a/src/Umbraco.Core/Events/QueuingEventDispatcher.cs +++ b/src/Umbraco.Core/Events/QueuingEventDispatcher.cs @@ -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); } } diff --git a/src/Umbraco.Core/Exceptions/BootFailedException.cs b/src/Umbraco.Core/Exceptions/BootFailedException.cs index ec07389d37..10a648fd76 100644 --- a/src/Umbraco.Core/Exceptions/BootFailedException.cs +++ b/src/Umbraco.Core/Exceptions/BootFailedException.cs @@ -1,9 +1,10 @@ using System; +using System.Text; namespace Umbraco.Core.Exceptions { /// - /// An exception that is thrown if the Umbraco application cannnot boot. + /// An exception that is thrown if the Umbraco application cannot boot. /// public class BootFailedException : Exception { @@ -29,5 +30,31 @@ namespace Umbraco.Core.Exceptions public BootFailedException(string message, Exception inner) : base(message, inner) { } + + /// + /// Rethrows a captured . + /// + /// The exception can be null, in which case a default message is used. + 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()); + } } } diff --git a/src/Umbraco.Core/IMainDom.cs b/src/Umbraco.Core/IMainDom.cs new file mode 100644 index 0000000000..3a8cd13ff1 --- /dev/null +++ b/src/Umbraco.Core/IMainDom.cs @@ -0,0 +1,38 @@ +using System; + +namespace Umbraco.Core +{ + /// + /// Represents the main AppDomain running for a given application. + /// + /// + /// There can be only one "main" AppDomain running for a given application at a time. + /// It is possible to register against the MainDom and be notified when it is released. + /// + public interface IMainDom + { + /// + /// Gets a value indicating whether the current domain is the main domain. + /// + bool IsMainDom { get; } + + /// + /// Registers a resource that requires the current AppDomain to be the main domain to function. + /// + /// An action to execute before the AppDomain releases the main domain status. + /// An optional weight (lower goes first). + /// A value indicating whether it was possible to register. + bool Register(Action release, int weight = 100); + + /// + /// Registers a resource that requires the current AppDomain to be the main domain to function. + /// + /// An action to execute when registering. + /// An action to execute before the AppDomain releases the main domain status. + /// An optional weight (lower goes first). + /// A value indicating whether it was possible to register. + /// If registering is successful, then the action + /// is guaranteed to execute before the AppDomain releases the main domain status. + bool Register(Action install, Action release, int weight = 100); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/IO/FileSystemProviderAttribute.cs b/src/Umbraco.Core/IO/FileSystemProviderAttribute.cs deleted file mode 100644 index b3b6cb6b79..0000000000 --- a/src/Umbraco.Core/IO/FileSystemProviderAttribute.cs +++ /dev/null @@ -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; - } - } -} diff --git a/src/Umbraco.Core/IO/FileSystemWrapper.cs b/src/Umbraco.Core/IO/FileSystemWrapper.cs index 2c4377b89b..a493b7166b 100644 --- a/src/Umbraco.Core/IO/FileSystemWrapper.cs +++ b/src/Umbraco.Core/IO/FileSystemWrapper.cs @@ -16,103 +16,103 @@ namespace Umbraco.Core.IO /// 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 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 GetFiles(string path) { - return Wrapped.GetFiles(path); + return InnerFileSystem.GetFiles(path); } public IEnumerable 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); } } } diff --git a/src/Umbraco.Core/IO/FileSystems.cs b/src/Umbraco.Core/IO/FileSystems.cs index 62ce25dff0..7fc846319b 100644 --- a/src/Umbraco.Core/IO/FileSystems.cs +++ b/src/Umbraco.Core/IO/FileSystems.cs @@ -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 _wrappers = new ConcurrentSet(); + private readonly IFactory _container; private readonly ILogger _logger; - private readonly ConcurrentDictionary _providerLookup = new ConcurrentDictionary(); - private readonly ConcurrentDictionary _filesystems = new ConcurrentDictionary(); + private readonly ConcurrentDictionary> _filesystems = new ConcurrentDictionary>(); // 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 _shadowWrappers = new List(); + 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 IsScoped { get; set; } = () => false; #endregion #region Well-Known FileSystems + /// public IFileSystem MacroPartialsFileSystem { get @@ -71,6 +72,7 @@ namespace Umbraco.Core.IO } } + /// public IFileSystem PartialViewsFileSystem { get @@ -80,6 +82,7 @@ namespace Umbraco.Core.IO } } + /// public IFileSystem StylesheetsFileSystem { get @@ -89,6 +92,7 @@ namespace Umbraco.Core.IO } } + /// public IFileSystem ScriptsFileSystem { get @@ -97,16 +101,18 @@ namespace Umbraco.Core.IO return _scriptsFileSystem; } } - + + /// public IFileSystem MasterPagesFileSystem { get { if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); - return _masterPagesFileSystem;// fixme - see 7.6?! + return _masterPagesFileSystem; } } + /// 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(); + // 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 - /// - /// used to cache the lookup of how to construct this object so we don't have to reflect each time. - /// - private class ProviderConstructionInfo - { - public object[] Parameters { get; set; } - public ConstructorInfo Constructor { get; set; } - //public string ProviderAlias { get; set; } - } - - /// - /// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem. - /// - /// The alias of the strongly-typed filesystem. - /// The non-typed filesystem supporting the strongly-typed filesystem with the specified alias. - /// This method should not be used directly, used instead. - public IFileSystem GetUnderlyingFileSystemProvider(string alias) - { - return GetUnderlyingFileSystemProvider(alias, null); - } - - /// - /// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem. - /// - /// The alias of the strongly-typed filesystem. - /// A fallback creator for the filesystem. - /// The non-typed filesystem supporting the strongly-typed filesystem with the specified alias. - /// This method should not be used directly, used instead. - internal IFileSystem GetUnderlyingFileSystemProvider(string alias, Func 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 fallback) - { - var ctorInfo = GetUnderlyingFileSystemCtor(alias, fallback); - return ctorInfo == null ? fallback() : (IFileSystem) ctorInfo.Constructor.Invoke(ctorInfo.Parameters); - } - - private ProviderConstructionInfo GetUnderlyingFileSystemCtor(string alias, Func 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 - }; - } - /// /// Gets a strongly-typed filesystem. /// /// The type of the filesystem. /// A strongly-typed filesystem of the specified type. /// - /// 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. /// Note that any filesystem created by this method *after* shadowing begins, will *not* be /// shadowing (and an exception will be thrown by the ShadowWrapper). /// - // fixme - should it change for v8? - public TFileSystem GetFileSystemProvider() + public TFileSystem GetFileSystem(IFileSystem supporting) where TFileSystem : FileSystemWrapper { - return GetFileSystemProvider(null); - } + if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems(); - /// - /// Gets a strongly-typed filesystem. - /// - /// The type of the filesystem. - /// A fallback creator for the inner filesystem. - /// A strongly-typed filesystem of the specified type. - /// - /// The fallback creator is used only if nothing is configured. - /// 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. - /// Note that any filesystem created by this method *after* shadowing begins, will *not* be - /// shadowing (and an exception will be thrown by the ShadowWrapper). - /// - public TFileSystem GetFileSystemProvider(Func fallback) - where TFileSystem : FileSystemWrapper - { - var alias = GetFileSystemAlias(); - return (TFileSystem)_filesystems.GetOrAdd(alias, _ => + return (TFileSystem) _filesystems.GetOrAdd(typeof(TFileSystem), _ => new Lazy(() => { - // 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() - { - var fsType = typeof(TFileSystem); - - // validate the ctor - var constructor = fsType.GetConstructors().SingleOrDefault(x - => x.GetParameters().Length == 1 && TypeHelper.IsTypeAssignableFrom(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(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 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("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("UnShadow '{ShadowId}' {Status}", id, completed ? "complete" : "abort"); + + var exceptions = new List(); + 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 - where T : class - { - private readonly HashSet _set = new HashSet(); - - 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(); - } - } - } } } diff --git a/src/Umbraco.Core/IO/IFileSystem.cs b/src/Umbraco.Core/IO/IFileSystem.cs index 115cb8a5c1..14b015a468 100644 --- a/src/Umbraco.Core/IO/IFileSystem.cs +++ b/src/Umbraco.Core/IO/IFileSystem.cs @@ -5,7 +5,7 @@ using System.IO; namespace Umbraco.Core.IO { /// - /// Provides methods allowing the manipulation of files within an Umbraco application. + /// Provides methods allowing the manipulation of files. /// public interface IFileSystem { diff --git a/src/Umbraco.Core/IO/IFileSystems.cs b/src/Umbraco.Core/IO/IFileSystems.cs new file mode 100644 index 0000000000..d74ad48145 --- /dev/null +++ b/src/Umbraco.Core/IO/IFileSystems.cs @@ -0,0 +1,38 @@ +namespace Umbraco.Core.IO +{ + /// + /// Provides the system filesystems. + /// + public interface IFileSystems + { + /// + /// Gets the macro partials filesystem. + /// + IFileSystem MacroPartialsFileSystem { get; } + + /// + /// Gets the partial views filesystem. + /// + IFileSystem PartialViewsFileSystem { get; } + + /// + /// Gets the stylesheets filesystem. + /// + IFileSystem StylesheetsFileSystem { get; } + + /// + /// Gets the scripts filesystem. + /// + IFileSystem ScriptsFileSystem { get; } + + /// + /// Gets the masterpages filesystem. + /// + IFileSystem MasterPagesFileSystem { get; } + + /// + /// Gets the MVC views filesystem. + /// + IFileSystem MvcViewsFileSystem { get; } + } +} diff --git a/src/Umbraco.Core/IO/IMediaFileSystem.cs b/src/Umbraco.Core/IO/IMediaFileSystem.cs new file mode 100644 index 0000000000..ed88516135 --- /dev/null +++ b/src/Umbraco.Core/IO/IMediaFileSystem.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Umbraco.Core.Models; + +namespace Umbraco.Core.IO +{ + /// + /// Provides methods allowing the manipulation of media files. + /// + public interface IMediaFileSystem : IFileSystem + { + /// + /// Delete media files. + /// + /// Files to delete (filesystem-relative paths). + void DeleteMediaFiles(IEnumerable files); + + /// + /// Gets the file path of a media file. + /// + /// The file name. + /// The unique identifier of the content/media owning the file. + /// The unique identifier of the property type owning the file. + /// The filesystem-relative path to the media file. + /// With the old media path scheme, this CREATES a new media path each time it is invoked. + string GetMediaPath(string filename, Guid cuid, Guid puid); + + /// + /// Gets the file path of a media file. + /// + /// The file name. + /// A previous file path. + /// The unique identifier of the content/media owning the file. + /// The unique identifier of the property type owning the file. + /// The filesystem-relative path to the media file. + /// In the old, legacy, number-based scheme, we try to re-use the media folder + /// specified by . Else, we CREATE a new one. Each time we are invoked. + string GetMediaPath(string filename, string prevpath, Guid cuid, Guid puid); + + /// + /// Stores a media file associated to a property of a content item. + /// + /// The content item owning the media file. + /// The property type owning the media file. + /// The media file name. + /// A stream containing the media bytes. + /// An optional filesystem-relative filepath to the previous media file. + /// The filesystem-relative filepath to the media file. + /// + /// The file is considered "owned" by the content/propertyType. + /// If an 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. + /// + string StoreFile(IContentBase content, PropertyType propertyType, string filename, Stream filestream, string oldpath); + + /// + /// Copies a media file as a new media file, associated to a property of a content item. + /// + /// The content item owning the copy of the media file. + /// The property type owning the copy of the media file. + /// The filesystem-relative path to the source media file. + /// The filesystem-relative path to the copy of the media file. + string CopyFile(IContentBase content, PropertyType propertyType, string sourcepath); + } +} diff --git a/src/Umbraco.Core/IO/IMediaPathScheme.cs b/src/Umbraco.Core/IO/IMediaPathScheme.cs index 5cfb43ed77..9a38cdc74f 100644 --- a/src/Umbraco.Core/IO/IMediaPathScheme.cs +++ b/src/Umbraco.Core/IO/IMediaPathScheme.cs @@ -7,35 +7,27 @@ namespace Umbraco.Core.IO /// 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! - - /// - /// Initialize. - /// - void Initialize(IFileSystem filesystem); - /// /// Gets a media file path. /// + /// The media filesystem. /// The (content, media) item unique identifier. /// The property type unique identifier. /// The file name. /// A previous filename. /// The filesystem-relative complete file path. - string GetFilePath(Guid itemGuid, Guid propertyGuid, string filename, string previous = null); + string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null); /// /// Gets the directory that can be deleted when the file is deleted. /// + /// The media filesystem. /// The filesystem-relative path of the file. /// The filesystem-relative path of the directory. /// /// The directory, and anything below it, will be deleted. /// Can return null (or empty) when no directory should be deleted. /// - string GetDeleteDirectory(string filepath); + string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath); } } diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index fd58f573cc..2ce1230bcc 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -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 /// /// A custom file system provider for media /// - [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; } - /// - /// Deletes all files passed in. + /// Initializes a new instance of the class. /// - /// - /// - /// - internal bool DeleteFiles(IEnumerable files, Action 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; } + /// public void DeleteMediaFiles(IEnumerable 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(e, "Failed to delete attached file '{File}'", file); + _logger.Error(e, "Failed to delete media file '{File}'.", file); } }); } #region Media Path - /// - /// Gets the file path of a media file. - /// - /// The file name. - /// The unique identifier of the content/media owning the file. - /// The unique identifier of the property type owning the file. - /// The filesystem-relative path to the media file. - /// With the old media path scheme, this CREATES a new media path each time it is invoked. + /// 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); } - /// - /// Gets the file path of a media file. - /// - /// The file name. - /// A previous file path. - /// The unique identifier of the content/media owning the file. - /// The unique identifier of the property type owning the file. - /// The filesystem-relative path to the media file. - /// In the old, legacy, number-based scheme, we try to re-use the media folder - /// specified by . Else, we CREATE a new one. Each time we are invoked. + /// 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 - /// - /// Stores a media file associated to a property of a content item. - /// - /// The content item owning the media file. - /// The property type owning the media file. - /// The media file name. - /// A stream containing the media bytes. - /// An optional filesystem-relative filepath to the previous media file. - /// The filesystem-relative filepath to the media file. - /// - /// The file is considered "owned" by the content/propertyType. - /// If an 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. - /// + /// 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; } - /// - /// Copies a media file as a new media file, associated to a property of a content item. - /// - /// The content item owning the copy of the media file. - /// The property type owning the copy of the media file. - /// The filesystem-relative path to the source media file. - /// The filesystem-relative path to the copy of the media file. + /// 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 - } } diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs index 37c6a7b209..59f8213414 100644 --- a/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs +++ b/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs @@ -12,11 +12,7 @@ namespace Umbraco.Core.IO.MediaPathSchemes public class CombinedGuidsMediaPathScheme : IMediaPathScheme { /// - public void Initialize(IFileSystem filesystem) - { } - - /// - 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 } /// - public string GetDeleteDirectory(string filepath) => Path.GetDirectoryName(filepath); + public string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath) => Path.GetDirectoryName(filepath); } } diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/OriginalMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/OriginalMediaPathScheme.cs index 1ed2ea59ce..14f0c7602b 100644 --- a/src/Umbraco.Core/IO/MediaPathSchemes/OriginalMediaPathScheme.cs +++ b/src/Umbraco.Core/IO/MediaPathSchemes/OriginalMediaPathScheme.cs @@ -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; /// - public void Initialize(IFileSystem filesystem) - { - _filesystem = filesystem; - } - - /// - 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 "/" OR "-" // and we want to reuse the "" 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; } /// - 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) diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/TwoGuidsMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/TwoGuidsMediaPathScheme.cs index 4a6fdfcdbe..3c06e295e6 100644 --- a/src/Umbraco.Core/IO/MediaPathSchemes/TwoGuidsMediaPathScheme.cs +++ b/src/Umbraco.Core/IO/MediaPathSchemes/TwoGuidsMediaPathScheme.cs @@ -12,17 +12,13 @@ namespace Umbraco.Core.IO.MediaPathSchemes public class TwoGuidsMediaPathScheme : IMediaPathScheme { /// - public void Initialize(IFileSystem filesystem) - { } - - /// - 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('\\', '/'); } /// - public string GetDeleteDirectory(string filepath) + public string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath) { return Path.GetDirectoryName(filepath); } diff --git a/src/Umbraco.Core/IO/ShadowFileSystems.cs b/src/Umbraco.Core/IO/ShadowFileSystems.cs index 289667b0db..bce0cc6df7 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystems.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystems.cs @@ -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("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("UnShadow " + _id + " (complete)."); - - // var exceptions = new List(); - // 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("UnShadow '{ShadowId}' {Status}", _id, _completed ? "complete" : "abort"); - - var exceptions = new List(); - 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("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); } } } diff --git a/src/Umbraco.Core/IRuntime.cs b/src/Umbraco.Core/IRuntime.cs index 37b265ad77..d433dde12d 100644 --- a/src/Umbraco.Core/IRuntime.cs +++ b/src/Umbraco.Core/IRuntime.cs @@ -1,4 +1,4 @@ -using LightInject; +using Umbraco.Core.Composing; namespace Umbraco.Core { @@ -10,8 +10,14 @@ namespace Umbraco.Core /// /// Boots the runtime. /// - /// The application service container. - void Boot(ServiceContainer container); + /// The application register. + /// The application factory. + IFactory Boot(IRegister register); + + /// + /// Gets the runtime state. + /// + IRuntimeState State { get; } /// /// Terminates the runtime. diff --git a/src/Umbraco.Core/Logging/IProfilingLogger.cs b/src/Umbraco.Core/Logging/IProfilingLogger.cs new file mode 100644 index 0000000000..1d7c231e95 --- /dev/null +++ b/src/Umbraco.Core/Logging/IProfilingLogger.cs @@ -0,0 +1,40 @@ +using System; + +namespace Umbraco.Core.Logging +{ + /// + /// Defines the profiling logging service. + /// + public interface IProfilingLogger : ILogger + { + /// + /// Profiles an action and log as information messages. + /// + DisposableTimer TraceDuration(string startMessage); + + /// + /// Profiles an action and log as information messages. + /// + DisposableTimer TraceDuration(string startMessage, string completeMessage, string failMessage = null); + + /// + /// Profiles an action and log as information messages. + /// + DisposableTimer TraceDuration(Type loggerType, string startMessage, string completeMessage, string failMessage = null); + + /// + /// Profiles an action and log as debug messages. + /// + DisposableTimer DebugDuration(string startMessage); + + /// + /// Profiles an action and log as debug messages. + /// + DisposableTimer DebugDuration(string startMessage, string completeMessage, string failMessage = null, int thresholdMilliseconds = 0); + + /// + /// Profiles an action and log as debug messages. + /// + DisposableTimer DebugDuration(Type loggerType, string startMessage, string completeMessage, string failMessage = null, int thresholdMilliseconds = 0); + } +} diff --git a/src/Umbraco.Core/Logging/MessageTemplates.cs b/src/Umbraco.Core/Logging/MessageTemplates.cs index 194d47c339..47de1230ff 100644 --- a/src/Umbraco.Core/Logging/MessageTemplates.cs +++ b/src/Umbraco.Core/Logging/MessageTemplates.cs @@ -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(); } } } diff --git a/src/Umbraco.Core/Logging/ProfilingLogger.cs b/src/Umbraco.Core/Logging/ProfilingLogger.cs index 80560e923a..d642926147 100644 --- a/src/Umbraco.Core/Logging/ProfilingLogger.cs +++ b/src/Umbraco.Core/Logging/ProfilingLogger.cs @@ -3,14 +3,23 @@ namespace Umbraco.Core.Logging { /// - /// Provides debug or trace logging with duration management. + /// Provides logging and profiling services. /// - public sealed class ProfilingLogger + public sealed class ProfilingLogger : IProfilingLogger { + /// + /// Gets the underlying implementation. + /// public ILogger Logger { get; } + /// + /// Gets the underlying implementation. + /// public IProfiler Profiler { get; } + /// + /// Initializes a new instance of the class. + /// 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 } } diff --git a/src/Umbraco.Core/Logging/Serilog/SerilogLogger.cs b/src/Umbraco.Core/Logging/Serilog/SerilogLogger.cs index c23a43a820..3fd6d18278 100644 --- a/src/Umbraco.Core/Logging/Serilog/SerilogLogger.cs +++ b/src/Umbraco.Core/Logging/Serilog/SerilogLogger.cs @@ -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(); diff --git a/src/Umbraco.Core/Logging/Viewer/LogViewerComponent.cs b/src/Umbraco.Core/Logging/Viewer/LogViewerComponent.cs deleted file mode 100644 index 5c22ae9282..0000000000 --- a/src/Umbraco.Core/Logging/Viewer/LogViewerComponent.cs +++ /dev/null @@ -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(); - } - } -} diff --git a/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs b/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs new file mode 100644 index 0000000000..9a441357f5 --- /dev/null +++ b/src/Umbraco.Core/Logging/Viewer/LogViewerComposer.cs @@ -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(); + } + } +} diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index eb036fd441..0b4551a7cc 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -8,15 +8,13 @@ using Umbraco.Core.Logging; namespace Umbraco.Core { /// - /// Represents the main AppDomain running for a given application. + /// Provides the full implementation of . /// /// - /// There can be only one "main" AppDomain running for a given application at a time. /// When an AppDomain starts, it tries to acquire the main domain status. /// When an AppDomain stops (eg the application is restarting) it should release the main domain status. - /// It is possible to register against the MainDom and be notified when it is released. /// - internal class MainDom : IRegisteredObject + internal class MainDom : IMainDom, IRegisteredObject { #region Vars @@ -84,9 +82,7 @@ namespace Umbraco.Core /// An optional weight (lower goes first). /// A value indicating whether it was possible to register. public bool Register(Action release, int weight = 100) - { - return Register(null, release, weight); - } + => Register(null, release, weight); /// /// 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 + /// + /// Gets a value indicating whether the current domain is the main domain. + /// public bool IsMainDom => _isMainDom; // IRegisteredObject diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs index ef6b3b720b..e2c34c31cb 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs @@ -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;"; /// /// 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 /// diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs index a0c1b181e9..8e91cb52ff 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs @@ -1,521 +1,527 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NPoco; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Install -{ - /// - /// Creates the initial database schema during install. - /// - internal class DatabaseSchemaCreator - { - private readonly IUmbracoDatabase _database; - private readonly ILogger _logger; - - public DatabaseSchemaCreator(IUmbracoDatabase database, ILogger logger) - { - _database = database; - _logger = logger; - } - - private ISqlSyntaxProvider SqlSyntax => _database.SqlContext.SqlSyntax; - - // all tables, in order - public static readonly List OrderedTables = new List - { - typeof (UserDto), - typeof (NodeDto), - typeof (ContentTypeDto), - typeof (TemplateDto), - typeof (ContentDto), - typeof (ContentVersionDto), - typeof (MediaVersionDto), - typeof (DocumentDto), - typeof (ContentTypeTemplateDto), - typeof (DataTypeDto), - typeof (DictionaryDto), - typeof (LanguageDto), - typeof (LanguageTextDto), - typeof (DomainDto), - typeof (LogDto), - typeof (MacroDto), - typeof (MacroPropertyDto), - typeof (MemberTypeDto), - typeof (MemberDto), - typeof (Member2MemberGroupDto), - typeof (ContentXmlDto), - typeof (PreviewXmlDto), - typeof (PropertyTypeGroupDto), - typeof (PropertyTypeDto), - typeof (PropertyDataDto), - typeof (RelationTypeDto), - typeof (RelationDto), - typeof (TagDto), - typeof (TagRelationshipDto), - typeof (ContentType2ContentTypeDto), - typeof (ContentTypeAllowedContentTypeDto), - typeof (User2NodeNotifyDto), - typeof (ServerRegistrationDto), - typeof (AccessDto), - typeof (AccessRuleDto), - typeof (CacheInstructionDto), - typeof (ExternalLoginDto), - typeof (RedirectUrlDto), - typeof (LockDto), - typeof (UserGroupDto), - typeof (User2UserGroupDto), - typeof (UserGroup2NodePermissionDto), - typeof (UserGroup2AppDto), - typeof (UserStartNodeDto), - typeof (ContentNuDto), - typeof (DocumentVersionDto), - typeof (KeyValueDto), - typeof (UserLoginDto), - typeof (ConsentDto), - typeof (AuditEntryDto), - typeof (ContentVersionCultureVariationDto), - typeof (DocumentCultureVariationDto), - typeof (ContentScheduleDto) - }; - - /// - /// Drops all Umbraco tables in the db. - /// - internal void UninstallDatabaseSchema() - { - _logger.Info("Start UninstallDatabaseSchema"); - - foreach (var table in OrderedTables.AsEnumerable().Reverse()) - { - var tableNameAttribute = table.FirstAttribute(); - var tableName = tableNameAttribute == null ? table.Name : tableNameAttribute.Value; - - _logger.Info("Uninstall {TableName}", tableName); - - try - { - if (TableExists(tableName)) - DropTable(tableName); - } - catch (Exception ex) - { - //swallow this for now, not sure how best to handle this with diff databases... though this is internal - // and only used for unit tests. If this fails its because the table doesn't exist... generally! - _logger.Error(ex, "Could not drop table {TableName}", tableName); - } - } - } - - /// - /// Initializes the database by creating the umbraco db schema. - /// - public void InitializeDatabaseSchema() - { - var e = new DatabaseCreationEventArgs(); - FireBeforeCreation(e); - - if (e.Cancel == false) - { - var dataCreation = new DatabaseDataCreator(_database, _logger); - foreach (var table in OrderedTables) - CreateTable(false, table, dataCreation); - } - - FireAfterCreation(e); - } - - /// - /// Validates the schema of the current database. - /// - public DatabaseSchemaResult ValidateSchema() - { - var result = new DatabaseSchemaResult(SqlSyntax); - - result.IndexDefinitions.AddRange(SqlSyntax.GetDefinedIndexes(_database) - .Select(x => new DbIndexDefinition(x))); - - result.TableDefinitions.AddRange(OrderedTables - .Select(x => DefinitionFactory.GetTableDefinition(x, SqlSyntax))); - - ValidateDbTables(result); - ValidateDbColumns(result); - ValidateDbIndexes(result); - ValidateDbConstraints(result); - - return result; - } - - /// - /// This validates the Primary/Foreign keys in the database - /// - /// - /// - /// This does not validate any database constraints that are not PKs or FKs because Umbraco does not create a database with non PK/FK contraints. - /// Any unique "constraints" in the database are done with unique indexes. - /// - private void ValidateDbConstraints(DatabaseSchemaResult result) - { - //MySql doesn't conform to the "normal" naming of constraints, so there is currently no point in doing these checks. - //TODO: At a later point we do other checks for MySql, but ideally it should be necessary to do special checks for different providers. - // ALso note that to get the constraints for MySql we have to open a connection which we currently have not. - if (SqlSyntax is MySqlSyntaxProvider) - return; - - //Check constraints in configured database against constraints in schema - var constraintsInDatabase = SqlSyntax.GetConstraintsPerColumn(_database).DistinctBy(x => x.Item3).ToList(); - var foreignKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("FK_")).Select(x => x.Item3).ToList(); - var primaryKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("PK_")).Select(x => x.Item3).ToList(); - - var unknownConstraintsInDatabase = - constraintsInDatabase.Where( - x => - x.Item3.InvariantStartsWith("FK_") == false && x.Item3.InvariantStartsWith("PK_") == false && - x.Item3.InvariantStartsWith("IX_") == false).Select(x => x.Item3).ToList(); - var foreignKeysInSchema = result.TableDefinitions.SelectMany(x => x.ForeignKeys.Select(y => y.Name)).ToList(); - var primaryKeysInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => y.PrimaryKeyName)) - .Where(x => x.IsNullOrWhiteSpace() == false).ToList(); - - //Add valid and invalid foreign key differences to the result object - // We'll need to do invariant contains with case insensitivity because foreign key, primary key, and even index naming w/ MySQL is not standardized - // In theory you could have: FK_ or fk_ ...or really any standard that your development department (or developer) chooses to use. - foreach (var unknown in unknownConstraintsInDatabase) - { - if (foreignKeysInSchema.InvariantContains(unknown) || primaryKeysInSchema.InvariantContains(unknown)) - { - result.ValidConstraints.Add(unknown); - } - else - { - result.Errors.Add(new Tuple("Unknown", unknown)); - } - } - - //Foreign keys: - - var validForeignKeyDifferences = foreignKeysInDatabase.Intersect(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase); - foreach (var foreignKey in validForeignKeyDifferences) - { - result.ValidConstraints.Add(foreignKey); - } - var invalidForeignKeyDifferences = - foreignKeysInDatabase.Except(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(foreignKeysInSchema.Except(foreignKeysInDatabase, StringComparer.InvariantCultureIgnoreCase)); - foreach (var foreignKey in invalidForeignKeyDifferences) - { - result.Errors.Add(new Tuple("Constraint", foreignKey)); - } - - - //Primary keys: - - //Add valid and invalid primary key differences to the result object - var validPrimaryKeyDifferences = primaryKeysInDatabase.Intersect(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase); - foreach (var primaryKey in validPrimaryKeyDifferences) - { - result.ValidConstraints.Add(primaryKey); - } - var invalidPrimaryKeyDifferences = - primaryKeysInDatabase.Except(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(primaryKeysInSchema.Except(primaryKeysInDatabase, StringComparer.InvariantCultureIgnoreCase)); - foreach (var primaryKey in invalidPrimaryKeyDifferences) - { - result.Errors.Add(new Tuple("Constraint", primaryKey)); - } - - } - - private void ValidateDbColumns(DatabaseSchemaResult result) - { - //Check columns in configured database against columns in schema - var columnsInDatabase = SqlSyntax.GetColumnsInSchema(_database); - var columnsPerTableInDatabase = columnsInDatabase.Select(x => string.Concat(x.TableName, ",", x.ColumnName)).ToList(); - var columnsPerTableInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => string.Concat(y.TableName, ",", y.Name))).ToList(); - //Add valid and invalid column differences to the result object - var validColumnDifferences = columnsPerTableInDatabase.Intersect(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase); - foreach (var column in validColumnDifferences) - { - result.ValidColumns.Add(column); - } - - var invalidColumnDifferences = - columnsPerTableInDatabase.Except(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(columnsPerTableInSchema.Except(columnsPerTableInDatabase, StringComparer.InvariantCultureIgnoreCase)); - foreach (var column in invalidColumnDifferences) - { - result.Errors.Add(new Tuple("Column", column)); - } - } - - private void ValidateDbTables(DatabaseSchemaResult result) - { - //Check tables in configured database against tables in schema - var tablesInDatabase = SqlSyntax.GetTablesInSchema(_database).ToList(); - var tablesInSchema = result.TableDefinitions.Select(x => x.Name).ToList(); - //Add valid and invalid table differences to the result object - var validTableDifferences = tablesInDatabase.Intersect(tablesInSchema, StringComparer.InvariantCultureIgnoreCase); - foreach (var tableName in validTableDifferences) - { - result.ValidTables.Add(tableName); - } - - var invalidTableDifferences = - tablesInDatabase.Except(tablesInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(tablesInSchema.Except(tablesInDatabase, StringComparer.InvariantCultureIgnoreCase)); - foreach (var tableName in invalidTableDifferences) - { - result.Errors.Add(new Tuple("Table", tableName)); - } - } - - private void ValidateDbIndexes(DatabaseSchemaResult result) - { - //These are just column indexes NOT constraints or Keys - //var colIndexesInDatabase = result.DbIndexDefinitions.Where(x => x.IndexName.InvariantStartsWith("IX_")).Select(x => x.IndexName).ToList(); - var colIndexesInDatabase = result.IndexDefinitions.Select(x => x.IndexName).ToList(); - var indexesInSchema = result.TableDefinitions.SelectMany(x => x.Indexes.Select(y => y.Name)).ToList(); - - //Add valid and invalid index differences to the result object - var validColIndexDifferences = colIndexesInDatabase.Intersect(indexesInSchema, StringComparer.InvariantCultureIgnoreCase); - foreach (var index in validColIndexDifferences) - { - result.ValidIndexes.Add(index); - } - - var invalidColIndexDifferences = - colIndexesInDatabase.Except(indexesInSchema, StringComparer.InvariantCultureIgnoreCase) - .Union(indexesInSchema.Except(colIndexesInDatabase, StringComparer.InvariantCultureIgnoreCase)); - foreach (var index in invalidColIndexDifferences) - { - result.Errors.Add(new Tuple("Index", index)); - } - } - - #region Events - - /// - /// The save event handler - /// - internal delegate void DatabaseEventHandler(DatabaseCreationEventArgs e); - - /// - /// Occurs when [before save]. - /// - internal static event DatabaseEventHandler BeforeCreation; - /// - /// Raises the event. - /// - /// The instance containing the event data. - internal virtual void FireBeforeCreation(DatabaseCreationEventArgs e) - { - BeforeCreation?.Invoke(e); - } - - /// - /// Occurs when [after save]. - /// - internal static event DatabaseEventHandler AfterCreation; - /// - /// Raises the event. - /// - /// The instance containing the event data. - internal virtual void FireAfterCreation(DatabaseCreationEventArgs e) - { - AfterCreation?.Invoke(e); - } - - #endregion - - #region Utilities - - /// - /// Returns whether a table with the specified exists in the database. - /// - /// The name of the table. - /// true if the table exists; otherwise false. - /// - /// - /// if (schemaHelper.TableExist("MyTable")) - /// { - /// // do something when the table exists - /// } - /// - /// - public bool TableExists(string tableName) - { - return SqlSyntax.DoesTableExist(_database, tableName); - } - - /// - /// Returns whether the table for the specified exists in the database. - /// - /// The type representing the DTO/table. - /// true if the table exists; otherwise false. - /// - /// - /// if (schemaHelper.TableExist<MyDto>) - /// { - /// // do something when the table exists - /// } - /// - /// - /// - /// If has been decorated with an , the name from that - /// attribute will be used for the table name. If the attribute is not present, the name - /// will be used instead. - /// - public bool TableExists() { - var table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); - return table != null && TableExists(table.Name); } - - /// - /// Creates a new table in the database based on the type of . - /// - /// The type representing the DTO/table. - /// Whether the table should be overwritten if it already exists. - /// - /// If has been decorated with an , the name from that - /// attribute will be used for the table name. If the attribute is not present, the name - /// will be used instead. - /// - /// If a table with the same name already exists, the parameter will determine - /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will - /// not do anything if the parameter is false. - /// - internal void CreateTable(bool overwrite = false) - where T : new() - { - var tableType = typeof(T); - CreateTable(overwrite, tableType, new DatabaseDataCreator(_database, _logger)); - } - - /// - /// Creates a new table in the database for the specified . - /// - /// Whether the table should be overwritten if it already exists. - /// The the representing the table. - /// - /// - /// If has been decorated with an , the name from - /// that attribute will be used for the table name. If the attribute is not present, the name - /// will be used instead. - /// - /// If a table with the same name already exists, the parameter will determine - /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will - /// not do anything if the parameter is false. - /// - public void CreateTable(bool overwrite, Type modelType, DatabaseDataCreator dataCreation) - { - var tableDefinition = DefinitionFactory.GetTableDefinition(modelType, SqlSyntax); - var tableName = tableDefinition.Name; - - var createSql = SqlSyntax.Format(tableDefinition); - var createPrimaryKeySql = SqlSyntax.FormatPrimaryKey(tableDefinition); - var foreignSql = SqlSyntax.Format(tableDefinition.ForeignKeys); - var indexSql = SqlSyntax.Format(tableDefinition.Indexes); - - var tableExist = TableExists(tableName); - if (overwrite && tableExist) - { - _logger.Info("Table {TableName} already exists, but will be recreated", tableName); - - DropTable(tableName); - tableExist = false; - } - - if (tableExist == false) - { - using (var transaction = _database.GetTransaction()) - { - //Execute the Create Table sql - var created = _database.Execute(new Sql(createSql)); - _logger.Info("Create Table {TableName} ({Created}): \n {Sql}", tableName, created, createSql); - - //If any statements exists for the primary key execute them here - if (string.IsNullOrEmpty(createPrimaryKeySql) == false) - { - var createdPk = _database.Execute(new Sql(createPrimaryKeySql)); - _logger.Info("Create Primary Key ({CreatedPk}):\n {Sql}", createdPk, createPrimaryKeySql); - } - - //Turn on identity insert if db provider is not mysql - if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) - _database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} ON ")); - - //Call the NewTable-event to trigger the insert of base/default data - //OnNewTable(tableName, _db, e, _logger); - - dataCreation.InitializeBaseData(tableName); - - //Turn off identity insert if db provider is not mysql - if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) - _database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} OFF;")); - - //Special case for MySql - if (SqlSyntax is MySqlSyntaxProvider && tableName.Equals("umbracoUser")) - { - _database.Update("SET id = @IdAfter WHERE id = @IdBefore AND userLogin = @Login", new { IdAfter = 0, IdBefore = 1, Login = "admin" }); - } - - //Loop through index statements and execute sql - foreach (var sql in indexSql) - { - var createdIndex = _database.Execute(new Sql(sql)); - _logger.Info("Create Index ({CreatedIndex}):\n {Sql}", createdIndex, sql); - } - - //Loop through foreignkey statements and execute sql - foreach (var sql in foreignSql) - { - var createdFk = _database.Execute(new Sql(sql)); - _logger.Info("Create Foreign Key ({CreatedFk}):\n {Sql}", createdFk, sql); - } - - transaction.Complete(); - - if (overwrite) - { - _logger.Info("Table {TableName} was recreated", tableName); - } - else - { - _logger.Info("New table {TableName} was created", tableName); - } - } - } - else - { - // The table exists and was not recreated/overwritten. - _logger.Info("Table {TableName} already exists - no changes were made", tableName); - } - } - - /// - /// Drops the table for the specified . - /// - /// The type representing the DTO/table. - /// - /// - /// schemaHelper.DropTable<MyDto>); - /// - /// - /// - /// If has been decorated with an , the name from that - /// attribute will be used for the table name. If the attribute is not present, the name - /// will be used instead. - /// - public void DropTable(string tableName) - { - var sql = new Sql(string.Format(SqlSyntax.DropTable, SqlSyntax.GetQuotedTableName(tableName))); - _database.Execute(sql); - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using NPoco; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Migrations.Install +{ + /// + /// Creates the initial database schema during install. + /// + internal class DatabaseSchemaCreator + { + private readonly IUmbracoDatabase _database; + private readonly ILogger _logger; + + public DatabaseSchemaCreator(IUmbracoDatabase database, ILogger logger) + { + _database = database; + _logger = logger; + } + + private ISqlSyntaxProvider SqlSyntax => _database.SqlContext.SqlSyntax; + + // all tables, in order + public static readonly List OrderedTables = new List + { + typeof (UserDto), + typeof (NodeDto), + typeof (ContentTypeDto), + typeof (TemplateDto), + typeof (ContentDto), + typeof (ContentVersionDto), + typeof (MediaVersionDto), + typeof (DocumentDto), + typeof (ContentTypeTemplateDto), + typeof (DataTypeDto), + typeof (DictionaryDto), + typeof (LanguageDto), + typeof (LanguageTextDto), + typeof (DomainDto), + typeof (LogDto), + typeof (MacroDto), + typeof (MacroPropertyDto), + typeof (MemberTypeDto), + typeof (MemberDto), + typeof (Member2MemberGroupDto), + typeof (ContentXmlDto), + typeof (PreviewXmlDto), + typeof (PropertyTypeGroupDto), + typeof (PropertyTypeDto), + typeof (PropertyDataDto), + typeof (RelationTypeDto), + typeof (RelationDto), + typeof (TagDto), + typeof (TagRelationshipDto), + typeof (ContentType2ContentTypeDto), + typeof (ContentTypeAllowedContentTypeDto), + typeof (User2NodeNotifyDto), + typeof (ServerRegistrationDto), + typeof (AccessDto), + typeof (AccessRuleDto), + typeof (CacheInstructionDto), + typeof (ExternalLoginDto), + typeof (RedirectUrlDto), + typeof (LockDto), + typeof (UserGroupDto), + typeof (User2UserGroupDto), + typeof (UserGroup2NodePermissionDto), + typeof (UserGroup2AppDto), + typeof (UserStartNodeDto), + typeof (ContentNuDto), + typeof (DocumentVersionDto), + typeof (KeyValueDto), + typeof (UserLoginDto), + typeof (ConsentDto), + typeof (AuditEntryDto), + typeof (ContentVersionCultureVariationDto), + typeof (DocumentCultureVariationDto), + typeof (ContentScheduleDto) + }; + + /// + /// Drops all Umbraco tables in the db. + /// + internal void UninstallDatabaseSchema() + { + _logger.Info("Start UninstallDatabaseSchema"); + + foreach (var table in OrderedTables.AsEnumerable().Reverse()) + { + var tableNameAttribute = table.FirstAttribute(); + var tableName = tableNameAttribute == null ? table.Name : tableNameAttribute.Value; + + _logger.Info("Uninstall {TableName}", tableName); + + try + { + if (TableExists(tableName)) + DropTable(tableName); + } + catch (Exception ex) + { + //swallow this for now, not sure how best to handle this with diff databases... though this is internal + // and only used for unit tests. If this fails its because the table doesn't exist... generally! + _logger.Error(ex, "Could not drop table {TableName}", tableName); + } + } + } + + /// + /// Initializes the database by creating the umbraco db schema. + /// + /// This needs to execute as part of a transaction. + public void InitializeDatabaseSchema() + { + if (!_database.InTransaction) + throw new InvalidOperationException("Database is not in a transaction."); + + var e = new DatabaseCreationEventArgs(); + FireBeforeCreation(e); + + if (e.Cancel == false) + { + var dataCreation = new DatabaseDataCreator(_database, _logger); + foreach (var table in OrderedTables) + CreateTable(false, table, dataCreation); + } + + FireAfterCreation(e); + } + + /// + /// Validates the schema of the current database. + /// + public DatabaseSchemaResult ValidateSchema() + { + var result = new DatabaseSchemaResult(SqlSyntax); + + result.IndexDefinitions.AddRange(SqlSyntax.GetDefinedIndexes(_database) + .Select(x => new DbIndexDefinition(x))); + + result.TableDefinitions.AddRange(OrderedTables + .Select(x => DefinitionFactory.GetTableDefinition(x, SqlSyntax))); + + ValidateDbTables(result); + ValidateDbColumns(result); + ValidateDbIndexes(result); + ValidateDbConstraints(result); + + return result; + } + + /// + /// This validates the Primary/Foreign keys in the database + /// + /// + /// + /// This does not validate any database constraints that are not PKs or FKs because Umbraco does not create a database with non PK/FK contraints. + /// Any unique "constraints" in the database are done with unique indexes. + /// + private void ValidateDbConstraints(DatabaseSchemaResult result) + { + //MySql doesn't conform to the "normal" naming of constraints, so there is currently no point in doing these checks. + //TODO: At a later point we do other checks for MySql, but ideally it should be necessary to do special checks for different providers. + // ALso note that to get the constraints for MySql we have to open a connection which we currently have not. + if (SqlSyntax is MySqlSyntaxProvider) + return; + + //Check constraints in configured database against constraints in schema + var constraintsInDatabase = SqlSyntax.GetConstraintsPerColumn(_database).DistinctBy(x => x.Item3).ToList(); + var foreignKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("FK_")).Select(x => x.Item3).ToList(); + var primaryKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("PK_")).Select(x => x.Item3).ToList(); + + var unknownConstraintsInDatabase = + constraintsInDatabase.Where( + x => + x.Item3.InvariantStartsWith("FK_") == false && x.Item3.InvariantStartsWith("PK_") == false && + x.Item3.InvariantStartsWith("IX_") == false).Select(x => x.Item3).ToList(); + var foreignKeysInSchema = result.TableDefinitions.SelectMany(x => x.ForeignKeys.Select(y => y.Name)).ToList(); + var primaryKeysInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => y.PrimaryKeyName)) + .Where(x => x.IsNullOrWhiteSpace() == false).ToList(); + + //Add valid and invalid foreign key differences to the result object + // We'll need to do invariant contains with case insensitivity because foreign key, primary key, and even index naming w/ MySQL is not standardized + // In theory you could have: FK_ or fk_ ...or really any standard that your development department (or developer) chooses to use. + foreach (var unknown in unknownConstraintsInDatabase) + { + if (foreignKeysInSchema.InvariantContains(unknown) || primaryKeysInSchema.InvariantContains(unknown)) + { + result.ValidConstraints.Add(unknown); + } + else + { + result.Errors.Add(new Tuple("Unknown", unknown)); + } + } + + //Foreign keys: + + var validForeignKeyDifferences = foreignKeysInDatabase.Intersect(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase); + foreach (var foreignKey in validForeignKeyDifferences) + { + result.ValidConstraints.Add(foreignKey); + } + var invalidForeignKeyDifferences = + foreignKeysInDatabase.Except(foreignKeysInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(foreignKeysInSchema.Except(foreignKeysInDatabase, StringComparer.InvariantCultureIgnoreCase)); + foreach (var foreignKey in invalidForeignKeyDifferences) + { + result.Errors.Add(new Tuple("Constraint", foreignKey)); + } + + + //Primary keys: + + //Add valid and invalid primary key differences to the result object + var validPrimaryKeyDifferences = primaryKeysInDatabase.Intersect(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase); + foreach (var primaryKey in validPrimaryKeyDifferences) + { + result.ValidConstraints.Add(primaryKey); + } + var invalidPrimaryKeyDifferences = + primaryKeysInDatabase.Except(primaryKeysInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(primaryKeysInSchema.Except(primaryKeysInDatabase, StringComparer.InvariantCultureIgnoreCase)); + foreach (var primaryKey in invalidPrimaryKeyDifferences) + { + result.Errors.Add(new Tuple("Constraint", primaryKey)); + } + + } + + private void ValidateDbColumns(DatabaseSchemaResult result) + { + //Check columns in configured database against columns in schema + var columnsInDatabase = SqlSyntax.GetColumnsInSchema(_database); + var columnsPerTableInDatabase = columnsInDatabase.Select(x => string.Concat(x.TableName, ",", x.ColumnName)).ToList(); + var columnsPerTableInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => string.Concat(y.TableName, ",", y.Name))).ToList(); + //Add valid and invalid column differences to the result object + var validColumnDifferences = columnsPerTableInDatabase.Intersect(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase); + foreach (var column in validColumnDifferences) + { + result.ValidColumns.Add(column); + } + + var invalidColumnDifferences = + columnsPerTableInDatabase.Except(columnsPerTableInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(columnsPerTableInSchema.Except(columnsPerTableInDatabase, StringComparer.InvariantCultureIgnoreCase)); + foreach (var column in invalidColumnDifferences) + { + result.Errors.Add(new Tuple("Column", column)); + } + } + + private void ValidateDbTables(DatabaseSchemaResult result) + { + //Check tables in configured database against tables in schema + var tablesInDatabase = SqlSyntax.GetTablesInSchema(_database).ToList(); + var tablesInSchema = result.TableDefinitions.Select(x => x.Name).ToList(); + //Add valid and invalid table differences to the result object + var validTableDifferences = tablesInDatabase.Intersect(tablesInSchema, StringComparer.InvariantCultureIgnoreCase); + foreach (var tableName in validTableDifferences) + { + result.ValidTables.Add(tableName); + } + + var invalidTableDifferences = + tablesInDatabase.Except(tablesInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(tablesInSchema.Except(tablesInDatabase, StringComparer.InvariantCultureIgnoreCase)); + foreach (var tableName in invalidTableDifferences) + { + result.Errors.Add(new Tuple("Table", tableName)); + } + } + + private void ValidateDbIndexes(DatabaseSchemaResult result) + { + //These are just column indexes NOT constraints or Keys + //var colIndexesInDatabase = result.DbIndexDefinitions.Where(x => x.IndexName.InvariantStartsWith("IX_")).Select(x => x.IndexName).ToList(); + var colIndexesInDatabase = result.IndexDefinitions.Select(x => x.IndexName).ToList(); + var indexesInSchema = result.TableDefinitions.SelectMany(x => x.Indexes.Select(y => y.Name)).ToList(); + + //Add valid and invalid index differences to the result object + var validColIndexDifferences = colIndexesInDatabase.Intersect(indexesInSchema, StringComparer.InvariantCultureIgnoreCase); + foreach (var index in validColIndexDifferences) + { + result.ValidIndexes.Add(index); + } + + var invalidColIndexDifferences = + colIndexesInDatabase.Except(indexesInSchema, StringComparer.InvariantCultureIgnoreCase) + .Union(indexesInSchema.Except(colIndexesInDatabase, StringComparer.InvariantCultureIgnoreCase)); + foreach (var index in invalidColIndexDifferences) + { + result.Errors.Add(new Tuple("Index", index)); + } + } + + #region Events + + /// + /// The save event handler + /// + internal delegate void DatabaseEventHandler(DatabaseCreationEventArgs e); + + /// + /// Occurs when [before save]. + /// + internal static event DatabaseEventHandler BeforeCreation; + /// + /// Raises the event. + /// + /// The instance containing the event data. + internal virtual void FireBeforeCreation(DatabaseCreationEventArgs e) + { + BeforeCreation?.Invoke(e); + } + + /// + /// Occurs when [after save]. + /// + internal static event DatabaseEventHandler AfterCreation; + /// + /// Raises the event. + /// + /// The instance containing the event data. + internal virtual void FireAfterCreation(DatabaseCreationEventArgs e) + { + AfterCreation?.Invoke(e); + } + + #endregion + + #region Utilities + + /// + /// Returns whether a table with the specified exists in the database. + /// + /// The name of the table. + /// true if the table exists; otherwise false. + /// + /// + /// if (schemaHelper.TableExist("MyTable")) + /// { + /// // do something when the table exists + /// } + /// + /// + public bool TableExists(string tableName) + { + return SqlSyntax.DoesTableExist(_database, tableName); + } + + /// + /// Returns whether the table for the specified exists in the database. + /// + /// The type representing the DTO/table. + /// true if the table exists; otherwise false. + /// + /// + /// if (schemaHelper.TableExist<MyDto>) + /// { + /// // do something when the table exists + /// } + /// + /// + /// + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// + public bool TableExists() + { + var table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); + return table != null && TableExists(table.Name); + } + + /// + /// Creates a new table in the database based on the type of . + /// + /// The type representing the DTO/table. + /// Whether the table should be overwritten if it already exists. + /// + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// + /// If a table with the same name already exists, the parameter will determine + /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will + /// not do anything if the parameter is false. + /// + internal void CreateTable(bool overwrite = false) + where T : new() + { + var tableType = typeof(T); + CreateTable(overwrite, tableType, new DatabaseDataCreator(_database, _logger)); + } + + /// + /// Creates a new table in the database for the specified . + /// + /// Whether the table should be overwritten if it already exists. + /// The the representing the table. + /// + /// + /// If has been decorated with an , the name from + /// that attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// + /// If a table with the same name already exists, the parameter will determine + /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will + /// not do anything if the parameter is false. + /// + /// This need to execute as part of a transaction. + /// + public void CreateTable(bool overwrite, Type modelType, DatabaseDataCreator dataCreation) + { + if (!_database.InTransaction) + throw new InvalidOperationException("Database is not in a transaction."); + + var tableDefinition = DefinitionFactory.GetTableDefinition(modelType, SqlSyntax); + var tableName = tableDefinition.Name; + + var createSql = SqlSyntax.Format(tableDefinition); + var createPrimaryKeySql = SqlSyntax.FormatPrimaryKey(tableDefinition); + var foreignSql = SqlSyntax.Format(tableDefinition.ForeignKeys); + var indexSql = SqlSyntax.Format(tableDefinition.Indexes); + + var tableExist = TableExists(tableName); + if (overwrite && tableExist) + { + _logger.Info("Table {TableName} already exists, but will be recreated", tableName); + + DropTable(tableName); + tableExist = false; + } + + if (tableExist) + { + // The table exists and was not recreated/overwritten. + _logger.Info("Table {TableName} already exists - no changes were made", tableName); + return; + } + + //Execute the Create Table sql + var created = _database.Execute(new Sql(createSql)); + _logger.Info("Create Table {TableName} ({Created}): \n {Sql}", tableName, created, createSql); + + //If any statements exists for the primary key execute them here + if (string.IsNullOrEmpty(createPrimaryKeySql) == false) + { + var createdPk = _database.Execute(new Sql(createPrimaryKeySql)); + _logger.Info("Create Primary Key ({CreatedPk}):\n {Sql}", createdPk, createPrimaryKeySql); + } + + //Turn on identity insert if db provider is not mysql + if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) + _database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} ON ")); + + //Call the NewTable-event to trigger the insert of base/default data + //OnNewTable(tableName, _db, e, _logger); + + dataCreation.InitializeBaseData(tableName); + + //Turn off identity insert if db provider is not mysql + if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) + _database.Execute(new Sql($"SET IDENTITY_INSERT {SqlSyntax.GetQuotedTableName(tableName)} OFF;")); + + //Special case for MySql + if (SqlSyntax is MySqlSyntaxProvider && tableName.Equals("umbracoUser")) + { + _database.Update("SET id = @IdAfter WHERE id = @IdBefore AND userLogin = @Login", new { IdAfter = 0, IdBefore = 1, Login = "admin" }); + } + + //Loop through index statements and execute sql + foreach (var sql in indexSql) + { + var createdIndex = _database.Execute(new Sql(sql)); + _logger.Info("Create Index ({CreatedIndex}):\n {Sql}", createdIndex, sql); + } + + //Loop through foreignkey statements and execute sql + foreach (var sql in foreignSql) + { + var createdFk = _database.Execute(new Sql(sql)); + _logger.Info("Create Foreign Key ({CreatedFk}):\n {Sql}", createdFk, sql); + } + + if (overwrite) + { + _logger.Info("Table {TableName} was recreated", tableName); + } + else + { + _logger.Info("New table {TableName} was created", tableName); + + } + } + + /// + /// Drops the table for the specified . + /// + /// The type representing the DTO/table. + /// + /// + /// schemaHelper.DropTable<MyDto>); + /// + /// + /// + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// + public void DropTable(string tableName) + { + var sql = new Sql(string.Format(SqlSyntax.DropTable, SqlSyntax.GetQuotedTableName(tableName))); + _database.Execute(sql); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Migrations/MigrationBuilder.cs b/src/Umbraco.Core/Migrations/MigrationBuilder.cs index 3d8c88c771..d2d2b7d32a 100644 --- a/src/Umbraco.Core/Migrations/MigrationBuilder.cs +++ b/src/Umbraco.Core/Migrations/MigrationBuilder.cs @@ -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); } } } diff --git a/src/Umbraco.Core/Migrations/PostMigrationCollectionBuilder.cs b/src/Umbraco.Core/Migrations/PostMigrationCollectionBuilder.cs index 63cdaf4454..b23d4f1c9c 100644 --- a/src/Umbraco.Core/Migrations/PostMigrationCollectionBuilder.cs +++ b/src/Umbraco.Core/Migrations/PostMigrationCollectionBuilder.cs @@ -1,16 +1,11 @@ -using LightInject; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; namespace Umbraco.Core.Migrations { public class PostMigrationCollectionBuilder : LazyCollectionBuilderBase { - public PostMigrationCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override PostMigrationCollectionBuilder This => this; - protected override ILifetime CollectionLifetime => null; // transient + protected override Lifetime CollectionLifetime => Lifetime.Transient; } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index f7e1ee9921..b469c02a3c 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -118,7 +118,9 @@ namespace Umbraco.Core.Migrations.Upgrade To("{EE429F1B-9B26-43CA-89F8-A86017C809A3}"); To("{08919C4B-B431-449C-90EC-2B8445B5C6B1}"); To("{7EB0254C-CB8B-4C75-B15B-D48C55B449EB}"); + To("{648A2D5F-7467-48F8-B309-E99CEEE00E2A}"); // fixed version To("{C39BF2A7-1454-4047-BBFE-89E40F66ED63}"); + To("{64EBCE53-E1F0-463A-B40B-E98EFCCA8AE2}"); //FINAL diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs index e8fd4f409e..008b3e4b5f 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropTaskTables.cs @@ -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(); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeRedirectUrlVariant.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeRedirectUrlVariant.cs new file mode 100644 index 0000000000..2e366c7c14 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeRedirectUrlVariant.cs @@ -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("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(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs index aa498583ff..6ddd49841d 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs @@ -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 diff --git a/src/Umbraco.Core/Models/IRedirectUrl.cs b/src/Umbraco.Core/Models/IRedirectUrl.cs index f3c65fe89c..e066881645 100644 --- a/src/Umbraco.Core/Models/IRedirectUrl.cs +++ b/src/Umbraco.Core/Models/IRedirectUrl.cs @@ -27,11 +27,18 @@ namespace Umbraco.Core.Models [DataMember] DateTime CreateDateUtc { get; set; } + /// + /// Gets or sets the culture. + /// + [DataMember] + string Culture { get; set; } + /// /// Gets or sets the redirect url route. /// /// Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo. [DataMember] string Url { get; set; } + } } diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs index 23c232324a..f163124c2d 100644 --- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using System.Security.Claims; using System.Threading.Tasks; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; @@ -66,7 +67,7 @@ namespace Umbraco.Core.Models.Identity _startContentIds = new int[] { }; _groups = new IReadOnlyUserGroup[] { }; _allowedSections = new string[] { }; - _culture = UmbracoConfig.For.GlobalSettings().DefaultUILanguage; //fixme inject somehow? + _culture = Current.Config.Global().DefaultUILanguage; //fixme inject somehow? _groups = new IReadOnlyUserGroup[0]; _roles = new ObservableCollection>(); _roles.CollectionChanged += _roles_CollectionChanged; @@ -83,7 +84,7 @@ namespace Umbraco.Core.Models.Identity _startContentIds = new int[] { }; _groups = new IReadOnlyUserGroup[] { }; _allowedSections = new string[] { }; - _culture = UmbracoConfig.For.GlobalSettings().DefaultUILanguage; //fixme inject somehow? + _culture = Current.Config.Global().DefaultUILanguage; //fixme inject somehow? _groups = groups.ToArray(); _roles = new ObservableCollection>(_groups.Select(x => new IdentityUserRole { @@ -442,6 +443,6 @@ namespace Umbraco.Core.Models.Identity groups => groups.GetHashCode()); } - + } } diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs index e190c8ad3b..03f8f87cd3 100644 --- a/src/Umbraco.Core/Models/Language.cs +++ b/src/Umbraco.Core/Models/Language.cs @@ -88,7 +88,7 @@ namespace Umbraco.Core.Models try { - var globalSettings = (IGlobalSettings) Composing.Current.Container.GetInstance(typeof(IGlobalSettings)); + var globalSettings = (IGlobalSettings) Composing.Current.Factory.GetInstance(typeof(IGlobalSettings)); var defaultUiCulture = CultureInfo.GetCultureInfo(globalSettings.DefaultUILanguage); Thread.CurrentThread.CurrentUICulture = defaultUiCulture; diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 0694194996..9387d3eb8f 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -27,7 +27,7 @@ namespace Umbraco.Core.Models.Membership { SessionTimeout = 60; _userGroups = new HashSet(); - _language = UmbracoConfig.For.GlobalSettings().DefaultUILanguage; //fixme inject somehow? + _language = Current.Config.Global().DefaultUILanguage; //fixme inject somehow? _isApproved = true; _isLockedOut = false; _startContentIds = new int[] { }; @@ -453,7 +453,7 @@ namespace Umbraco.Core.Models.Membership base.PerformDeepClone(clone); var clonedEntity = (User)clone; - + //manually clone the start node props clonedEntity._startContentIds = _startContentIds.ToArray(); clonedEntity._startMediaIds = _startMediaIds.ToArray(); @@ -483,7 +483,7 @@ namespace Umbraco.Core.Models.Membership //need to create new collections otherwise they'll get copied by ref clonedEntity._userGroups = new HashSet(_userGroups); clonedEntity._allowedSections = _allowedSections != null ? new List(_allowedSections) : null; - + } /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs index 67758c1c69..ee3fd62985 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs @@ -112,7 +112,7 @@ namespace Umbraco.Core.Models.PublishedContent if (ctor != null) return ctor(); var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType); - ctor = modelInfo.ListCtor = ReflectionUtilities.EmitConstuctor>(declaring: listType); + ctor = modelInfo.ListCtor = ReflectionUtilities.EmitConstructor>(declaring: listType); return ctor(); } diff --git a/src/Umbraco.Core/Models/RedirectUrl.cs b/src/Umbraco.Core/Models/RedirectUrl.cs index 187d9fdd6e..55b799244e 100644 --- a/src/Umbraco.Core/Models/RedirectUrl.cs +++ b/src/Umbraco.Core/Models/RedirectUrl.cs @@ -28,12 +28,14 @@ namespace Umbraco.Core.Models public readonly PropertyInfo ContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentId); public readonly PropertyInfo ContentKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ContentKey); public readonly PropertyInfo CreateDateUtcSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDateUtc); + public readonly PropertyInfo CultureSelector = ExpressionHelper.GetPropertyInfo(x => x.Culture); public readonly PropertyInfo UrlSelector = ExpressionHelper.GetPropertyInfo(x => x.Url); } private int _contentId; private Guid _contentKey; private DateTime _createDateUtc; + private string _culture; private string _url; /// @@ -57,6 +59,13 @@ namespace Umbraco.Core.Models set { SetPropertyValueAndDetectChanges(value, ref _createDateUtc, Ps.Value.CreateDateUtcSelector); } } + /// + public string Culture + { + get { return _culture; } + set { SetPropertyValueAndDetectChanges(value, ref _culture, Ps.Value.CultureSelector); } + } + /// public string Url { diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 6733eea500..ba4d8cf590 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -105,7 +105,7 @@ namespace Umbraco.Core.Models } //use the custom avatar - var avatarUrl = Current.FileSystems.MediaFileSystem.GetUrl(user.Avatar); + var avatarUrl = Current.MediaFileSystem.GetUrl(user.Avatar); return new[] { avatarUrl + "?width=30&height=30&mode=crop", diff --git a/src/Umbraco.Core/Persistence/Dtos/RedirectUrlDto.cs b/src/Umbraco.Core/Persistence/Dtos/RedirectUrlDto.cs index b2bc990f6b..57e7138827 100644 --- a/src/Umbraco.Core/Persistence/Dtos/RedirectUrlDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/RedirectUrlDto.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence.Dtos // notes // - // we want a unique, non-clustered index on (url ASC, contentId ASC, createDate DESC) but the + // we want a unique, non-clustered index on (url ASC, contentId ASC, culture ASC, createDate DESC) but the // problem is that the index key must be 900 bytes max. should we run without an index? done // some perfs comparisons, and running with an index on a hash is only slightly slower on // inserts, and much faster on reads, so... we have an index on a hash. @@ -41,9 +41,13 @@ namespace Umbraco.Core.Persistence.Dtos [NullSetting(NullSetting = NullSettings.NotNull)] public string Url { get; set; } + [Column("culture")] + [NullSetting(NullSetting = NullSettings.Null)] + public string Culture { get; set; } + [Column("urlHash")] [NullSetting(NullSetting = NullSettings.NotNull)] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRedirectUrl", ForColumns = "urlHash, contentKey, createDateUtc")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRedirectUrl", ForColumns = "urlHash, contentKey, culture, createDateUtc")] [Length(40)] public string UrlHash { get; set; } } diff --git a/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs b/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs index 6d641cab3d..80819933f5 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MapperCollectionBuilder.cs @@ -1,19 +1,14 @@ -using LightInject; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; namespace Umbraco.Core.Persistence.Mappers { public class MapperCollectionBuilder : LazyCollectionBuilderBase { - public MapperCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override MapperCollectionBuilder This => this; - protected override void Initialize() + public override void RegisterWith(IRegister register) { - base.Initialize(); + base.RegisterWith(register); // default initializer registers // - service MapperCollectionBuilder, returns MapperCollectionBuilder @@ -21,10 +16,10 @@ namespace Umbraco.Core.Persistence.Mappers // we want to register extra // - service IMapperCollection, returns MappersCollectionBuilder's collection - Container.Register(factory => factory.GetInstance()); + register.Register(factory => factory.GetInstance()); } - public MapperCollectionBuilder AddCore() + public MapperCollectionBuilder AddCoreMappers() { Add(); Add(); diff --git a/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs index 47a56bb530..d05f4e007c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs @@ -14,8 +14,9 @@ namespace Umbraco.Core.Persistence.Repositories /// /// The Umbraco redirect url route. /// The content unique key. + /// The culture. /// - IRedirectUrl Get(string url, Guid contentKey); + IRedirectUrl Get(string url, Guid contentKey, string culture); /// /// Deletes a redirect url. diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs index 6c61fe7ad5..45f083bc6b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; using NPoco; using Umbraco.Core.Cache; -using Umbraco.Core.Composing.CompositionRoots; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -16,8 +14,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { internal class AuditRepository : NPocoRepositoryBase, IAuditRepository { - public AuditRepository(IScopeAccessor scopeAccessor, [Inject(RepositoryCompositionRoot.DisabledCache)] CacheHelper cache, ILogger logger) - : base(scopeAccessor, cache, logger) + public AuditRepository(IScopeAccessor scopeAccessor, ILogger logger) + : base(scopeAccessor, CacheHelper.NoCache, logger) { } protected override void PersistNewItem(IAuditItem entity) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/PartialViewMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/PartialViewMacroRepository.cs index 741bb98e7c..d707bcee10 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/PartialViewMacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/PartialViewMacroRepository.cs @@ -1,14 +1,12 @@ -using LightInject; -using Umbraco.Core.IO; +using Umbraco.Core.IO; using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories.Implement { internal class PartialViewMacroRepository : PartialViewRepository, IPartialViewMacroRepository { - - public PartialViewMacroRepository([Inject("PartialViewMacroFileSystem")] IFileSystem fileSystem) - : base(fileSystem) + public PartialViewMacroRepository(IFileSystems fileSystems) + : base(fileSystems.MacroPartialsFileSystem) { } protected override PartialViewType ViewType => PartialViewType.PartialViewMacro; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/PartialViewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/PartialViewRepository.cs index 2aa63813e5..d04bc47cd8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/PartialViewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/PartialViewRepository.cs @@ -2,7 +2,6 @@ using System.IO; using System.Linq; using System.Text; -using LightInject; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -10,7 +9,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { internal class PartialViewRepository : FileRepository, IPartialViewRepository { - public PartialViewRepository([Inject("PartialViewFileSystem")] IFileSystem fileSystem) + public PartialViewRepository(IFileSystems fileSystems) + : base(fileSystems.PartialViewsFileSystem) + { } + + protected PartialViewRepository(IFileSystem fileSystem) : base(fileSystem) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs index 5ec7fd6f3c..3ca937ffcd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs @@ -104,6 +104,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); ContentKey = redirectUrl.ContentKey, CreateDateUtc = redirectUrl.CreateDateUtc, Url = redirectUrl.Url, + Culture = redirectUrl.Culture, UrlHash = redirectUrl.Url.ToSHA1() }; } @@ -121,6 +122,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); url.ContentId = dto.ContentId; url.ContentKey = dto.ContentKey; url.CreateDateUtc = dto.CreateDateUtc; + url.Culture = dto.Culture; url.Url = dto.Url; return url; } @@ -130,10 +132,10 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); } } - public IRedirectUrl Get(string url, Guid contentKey) + public IRedirectUrl Get(string url, Guid contentKey, string culture) { var urlHash = url.ToSHA1(); - var sql = GetBaseQuery(false).Where(x => x.Url == url && x.UrlHash == urlHash && x.ContentKey == contentKey); + var sql = GetBaseQuery(false).Where(x => x.Url == url && x.UrlHash == urlHash && x.ContentKey == contentKey && x.Culture == culture); var dto = Database.Fetch(sql).FirstOrDefault(); return dto == null ? null : Map(dto); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index f79344028f..fb5ba00ea0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; using NPoco; using Umbraco.Core.Cache; -using Umbraco.Core.Composing.CompositionRoots; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -22,8 +20,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { private readonly IRelationTypeRepository _relationTypeRepository; - public RelationRepository(IScopeAccessor scopeAccessor, [Inject(RepositoryCompositionRoot.DisabledCache)] CacheHelper cache, ILogger logger, IRelationTypeRepository relationTypeRepository) - : base(scopeAccessor, cache, logger) + public RelationRepository(IScopeAccessor scopeAccessor, ILogger logger, IRelationTypeRepository relationTypeRepository) + : base(scopeAccessor, CacheHelper.NoCache, logger) { _relationTypeRepository = relationTypeRepository; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ScriptRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ScriptRepository.cs index d5719554c9..85b41a2a1c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ScriptRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ScriptRepository.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using LightInject; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -16,8 +15,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { private readonly IContentSection _contentConfig; - public ScriptRepository([Inject("ScriptFileSystem")] IFileSystem fileSystem, IContentSection contentConfig) - : base(fileSystem) + public ScriptRepository(IFileSystems fileSystems, IContentSection contentConfig) + : base(fileSystems.ScriptsFileSystem) { _contentConfig = contentConfig ?? throw new ArgumentNullException(nameof(contentConfig)); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs index 531df1ba13..2679f8f92f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs @@ -14,9 +14,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { internal class ServerRegistrationRepository : NPocoRepositoryBase, IServerRegistrationRepository { - // fixme - should we use NoCache instead of CreateDisabledCacheHelper?! public ServerRegistrationRepository(IScopeAccessor scopeAccessor, ILogger logger) - : base(scopeAccessor, CacheHelper.CreateDisabledCacheHelper(), logger) + : base(scopeAccessor, CacheHelper.NoCache, logger) { } protected override IRepositoryCachePolicy CreateCachePolicy() diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs index 73dcb44fef..4c02a8f4b5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using LightInject; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -12,8 +11,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class StylesheetRepository : FileRepository, IStylesheetRepository { - public StylesheetRepository([Inject("StylesheetFileSystem")] IFileSystem fileSystem) - : base(fileSystem) + public StylesheetRepository(IFileSystems fileSystems) + : base(fileSystems.StylesheetsFileSystem) { } #region Overrides of FileRepository diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs index 83876db599..19ef303ebe 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using LightInject; using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; @@ -30,13 +29,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly ViewHelper _viewHelper; private readonly MasterPageHelper _masterPageHelper; - public TemplateRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger, ITemplatesSection templateConfig, - [Inject(Constants.Composing.FileSystems.MasterpageFileSystem)] IFileSystem masterpageFileSystem, - [Inject(Constants.Composing.FileSystems.ViewFileSystem)] IFileSystem viewFileSystem) + public TemplateRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger, ITemplatesSection templateConfig, IFileSystems fileSystems) : base(scopeAccessor, cache, logger) { - _masterpagesFileSystem = masterpageFileSystem; - _viewsFileSystem = viewFileSystem; + _masterpagesFileSystem = fileSystems.MasterPagesFileSystem; + _viewsFileSystem = fileSystems.MvcViewsFileSystem; _templateConfig = templateConfig; _viewHelper = new ViewHelper(_viewsFileSystem); _masterPageHelper = new MasterPageHelper(_masterpagesFileSystem); @@ -152,7 +149,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //Save to db var template = (Template)entity; template.AddingEntity(); - + var dto = TemplateFactory.BuildDto(template, NodeObjectTypeId, template.Id); //Create the (base) node data - umbracoNode diff --git a/src/Umbraco.Core/Persistence/SqlContext.cs b/src/Umbraco.Core/Persistence/SqlContext.cs index d11bce166f..6f9f91114c 100644 --- a/src/Umbraco.Core/Persistence/SqlContext.cs +++ b/src/Umbraco.Core/Persistence/SqlContext.cs @@ -12,6 +12,8 @@ namespace Umbraco.Core.Persistence /// public class SqlContext : ISqlContext { + private readonly Lazy _mappers; + /// /// Initializes a new instance of the class. /// @@ -20,38 +22,20 @@ namespace Umbraco.Core.Persistence /// The database type. /// The mappers. public SqlContext(ISqlSyntaxProvider sqlSyntax, DatabaseType databaseType, IPocoDataFactory pocoDataFactory, IMapperCollection mappers = null) - { - // for tests - Mappers = mappers ?? new Mappers.MapperCollection(Enumerable.Empty()); - - SqlSyntax = sqlSyntax ?? throw new ArgumentNullException(nameof(sqlSyntax)); - PocoDataFactory = pocoDataFactory ?? throw new ArgumentNullException(nameof(pocoDataFactory)); - DatabaseType = databaseType ?? throw new ArgumentNullException(nameof(databaseType)); - Templates = new SqlTemplates(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// Initializes an empty context which must be fully initialized using the - /// method; this is done in - /// as soon as the factory is fully configured. - internal SqlContext() + : this(sqlSyntax, databaseType, pocoDataFactory, new Lazy(() => mappers ?? new Mappers.MapperCollection(Enumerable.Empty()))) { } /// - /// Initializes this . + /// Initializes a new instance of the class. /// /// The sql syntax provider. /// The Poco data factory. /// The database type. /// The mappers. - /// Fully initializes an initially empty context; this is done in - /// as soon as the factory is fully configured. - internal void Initialize(ISqlSyntaxProvider sqlSyntax, DatabaseType databaseType, IPocoDataFactory pocoDataFactory, IMapperCollection mappers = null) + public SqlContext(ISqlSyntaxProvider sqlSyntax, DatabaseType databaseType, IPocoDataFactory pocoDataFactory, Lazy mappers) { // for tests - Mappers = mappers ?? new Mappers.MapperCollection(Enumerable.Empty()); + _mappers = mappers; SqlSyntax = sqlSyntax ?? throw new ArgumentNullException(nameof(sqlSyntax)); PocoDataFactory = pocoDataFactory ?? throw new ArgumentNullException(nameof(pocoDataFactory)); @@ -60,10 +44,10 @@ namespace Umbraco.Core.Persistence } /// - public ISqlSyntaxProvider SqlSyntax { get; private set; } + public ISqlSyntaxProvider SqlSyntax { get; } /// - public DatabaseType DatabaseType { get; private set; } + public DatabaseType DatabaseType { get; } /// public Sql Sql() => NPoco.Sql.BuilderFor((ISqlContext) this); @@ -75,12 +59,12 @@ namespace Umbraco.Core.Persistence public IQuery Query() => new Query(this); /// - public SqlTemplates Templates { get; private set; } + public SqlTemplates Templates { get; } /// - public IPocoDataFactory PocoDataFactory { get; private set; } + public IPocoDataFactory PocoDataFactory { get; } /// - public IMapperCollection Mappers { get; private set; } + public IMapperCollection Mappers => _mappers.Value; } } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index d69786fbfc..5cf9fa3af8 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -11,7 +11,6 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// /// Represents an SqlSyntaxProvider for MySql /// - [SqlSyntaxProvider(Constants.DbProviderNames.MySql)] public class MySqlSyntaxProvider : SqlSyntaxProviderBase { private readonly ILogger _logger; @@ -334,7 +333,7 @@ ORDER BY TABLE_NAME, INDEX_NAME", switch (systemMethod) { case SystemMethods.NewGuid: - return null; // NOT SUPPORTED! + return null; // NOT SUPPORTED! case SystemMethods.CurrentDateTime: return "CURRENT_TIMESTAMP"; } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 75fc9c0b69..8f39e36fb9 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -4,14 +4,12 @@ using System.Linq; using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.SqlSyntax { /// /// Represents an SqlSyntaxProvider for Sql Ce /// - [SqlSyntaxProvider(Constants.DbProviderNames.SqlCe)] public class SqlCeSyntaxProvider : MicrosoftSqlSyntaxProviderBase { public override Sql SelectTop(Sql sql, int top) diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 90a2215e3d..8b8550b694 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data.Common; using System.Linq; using NPoco; +using Umbraco.Core.Logging; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Scoping; @@ -11,29 +12,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// /// Represents an SqlSyntaxProvider for Sql Server. /// - [SqlSyntaxProvider(Constants.DbProviderNames.SqlServer)] public class SqlServerSyntaxProvider : MicrosoftSqlSyntaxProviderBase { - // IUmbracoDatabaseFactory to be lazily injected - public SqlServerSyntaxProvider(Lazy lazyScopeProvider) - { - _serverVersion = new Lazy(() => - { - var scopeProvider = lazyScopeProvider.Value; - if (scopeProvider == null) - throw new InvalidOperationException("Failed to determine Sql Server version (no scope provider)."); - using (var scope = scopeProvider.CreateScope()) - { - var version = DetermineVersion(scope.Database); - scope.Complete(); - return version; - } - }); - } - - private readonly Lazy _serverVersion; - - internal ServerVersionInfo ServerVersion => _serverVersion.Value; + internal ServerVersionInfo ServerVersion { get; private set; } internal enum VersionName { @@ -62,19 +43,31 @@ namespace Umbraco.Core.Persistence.SqlSyntax internal class ServerVersionInfo { - public string Edition { get; set; } - public string InstanceName { get; set; } - public string ProductVersion { get; set; } - public VersionName ProductVersionName { get; private set; } - public EngineEdition EngineEdition { get; set; } - public bool IsAzure => EngineEdition == EngineEdition.Azure; - public string MachineName { get; set; } - public string ProductLevel { get; set; } - - public void Initialize() + public ServerVersionInfo() { - ProductVersionName = MapProductVersion(ProductVersion); + ProductVersionName = VersionName.Unknown; + EngineEdition = EngineEdition.Unknown; } + + public ServerVersionInfo(string edition, string instanceName, string productVersion, EngineEdition engineEdition, string machineName, string productLevel) + { + Edition = edition; + InstanceName = instanceName; + ProductVersion = productVersion; + ProductVersionName = MapProductVersion(ProductVersion); + EngineEdition = engineEdition; + MachineName = machineName; + ProductLevel = productLevel; + } + + public string Edition { get; } + public string InstanceName { get; } + public string ProductVersion { get; } + public VersionName ProductVersionName { get; } + public EngineEdition EngineEdition { get; } + public bool IsAzure => EngineEdition == EngineEdition.Azure; + public string MachineName { get; } + public string ProductLevel { get; } } private static VersionName MapProductVersion(string productVersion) @@ -105,8 +98,14 @@ namespace Umbraco.Core.Persistence.SqlSyntax } } - private static ServerVersionInfo DetermineVersion(IUmbracoDatabase database) + internal ServerVersionInfo GetSetVersion(string connectionString, string providerName, ILogger logger) { + var factory = DbProviderFactories.GetFactory(providerName); + var connection = factory.CreateConnection(); + + if (connection == null) + throw new InvalidOperationException($"Could not create a connection for provider \"{providerName}\"."); + // Edition: "Express Edition", "Windows Azure SQL Database..." // EngineEdition: 1/Desktop 2/Standard 3/Enterprise 4/Express 5/Azure // ProductLevel: RTM, SPx, CTP... @@ -123,44 +122,30 @@ namespace Umbraco.Core.Persistence.SqlSyntax SERVERPROPERTY('ResourceLastUpdateDateTime') ResourceLastUpdateDateTime, SERVERPROPERTY('ProductLevel') ProductLevel;"; - try - { - var version = database.Fetch(sql).First(); - version.Initialize(); - return version; - } - catch (Exception e) - { - // can't ignore, really - throw new Exception("Failed to determine Sql Server version (see inner exception).", e); - } - } - - internal static VersionName GetVersionName(string connectionString, string providerName) - { - var factory = DbProviderFactories.GetFactory(providerName); - var connection = factory.CreateConnection(); - - if (connection == null) - throw new InvalidOperationException($"Could not create a connection for provider \"{providerName}\"."); - connection.ConnectionString = connectionString; + ServerVersionInfo version; using (connection) { try { connection.Open(); var command = connection.CreateCommand(); - command.CommandText = "SELECT SERVERPROPERTY('ProductVersion');"; - var productVersion = command.ExecuteScalar().ToString(); + command.CommandText = sql; + using (var reader = command.ExecuteReader()) + { + reader.Read(); + version = new ServerVersionInfo(reader.GetString(0), reader.GetString(2), reader.GetString(3), (EngineEdition) reader.GetInt32(5), reader.GetString(7), reader.GetString(9)); + } connection.Close(); - return MapProductVersion(productVersion); } - catch + catch (Exception e) { - return VersionName.Unknown; + logger.Error(e, "Failed to detected SqlServer version."); + version = new ServerVersionInfo(); // all unknown } } + + return ServerVersion = version; } /// diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderAttribute.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderAttribute.cs deleted file mode 100644 index 191ee86bac..0000000000 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderAttribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace Umbraco.Core.Persistence.SqlSyntax -{ - /// - /// Attribute for implementations of an ISqlSyntaxProvider - /// - [AttributeUsage(AttributeTargets.Class)] - public class SqlSyntaxProviderAttribute : Attribute - { - public SqlSyntaxProviderAttribute(string providerName) - { - ProviderName = providerName; - } - - /// - /// Gets or sets the ProviderName that corresponds to the sql syntax in a provider. - /// - public string ProviderName { get; set; } - } -} diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs index 1e67993895..c9a509fe94 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs @@ -1,14 +1,11 @@ using System; -using System.Collections.Generic; using System.Configuration; using System.Data.Common; -using System.Linq; using System.Threading; using NPoco; using NPoco.FluentMappings; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; -using Umbraco.Core.Migrations.Install; using Umbraco.Core.Persistence.FaultHandling; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; @@ -27,10 +24,8 @@ namespace Umbraco.Core.Persistence /// internal class UmbracoDatabaseFactory : DisposableObject, IUmbracoDatabaseFactory { - private readonly ISqlSyntaxProvider[] _sqlSyntaxProviders; - private readonly IMapperCollection _mappers; + private readonly Lazy _mappers; private readonly ILogger _logger; - private readonly SqlContext _sqlContext = new SqlContext(); private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); private DatabaseFactory _npocoDatabaseFactory; @@ -51,24 +46,20 @@ namespace Umbraco.Core.Persistence /// /// Initializes a new instance of the . /// - /// Used by LightInject. - public UmbracoDatabaseFactory(IEnumerable sqlSyntaxProviders, ILogger logger, IMapperCollection mappers) - : this(Constants.System.UmbracoConnectionName, sqlSyntaxProviders, logger, mappers) - { - if (Configured == false) - DatabaseBuilder.GiveLegacyAChance(this, logger); - } + /// Used by core runtime. + public UmbracoDatabaseFactory(ILogger logger, Lazy mappers) + : this(Constants.System.UmbracoConnectionName, logger, mappers) + { } /// /// Initializes a new instance of the . /// /// Used by the other ctor and in tests. - public UmbracoDatabaseFactory(string connectionStringName, IEnumerable sqlSyntaxProviders, ILogger logger, IMapperCollection mappers) + public UmbracoDatabaseFactory(string connectionStringName, ILogger logger, Lazy mappers) { if (string.IsNullOrWhiteSpace(connectionStringName)) throw new ArgumentNullOrEmptyException(nameof(connectionStringName)); _mappers = mappers ?? throw new ArgumentNullException(nameof(mappers)); - _sqlSyntaxProviders = sqlSyntaxProviders?.ToArray() ?? throw new ArgumentNullException(nameof(sqlSyntaxProviders)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); var settings = ConfigurationManager.ConnectionStrings[connectionStringName]; @@ -92,10 +83,9 @@ namespace Umbraco.Core.Persistence /// Initializes a new instance of the . /// /// Used in tests. - public UmbracoDatabaseFactory(string connectionString, string providerName, IEnumerable sqlSyntaxProviders, ILogger logger, IMapperCollection mappers) + public UmbracoDatabaseFactory(string connectionString, string providerName, ILogger logger, Lazy mappers) { _mappers = mappers ?? throw new ArgumentNullException(nameof(mappers)); - _sqlSyntaxProviders = sqlSyntaxProviders?.ToArray() ?? throw new ArgumentNullException(nameof(sqlSyntaxProviders)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); if (string.IsNullOrWhiteSpace(connectionString) || string.IsNullOrWhiteSpace(providerName)) @@ -139,7 +129,7 @@ namespace Umbraco.Core.Persistence if (setting.IsNullOrWhiteSpace() || !setting.StartsWith("SqlServer.") || !Enum.TryParse(setting.Substring("SqlServer.".Length), out var versionName, true)) { - versionName = SqlServerSyntaxProvider.GetVersionName(_connectionString, _providerName); + versionName = ((SqlServerSyntaxProvider) _sqlSyntax).GetSetVersion(_connectionString, _providerName, _logger).ProductVersionName; } else { @@ -165,7 +155,7 @@ namespace Umbraco.Core.Persistence } /// - public ISqlContext SqlContext => _sqlContext; + public ISqlContext SqlContext { get; private set; } /// public void ConfigureForUpgrade() @@ -218,9 +208,7 @@ namespace Umbraco.Core.Persistence if (_npocoDatabaseFactory == null) throw new NullReferenceException("The call to UmbracoDatabaseFactory.Config yielded a null UmbracoDatabaseFactory instance."); - // can initialize now because it is the UmbracoDatabaseFactory that determines - // the sql syntax, poco data factory, and database type - _sqlContext.Initialize(_sqlSyntax, _databaseType, _pocoDataFactory, _mappers); + SqlContext = new SqlContext(_sqlSyntax, _databaseType, _pocoDataFactory, _mappers); _logger.Debug("Configured."); Configured = true; @@ -245,17 +233,17 @@ namespace Umbraco.Core.Persistence // gets the sql syntax provider that corresponds, from attribute private ISqlSyntaxProvider GetSqlSyntaxProvider(string providerName) { - var name = providerName.ToLowerInvariant(); - var provider = _sqlSyntaxProviders.FirstOrDefault(x => - x.GetType() - .FirstAttribute() - .ProviderName.ToLowerInvariant() - .Equals(name)); - if (provider != null) return provider; - throw new InvalidOperationException($"Unknown provider name \"{providerName}\""); - - // previously we'd try to return SqlServerSyntaxProvider by default but this is bad - //provider = _syntaxProviders.FirstOrDefault(x => x.GetType() == typeof(SqlServerSyntaxProvider)); + switch (providerName) + { + case Constants.DbProviderNames.MySql: + return new MySqlSyntaxProvider(_logger); + case Constants.DbProviderNames.SqlCe: + return new SqlCeSyntaxProvider(); + case Constants.DbProviderNames.SqlServer: + return new SqlServerSyntaxProvider(); + default: + throw new InvalidOperationException($"Unknown provider name \"{providerName}\""); + } } // ensures that the database is configured, else throws @@ -277,7 +265,7 @@ namespace Umbraco.Core.Persistence // method used by NPoco's UmbracoDatabaseFactory to actually create the database instance private UmbracoDatabase CreateDatabaseInstance() { - return new UmbracoDatabase(_connectionString, _sqlContext, _dbProviderFactory, _logger, _connectionRetryPolicy, _commandRetryPolicy); + return new UmbracoDatabase(_connectionString, SqlContext, _dbProviderFactory, _logger, _connectionRetryPolicy, _commandRetryPolicy); } protected override void DisposeResources() diff --git a/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs index 2a53142a1c..c0c0a3651e 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs @@ -1,14 +1,9 @@ -using LightInject; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; namespace Umbraco.Core.PropertyEditors { public class DataEditorCollectionBuilder : LazyCollectionBuilderBase { - public DataEditorCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override DataEditorCollectionBuilder This => this; } } diff --git a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs index d616ecf715..8f7c68c813 100644 --- a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs @@ -1,14 +1,9 @@ -using LightInject; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; namespace Umbraco.Core.PropertyEditors { internal class ManifestValueValidatorCollectionBuilder : LazyCollectionBuilderBase { - public ManifestValueValidatorCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override ManifestValueValidatorCollectionBuilder This => this; } } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs index 5c5a8c16c8..a64fe8c43a 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs @@ -1,14 +1,9 @@ -using LightInject; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; namespace Umbraco.Core.PropertyEditors { public class PropertyValueConverterCollectionBuilder : OrderedCollectionBuilderBase { - public PropertyValueConverterCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override PropertyValueConverterCollectionBuilder This => this; } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs index 2131764ad6..29f6de0271 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core.Configuration; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.Grid; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; @@ -19,9 +20,13 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter(typeof(JsonValueConverter))] //this shadows the JsonValueConverter public class GridValueConverter : JsonValueConverter { - public GridValueConverter(PropertyEditorCollection propertyEditors) + private readonly IGridConfig _config; + + public GridValueConverter(PropertyEditorCollection propertyEditors, IGridConfig config) : base(propertyEditors) - { } + { + _config = config; + } public override bool IsConverter(PublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Grid); @@ -46,15 +51,6 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters //so we have the grid json... we need to merge in the grid's configuration values with the values // we've saved in the database so that when the front end gets this value, it is up-to-date. - //TODO: Change all singleton access to use ctor injection in v8!!! - //TODO: That would mean that property value converters would need to be request lifespan, hrm.... - var gridConfig = UmbracoConfig.For.GridConfig( - Current.ProfilingLogger.Logger, - Current.ApplicationCache.RuntimeCache, - new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins)), - new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config)), - Current.RuntimeState.Debug); - var sections = GetArray(obj, "sections"); foreach (var section in sections.Cast()) { @@ -74,7 +70,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters if (alias.IsNullOrWhiteSpace() == false) { //find the alias in config - var found = gridConfig.EditorsConfig.Editors.FirstOrDefault(x => x.Alias == alias); + var found = _config.EditorsConfig.Editors.FirstOrDefault(x => x.Alias == alias); if (found != null) { //add/replace the editor value with the one from config diff --git a/src/Umbraco.Core/ReflectionUtilities.cs b/src/Umbraco.Core/ReflectionUtilities.cs index 870cb9ec13..622d81f5f2 100644 --- a/src/Umbraco.Core/ReflectionUtilities.cs +++ b/src/Umbraco.Core/ReflectionUtilities.cs @@ -295,7 +295,7 @@ namespace Umbraco.Core /// Occurs when the constructor does not exist and is true. /// Occurs when is not a Func or when /// is specified and does not match the function's returned type. - public static TLambda EmitConstuctor(bool mustExist = true, Type declaring = null) + public static TLambda EmitConstructor(bool mustExist = true, Type declaring = null) { var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true); diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs old mode 100755 new mode 100644 index dde91ad385..3a698dd30d --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -1,14 +1,12 @@ using System; using System.Collections.Generic; -using System.Configuration; +using System.Linq; using System.Reflection; using System.Threading; using System.Web; -using LightInject; using Umbraco.Core.Cache; using Umbraco.Core.Components; using Umbraco.Core.Composing; -using Umbraco.Core.Composing.CompositionRoots; using Umbraco.Core.Configuration; using Umbraco.Core.Exceptions; using Umbraco.Core.IO; @@ -16,11 +14,9 @@ using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Migrations.Upgrade; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Scoping; using Umbraco.Core.Services.Implement; +using Umbraco.Core.Sync; namespace Umbraco.Core.Runtime { @@ -31,44 +27,38 @@ namespace Umbraco.Core.Runtime /// should be possible to use this runtime in console apps. public class CoreRuntime : IRuntime { - private BootLoader _bootLoader; + private ComponentCollection _components; + private IFactory _factory; private RuntimeState _state; /// - /// Initializes a new instance of the class. + /// Gets the logger. /// - public CoreRuntime() - { } + protected ILogger Logger { get; private set; } + + /// + /// Gets the profiler. + /// + protected IProfiler Profiler { get; private set; } + + /// + /// Gets the profiling logger. + /// + protected IProfilingLogger ProfilingLogger { get; private set; } + + /// + public IRuntimeState State => _state; /// - public virtual void Boot(ServiceContainer container) + public virtual IFactory Boot(IRegister register) { - container.ConfigureUmbracoCore(); // also sets Current.Container + // create and register the essential services + // ie the bare minimum required to boot - // register the essential stuff, - // ie the global application logger - // (profiler etc depend on boot manager) - var logger = GetLogger(); - container.RegisterInstance(logger); - // now it is ok to use Current.Logger - - ConfigureUnhandledException(logger); - ConfigureAssemblyResolve(logger); - - Compose(container); - - // prepare essential stuff - - var path = GetApplicationRootPath(); - if (string.IsNullOrWhiteSpace(path) == false) - IOHelper.SetRootDirectory(path); - - _state = (RuntimeState) container.GetInstance(); - _state.Level = RuntimeLevel.Boot; - - Logger = container.GetInstance(); - Profiler = container.GetInstance(); - ProfilingLogger = container.GetInstance(); + // loggers + var logger = Logger = GetLogger(); + var profiler = Profiler = GetProfiler(); + var profilingLogger = ProfilingLogger = new ProfilingLogger(logger, profiler); // the boot loader boots using a container scope, so anything that is PerScope will // be disposed after the boot loader has booted, and anything else will remain. @@ -79,56 +69,116 @@ namespace Umbraco.Core.Runtime // are NOT disposed - which is not a big deal as long as they remain lightweight // objects. - using (var bootTimer = ProfilingLogger.TraceDuration( + using (var timer = profilingLogger.TraceDuration( $"Booting Umbraco {UmbracoVersion.SemanticVersion.ToSemanticString()} on {NetworkHelper.MachineName}.", "Booted.", "Boot failed.")) { - // throws if not full-trust - new AspNetHostingPermission(AspNetHostingPermissionLevel.Unrestricted).Demand(); + logger.Debug("Runtime: {Runtime}", GetType().FullName); - try - { - Logger.Debug("Runtime: {Runtime}", GetType().FullName); + // application environment + ConfigureUnhandledException(); + ConfigureAssemblyResolve(); + ConfigureApplicationRootPath(); - AquireMainDom(container); - DetermineRuntimeLevel(container); - var componentTypes = ResolveComponentTypes(); - _bootLoader = new BootLoader(container); - _bootLoader.Boot(componentTypes, _state.Level); - } - catch (Exception e) - { - _state.Level = RuntimeLevel.BootFailed; - var bfe = e as BootFailedException ?? new BootFailedException("Boot failed.", e); - _state.BootFailedException = bfe; - bootTimer.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves - - // throwing here can cause w3wp to hard-crash and we want to avoid it. - // instead, we're logging the exception and setting level to BootFailed. - // various parts of Umbraco such as UmbracoModule and UmbracoDefaultOwinStartup - // understand this and will nullify themselves, while UmbracoModule will - // throw a BootFailedException for every requests. - } + Boot(register, timer); } - //fixme - // after Umbraco has started there is a scope in "context" and that context is - // going to stay there and never get destroyed nor reused, so we have to ensure that - // everything is cleared - //var sa = container.GetInstance(); - //sa.Scope?.Dispose(); + return _factory; } /// - /// Gets a logger. + /// Boots the runtime within a timer. /// - protected virtual ILogger GetLogger() + protected virtual IFactory Boot(IRegister register, DisposableTimer timer) { - return SerilogLogger.CreateWithDefaultConfiguration(); + Composition composition = null; + + try + { + // throws if not full-trust + new AspNetHostingPermission(AspNetHostingPermissionLevel.Unrestricted).Demand(); + + // application caches + var appCaches = GetAppCaches(); + var runtimeCache = appCaches.RuntimeCache; + + // database factory + var databaseFactory = GetDatabaseFactory(); + + // type loader + var globalSettings = Current.Config.Global(); + var localTempStorage = globalSettings.LocalTempStorageLocation; + var typeLoader = new TypeLoader(runtimeCache, localTempStorage, ProfilingLogger); + + // runtime state + // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance' + // as the second one captures the current value (null) and therefore fails + _state = new RuntimeState(Logger, + Current.Config.Umbraco(), Current.Config.Global(), + new Lazy(() => _factory.GetInstance()), + new Lazy(() => _factory.GetInstance())) + { + Level = RuntimeLevel.Boot + }; + + // main dom + var mainDom = new MainDom(Logger); + + // create the composition + composition = new Composition(register, typeLoader, ProfilingLogger, _state); + composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, mainDom, appCaches, databaseFactory, typeLoader, _state); + + // register runtime-level services + // there should be none, really - this is here "just in case" + Compose(composition); + + // acquire the main domain, determine our runtime level + AcquireMainDom(mainDom); + DetermineRuntimeLevel(databaseFactory, ProfilingLogger); + + // get composers, and compose + var composerTypes = ResolveComposerTypes(typeLoader); + composition.WithCollectionBuilder(); + var composers = new Composers(composition, composerTypes, ProfilingLogger); + composers.Compose(); + + // create the factory + _factory = Current.Factory = composition.CreateFactory(); + + // create the components + _components = _factory.GetInstance(); + } + catch (Exception e) + { + _state.Level = RuntimeLevel.BootFailed; + var bfe = e as BootFailedException ?? new BootFailedException("Boot failed.", e); + _state.BootFailedException = bfe; + timer.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves + + // if something goes wrong above, we may end up with no factory + // meaning nothing can get the runtime state, etc - so let's try + // to make sure we have a factory + if (_factory == null) + { + try + { + _factory = Current.Factory = composition?.CreateFactory(); + } + catch { /* yea */ } + } + + // throwing here can cause w3wp to hard-crash and we want to avoid it. + // instead, we're logging the exception and setting level to BootFailed. + // various parts of Umbraco such as UmbracoModule and UmbracoDefaultOwinStartup + // understand this and will nullify themselves, while UmbracoModule will + // throw a BootFailedException for every requests. + } + + return _factory; } - protected virtual void ConfigureUnhandledException(ILogger logger) + protected virtual void ConfigureUnhandledException() { //take care of unhandled exceptions - there is nothing we can do to // prevent the launch process to go down but at least we can try @@ -141,32 +191,37 @@ namespace Umbraco.Core.Runtime var msg = "Unhandled exception in AppDomain"; if (isTerminating) msg += " (terminating)"; msg += "."; - logger.Error(exception, msg); + Logger.Error(exception, msg); }; } - protected virtual void ConfigureAssemblyResolve(ILogger logger) + protected virtual void ConfigureAssemblyResolve() { // When an assembly can't be resolved. In here we can do magic with the assembly name and try loading another. // This is used for loading a signed assembly of AutoMapper (v. 3.1+) without having to recompile old code. AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { // ensure the assembly is indeed AutoMapper and that the PublicKeyToken is null before trying to Load again - // do NOT just replace this with 'return Assembly', as it will cause an infinite loop -> stackoverflow + // do NOT just replace this with 'return Assembly', as it will cause an infinite loop -> stack overflow if (args.Name.StartsWith("AutoMapper") && args.Name.EndsWith("PublicKeyToken=null")) return Assembly.Load(args.Name.Replace(", PublicKeyToken=null", ", PublicKeyToken=be96cd2c38ef1005")); return null; }; } - - private void AquireMainDom(IServiceFactory container) + protected virtual void ConfigureApplicationRootPath() { - using (var timer = ProfilingLogger.DebugDuration("Acquiring MainDom.", "Aquired.")) + var path = GetApplicationRootPath(); + if (string.IsNullOrWhiteSpace(path) == false) + IOHelper.SetRootDirectory(path); + } + + private void AcquireMainDom(MainDom mainDom) + { + using (var timer = ProfilingLogger.DebugDuration("Acquiring MainDom.", "Acquired.")) { try { - var mainDom = container.GetInstance(); mainDom.Acquire(); } catch @@ -178,38 +233,38 @@ namespace Umbraco.Core.Runtime } // internal for tests - internal void DetermineRuntimeLevel(IServiceFactory container) + internal void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, IProfilingLogger profilingLogger) { - using (var timer = ProfilingLogger.DebugDuration("Determining runtime level.", "Determined.")) + using (var timer = profilingLogger.DebugDuration("Determining runtime level.", "Determined.")) { try { - var dbfactory = container.GetInstance(); - SetRuntimeStateLevel(dbfactory, Logger); + _state.DetermineRuntimeLevel(databaseFactory, profilingLogger); - Logger.Debug("Runtime level: {RuntimeLevel}", _state.Level); + profilingLogger.Debug("Runtime level: {RuntimeLevel}", _state.Level); if (_state.Level == RuntimeLevel.Upgrade) { - Logger.Debug("Configure database factory for upgrades."); - dbfactory.ConfigureForUpgrade(); + profilingLogger.Debug("Configure database factory for upgrades."); + databaseFactory.ConfigureForUpgrade(); } } catch { + _state.Level = RuntimeLevel.BootFailed; timer.Fail(); throw; } } } - private IEnumerable ResolveComponentTypes() + private IEnumerable ResolveComposerTypes(TypeLoader typeLoader) { - using (var timer = ProfilingLogger.TraceDuration("Resolving component types.", "Resolved.")) + using (var timer = ProfilingLogger.TraceDuration("Resolving composer types.", "Resolved.")) { try { - return GetComponentTypes(); + return GetComposerTypes(typeLoader); } catch { @@ -222,208 +277,66 @@ namespace Umbraco.Core.Runtime /// public virtual void Terminate() { - using (ProfilingLogger.DebugDuration("Terminating Umbraco.", "Terminated.")) - { - _bootLoader?.Terminate(); - } + _components.Terminate(); } /// /// Composes the runtime. /// - public virtual void Compose(ServiceContainer container) + public virtual void Compose(Composition composition) { - // compose the very essential things that are needed to bootstrap, before anything else, - // and only these things - the rest should be composed in runtime components - - // register basic things - // logging, runtime state, configuration - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterFrom(); - - // register caches - // need the deep clone runtime cache profiver to ensure entities are cached properly, ie - // are cloned in and cloned out - no request-based cache here since no web-based context, - // will be overriden later or - container.RegisterSingleton(_ => new CacheHelper( - new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()), - new StaticCacheProvider(), - NullCacheProvider.Instance, - new IsolatedRuntimeCache(type => new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider())))); - container.RegisterSingleton(f => f.GetInstance().RuntimeCache); - - // register the type loader - container.RegisterSingleton(); - - // register syntax providers - required by database factory - container.Register("MySqlSyntaxProvider"); - container.Register("SqlCeSyntaxProvider"); - container.Register("SqlServerSyntaxProvider"); - - // register persistence mappers - required by database factory so needs to be done here - // means the only place the collection can be modified is in a runtime - afterwards it - // has been frozen and it is too late - var mapperCollectionBuilder = container.RegisterCollectionBuilder(); - ComposeMapperCollection(mapperCollectionBuilder); - - // register database factory - required to check for migrations - // will be initialized with syntax providers and a logger, and will try to configure - // from the default connection string name, if possible, else will remain non-configured - // until properly configured (eg when installing) - container.RegisterSingleton(); - container.RegisterSingleton(f => f.GetInstance().SqlContext); - - // register the scope provider - container.RegisterSingleton(); // implements both IScopeProvider and IScopeAccessor - container.RegisterSingleton(f => f.GetInstance()); - container.RegisterSingleton(f => f.GetInstance()); - - // register MainDom - container.RegisterSingleton(); + // nothing } - protected virtual void ComposeMapperCollection(MapperCollectionBuilder builder) - { - builder.AddCore(); - } - - private void SetRuntimeStateLevel(IUmbracoDatabaseFactory databaseFactory, ILogger logger) - { - var localVersion = UmbracoVersion.LocalVersion; // the local, files, version - var codeVersion = _state.SemanticVersion; // the executing code version - var connect = false; - - // we don't know yet - _state.Level = RuntimeLevel.Unknown; - - if (localVersion == null) - { - // there is no local version, we are not installed - logger.Debug("No local version, need to install Umbraco."); - _state.Level = RuntimeLevel.Install; - } - else if (localVersion < codeVersion) - { - // there *is* a local version, but it does not match the code version - // need to upgrade - logger.Debug("Local version '{LocalVersion}' < code version '{CodeVersion}', need to upgrade Umbraco.", localVersion, codeVersion); - _state.Level = RuntimeLevel.Upgrade; - } - else if (localVersion > codeVersion) - { - logger.Warn("Local version '{LocalVersion}' > code version '{CodeVersion}', downgrading is not supported.", localVersion, codeVersion); - _state.Level = RuntimeLevel.BootFailed; - - // in fact, this is bad enough that we want to throw - throw new BootFailedException($"Local version \"{localVersion}\" > code version \"{codeVersion}\", downgrading is not supported."); - } - else if (databaseFactory.Configured == false) - { - // local version *does* match code version, but the database is not configured - // install (again? this is a weird situation...) - logger.Debug("Database is not configured, need to install Umbraco."); - _state.Level = RuntimeLevel.Install; - } - - // install? not going to test anything else - if (_state.Level == RuntimeLevel.Install) - return; - - // else, keep going, - // anything other than install wants a database - see if we can connect - // (since this is an already existing database, assume localdb is ready) - for (var i = 0; i < 5; i++) - { - connect = databaseFactory.CanConnect; - if (connect) break; - logger.Debug("Could not immediately connect to database, trying again."); - Thread.Sleep(1000); - } - - if (connect == false) - { - // cannot connect to configured database, this is bad, fail - logger.Debug("Could not connect to database."); - _state.Level = RuntimeLevel.BootFailed; - - // in fact, this is bad enough that we want to throw - throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database."); - } - - // if we already know we want to upgrade, - // still run EnsureUmbracoUpgradeState to get the states - // (v7 will just get a null state, that's ok) - - // else - // look for a matching migration entry - bypassing services entirely - they are not 'up' yet - // fixme - in a LB scenario, ensure that the DB gets upgraded only once! - bool noUpgrade; - try - { - noUpgrade = EnsureUmbracoUpgradeState(databaseFactory, logger); - } - catch (Exception e) - { - // can connect to the database but cannot check the upgrade state... oops - logger.Warn(e, "Could not check the upgrade state."); - throw new BootFailedException("Could not check the upgrade state.", e); - } - - if (noUpgrade) - { - // the database version matches the code & files version, all clear, can run - _state.Level = RuntimeLevel.Run; - return; - } - - // the db version does not match... but we do have a migration table - // so, at least one valid table, so we quite probably are installed & need to upgrade - - // although the files version matches the code version, the database version does not - // which means the local files have been upgraded but not the database - need to upgrade - logger.Debug("Has not reached the final upgrade step, need to upgrade Umbraco."); - _state.Level = RuntimeLevel.Upgrade; - } - - protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) - { - var upgrader = new UmbracoUpgrader(); - var stateValueKey = upgrader.StateValueKey; - - // no scope, no service - just directly accessing the database - using (var database = databaseFactory.CreateDatabase()) - { - _state.CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey); - _state.FinalMigrationState = upgrader.Plan.FinalState; - } - - logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", _state.FinalMigrationState, _state.CurrentMigrationState ?? ""); - - return _state.CurrentMigrationState == _state.FinalMigrationState; - } - - #region Locals - - protected ILogger Logger { get; private set; } - - protected IProfiler Profiler { get; private set; } - - protected ProfilingLogger ProfilingLogger { get; private set; } - - #endregion - #region Getters // getters can be implemented by runtimes inheriting from CoreRuntime - // fixme - inject! no Current! - protected virtual IEnumerable GetComponentTypes() => Current.TypeLoader.GetTypes(); + /// + /// Gets all composer types. + /// + protected virtual IEnumerable GetComposerTypes(TypeLoader typeLoader) + => typeLoader.GetTypes(); + + /// + /// Gets a logger. + /// + protected virtual ILogger GetLogger() + => SerilogLogger.CreateWithDefaultConfiguration(); + + /// + /// Gets a profiler. + /// + protected virtual IProfiler GetProfiler() + => new LogProfiler(ProfilingLogger); + + /// + /// Gets the application caches. + /// + protected virtual CacheHelper GetAppCaches() + { + // need the deep clone runtime cache provider to ensure entities are cached properly, ie + // are cloned in and cloned out - no request-based cache here since no web-based context, + // is overriden by the web runtime + + return new CacheHelper( + new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()), + new StaticCacheProvider(), + NullCacheProvider.Instance, + new IsolatedRuntimeCache(type => new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); + } // by default, returns null, meaning that Umbraco should auto-detect the application root path. // override and return the absolute path to the Umbraco site/solution, if needed - protected virtual string GetApplicationRootPath() => null; + protected virtual string GetApplicationRootPath() + => null; + + /// + /// Gets the database factory. + /// + /// This is strictly internal, for tests only. + protected internal virtual IUmbracoDatabaseFactory GetDatabaseFactory() + => new UmbracoDatabaseFactory(Logger, new Lazy(() => _factory.GetInstance())); #endregion } diff --git a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs b/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs index 90d3ece254..9e61feb15d 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs @@ -1,128 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.IO; +using System.Collections.Generic; using AutoMapper; -using LightInject; -using Umbraco.Core.Cache; using Umbraco.Core.Components; -using Umbraco.Core.Composing; -using Umbraco.Core.Composing.CompositionRoots; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; -using Umbraco.Core.IO.MediaPathSchemes; -using Umbraco.Core.Logging; -using Umbraco.Core.Manifest; -using Umbraco.Core.Migrations; -using Umbraco.Core.Migrations.Install; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Persistence; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.Validators; -using Umbraco.Core.Scoping; -using Umbraco.Core.Services; -using Umbraco.Core.Strings; -using Umbraco.Core.Sync; -using Umbraco.Core._Legacy.PackageActions; -using IntegerValidator = Umbraco.Core.PropertyEditors.Validators.IntegerValidator; namespace Umbraco.Core.Runtime { - public class CoreRuntimeComponent : UmbracoComponentBase, IRuntimeComponent + public class CoreRuntimeComponent : IComponent { - public override void Compose(Composition composition) - { - base.Compose(composition); - - // register from roots - composition.Container.RegisterFrom(); - composition.Container.RegisterFrom(); - composition.Container.RegisterFrom(); - - // register database builder - // *not* a singleton, don't want to keep it around - composition.Container.Register(); - - // register filesystems - composition.Container.RegisterSingleton(); - composition.Container.RegisterSingleton(factory => factory.GetInstance().MediaFileSystem); - composition.Container.RegisterSingleton(factory => factory.GetInstance().ScriptsFileSystem, Constants.Composing.FileSystems.ScriptFileSystem); - composition.Container.RegisterSingleton(factory => factory.GetInstance().PartialViewsFileSystem, Constants.Composing.FileSystems.PartialViewFileSystem); - composition.Container.RegisterSingleton(factory => factory.GetInstance().MacroPartialsFileSystem, Constants.Composing.FileSystems.PartialViewMacroFileSystem); - composition.Container.RegisterSingleton(factory => factory.GetInstance().StylesheetsFileSystem, Constants.Composing.FileSystems.StylesheetFileSystem); - composition.Container.RegisterSingleton(factory => factory.GetInstance().MasterPagesFileSystem, Constants.Composing.FileSystems.MasterpageFileSystem); - composition.Container.RegisterSingleton(factory => factory.GetInstance().MvcViewsFileSystem, Constants.Composing.FileSystems.ViewFileSystem); - - // register manifest parser, will be injected in collection builders where needed - composition.Container.RegisterSingleton(); - - // register our predefined validators - composition.Container.RegisterCollectionBuilder() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add(); - - // properties and parameters derive from data editors - composition.Container.RegisterCollectionBuilder() - .Add(factory => factory.GetInstance().GetDataEditors()); - composition.Container.RegisterSingleton(); - composition.Container.RegisterSingleton(); - - // register a server registrar, by default it's the db registrar - composition.Container.RegisterSingleton(f => - { - if ("true".InvariantEquals(ConfigurationManager.AppSettings["umbracoDisableElectionForSingleServer"])) - return new SingleServerRegistrar(f.GetInstance()); - return new DatabaseServerRegistrar( - new Lazy(f.GetInstance), - new DatabaseServerRegistrarOptions()); - }); - - // by default we'll use the database server messenger with default options (no callbacks), - // this will be overridden by either the legacy thing or the db thing in the corresponding - // components in the web project - fixme - should obsolete the legacy thing - composition.Container.RegisterSingleton(factory - => new DatabaseServerMessenger( - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - true, new DatabaseServerMessengerOptions())); - - composition.Container.RegisterCollectionBuilder() - .Add(factory => factory.GetInstance().GetCacheRefreshers()); - - composition.Container.RegisterCollectionBuilder() - .Add(f => f.GetInstance().GetPackageActions()); - - composition.Container.RegisterCollectionBuilder() - .Append(factory => factory.GetInstance().GetTypes()); - - composition.Container.Register(new PerContainerLifetime()); - - composition.Container.RegisterSingleton(factory - => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetInstance()))); - - composition.Container.RegisterCollectionBuilder() - .Append(); - - composition.Container.RegisterCollectionBuilder() - .Add(factory => factory.GetInstance().GetTypes()); - - composition.Container.RegisterSingleton(); - - // by default, register a noop factory - composition.Container.RegisterSingleton(); - - composition.Container.RegisterSingleton(); - } - - internal void Initialize(IEnumerable mapperProfiles) + public CoreRuntimeComponent(IEnumerable mapperProfiles) { // mapper profiles have been registered & are created by the container Mapper.Initialize(configuration => diff --git a/src/Umbraco.Core/Runtime/CoreRuntimeComposer.cs b/src/Umbraco.Core/Runtime/CoreRuntimeComposer.cs new file mode 100644 index 0000000000..3d959f5263 --- /dev/null +++ b/src/Umbraco.Core/Runtime/CoreRuntimeComposer.cs @@ -0,0 +1,121 @@ +using System; +using System.Configuration; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; +using Umbraco.Core.Composing; +using Umbraco.Core.Composing.Composers; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Logging; +using Umbraco.Core.Manifest; +using Umbraco.Core.Migrations; +using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.Validators; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Core.Sync; +using Umbraco.Core._Legacy.PackageActions; +using IntegerValidator = Umbraco.Core.PropertyEditors.Validators.IntegerValidator; + +namespace Umbraco.Core.Runtime +{ + public class CoreRuntimeComposer : ComponentComposer, IRuntimeComposer + { + public override void Compose(Composition composition) + { + base.Compose(composition); + + // composers + composition + .ComposeConfiguration() + .ComposeRepositories() + .ComposeServices() + .ComposeCoreMappingProfiles() + .ComposeFileSystems(); + + // register persistence mappers - required by database factory so needs to be done here + // means the only place the collection can be modified is in a runtime - afterwards it + // has been frozen and it is too late + composition.WithCollectionBuilder().AddCoreMappers(); + + // register the scope provider + composition.RegisterUnique(); // implements both IScopeProvider and IScopeAccessor + composition.RegisterUnique(f => f.GetInstance()); + composition.RegisterUnique(f => f.GetInstance()); + + // register database builder + // *not* a singleton, don't want to keep it around + composition.Register(); + + // register manifest parser, will be injected in collection builders where needed + composition.RegisterUnique(); + + // register our predefined validators + composition.WithCollectionBuilder() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add(); + + // properties and parameters derive from data editors + composition.WithCollectionBuilder() + .Add(() => composition.TypeLoader.GetDataEditors()); + composition.RegisterUnique(); + composition.RegisterUnique(); + + // register a server registrar, by default it's the db registrar + composition.RegisterUnique(f => + { + if ("true".InvariantEquals(ConfigurationManager.AppSettings["umbracoDisableElectionForSingleServer"])) + return new SingleServerRegistrar(f.GetInstance()); + return new DatabaseServerRegistrar( + new Lazy(f.GetInstance), + new DatabaseServerRegistrarOptions()); + }); + + // by default we'll use the database server messenger with default options (no callbacks), + // this will be overridden by either the legacy thing or the db thing in the corresponding + // components in the web project - fixme - should obsolete the legacy thing + composition.RegisterUnique(factory + => new DatabaseServerMessenger( + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + true, new DatabaseServerMessengerOptions())); + + composition.WithCollectionBuilder() + .Add(() => composition.TypeLoader.GetCacheRefreshers()); + + composition.WithCollectionBuilder() + .Add(() => composition.TypeLoader.GetPackageActions()); + + composition.WithCollectionBuilder() + .Append(composition.TypeLoader.GetTypes()); + + composition.RegisterUnique(); + + composition.RegisterUnique(factory + => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetInstance()))); + + composition.WithCollectionBuilder() + .Append(); + + composition.WithCollectionBuilder() + .Add(() => composition.TypeLoader.GetTypes()); + + composition.RegisterUnique(factory => new MigrationBuilder(factory)); + + // by default, register a noop factory + composition.RegisterUnique(); + } + } +} diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs index 4f6f56531b..df2ee44a7d 100644 --- a/src/Umbraco.Core/RuntimeState.cs +++ b/src/Umbraco.Core/RuntimeState.cs @@ -7,6 +7,9 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Upgrade; +using Umbraco.Core.Persistence; +using Umbraco.Core.Services.Implement; using Umbraco.Core.Sync; namespace Umbraco.Core @@ -17,76 +20,64 @@ namespace Umbraco.Core internal class RuntimeState : IRuntimeState { private readonly ILogger _logger; - private readonly Lazy _serverRegistrar; - private readonly Lazy _mainDom; private readonly IUmbracoSettingsSection _settings; private readonly IGlobalSettings _globalSettings; private readonly HashSet _applicationUrls = new HashSet(); - private RuntimeLevel _level; + private readonly Lazy _mainDom; + private readonly Lazy _serverRegistrar; + private RuntimeLevel _level = RuntimeLevel.Unknown; /// /// Initializes a new instance of the class. /// - /// A logger. - /// A (lazy) server registrar. - /// A (lazy) MainDom. - public RuntimeState(ILogger logger, Lazy serverRegistrar, Lazy mainDom, IUmbracoSettingsSection settings, IGlobalSettings globalSettings) + public RuntimeState(ILogger logger, IUmbracoSettingsSection settings, IGlobalSettings globalSettings, + Lazy mainDom, Lazy serverRegistrar) { _logger = logger; - _serverRegistrar = serverRegistrar; - _mainDom = mainDom; _settings = settings; _globalSettings = globalSettings; + _mainDom = mainDom; + _serverRegistrar = serverRegistrar; } + /// + /// Gets the server registrar. + /// + /// + /// This is NOT exposed in the interface. + /// private IServerRegistrar ServerRegistrar => _serverRegistrar.Value; /// /// Gets the application MainDom. /// - /// This is NOT exposed in the interface as MainDom is internal. - public MainDom MainDom => _mainDom.Value; + /// + /// This is NOT exposed in the interface as MainDom is internal. + /// + public IMainDom MainDom => _mainDom.Value; - /// - /// Gets the version of the executing code. - /// + /// public Version Version => UmbracoVersion.Current; - /// - /// Gets the version comment of the executing code. - /// + /// public string VersionComment => UmbracoVersion.Comment; - /// - /// Gets the semantic version of the executing code. - /// + /// public SemVersion SemanticVersion => UmbracoVersion.SemanticVersion; - /// - /// Gets a value indicating whether the application is running in debug mode. - /// + /// public bool Debug { get; } = GlobalSettings.DebugMode; - /// - /// Gets a value indicating whether the runtime is the current main domain. - /// + /// public bool IsMainDom => MainDom.IsMainDom; - /// - /// Get the server's current role. - /// + /// public ServerRole ServerRole => ServerRegistrar.GetCurrentServerRole(); - /// - /// Gets the Umbraco application url. - /// - /// This is eg "http://www.example.com". + /// public Uri ApplicationUrl { get; private set; } - /// - /// Gets the Umbraco application virtual path. - /// - /// This is either "/" or eg "/virtual". + /// public string ApplicationVirtualPath { get; } = HttpRuntime.AppDomainAppVirtualPath; /// @@ -95,9 +86,7 @@ namespace Umbraco.Core /// public string FinalMigrationState { get; internal set; } - /// - /// Gets the runtime level of execution. - /// + /// public RuntimeLevel Level { get => _level; @@ -137,9 +126,123 @@ namespace Umbraco.Core return _runLevel.WaitHandle.WaitOne(timeout); } - /// - /// Gets the exception that caused the boot to fail. - /// + /// public BootFailedException BootFailedException { get; internal set; } + + /// + /// Determines the runtime level. + /// + public void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, ILogger logger) + { + var localVersion = UmbracoVersion.LocalVersion; // the local, files, version + var codeVersion = SemanticVersion; // the executing code version + var connect = false; + + if (localVersion == null) + { + // there is no local version, we are not installed + logger.Debug("No local version, need to install Umbraco."); + Level = RuntimeLevel.Install; + return; + } + + if (localVersion < codeVersion) + { + // there *is* a local version, but it does not match the code version + // need to upgrade + logger.Debug("Local version '{LocalVersion}' < code version '{CodeVersion}', need to upgrade Umbraco.", localVersion, codeVersion); + Level = RuntimeLevel.Upgrade; + } + else if (localVersion > codeVersion) + { + logger.Warn("Local version '{LocalVersion}' > code version '{CodeVersion}', downgrading is not supported.", localVersion, codeVersion); + + // in fact, this is bad enough that we want to throw + throw new BootFailedException($"Local version \"{localVersion}\" > code version \"{codeVersion}\", downgrading is not supported."); + } + else if (databaseFactory.Configured == false) + { + // local version *does* match code version, but the database is not configured + // install (again? this is a weird situation...) + logger.Debug("Database is not configured, need to install Umbraco."); + Level = RuntimeLevel.Install; + return; + } + + // else, keep going, + // anything other than install wants a database - see if we can connect + // (since this is an already existing database, assume localdb is ready) + for (var i = 0; i < 5; i++) + { + connect = databaseFactory.CanConnect; + if (connect) break; + logger.Debug("Could not immediately connect to database, trying again."); + Thread.Sleep(1000); + } + + if (connect == false) + { + // cannot connect to configured database, this is bad, fail + logger.Debug("Could not connect to database."); + + // in fact, this is bad enough that we want to throw + throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database."); + } + + // if we already know we want to upgrade, + // still run EnsureUmbracoUpgradeState to get the states + // (v7 will just get a null state, that's ok) + + // else + // look for a matching migration entry - bypassing services entirely - they are not 'up' yet + // fixme - in a LB scenario, ensure that the DB gets upgraded only once! + bool noUpgrade; + try + { + noUpgrade = EnsureUmbracoUpgradeState(databaseFactory, logger); + } + catch (Exception e) + { + // can connect to the database but cannot check the upgrade state... oops + logger.Warn(e, "Could not check the upgrade state."); + throw new BootFailedException("Could not check the upgrade state.", e); + } + + // if we already know we want to upgrade, exit here + if (Level == RuntimeLevel.Upgrade) + return; + + if (noUpgrade) + { + // the database version matches the code & files version, all clear, can run + Level = RuntimeLevel.Run; + return; + } + + // the db version does not match... but we do have a migration table + // so, at least one valid table, so we quite probably are installed & need to upgrade + + // although the files version matches the code version, the database version does not + // which means the local files have been upgraded but not the database - need to upgrade + logger.Debug("Has not reached the final upgrade step, need to upgrade Umbraco."); + Level = RuntimeLevel.Upgrade; + } + + protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) + { + var upgrader = new UmbracoUpgrader(); + var stateValueKey = upgrader.StateValueKey; + + // no scope, no service - just directly accessing the database + using (var database = databaseFactory.CreateDatabase()) + { + CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey); + FinalMigrationState = upgrader.Plan.FinalState; + } + + logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", CurrentMigrationState, FinalMigrationState ?? ""); + + return CurrentMigrationState == FinalMigrationState; + } } } diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index adc5482e68..daef4c4cd7 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -1,6 +1,7 @@ using System; using System.Data; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.IO; @@ -491,7 +492,7 @@ namespace Umbraco.Core.Scoping // caching config // true if Umbraco.CoreDebug.LogUncompletedScope appSetting is set to "true" private static bool LogUncompletedScopes => (_logUncompletedScopes - ?? (_logUncompletedScopes = UmbracoConfig.For.CoreDebug().LogUncompletedScopes)).Value; + ?? (_logUncompletedScopes = Current.Config.CoreDebug().LogUncompletedScopes)).Value; /// public void ReadLock(params int[] lockIds) diff --git a/src/Umbraco.Core/Security/MembershipProviderExtensions.cs b/src/Umbraco.Core/Security/MembershipProviderExtensions.cs index ca01330212..10f44c46e7 100644 --- a/src/Umbraco.Core/Security/MembershipProviderExtensions.cs +++ b/src/Umbraco.Core/Security/MembershipProviderExtensions.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Web; using System.Web.Hosting; using System.Web.Security; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -87,11 +88,11 @@ namespace Umbraco.Core.Security /// public static MembershipProvider GetUsersMembershipProvider() { - if (Membership.Providers[UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider] == null) + if (Membership.Providers[Current.Config.Umbraco().Providers.DefaultBackOfficeUserProvider] == null) { - throw new InvalidOperationException("No membership provider found with name " + UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider); + throw new InvalidOperationException("No membership provider found with name " + Current.Config.Umbraco().Providers.DefaultBackOfficeUserProvider); } - return Membership.Providers[UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider]; + return Membership.Providers[Current.Config.Umbraco().Providers.DefaultBackOfficeUserProvider]; } /// diff --git a/src/Umbraco.Core/Services/IRedirectUrlService.cs b/src/Umbraco.Core/Services/IRedirectUrlService.cs index 62e59e910c..3bd9b6f2cf 100644 --- a/src/Umbraco.Core/Services/IRedirectUrlService.cs +++ b/src/Umbraco.Core/Services/IRedirectUrlService.cs @@ -14,8 +14,9 @@ namespace Umbraco.Core.Services /// /// The Umbraco url route. /// The content unique key. + /// The culture. /// Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo. - void Register(string url, Guid contentKey); + void Register(string url, Guid contentKey, string culture = null); /// /// Deletes all redirect urls for a given content. diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 22ff9d5917..3464823856 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -27,13 +27,13 @@ namespace Umbraco.Core.Services.Implement private readonly IContentTypeRepository _contentTypeRepository; private readonly IDocumentBlueprintRepository _documentBlueprintRepository; private readonly ILanguageRepository _languageRepository; - private readonly MediaFileSystem _mediaFileSystem; + private readonly IMediaFileSystem _mediaFileSystem; private IQuery _queryNotTrashed; #region Constructors public ContentService(IScopeProvider provider, ILogger logger, - IEventMessagesFactory eventMessagesFactory, MediaFileSystem mediaFileSystem, + IEventMessagesFactory eventMessagesFactory, IMediaFileSystem mediaFileSystem, IDocumentRepository documentRepository, IEntityRepository entityRepository, IAuditRepository auditRepository, IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository, ILanguageRepository languageRepository) : base(provider, logger, eventMessagesFactory) @@ -1552,9 +1552,7 @@ namespace Umbraco.Core.Services.Implement var args = new DeleteEventArgs(c, false); // raise event & get flagged files scope.Events.Dispatch(Deleted, this, args, nameof(Deleted)); - // fixme not going to work, do it differently - _mediaFileSystem.DeleteFiles(args.MediaFilesToDelete, // remove flagged files - (file, e) => Logger.Error(e, "An error occurred while deleting file attached to nodes: {File}", file)); + // media files deleted by QueuingEventDispatcher } const int pageSize = 500; diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index 1d04462836..f8c6badb37 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -26,11 +26,11 @@ namespace Umbraco.Core.Services.Implement private readonly IAuditRepository _auditRepository; private readonly IEntityRepository _entityRepository; - private readonly MediaFileSystem _mediaFileSystem; + private readonly IMediaFileSystem _mediaFileSystem; #region Constructors - public MediaService(IScopeProvider provider, MediaFileSystem mediaFileSystem, ILogger logger, IEventMessagesFactory eventMessagesFactory, + public MediaService(IScopeProvider provider, IMediaFileSystem mediaFileSystem, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMediaRepository mediaRepository, IAuditRepository auditRepository, IMediaTypeRepository mediaTypeRepository, IEntityRepository entityRepository) : base(provider, logger, eventMessagesFactory) @@ -757,8 +757,7 @@ namespace Umbraco.Core.Services.Implement var args = new DeleteEventArgs(c, false); // raise event & get flagged files scope.Events.Dispatch(Deleted, this, args); - _mediaFileSystem.DeleteFiles(args.MediaFilesToDelete, // remove flagged files - (file, e) => Logger.Error(e, "An error occurred while deleting file attached to nodes: {File}", file)); + // media files deleted by QueuingEventDispatcher } const int pageSize = 500; diff --git a/src/Umbraco.Core/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/Implement/MemberService.cs index e191493736..2f8c2f9a79 100644 --- a/src/Umbraco.Core/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberService.cs @@ -28,14 +28,14 @@ namespace Umbraco.Core.Services.Implement private readonly IAuditRepository _auditRepository; private readonly IMemberGroupService _memberGroupService; - private readonly MediaFileSystem _mediaFileSystem; + private readonly IMediaFileSystem _mediaFileSystem; //only for unit tests! internal MembershipProviderBase MembershipProvider { get; set; } #region Constructor - public MemberService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, MediaFileSystem mediaFileSystem, + public MemberService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, IMediaFileSystem mediaFileSystem, IMemberRepository memberRepository, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, IAuditRepository auditRepository) : base(provider, logger, eventMessagesFactory) { @@ -929,10 +929,7 @@ namespace Umbraco.Core.Services.Implement args.CanCancel = false; scope.Events.Dispatch(Deleted, this, args); - // fixme - this is MOOT because the event will not trigger immediately - // it's been refactored already (think it's the dispatcher that deals with it?) - _mediaFileSystem.DeleteFiles(args.MediaFilesToDelete, // remove flagged files - (file, e) => Logger.Error(e, "An error occurred while deleting file attached to nodes: {File}", file)); + // media files deleted by QueuingEventDispatcher } #endregion diff --git a/src/Umbraco.Core/Services/Implement/PackagingService.cs b/src/Umbraco.Core/Services/Implement/PackagingService.cs index fff865e097..106d2b9f12 100644 --- a/src/Umbraco.Core/Services/Implement/PackagingService.cs +++ b/src/Umbraco.Core/Services/Implement/PackagingService.cs @@ -66,7 +66,7 @@ namespace Umbraco.Core.Services.Implement IEntityService entityService, IUserService userService, IScopeProvider scopeProvider, - IEnumerable urlSegmentProviders, + UrlSegmentProviderCollection urlSegmentProviders, IAuditRepository auditRepository, IContentTypeRepository contentTypeRepository, PropertyEditorCollection propertyEditors) { diff --git a/src/Umbraco.Core/Services/Implement/RedirectUrlService.cs b/src/Umbraco.Core/Services/Implement/RedirectUrlService.cs index e9703bd85c..80816961fc 100644 --- a/src/Umbraco.Core/Services/Implement/RedirectUrlService.cs +++ b/src/Umbraco.Core/Services/Implement/RedirectUrlService.cs @@ -19,15 +19,15 @@ namespace Umbraco.Core.Services.Implement _redirectUrlRepository = redirectUrlRepository; } - public void Register(string url, Guid contentKey) + public void Register(string url, Guid contentKey, string culture = null) { using (var scope = ScopeProvider.CreateScope()) { - var redir = _redirectUrlRepository.Get(url, contentKey); + var redir = _redirectUrlRepository.Get(url, contentKey, culture); if (redir != null) redir.CreateDateUtc = DateTime.UtcNow; else - redir = new RedirectUrl { Key = Guid.NewGuid(), Url = url, ContentKey = contentKey }; + redir = new RedirectUrl { Key = Guid.NewGuid(), Url = url, ContentKey = contentKey, Culture = culture}; _redirectUrlRepository.Save(redir); scope.Complete(); } diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index f90b1d8d64..731d3a58c6 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -38,7 +38,6 @@ namespace Umbraco.Core.Services /// /// Initializes a new instance of the class with lazy services. /// - /// Used by IoC. Note that LightInject will favor lazy args when picking a constructor. public ServiceContext(Lazy publicAccessService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy mediaTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy treeService, Lazy sectionService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService, Lazy redirectUrlService, Lazy consentService) { _publicAccessService = publicAccessService; @@ -71,10 +70,13 @@ namespace Umbraco.Core.Services } /// - /// Initializes a new instance of the class with services. + /// Creates a partial service context with only some services (for tests). /// - /// Used in tests. All items are optional and remain null if not specified. - public ServiceContext(IContentService contentService = null, + /// + /// Using a true constructor for this confuses DI containers. + /// + public static ServiceContext CreatePartial( + IContentService contentService = null, IMediaService mediaService = null, IContentTypeService contentTypeService = null, IMediaTypeService mediaTypeService = null, @@ -102,40 +104,43 @@ namespace Umbraco.Core.Services IRedirectUrlService redirectUrlService = null, IConsentService consentService = null) { - if (serverRegistrationService != null) _serverRegistrationService = new Lazy(() => serverRegistrationService); - if (externalLoginService != null) _externalLoginService = new Lazy(() => externalLoginService); - if (auditService != null) _auditService = new Lazy(() => auditService); - if (localizedTextService != null) _localizedTextService = new Lazy(() => localizedTextService); - if (tagService != null) _tagService = new Lazy(() => tagService); - if (contentService != null) _contentService = new Lazy(() => contentService); - if (mediaService != null) _mediaService = new Lazy(() => mediaService); - if (contentTypeService != null) _contentTypeService = new Lazy(() => contentTypeService); - if (mediaTypeService != null) _mediaTypeService = new Lazy(() => mediaTypeService); - if (dataTypeService != null) _dataTypeService = new Lazy(() => dataTypeService); - if (fileService != null) _fileService = new Lazy(() => fileService); - if (localizationService != null) _localizationService = new Lazy(() => localizationService); - if (packagingService != null) _packagingService = new Lazy(() => packagingService); - if (entityService != null) _entityService = new Lazy(() => entityService); - if (relationService != null) _relationService = new Lazy(() => relationService); - if (sectionService != null) _sectionService = new Lazy(() => sectionService); - if (memberGroupService != null) _memberGroupService = new Lazy(() => memberGroupService); - if (memberTypeService != null) _memberTypeService = new Lazy(() => memberTypeService); - if (treeService != null) _treeService = new Lazy(() => treeService); - if (memberService != null) _memberService = new Lazy(() => memberService); - if (userService != null) _userService = new Lazy(() => userService); - if (notificationService != null) _notificationService = new Lazy(() => notificationService); - if (domainService != null) _domainService = new Lazy(() => domainService); - if (macroService != null) _macroService = new Lazy(() => macroService); - if (publicAccessService != null) _publicAccessService = new Lazy(() => publicAccessService); - if (redirectUrlService != null) _redirectUrlService = new Lazy(() => redirectUrlService); - if (consentService != null) _consentService = new Lazy(() => consentService); + Lazy Lazy(T service) => service == null ? null : new Lazy(() => service); + + return new ServiceContext( + Lazy(publicAccessService), + Lazy(domainService), + Lazy(auditService), + Lazy(localizedTextService), + Lazy(tagService), + Lazy(contentService), + Lazy(userService), + Lazy(memberService), + Lazy(mediaService), + Lazy(contentTypeService), + Lazy(mediaTypeService), + Lazy(dataTypeService), + Lazy(fileService), + Lazy(localizationService), + Lazy(packagingService), + Lazy(serverRegistrationService), + Lazy(entityService), + Lazy(relationService), + Lazy(treeService), + Lazy(sectionService), + Lazy(macroService), + Lazy(memberTypeService), + Lazy(memberGroupService), + Lazy(notificationService), + Lazy(externalLoginService), + Lazy(redirectUrlService), + Lazy(consentService)); } - + /// /// Gets the /// public IPublicAccessService PublicAccessService => _publicAccessService.Value; - + /// /// Gets the /// diff --git a/src/Umbraco.Core/SimpleMainDom.cs b/src/Umbraco.Core/SimpleMainDom.cs new file mode 100644 index 0000000000..87cc7bcff1 --- /dev/null +++ b/src/Umbraco.Core/SimpleMainDom.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core +{ + /// + /// Provides a simple implementation of . + /// + public class SimpleMainDom : IMainDom + { + private readonly object _locko = new object(); + private readonly List> _callbacks = new List>(); + private bool _isStopping; + + /// + public bool IsMainDom { get; private set; } = true; + + /// + public bool Register(Action release, int weight = 100) + => Register(null, release, weight); + + /// + public bool Register(Action install, Action release, int weight = 100) + { + lock (_locko) + { + if (_isStopping) return false; + install?.Invoke(); + if (release != null) + _callbacks.Add(new KeyValuePair(weight, release)); + return true; + } + } + + public void Stop() + { + lock (_locko) + { + if (_isStopping) return; + if (IsMainDom == false) return; // probably not needed + _isStopping = true; + } + + try + { + foreach (var callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) + { + callback(); // no timeout on callbacks + } + } + finally + { + // in any case... + IsMainDom = false; + } + } + } +} diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 03a371204c..98eaf9f4e9 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -187,7 +187,6 @@ namespace Umbraco.Core outputArray[i] = char.IsLetterOrDigit(inputArray[i]) ? inputArray[i] : replacement; return new string(outputArray); } - private static readonly char[] CleanForXssChars = "*?(){}[];:%<>/\\|&'\"".ToCharArray(); /// @@ -1115,7 +1114,7 @@ namespace Umbraco.Core /// Checks UmbracoSettings.ForceSafeAliases to determine whether it should filter the text. public static string ToSafeAliasWithForcingCheck(this string alias) { - return UmbracoConfig.For.UmbracoSettings().Content.ForceSafeAliases ? alias.ToSafeAlias() : alias; + return Current.Config.Umbraco().Content.ForceSafeAliases ? alias.ToSafeAlias() : alias; } /// @@ -1127,7 +1126,7 @@ namespace Umbraco.Core /// Checks UmbracoSettings.ForceSafeAliases to determine whether it should filter the text. public static string ToSafeAliasWithForcingCheck(this string alias, string culture) { - return UmbracoConfig.For.UmbracoSettings().Content.ForceSafeAliases ? alias.ToSafeAlias(culture) : alias; + return Current.Config.Umbraco().Content.ForceSafeAliases ? alias.ToSafeAlias(culture) : alias; } // the new methods to get a url segment diff --git a/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs b/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs index a9b8234e14..5183c28e15 100644 --- a/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs +++ b/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs @@ -1,14 +1,9 @@ -using LightInject; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; namespace Umbraco.Core.Strings { public class UrlSegmentProviderCollectionBuilder : OrderedCollectionBuilderBase { - public UrlSegmentProviderCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override UrlSegmentProviderCollectionBuilder This => this; } } diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index 20f9276ba1..5cfcb501e5 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Sync private readonly IRuntimeState _runtime; private readonly ManualResetEvent _syncIdle; private readonly object _locko = new object(); - private readonly ProfilingLogger _profilingLogger; + private readonly IProfilingLogger _profilingLogger; private readonly ISqlContext _sqlContext; private readonly Lazy _distCacheFilePath; private int _lastId = -1; @@ -46,7 +46,7 @@ namespace Umbraco.Core.Sync public DatabaseServerMessengerOptions Options { get; } public DatabaseServerMessenger( - IRuntimeState runtime, IScopeProvider scopeProvider, ISqlContext sqlContext, ProfilingLogger proflog, IGlobalSettings globalSettings, + IRuntimeState runtime, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, IGlobalSettings globalSettings, bool distributedEnabled, DatabaseServerMessengerOptions options) : base(distributedEnabled) { @@ -54,7 +54,7 @@ namespace Umbraco.Core.Sync _sqlContext = sqlContext; _runtime = runtime; _profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog)); - Logger = proflog.Logger; + Logger = proflog; Options = options ?? throw new ArgumentNullException(nameof(options)); _lastPruned = _lastSync = DateTime.UtcNow; _syncIdle = new ManualResetEvent(true); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index b1b439c996..c7ab787406 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -157,34 +157,47 @@ - + + + - - - - - - - - + + + + + + + + + + + + - - + + - - - - + + + + + + + + + + - - + + + @@ -317,11 +330,15 @@ + + + + @@ -338,8 +355,9 @@ - + + @@ -350,7 +368,6 @@ - @@ -384,6 +401,7 @@ + @@ -585,7 +603,7 @@ - + @@ -602,7 +620,6 @@ - @@ -1237,7 +1254,6 @@ - @@ -1296,6 +1312,7 @@ + @@ -1440,6 +1457,7 @@ + diff --git a/src/Umbraco.Core/_Legacy/PackageActions/PackageActionCollectionBuilder.cs b/src/Umbraco.Core/_Legacy/PackageActions/PackageActionCollectionBuilder.cs index 42ab3ec7c2..2f73a2b489 100644 --- a/src/Umbraco.Core/_Legacy/PackageActions/PackageActionCollectionBuilder.cs +++ b/src/Umbraco.Core/_Legacy/PackageActions/PackageActionCollectionBuilder.cs @@ -1,14 +1,9 @@ -using LightInject; -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; namespace Umbraco.Core._Legacy.PackageActions { internal class PackageActionCollectionBuilder : LazyCollectionBuilderBase { - public PackageActionCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override PackageActionCollectionBuilder This => this; } } diff --git a/src/Umbraco.Examine/MediaValueSetBuilder.cs b/src/Umbraco.Examine/MediaValueSetBuilder.cs index 8df51570c1..f0e5e895e6 100644 --- a/src/Umbraco.Examine/MediaValueSetBuilder.cs +++ b/src/Umbraco.Examine/MediaValueSetBuilder.cs @@ -14,7 +14,7 @@ namespace Umbraco.Examine private readonly IUserService _userService; public MediaValueSetBuilder(PropertyEditorCollection propertyEditors, - IEnumerable urlSegmentProviders, + UrlSegmentProviderCollection urlSegmentProviders, IUserService userService) : base(propertyEditors, false) { diff --git a/src/Umbraco.Examine/UmbracoContentIndex.cs b/src/Umbraco.Examine/UmbracoContentIndex.cs index c584f5bf51..e16015e192 100644 --- a/src/Umbraco.Examine/UmbracoContentIndex.cs +++ b/src/Umbraco.Examine/UmbracoContentIndex.cs @@ -55,7 +55,7 @@ namespace Umbraco.Examine FieldDefinitionCollection fieldDefinitions, Directory luceneDirectory, Analyzer defaultAnalyzer, - ProfilingLogger profilingLogger, + IProfilingLogger profilingLogger, ILocalizationService languageService, IContentValueSetValidator validator, IReadOnlyDictionary indexValueTypes = null) @@ -204,7 +204,7 @@ namespace Umbraco.Examine var filtered = c.NativeQuery(rawQuery); var results = filtered.Execute(); - ProfilingLogger.Logger.Debug(GetType(), "DeleteFromIndex with query: {Query} (found {TotalItems} results)", rawQuery, results.TotalItemCount); + ProfilingLogger.Debug(GetType(), "DeleteFromIndex with query: {Query} (found {TotalItems} results)", rawQuery, results.TotalItemCount); //need to queue a delete item for each one found QueueIndexOperation(results.Select(r => new IndexOperation(new ValueSet(r.Id), IndexOperationType.Delete))); diff --git a/src/Umbraco.Examine/UmbracoExamineIndex.cs b/src/Umbraco.Examine/UmbracoExamineIndex.cs index fc7f834a29..02cd80b54b 100644 --- a/src/Umbraco.Examine/UmbracoExamineIndex.cs +++ b/src/Umbraco.Examine/UmbracoExamineIndex.cs @@ -48,11 +48,10 @@ namespace Umbraco.Examine /// [EditorBrowsable(EditorBrowsableState.Never)] protected UmbracoExamineIndex() - : base() { ProfilingLogger = Current.ProfilingLogger; _configBased = true; - _diagnostics = new UmbracoExamineIndexDiagnostics(this, ProfilingLogger.Logger); + _diagnostics = new UmbracoExamineIndexDiagnostics(this, ProfilingLogger); } /// @@ -70,7 +69,7 @@ namespace Umbraco.Examine FieldDefinitionCollection fieldDefinitions, Directory luceneDirectory, Analyzer defaultAnalyzer, - ProfilingLogger profilingLogger, + IProfilingLogger profilingLogger, IValueSetValidator validator = null, IReadOnlyDictionary indexValueTypes = null) : base(name, fieldDefinitions, luceneDirectory, defaultAnalyzer, validator, indexValueTypes) @@ -81,14 +80,12 @@ namespace Umbraco.Examine if (luceneDirectory is FSDirectory fsDir) LuceneIndexFolder = fsDir.Directory; - _diagnostics = new UmbracoExamineIndexDiagnostics(this, ProfilingLogger.Logger); + _diagnostics = new UmbracoExamineIndexDiagnostics(this, ProfilingLogger); } private readonly bool _configBased = false; - - - protected ProfilingLogger ProfilingLogger { get; } + protected IProfilingLogger ProfilingLogger { get; } /// /// When set to true Umbraco will keep the index in sync with Umbraco data automatically @@ -125,7 +122,7 @@ namespace Umbraco.Examine /// public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) { - ProfilingLogger.Logger.Debug(GetType(), "{IndexerName} indexer initializing", name); + ProfilingLogger.Debug(GetType(), "{IndexerName} indexer initializing", name); if (config["enableDefaultEventHandler"] != null && bool.TryParse(config["enableDefaultEventHandler"], out var enabled)) { @@ -189,7 +186,7 @@ namespace Umbraco.Examine base.Initialize(name, config); } - + #endregion /// @@ -226,7 +223,7 @@ namespace Umbraco.Examine /// protected override void OnIndexingError(IndexingErrorEventArgs ex) { - ProfilingLogger.Logger.Error(GetType(), ex.InnerException, ex.Message); + ProfilingLogger.Error(GetType(), ex.InnerException, ex.Message); base.OnIndexingError(ex); } @@ -262,7 +259,7 @@ namespace Umbraco.Examine /// protected override void AddDocument(Document doc, ValueSet valueSet, IndexWriter writer) { - ProfilingLogger.Logger.Debug(GetType(), + ProfilingLogger.Debug(GetType(), "Write lucene doc id:{DocumentId}, category:{DocumentCategory}, type:{DocumentItemType}", valueSet.Id, valueSet.Category, diff --git a/src/Umbraco.Examine/UmbracoMemberIndex.cs b/src/Umbraco.Examine/UmbracoMemberIndex.cs index 27490e0e18..fae818a4a5 100644 --- a/src/Umbraco.Examine/UmbracoMemberIndex.cs +++ b/src/Umbraco.Examine/UmbracoMemberIndex.cs @@ -46,7 +46,7 @@ namespace Umbraco.Examine FieldDefinitionCollection fieldDefinitions, Directory luceneDirectory, Analyzer analyzer, - ProfilingLogger profilingLogger, + IProfilingLogger profilingLogger, IValueSetValidator validator = null) : base(name, fieldDefinitions, luceneDirectory, analyzer, profilingLogger, validator) { diff --git a/src/Umbraco.Tests.Benchmarks/BulkInsertBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/BulkInsertBenchmarks.cs index 0505974304..ee2e75cfad 100644 --- a/src/Umbraco.Tests.Benchmarks/BulkInsertBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/BulkInsertBenchmarks.cs @@ -4,18 +4,12 @@ using System.Data.SqlServerCe; using System.IO; using System.Linq; using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Horology; -using BenchmarkDotNet.Jobs; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Install; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; using Umbraco.Tests.Benchmarks.Config; using Umbraco.Tests.TestHelpers; @@ -34,13 +28,11 @@ namespace Umbraco.Tests.Benchmarks { IScopeProvider f = null; var l = new Lazy(() => f); - var p = new SqlServerSyntaxProvider(l); var factory = new UmbracoDatabaseFactory( "server=.\\SQLExpress;database=YOURDB;user id=YOURUSER;password=YOURPASS", Constants.DatabaseProviders.SqlServer, - new [] { p }, logger, - new MapperCollection(Enumerable.Empty())); + new Lazy(() => new MapperCollection(Enumerable.Empty()))); return factory.CreateDatabase(); } @@ -49,9 +41,8 @@ namespace Umbraco.Tests.Benchmarks var f = new UmbracoDatabaseFactory( cstr, Constants.DatabaseProviders.SqlCe, - new[] { new SqlCeSyntaxProvider() }, logger, - new MapperCollection(Enumerable.Empty())); + new Lazy(() => new MapperCollection(Enumerable.Empty()))); return f.CreateDatabase(); } diff --git a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs index ccd9b01969..8d15613791 100644 --- a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs @@ -150,7 +150,7 @@ namespace Umbraco.Tests.Benchmarks // however, unfortunately, the generated "compiled to delegate" code cannot access private stuff :( - _emittedCtor = ReflectionUtilities.EmitConstuctor>(); + _emittedCtor = ReflectionUtilities.EmitConstructor>(); } public IFoo IlCtor(IFoo foo) @@ -167,7 +167,7 @@ namespace Umbraco.Tests.Benchmarks [Benchmark] public void EmitCtor() { - var ctor = ReflectionUtilities.EmitConstuctor>(); + var ctor = ReflectionUtilities.EmitConstructor>(); var foo = ctor(_foo); } diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index f76f6b73b6..73f9656f56 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -67,7 +67,7 @@ - + diff --git a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs index dc67bb532f..68b666632c 100644 --- a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs @@ -1,11 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; +using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Core.Sync; +using Umbraco.Tests.Components; namespace Umbraco.Tests.Cache.DistributedCache { @@ -20,15 +24,18 @@ namespace Umbraco.Tests.Cache.DistributedCache [SetUp] public void Setup() { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); + var register = RegisterFactory.Create(); - container.Register(_ => new TestServerRegistrar()); - container.Register(_ => new TestServerMessenger(), new PerContainerLifetime()); + var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - container.RegisterCollectionBuilder() + composition.RegisterUnique(_ => new TestServerRegistrar()); + composition.RegisterUnique(_ => new TestServerMessenger()); + + composition.WithCollectionBuilder() .Add(); + Current.Factory = composition.CreateFactory(); + _distributedCache = new Umbraco.Web.Cache.DistributedCache(); } diff --git a/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs index 3cc5e061a5..50b27da89f 100644 --- a/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading; using Moq; using NUnit.Framework; +using Umbraco.Core.Components; using Umbraco.Core.Composing; using Umbraco.Core.Events; using Umbraco.Core.Models; @@ -19,6 +20,15 @@ namespace Umbraco.Tests.Cache [UmbracoTest(WithApplication = true)] public class DistributedCacheBinderTests : UmbracoTestBase { + protected override void Compose(Composition composition) + { + base.Compose(composition); + // refreshers.HandleEvents wants a UmbracoContext + // which wants these + composition.RegisterUnique(_ => Mock.Of()); + composition.WithCollectionBuilder(); + } + [Test] public void Can_Find_All_Event_Handlers() { @@ -138,11 +148,6 @@ namespace Umbraco.Tests.Cache if (domain.GetData(".appVPath") == null) domain.SetData(".appVPath", ""); - // refreshers.HandleEvents wants a UmbracoContext - // which wants these - Container.RegisterSingleton(_ => Mock.Of()); - Container.RegisterCollectionBuilder(); - // create some event definitions var definitions = new IEventDefinition[] { diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index aeda2eaca2..5237b92ab8 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Xml; @@ -7,13 +6,13 @@ using Examine; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Tests.Testing; using Current = Umbraco.Web.Composing.Current; -using LightInject; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Tests.PublishedContent; @@ -30,7 +29,7 @@ namespace Umbraco.Tests.Cache.PublishedCache { base.Compose(); - Container.GetInstance() + Composition.WithCollectionBuilder() .Clear() .Append(); } diff --git a/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs b/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs index 46dae8bcfd..f40ca3f500 100644 --- a/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs +++ b/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reflection; using NUnit.Framework; using Umbraco.Core; @@ -13,16 +14,16 @@ namespace Umbraco.Tests.Clr [Test] public void EmitCtorEmits() { - var ctor1 = ReflectionUtilities.EmitConstuctor>(); + var ctor1 = ReflectionUtilities.EmitConstructor>(); Assert.IsInstanceOf(ctor1()); - var ctor2 = ReflectionUtilities.EmitConstuctor>(declaring: typeof(Class1)); + var ctor2 = ReflectionUtilities.EmitConstructor>(declaring: typeof(Class1)); Assert.IsInstanceOf(ctor2()); - var ctor3 = ReflectionUtilities.EmitConstuctor>(); + var ctor3 = ReflectionUtilities.EmitConstructor>(); Assert.IsInstanceOf(ctor3(42)); - var ctor4 = ReflectionUtilities.EmitConstuctor>(declaring: typeof(Class3)); + var ctor4 = ReflectionUtilities.EmitConstructor>(declaring: typeof(Class3)); Assert.IsInstanceOf(ctor4(42)); } @@ -43,14 +44,14 @@ namespace Umbraco.Tests.Clr [Test] public void EmitCtorEmitsPrivateCtor() { - var ctor = ReflectionUtilities.EmitConstuctor>(); + var ctor = ReflectionUtilities.EmitConstructor>(); Assert.IsInstanceOf(ctor("foo")); } [Test] public void EmitCtorThrowsIfNotFound() { - Assert.Throws(() => ReflectionUtilities.EmitConstuctor>()); + Assert.Throws(() => ReflectionUtilities.EmitConstructor>()); } [Test] @@ -63,7 +64,7 @@ namespace Umbraco.Tests.Clr [Test] public void EmitCtorReturnsNull() { - Assert.IsNull(ReflectionUtilities.EmitConstuctor>(false)); + Assert.IsNull(ReflectionUtilities.EmitConstructor>(false)); } [Test] @@ -553,6 +554,22 @@ namespace Umbraco.Tests.Clr // fixme - missing tests specifying 'returned' on method, property + [Test] + public void DeconstructAnonymousType() + { + var o = new { a = 1, b = "hello" }; + + var getters = new Dictionary>(); + foreach (var prop in o.GetType().GetProperties()) + getters[prop.Name] = ReflectionUtilities.EmitMethodUnsafe>(prop.GetMethod); + + Assert.AreEqual(2, getters.Count); + Assert.IsTrue(getters.ContainsKey("a")); + Assert.IsTrue(getters.ContainsKey("b")); + Assert.AreEqual(1, getters["a"](o)); + Assert.AreEqual("hello", getters["b"](o)); + } + #region IL Code // these functions can be examined in eg DotPeek to understand IL works diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 995350f80e..903c53292c 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -1,17 +1,16 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Components; +using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Scoping; -using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Components { @@ -21,132 +20,176 @@ namespace Umbraco.Tests.Components private static readonly List Composed = new List(); private static readonly List Initialized = new List(); - private static IServiceContainer MockContainer(Action> setup = null) + private static IFactory MockFactory(Action> setup = null) { // fixme use IUmbracoDatabaseFactory vs UmbracoDatabaseFactory, clean it all up! - var testObjects = new TestObjects(null); + var mock = new Mock(); + var logger = Mock.Of(); - var s = testObjects.GetDefaultSqlSyntaxProviders(logger); - var f = new UmbracoDatabaseFactory(s, logger, new MapperCollection(Enumerable.Empty())); - var fs = new FileSystems(logger); + var f = new UmbracoDatabaseFactory(logger, new Lazy(() => new MapperCollection(Enumerable.Empty()))); + var fs = new FileSystems(mock.Object, logger); var p = new ScopeProvider(f, fs, logger); - var mock = new Mock(); mock.Setup(x => x.GetInstance(typeof (ILogger))).Returns(logger); - mock.Setup(x => x.GetInstance(typeof (ProfilingLogger))).Returns(new ProfilingLogger(Mock.Of(), Mock.Of())); + mock.Setup(x => x.GetInstance(typeof (IProfilingLogger))).Returns(new ProfilingLogger(Mock.Of(), Mock.Of())); mock.Setup(x => x.GetInstance(typeof (IUmbracoDatabaseFactory))).Returns(f); mock.Setup(x => x.GetInstance(typeof (IScopeProvider))).Returns(p); + setup?.Invoke(mock); return mock.Object; } + private static IRegister MockRegister() + { + return Mock.Of(); + } + + private static TypeLoader MockTypeLoader() + { + return new TypeLoader(); + } + + public static IRuntimeState MockRuntimeState(RuntimeLevel level) + { + var runtimeState = Mock.Of(); + Mock.Get(runtimeState).Setup(x => x.Level).Returns(level); + return runtimeState; + } + [Test] public void Boot1A() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var loader = new BootLoader(container); + var types = TypeArray(); + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 3 is User - goes away with RuntimeLevel.Unknown // => reorder components accordingly - loader.Boot(TypeArray(), RuntimeLevel.Unknown); - AssertTypeArray(TypeArray(), Composed); + components.Compose(); + AssertTypeArray(TypeArray(), Composed); } [Test] public void Boot1B() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); - var loader = new BootLoader(container); + var types = TypeArray(); + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 3 is User - stays with RuntimeLevel.Run // => reorder components accordingly - loader.Boot(TypeArray(), RuntimeLevel.Run); - AssertTypeArray(TypeArray(), Composed); + components.Compose(); + AssertTypeArray(TypeArray(), Composed); } [Test] public void Boot2() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var loader = new BootLoader(container); + var types = TypeArray(); + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); // 21 is required by 20 // => reorder components accordingly - loader.Boot(TypeArray(), RuntimeLevel.Unknown); - AssertTypeArray(TypeArray(), Composed); + components.Compose(); + AssertTypeArray(TypeArray(), Composed); } [Test] public void Boot3() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var loader = new BootLoader(container); + var types = TypeArray(); + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); // i23 requires 22 // 24, 25 implement i23 // 25 required by i23 // => reorder components accordingly - loader.Boot(TypeArray(), RuntimeLevel.Unknown); - AssertTypeArray(TypeArray(), Composed); + components.Compose(); + AssertTypeArray(TypeArray(), Composed); } [Test] public void BrokenRequire() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var thing = new BootLoader(container); + var types = TypeArray(); + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); try { // 2 is Core and requires 4 // 4 is missing // => throw - thing.Boot(TypeArray < Component1, Component2, Component3>(), RuntimeLevel.Unknown); + components.Compose(); Assert.Fail("Expected exception."); } catch (Exception e) { - Assert.AreEqual("Broken component dependency: Umbraco.Tests.Components.ComponentTests+Component2 -> Umbraco.Tests.Components.ComponentTests+Component4.", e.Message); + Assert.AreEqual("Broken composer dependency: Umbraco.Tests.Components.ComponentTests+Composer2 -> Umbraco.Tests.Components.ComponentTests+Composer4.", e.Message); } } [Test] public void BrokenRequired() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var thing = new BootLoader(container); + var types = TypeArray(); + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 13 is required by 1 // 1 is missing // => reorder components accordingly - thing.Boot(TypeArray(), RuntimeLevel.Unknown); - AssertTypeArray(TypeArray(), Composed); + components.Compose(); + AssertTypeArray(TypeArray(), Composed); } [Test] public void Initialize() { - var container = MockContainer(m => + var register = MockRegister(); + var factory = MockFactory(m => { m.Setup(x => x.TryGetInstance(It.Is(t => t == typeof (ISomeResource)))).Returns(() => new SomeResource()); + m.Setup(x => x.GetInstance(It.IsAny())).Returns((type) => + { + if (type == typeof(Composer1)) return new Composer1(); + if (type == typeof(Composer5)) return new Composer5(); + if (type == typeof(Component5)) return new Component5(new SomeResource()); + if (type == typeof(IProfilingLogger)) return new ProfilingLogger(Mock.Of(), Mock.Of()); + throw new NotSupportedException(type.FullName); + }); }); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var thing = new BootLoader(container); + var types = new[] { typeof(Composer1), typeof(Composer5) }; + var composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); - thing.Boot(new[] { typeof(Component1), typeof(Component5) }, RuntimeLevel.Unknown); + Initialized.Clear(); + composers.Compose(); + var builder = composition.WithCollectionBuilder(); + builder.RegisterWith(register); + var components = builder.CreateCollection(factory); Assert.AreEqual(2, Composed.Count); - Assert.AreEqual(typeof(Component1), Composed[0]); - Assert.AreEqual(typeof(Component5), Composed[1]); + Assert.AreEqual(typeof(Composer1), Composed[0]); + Assert.AreEqual(typeof(Composer5), Composed[1]); Assert.AreEqual(1, Initialized.Count); Assert.AreEqual("Umbraco.Tests.Components.ComponentTests+SomeResource", Initialized[0]); } @@ -154,174 +197,202 @@ namespace Umbraco.Tests.Components [Test] public void Requires1() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var thing = new BootLoader(container); + var types = new[] { typeof(Composer6), typeof(Composer7), typeof(Composer8) }; + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); - thing.Boot(new[] { typeof(Component6), typeof(Component7), typeof(Component8) }, RuntimeLevel.Unknown); + components.Compose(); Assert.AreEqual(2, Composed.Count); - Assert.AreEqual(typeof(Component6), Composed[0]); - Assert.AreEqual(typeof(Component8), Composed[1]); + Assert.AreEqual(typeof(Composer6), Composed[0]); + Assert.AreEqual(typeof(Composer8), Composed[1]); } [Test] public void Requires2A() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var thing = new BootLoader(container); + var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); - thing.Boot(new[] { typeof(Component9), typeof(Component2), typeof(Component4) }, RuntimeLevel.Unknown); + components.Compose(); Assert.AreEqual(2, Composed.Count); - Assert.AreEqual(typeof(Component4), Composed[0]); - Assert.AreEqual(typeof(Component2), Composed[1]); + Assert.AreEqual(typeof(Composer4), Composed[0]); + Assert.AreEqual(typeof(Composer2), Composed[1]); //Assert.AreEqual(typeof(Component9), Composed[2]); -- goes away with RuntimeLevel.Unknown } [Test] public void Requires2B() { - var container = MockContainer(); + var register = MockRegister(); + var factory = MockFactory(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); - var thing = new BootLoader(container); + var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; + var composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); - thing.Boot(new[] { typeof(Component9), typeof(Component2), typeof(Component4) }, RuntimeLevel.Run); + composers.Compose(); + var builder = composition.WithCollectionBuilder(); + builder.RegisterWith(register); + var components = builder.CreateCollection(factory); Assert.AreEqual(3, Composed.Count); - Assert.AreEqual(typeof(Component4), Composed[0]); - Assert.AreEqual(typeof(Component2), Composed[1]); - Assert.AreEqual(typeof(Component9), Composed[2]); + Assert.AreEqual(typeof(Composer4), Composed[0]); + Assert.AreEqual(typeof(Composer2), Composed[1]); + Assert.AreEqual(typeof(Composer9), Composed[2]); } [Test] public void WeakDependencies() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var thing = new BootLoader(container); + var types = new[] { typeof(Composer10) }; + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); - thing.Boot(new[] { typeof(Component10) }, RuntimeLevel.Unknown); + components.Compose(); Assert.AreEqual(1, Composed.Count); - Assert.AreEqual(typeof(Component10), Composed[0]); + Assert.AreEqual(typeof(Composer10), Composed[0]); - thing = new BootLoader(container); + types = new[] { typeof(Composer11) }; + components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); - Assert.Throws(() => thing.Boot(new[] { typeof(Component11) }, RuntimeLevel.Unknown)); + Assert.Throws(() => components.Compose()); - thing = new BootLoader(container); + types = new[] { typeof(Composer2) }; + components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); - Assert.Throws(() => thing.Boot(new[] { typeof(Component2) }, RuntimeLevel.Unknown)); + Assert.Throws(() => components.Compose()); - thing = new BootLoader(container); + types = new[] { typeof(Composer12) }; + components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); - thing.Boot(new[] { typeof(Component12) }, RuntimeLevel.Unknown); + components.Compose(); Assert.AreEqual(1, Composed.Count); - Assert.AreEqual(typeof(Component12), Composed[0]); + Assert.AreEqual(typeof(Composer12), Composed[0]); } [Test] public void DisableMissing() { - var container = MockContainer(); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); - var thing = new BootLoader(container); + var types = new[] { typeof(Composer6), typeof(Composer8) }; // 8 disables 7 which is not in the list + var components = new Core.Components.Composers(composition, types, Mock.Of()); Composed.Clear(); - thing.Boot(new[] { typeof(Component6), typeof(Component8) }, RuntimeLevel.Unknown); // 8 disables 7 which is not in the list + components.Compose(); Assert.AreEqual(2, Composed.Count); - Assert.AreEqual(typeof(Component6), Composed[0]); - Assert.AreEqual(typeof(Component8), Composed[1]); + Assert.AreEqual(typeof(Composer6), Composed[0]); + Assert.AreEqual(typeof(Composer8), Composed[1]); } #region Components - public class TestComponentBase : UmbracoComponentBase + public class TestComposerBase : IComposer { - public override void Compose(Composition composition) + public virtual void Compose(Composition composition) { - base.Compose(composition); Composed.Add(GetType()); } } - public class Component1 : TestComponentBase + public class TestComponentBase : IComponent { } - [RequireComponent(typeof(Component4))] - public class Component2 : TestComponentBase, IUmbracoCoreComponent + public class Composer1 : TestComposerBase { } - public class Component3 : TestComponentBase, IUmbracoUserComponent + [ComposeAfter(typeof(Composer4))] + public class Composer2 : TestComposerBase, ICoreComposer { } - public class Component4 : TestComponentBase + public class Composer3 : TestComposerBase, IUserComposer { } - public class Component5 : TestComponentBase + public class Composer4 : TestComposerBase + { } + + public class Composer5 : TestComposerBase { - public void Initialize(ISomeResource resource) + public override void Compose(Composition composition) + { + base.Compose(composition); + composition.Components().Append(); + } + } + + public class Component5 : IComponent + { + public Component5(ISomeResource resource) { Initialized.Add(resource.GetType().FullName); } } - [DisableComponent] - public class Component6 : TestComponentBase + [Disable] + public class Composer6 : TestComposerBase { } - public class Component7 : TestComponentBase + public class Composer7 : TestComposerBase { } - [DisableComponent(typeof(Component7))] - [EnableComponent(typeof(Component6))] - public class Component8 : TestComponentBase + [Disable(typeof(Composer7))] + [Enable(typeof(Composer6))] + public class Composer8 : TestComposerBase { } - public interface ITestComponent : IUmbracoUserComponent + public interface ITestComposer : IUserComposer { } - public class Component9 : TestComponentBase, ITestComponent + public class Composer9 : TestComposerBase, ITestComposer { } - [RequireComponent(typeof(ITestComponent))] - public class Component10 : TestComponentBase + [ComposeAfter(typeof(ITestComposer))] + public class Composer10 : TestComposerBase { } - [RequireComponent(typeof(ITestComponent), false)] - public class Component11 : TestComponentBase + [ComposeAfter(typeof(ITestComposer), false)] + public class Composer11 : TestComposerBase { } - [RequireComponent(typeof(Component4), true)] - public class Component12 : TestComponentBase, IUmbracoCoreComponent + [ComposeAfter(typeof(Composer4), true)] + public class Composer12 : TestComposerBase, ICoreComposer { } - [RequiredComponent(typeof(Component1))] - public class Component13 : TestComponentBase + [ComposeBefore(typeof(Composer1))] + public class Composer13 : TestComposerBase { } public interface ISomeResource { } public class SomeResource : ISomeResource { } - public class Component20 : TestComponentBase + public class Composer20 : TestComposerBase { } - [RequiredComponent(typeof(Component20))] - public class Component21 : TestComponentBase + [ComposeBefore(typeof(Composer20))] + public class Composer21 : TestComposerBase { } - public class Component22 : TestComponentBase + public class Composer22 : TestComposerBase { } - [RequireComponent(typeof(Component22))] - public interface IComponent23 : IUmbracoComponent + [ComposeAfter(typeof(Composer22))] + public interface IComposer23 : IComposer { } - public class Component24 : TestComponentBase, IComponent23 + public class Composer24 : TestComposerBase, IComposer23 { } // should insert itself between 22 and anything i23 - [RequiredComponent(typeof(IComponent23))] + [ComposeBefore(typeof(IComposer23))] //[RequireComponent(typeof(Component22))] - not needed, implement i23 - public class Component25 : TestComponentBase, IComponent23 + public class Composer25 : TestComposerBase, IComposer23 { } #endregion diff --git a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs index 87b0cd5173..4c262fbf82 100644 --- a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs +++ b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs @@ -1,39 +1,40 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; +using Moq; using NUnit.Framework; using Umbraco.Core.Composing; +using Umbraco.Core; +using Umbraco.Core.Components; +using Umbraco.Core.Logging; +using Umbraco.Tests.Components; namespace Umbraco.Tests.Composing { [TestFixture] public class CollectionBuildersTests { - private ServiceContainer _container; + private Composition _composition; [SetUp] public void Setup() { Current.Reset(); - _container = new ServiceContainer(); - _container.ConfigureUmbracoCore(); + var register = RegisterFactory.Create(); + _composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); } [TearDown] public void TearDown() { Current.Reset(); - - _container.Dispose(); - _container = null; } [Test] public void ContainsTypes() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append(); @@ -42,14 +43,15 @@ namespace Umbraco.Tests.Composing Assert.IsFalse(builder.Has()); //Assert.IsFalse(col.ContainsType()); // does not compile - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved2)); } [Test] public void CanClearBuilderBeforeCollectionIsCreated() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append(); @@ -57,18 +59,20 @@ namespace Umbraco.Tests.Composing Assert.IsFalse(builder.Has()); Assert.IsFalse(builder.Has()); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col); } [Test] public void CannotClearBuilderOnceCollectionIsCreated() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); Assert.Throws(() => builder.Clear()); } @@ -76,7 +80,7 @@ namespace Umbraco.Tests.Composing [Test] public void CanAppendToBuilder() { - var builder = _container.RegisterCollectionBuilder(); + var builder = _composition.WithCollectionBuilder(); builder.Append(); builder.Append(); @@ -84,16 +88,18 @@ namespace Umbraco.Tests.Composing Assert.IsTrue(builder.Has()); Assert.IsFalse(builder.Has()); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved2)); } [Test] public void CannotAppendToBuilderOnceCollectionIsCreated() { - var builder = _container.RegisterCollectionBuilder(); + var builder = _composition.WithCollectionBuilder(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); Assert.Throws(() => builder.Append() @@ -103,18 +109,21 @@ namespace Umbraco.Tests.Composing [Test] public void CanAppendDuplicateToBuilderAndDeDuplicate() { - var builder = _container.RegisterCollectionBuilder(); + var builder = _composition.WithCollectionBuilder(); builder.Append(); builder.Append(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1)); } [Test] public void CannotAppendInvalidTypeToBUilder() { - var builder = _container.RegisterCollectionBuilder(); + var builder = _composition.WithCollectionBuilder(); + //builder.Append(); // does not compile Assert.Throws(() => builder.Append(new[] { typeof (Resolved4) }) // throws @@ -124,7 +133,7 @@ namespace Umbraco.Tests.Composing [Test] public void CanRemoveFromBuilder() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append() .Remove(); @@ -133,30 +142,33 @@ namespace Umbraco.Tests.Composing Assert.IsFalse(builder.Has()); Assert.IsFalse(builder.Has()); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1)); } [Test] public void CanRemoveMissingFromBuilder() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append() .Remove(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved2)); } [Test] public void CannotRemoveFromBuilderOnceCollectionIsCreated() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); Assert.Throws(() => builder.Remove() // throws ); @@ -165,7 +177,7 @@ namespace Umbraco.Tests.Composing [Test] public void CanInsertIntoBuilder() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append() .Insert(); @@ -174,18 +186,20 @@ namespace Umbraco.Tests.Composing Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved3), typeof(Resolved1), typeof(Resolved2)); } [Test] public void CannotInsertIntoBuilderOnceCollectionIsCreated() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); Assert.Throws(() => builder.Insert() // throws ); @@ -194,29 +208,31 @@ namespace Umbraco.Tests.Composing [Test] public void CanInsertDuplicateIntoBuilderAndDeDuplicate() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append() .Insert(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved2), typeof(Resolved1)); } [Test] public void CanInsertIntoEmptyBuilder() { - var builder = _container.RegisterCollectionBuilder(); + var builder = _composition.WithCollectionBuilder(); builder.Insert(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved2)); } [Test] public void CannotInsertIntoBuilderAtWrongIndex() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append(); @@ -232,7 +248,7 @@ namespace Umbraco.Tests.Composing [Test] public void CanInsertIntoBuilderBefore() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append() .InsertBefore(); @@ -241,18 +257,20 @@ namespace Umbraco.Tests.Composing Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved3), typeof(Resolved2)); } [Test] public void CannotInsertIntoBuilderBeforeOnceCollectionIsCreated() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); Assert.Throws(() => builder.InsertBefore() ); @@ -261,19 +279,20 @@ namespace Umbraco.Tests.Composing [Test] public void CanInsertDuplicateIntoBuilderBeforeAndDeDuplicate() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Append() .InsertBefore(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved2), typeof(Resolved1)); } [Test] public void CannotInsertIntoBuilderBeforeMissing() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append(); Assert.Throws(() => @@ -284,7 +303,7 @@ namespace Umbraco.Tests.Composing [Test] public void ScopeBuilderCreatesScopedCollection() { - _container.RegisterCollectionBuilder() + _composition.WithCollectionBuilder() .Append() .Append(); @@ -292,19 +311,25 @@ namespace Umbraco.Tests.Composing // but the container manages the scope, so to test the scope // the collection must come from the container - var col1 = _container.GetInstance(); - AssertCollection(col1, typeof(Resolved1), typeof(Resolved2)); + var factory = _composition.CreateFactory(); - var col2 = _container.GetInstance(); - AssertCollection(col2, typeof(Resolved1), typeof(Resolved2)); + using (factory.BeginScope()) + { + var col1 = factory.GetInstance(); + AssertCollection(col1, typeof(Resolved1), typeof(Resolved2)); + + var col2 = factory.GetInstance(); + AssertCollection(col2, typeof(Resolved1), typeof(Resolved2)); + + AssertSameCollection(col1, col2); + } - AssertSameCollection(col1, col2); } [Test] public void TransientBuilderCreatesTransientCollection() { - _container.RegisterCollectionBuilder() + _composition.WithCollectionBuilder() .Append() .Append(); @@ -312,10 +337,12 @@ namespace Umbraco.Tests.Composing // but the container manages the scope, so to test the scope // the collection must come from the container - var col1 = _container.GetInstance(); + var factory = _composition.CreateFactory(); + + var col1 = factory.GetInstance(); AssertCollection(col1, typeof(Resolved1), typeof(Resolved2)); - var col2 = _container.GetInstance(); + var col2 = factory.GetInstance(); AssertCollection(col1, typeof(Resolved1), typeof(Resolved2)); AssertNotSameCollection(col1, col2); @@ -324,19 +351,20 @@ namespace Umbraco.Tests.Composing [Test] public void BuilderRespectsTypesOrder() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Append() .Insert() .InsertBefore(); - var col1 = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col1 = builder.CreateCollection(factory); AssertCollection(col1, typeof(Resolved1), typeof(Resolved2), typeof(Resolved3)); } [Test] public void ScopeBuilderRespectsContainerScope() { - _container.RegisterCollectionBuilder() + _composition.WithCollectionBuilder() .Append() .Append(); @@ -344,34 +372,40 @@ namespace Umbraco.Tests.Composing // but the container manages the scope, so to test the scope // the collection must come from the container - var scope1 = _container.BeginScope(); + TestCollection col1A, col1B; + + var factory = _composition.CreateFactory(); + + using (factory.BeginScope()) + { + col1A = factory.GetInstance(); + col1B = factory.GetInstance(); + } - var col1A = _container.GetInstance(); AssertCollection(col1A, typeof(Resolved1), typeof(Resolved2)); - var col1B = _container.GetInstance(); AssertCollection(col1B, typeof(Resolved1), typeof(Resolved2)); - AssertSameCollection(col1A, col1B); - _container.ScopeManagerProvider.GetScopeManager(_container).CurrentScope.Dispose(); - var scope2 = _container.BeginScope(); + TestCollection col2; + + using (factory.BeginScope()) + { + col2 = factory.GetInstance(); + } - var col2 = _container.GetInstance(); AssertCollection(col2, typeof(Resolved1), typeof(Resolved2)); - AssertNotSameCollection(col1A, col2); - - _container.ScopeManagerProvider.GetScopeManager(_container).CurrentScope.Dispose(); } [Test] public void WeightedBuilderCreatesWeightedCollection() { - var builder = _container.RegisterCollectionBuilder() + var builder = _composition.WithCollectionBuilder() .Add() .Add(); - var col = builder.CreateCollection(); + var factory = _composition.CreateFactory(); + var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved2), typeof(Resolved1)); } @@ -432,44 +466,28 @@ namespace Umbraco.Tests.Composing // ReSharper disable once ClassNeverInstantiated.Local private class TestCollectionBuilder : OrderedCollectionBuilderBase { - public TestCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override TestCollectionBuilder This => this; } // ReSharper disable once ClassNeverInstantiated.Local private class TestCollectionBuilderTransient : OrderedCollectionBuilderBase { - public TestCollectionBuilderTransient(IServiceContainer container) - : base(container) - { } - protected override TestCollectionBuilderTransient This => this; - protected override ILifetime CollectionLifetime => null; // transient + protected override Lifetime CollectionLifetime => Lifetime.Transient; // transient } // ReSharper disable once ClassNeverInstantiated.Local private class TestCollectionBuilderScope : OrderedCollectionBuilderBase { - public TestCollectionBuilderScope(IServiceContainer container) - : base(container) - { } - protected override TestCollectionBuilderScope This => this; - protected override ILifetime CollectionLifetime => new PerScopeLifetime(); + protected override Lifetime CollectionLifetime => Lifetime.Scope; } // ReSharper disable once ClassNeverInstantiated.Local private class TestCollectionBuilderWeighted : WeightedCollectionBuilderBase { - public TestCollectionBuilderWeighted(IServiceContainer container) - : base(container) - { } - protected override TestCollectionBuilderWeighted This => this; } diff --git a/src/Umbraco.Tests/Composing/ComposingTestBase.cs b/src/Umbraco.Tests/Composing/ComposingTestBase.cs index be595885e7..48850afd97 100644 --- a/src/Umbraco.Tests/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests/Composing/ComposingTestBase.cs @@ -4,6 +4,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Tests.TestHelpers; @@ -13,14 +14,14 @@ namespace Umbraco.Tests.Composing { protected TypeLoader TypeLoader { get; private set; } - protected ProfilingLogger ProfilingLogger { get; private set; } + protected IProfilingLogger ProfilingLogger { get; private set; } [SetUp] public void Initialize() { ProfilingLogger = new ProfilingLogger(Mock.Of(), Mock.Of()); - TypeLoader = new TypeLoader(NullCacheProvider.Instance, SettingsForTests.GenerateMockGlobalSettings(), ProfilingLogger, detectChanges: false) + TypeLoader = new TypeLoader(NullCacheProvider.Instance, LocalTempStorage.Default, ProfilingLogger, detectChanges: false) { AssembliesToScan = AssembliesToScan }; diff --git a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs new file mode 100644 index 0000000000..9585c98cb2 --- /dev/null +++ b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs @@ -0,0 +1,356 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Composing; + +namespace Umbraco.Tests.Composing +{ + [TestFixture] + public class ContainerConformingTests + { + // tests that a container conforms + + private IRegister GetRegister() => RegisterFactory.Create(); + + [Test] + public void CanRegisterAndGet() + { + var register = GetRegister(); + + register.Register(); + + var factory = register.CreateFactory(); + + var thing = factory.GetInstance(); + Assert.IsNotNull(thing); + Assert.IsInstanceOf(thing); + } + + [Test] + public void CanRegisterAndGetLazy() + { + var register = GetRegister(); + + register.Register(); + + var factory = register.CreateFactory(); + + var lazyThing = factory.GetInstance>(); + Assert.IsNotNull(lazyThing); + Assert.IsInstanceOf>(lazyThing); + var thing = lazyThing.Value; + Assert.IsNotNull(thing); + Assert.IsInstanceOf(thing); + } + + [Test] + public void CannotRegistedAndGetBase() + { + var register = GetRegister(); + + register.Register(); + + var factory = register.CreateFactory(); + + Assert.IsNull(factory.TryGetInstance()); + } + + [Test] + public void CannotRegisterAndGetInterface() + { + var register = GetRegister(); + + register.Register(); + + var factory = register.CreateFactory(); + + Assert.IsNull(factory.TryGetInstance()); + } + + [Test] + public void CanRegisterAndGetAllBase() + { + var register = GetRegister(); + + register.Register(); + + var factory = register.CreateFactory(); + + var things = factory.GetAllInstances(); + Assert.AreEqual(1, things.Count()); + + // lightInject: would be zero with option EnableVariance set to false + } + + [Test] + public void CanRegisterAndGetAllInterface() + { + var register = GetRegister(); + + register.Register(); + + var factory = register.CreateFactory(); + + var things = factory.GetAllInstances(); + Assert.AreEqual(1, things.Count()); + + // lightInject: would be zero with option EnableVariance set to false + } + + [Test] + public void CanRegisterBaseAndGet() + { + var register = GetRegister(); + + register.Register(); + + var factory = register.CreateFactory(); + + var thing = factory.GetInstance(); + Assert.IsNotNull(thing); + Assert.IsInstanceOf(thing); + } + + [Test] + public void CanRegisterInterfaceAndGet() + { + var register = GetRegister(); + + register.Register(); + + var factory = register.CreateFactory(); + + var thing = factory.GetInstance(); + Assert.IsNotNull(thing); + Assert.IsInstanceOf(thing); + } + + [Test] + public void NonSingletonServiceIsNotUnique() + { + var register = GetRegister(); + + register.Register(); + register.Register(); + + var factory = register.CreateFactory(); + + var things = factory.GetInstance>(); + Assert.AreEqual(2, things.Count()); + + Assert.IsNull(factory.TryGetInstance()); + } + + [Test] + public void SingletonServiceIsUnique() // fixme - but what is LightInject actually doing + { + var register = GetRegister(); + + // fixme + // LightInject is 'unique' per serviceType+serviceName + // but that's not how all containers work + // and we should not rely on it + // if we need unique, use RegisterUnique + + // for Core services that ppl may want to redefine in components, + // it is important to be able to have a unique, singleton implementation, + // and to redefine it - how it's done at container's level depends + // on each container + + // redefine the service + register.Register(Lifetime.Singleton); + register.Register(Lifetime.Singleton); + + var factory = register.CreateFactory(); + + var things = factory.GetInstance>(); + Assert.AreEqual(1, things.Count()); + + var thing = factory.GetInstance(); + Assert.IsInstanceOf(thing); + } + + [Test] + public void SingletonImplementationIsNotUnique() + { + var register = GetRegister(); + + // define two implementations + register.Register(Lifetime.Singleton); + register.Register(Lifetime.Singleton); + + var factory = register.CreateFactory(); + + var things = factory.GetInstance>(); + Assert.AreEqual(2, things.Count()); + + Assert.IsNull(factory.TryGetInstance()); + } + + [Test] + public void ActualInstanceIsNotUnique() + { + var register = GetRegister(); + + // define two instances + register.RegisterInstance(typeof(Thing1), new Thing1()); + register.RegisterInstance(typeof(Thing1), new Thing2()); + + var factory = register.CreateFactory(); + + var things = factory.GetInstance>(); + //Assert.AreEqual(2, things.Count()); + Assert.AreEqual(1, things.Count()); // well, yes they are unique? + + Assert.IsNull(factory.TryGetInstance()); + } + + [Test] + public void InterfaceInstanceIsNotUnique() + { + var register = GetRegister(); + + // define two instances + register.RegisterInstance(typeof(IThing), new Thing1()); + register.RegisterInstance(typeof(IThing), new Thing2()); + + var factory = register.CreateFactory(); + + var things = factory.GetInstance>(); + //Assert.AreEqual(2, things.Count()); + Assert.AreEqual(1, things.Count()); // well, yes they are unique? + + //Assert.IsNull(factory.TryGetInstance()); + Assert.IsNotNull(factory.TryGetInstance()); // well, what? + } + + [Test] + public void CanInjectEnumerableOfBase() + { + var register = GetRegister(); + + register.Register(); + register.Register(); + register.Register(); + + var factory = register.CreateFactory(); + + var needThings = factory.GetInstance(); + Assert.AreEqual(2, needThings.Things.Count()); + } + + [Test] + public void CanGetEnumerableOfBase() + { + var register = GetRegister(); + + register.Register(); + register.Register(); + + var factory = register.CreateFactory(); + + var things = factory.GetInstance>(); + Assert.AreEqual(2, things. Count()); + } + + [Test] + public void CanGetEmptyEnumerableOfBase() + { + var register = GetRegister(); + var factory = register.CreateFactory(); + + var things = factory.GetInstance>(); + Assert.AreEqual(0, things.Count()); + } + + [Test] + public void CanGetEmptyAllInstancesOfBase() + { + var register = GetRegister(); + var factory = register.CreateFactory(); + + var things = factory.GetAllInstances(); + Assert.AreEqual(0, things.Count()); + } + + [Test] + public void CanTryGetEnumerableOfBase() + { + var register = GetRegister(); + + register.Register(); + register.Register(); + + var factory = register.CreateFactory(); + + var things = factory.TryGetInstance>(); + Assert.AreEqual(2, things.Count()); + } + + [Test] + public void CanRegisterSingletonInterface() + { + var register = GetRegister(); + register.Register(Lifetime.Singleton); + var factory = register.CreateFactory(); + var s1 = factory.GetInstance(); + var s2 = factory.GetInstance(); + Assert.AreSame(s1, s2); + } + + [Test] + public void CanRegisterSingletonClass() + { + var register = GetRegister(); + register.Register(Lifetime.Singleton); + var factory = register.CreateFactory(); + var s1 = factory.GetInstance(); + var s2 = factory.GetInstance(); + Assert.AreSame(s1, s2); + } + + [Test] + public void CanReRegisterSingletonInterface() + { + var register = GetRegister(); + register.Register(Lifetime.Singleton); + register.Register(Lifetime.Singleton); + var factory = register.CreateFactory(); + var s = factory.GetInstance(); + Assert.IsInstanceOf(s); + } + + [Test] + public void CanRegisterSingletonWithCreate() + { + var register = GetRegister(); + register.Register(c => c.CreateInstance(new Thing1()), Lifetime.Singleton); + var factory = register.CreateFactory(); + var s1 = factory.GetInstance(); + var s2 = factory.GetInstance(); + Assert.AreSame(s1, s2); + } + + public interface IThing { } + + public abstract class ThingBase : IThing { } + public class Thing1 : ThingBase { } + public class Thing2 : ThingBase { } + + public class Thing3 : ThingBase + { + public Thing3(Thing1 thing) { } + } + + public class NeedThings + { + public NeedThings(IEnumerable things) + { + Things = things; + } + + public IEnumerable Things { get; } + } + } +} diff --git a/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs b/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs index 7a39186fea..cbabae1a83 100644 --- a/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs +++ b/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs @@ -1,10 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; +using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Tests.Components; namespace Umbraco.Tests.Composing { @@ -23,6 +28,11 @@ namespace Umbraco.Tests.Composing Current.Reset(); } + private IRegister CreateRegister() + { + return RegisterFactory.Create(); + } + // note // lazy collection builder does not throw on duplicate, just uses distinct types // so we don't have a test for duplicates as we had with resolvers in v7 @@ -30,22 +40,24 @@ namespace Umbraco.Tests.Composing [Test] public void LazyCollectionBuilderHandlesTypes() { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); + var container = CreateRegister(); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - container.RegisterCollectionBuilder() + composition.WithCollectionBuilder() .Add() .Add() .Add() .Add(); - var values = container.GetInstance(); + var factory = composition.CreateFactory(); + + var values = factory.GetInstance(); Assert.AreEqual(3, values.Count()); Assert.IsTrue(values.Select(x => x.GetType()) .ContainsAll(new[] { typeof(TransientObject1), typeof(TransientObject2), typeof(TransientObject3) })); - var other = container.GetInstance(); + var other = factory.GetInstance(); Assert.AreNotSame(values, other); // transient var o1 = other.FirstOrDefault(x => x is TransientObject1); Assert.IsFalse(values.Contains(o1)); // transient @@ -54,21 +66,23 @@ namespace Umbraco.Tests.Composing [Test] public void LazyCollectionBuilderHandlesProducers() { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); + var container = CreateRegister(); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - container.RegisterCollectionBuilder() + composition.WithCollectionBuilder() .Add(() => new[] { typeof(TransientObject3), typeof(TransientObject2) }) .Add(() => new[] { typeof(TransientObject3), typeof(TransientObject2) }) .Add(() => new[] { typeof(TransientObject1) }); - var values = container.GetInstance(); + var factory = composition.CreateFactory(); + + var values = factory.GetInstance(); Assert.AreEqual(3, values.Count()); Assert.IsTrue(values.Select(x => x.GetType()) .ContainsAll(new[] { typeof(TransientObject1), typeof(TransientObject2), typeof(TransientObject3) })); - var other = container.GetInstance(); + var other = factory.GetInstance(); Assert.AreNotSame(values, other); // transient var o1 = other.FirstOrDefault(x => x is TransientObject1); Assert.IsFalse(values.Contains(o1)); // transient @@ -77,22 +91,24 @@ namespace Umbraco.Tests.Composing [Test] public void LazyCollectionBuilderHandlesTypesAndProducers() { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); + var container = CreateRegister(); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - container.RegisterCollectionBuilder() + composition.WithCollectionBuilder() .Add() .Add() .Add() .Add(() => new[] { typeof(TransientObject1) }); - var values = container.GetInstance(); + var factory = composition.CreateFactory(); + + var values = factory.GetInstance(); Assert.AreEqual(3, values.Count()); Assert.IsTrue(values.Select(x => x.GetType()) .ContainsAll(new[] { typeof(TransientObject1), typeof(TransientObject2), typeof(TransientObject3) })); - var other = container.GetInstance(); + var other = factory.GetInstance(); Assert.AreNotSame(values, other); // transient var o1 = other.FirstOrDefault(x => x is TransientObject1); Assert.IsFalse(values.Contains(o1)); // transient @@ -101,10 +117,10 @@ namespace Umbraco.Tests.Composing [Test] public void LazyCollectionBuilderThrowsOnIllegalTypes() { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); + var container = CreateRegister(); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - container.RegisterCollectionBuilder() + composition.WithCollectionBuilder() .Add() // illegal, does not implement the interface! @@ -115,23 +131,25 @@ namespace Umbraco.Tests.Composing Assert.Throws(() => { - // but throws here when trying to register the types - var values = container.GetInstance(); + // but throws here when trying to register the types, right before creating the factory + var factory = composition.CreateFactory(); }); } [Test] public void LazyCollectionBuilderCanExcludeTypes() { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); + var container = CreateRegister(); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - container.RegisterCollectionBuilder() + composition.WithCollectionBuilder() .Add() .Add(() => new[] { typeof(TransientObject3), typeof(TransientObject2), typeof(TransientObject1) }) .Exclude(); - var values = container.GetInstance(); + var factory = composition.CreateFactory(); + + var values = factory.GetInstance(); Assert.AreEqual(2, values.Count()); Assert.IsFalse(values.Select(x => x.GetType()) @@ -139,7 +157,7 @@ namespace Umbraco.Tests.Composing Assert.IsTrue(values.Select(x => x.GetType()) .ContainsAll(new[] { typeof(TransientObject1), typeof(TransientObject2) })); - var other = container.GetInstance(); + var other = factory.GetInstance(); Assert.AreNotSame(values, other); // transient var o1 = other.FirstOrDefault(x => x is TransientObject1); Assert.IsFalse(values.Contains(o1)); // transient @@ -165,13 +183,9 @@ namespace Umbraco.Tests.Composing // ReSharper disable once ClassNeverInstantiated.Local private class TestCollectionBuilder : LazyCollectionBuilderBase { - public TestCollectionBuilder(IServiceContainer container) - : base(container) - { } - protected override TestCollectionBuilder This => this; - protected override ILifetime CollectionLifetime => null; // transient + protected override Lifetime CollectionLifetime => Lifetime.Transient; // transient } // ReSharper disable once ClassNeverInstantiated.Local diff --git a/src/Umbraco.Tests/Composing/LightInjectValidation.cs b/src/Umbraco.Tests/Composing/LightInjectValidation.cs new file mode 100644 index 0000000000..75062e613c --- /dev/null +++ b/src/Umbraco.Tests/Composing/LightInjectValidation.cs @@ -0,0 +1,349 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LightInject; +using System.Collections.Concurrent; +using System.Collections.ObjectModel; +using System.Reflection; +using ServiceMap = System.Collections.Generic.Dictionary>; + +/********************************************************************************* + The MIT License (MIT) + + Copyright (c) 2017 bernhard.richter@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +****************************************************************************** + LightInject.Validation version 1.0.1 + http://www.lightinject.net/ + http://twitter.com/bernhardrichter +******************************************************************************/ + +namespace Umbraco.Tests.Composing +{ + public static class LightInjectValidation + { + private static readonly ConcurrentDictionary LifeSpans = new ConcurrentDictionary(); + + private const string NotDisposeMessageServiceType = + @"The service {0} is being injected as a constructor argument into {1} implements IDisposable, " + + "but is registered without a lifetime (transient). LightInject will not be able to dispose the instance represented by {0}. " + + "If the intent was to manually control the instantiation and destruction, inject Func<{0}> instead. " + + "Otherwise register `{0}` with a lifetime (PerContainer, PerRequest or PerScope)."; + + private const string NotDisposeMessageImplementingType = + @"The service {0} represented by {1} is being injected as a constructor argument into {2} implements IDisposable, " + + "but is registered without a lifetime (transient). LightInject will not be able to dispose the instance represented by {0}. " + + "If the intent was to manually control the instantiation and destruction, inject Func<{0}> instead. " + + "Otherwise register `{0}` with a lifetime (PerContainer, PerRequest or PerScope)."; + + + private const string MissingDeferredDependency = + @"The injected '{0}' does not contain a registration for the underlying type '{1}'. " + + "Ensure that '{1}' is registered so that the service can be resolved by '{0}'"; + + /* + The service 'NameSpace.IBar' that is being injected into 'NameSpace.Foo' is registered with +with a 'Transient' lifetime while the 'NameSpace.Foo' is registered with the 'PerScope' lifetime. +Ensure that 'NameSpace.IBar' is registered with a lifetime that is equal to or has a longer lifetime than the 'PerScope' lifetime. + */ + private const string CaptiveDependency = + @"The service '{0}' that is being injected into {1} is registered with " + + "a '{2}' lifetime while the {1} is registered with the '{3}' lifetime. " + + "Ensure that '{0}' is registered with a lifetime that is equal to or has a longer lifetime than the '{3}' lifetime. " + + "Alternatively ensure that `{1}` is registered with a lifetime that is equal to or " + + "has a shorter lifetime than `{2}` lifetime."; + + private const string MissingDependency = + "Class: 'NameSpace.Foo', Parameter 'NameSpace.IBar bar' -> The injected service NameSpace IBar is not registered." + ; + + + static LightInjectValidation() + { + LifeSpans.TryAdd(typeof(PerRequestLifeTime), 10); + LifeSpans.TryAdd(typeof(PerScopeLifetime), 20); + LifeSpans.TryAdd(typeof(PerContainerLifetime), 30); + } + + public static IEnumerable Validate(this ServiceContainer container) + { + var serviceMap = container.AvailableServices.GroupBy(sr => sr.ServiceType).ToDictionary(gr => gr.Key, + gr => gr.ToDictionary(sr => sr.ServiceName, sr => sr, StringComparer.OrdinalIgnoreCase)); + + var verifyableServices = container.AvailableServices.Where(sr => sr.ImplementingType != null); + + return verifyableServices.SelectMany(sr => + ValidateConstructor(serviceMap, sr, container.ConstructorSelector.Execute(sr.ImplementingType))); + } + + private static IReadOnlyCollection ValidateConstructor(ServiceMap serviceMap, + ServiceRegistration serviceRegistration, ConstructorInfo constructorInfo) + { + var result = new Collection(); + + foreach (var parameter in constructorInfo.GetParameters()) + { + var validationTarget = new ValidationTarget(serviceRegistration, parameter); + Validate(validationTarget, serviceMap, result); + } + return result; + } + + private static void Validate(ValidationTarget validationTarget, ServiceMap serviceMap, ICollection result) + { + var registration = GetServiceRegistration(serviceMap, validationTarget); + if (registration == null) + { + if (validationTarget.ServiceType.IsFunc() || validationTarget.ServiceType.IsLazy()) + { + var serviceType = validationTarget.ServiceType.GenericTypeArguments[0]; + var underlyingvalidationTarget = validationTarget.WithServiceDescription(serviceType, string.Empty); + registration = GetServiceRegistration(serviceMap, underlyingvalidationTarget); + + if (registration != null) + { + return; + } + + if (serviceMap.ContainsAmbiguousRegistrationFor(serviceType)) + { + result.Add(new ValidationResult("", ValidationSeverity.Ambiguous, underlyingvalidationTarget)); + } + else + { + string message = string.Format(MissingDeferredDependency, validationTarget.ServiceType, underlyingvalidationTarget.ServiceType); + result.Add(new ValidationResult(message, ValidationSeverity.MissingDependency, underlyingvalidationTarget)); + } + } + else if (validationTarget.ServiceType.IsGenericType && validationTarget.ServiceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + var serviceType = validationTarget.ServiceType.GenericTypeArguments[0]; + var underlyingvalidationTarget = validationTarget.WithServiceDescription(serviceType, string.Empty); + var registrations = GetServiceRegistrations(serviceMap, underlyingvalidationTarget); + if (registrations.Any()) return; + + // strict: there has to be at least 1 + string message = string.Format(MissingDeferredDependency, validationTarget.ServiceType, underlyingvalidationTarget.ServiceType); + result.Add(new ValidationResult(message, ValidationSeverity.MissingDependency, underlyingvalidationTarget)); + } + else + { + if (serviceMap.ContainsAmbiguousRegistrationFor(validationTarget.ServiceType)) + { + result.Add(new ValidationResult("", ValidationSeverity.Ambiguous, validationTarget)); + } + else + { + result.Add(new ValidationResult("", ValidationSeverity.MissingDependency, validationTarget)); + } + } + } + else + { + ValidateDisposable(validationTarget, result, registration); + ValidateLifetime(validationTarget, registration, result); + } + } + + private static void ValidateDisposable(ValidationTarget validationTarget, ICollection result, + ServiceRegistration registration) + { + if (registration.ServiceType.Implements()) + { + var message = string.Format(NotDisposeMessageServiceType, registration.ServiceType, + validationTarget.DeclaringService.ImplementingType); + result.Add(new ValidationResult(message, ValidationSeverity.NotDisposed, validationTarget)); + } + + else if (registration.ImplementingType != null && registration.ImplementingType.Implements()) + { + var message = string.Format(NotDisposeMessageImplementingType, registration.ImplementingType, + registration.ServiceType, + validationTarget.DeclaringService.ImplementingType); + result.Add(new ValidationResult(message, ValidationSeverity.NotDisposed, validationTarget)); + } + } + + + private static void ValidateLifetime(ValidationTarget validationTarget, ServiceRegistration dependencyRegistration, ICollection result) + { + if (GetLifespan(validationTarget.DeclaringService.Lifetime) > GetLifespan(dependencyRegistration.Lifetime)) + { + var message = string.Format(CaptiveDependency, dependencyRegistration.ServiceType, + validationTarget.DeclaringService.ServiceType, GetLifetimeName(dependencyRegistration.Lifetime), + GetLifetimeName(validationTarget.DeclaringService.Lifetime)); + result.Add(new ValidationResult(message, ValidationSeverity.Captive, validationTarget)); + } + } + + public static void SetLifespan(int lifeSpan) where TLifetime : ILifetime + { + LifeSpans.TryAdd(typeof(TLifetime), lifeSpan); + } + + private static IEnumerable GetServiceRegistrations(ServiceMap serviceMap, ValidationTarget validationTarget) + { + return serviceMap.Where(x => validationTarget.ServiceType.IsAssignableFrom(x.Key)).SelectMany(x => x.Value.Values); + } + + private static ServiceRegistration GetServiceRegistration(ServiceMap serviceMap, ValidationTarget validationTarget) + { + if (!serviceMap.TryGetValue(validationTarget.ServiceType, out var registrations)) + { + return null; + } + + if (registrations.TryGetValue(string.Empty, out var registration)) + { + return registration; + } + + if (registrations.Count == 1) + { + return registrations.Values.First(); + } + + if (registrations.TryGetValue(validationTarget.ServiceName, out registration)) + { + return registration; + } + + return null; + } + + private static string GetLifetimeName(ILifetime lifetime) + { + if (lifetime == null) + { + return "Transient"; + } + return lifetime.GetType().Name; + } + + private static int GetLifespan(ILifetime lifetime) + { + if (lifetime == null) + { + return 0; + } + if (LifeSpans.TryGetValue(lifetime.GetType(), out var lifespan)) + { + return lifespan; + } + return 0; + } + } + + + public class ValidationTarget + { + public ServiceRegistration DeclaringService { get; } + public ParameterInfo Parameter { get; } + public Type ServiceType { get; } + public string ServiceName { get; } + + + public ValidationTarget(ServiceRegistration declaringRegistration, ParameterInfo parameter) : this(declaringRegistration, parameter, parameter.ParameterType, string.Empty) + { + } + + + public ValidationTarget(ServiceRegistration declaringService, ParameterInfo parameter, Type serviceType, string serviceName) + { + DeclaringService = declaringService; + Parameter = parameter; + ServiceType = serviceType; + ServiceName = serviceName; + + + if (serviceType.GetTypeInfo().IsGenericType && serviceType.GetTypeInfo().ContainsGenericParameters) + { + ServiceType = serviceType.GetGenericTypeDefinition(); + } + + } + + public ValidationTarget WithServiceDescription(Type serviceType, string serviceName) + { + return new ValidationTarget(DeclaringService, Parameter, serviceType, serviceName); + } + + } + + + + + + public class ValidationResult + { + public ValidationResult(string message, ValidationSeverity severity, ValidationTarget validationTarget) + { + Message = message; + Severity = severity; + ValidationTarget = validationTarget; + } + + public string Message { get; } + + public ValidationSeverity Severity { get; } + public ValidationTarget ValidationTarget { get; } + } + + public enum ValidationSeverity + { + NoIssues, + Captive, + NotDisposed, + MissingDependency, + Ambiguous + } + + internal static class TypeExtensions + { + public static bool Implements(this Type type) + { + return type.GetTypeInfo().ImplementedInterfaces.Contains(typeof(TBaseType)); + } + + public static bool IsFunc(this Type type) + { + var typeInfo = type.GetTypeInfo(); + return typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Func<>); + } + + public static bool IsLazy(this Type type) + { + var typeInfo = type.GetTypeInfo(); + return typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Lazy<>); + } + } + + internal static class ServiceMapExtensions + { + public static bool ContainsAmbiguousRegistrationFor(this ServiceMap serviceMap, Type serviceType) + { + if (!serviceMap.TryGetValue(serviceType, out var registrations)) + { + return false; + } + return registrations.Count > 1; + } + } +} diff --git a/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs b/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs index e2145f557a..d100713102 100644 --- a/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs +++ b/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs @@ -1,10 +1,14 @@ using System; using System.Linq; using System.Xml; -using LightInject; +using Moq; using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Core._Legacy.PackageActions; +using Umbraco.Tests.Components; namespace Umbraco.Tests.Composing { @@ -14,18 +18,21 @@ namespace Umbraco.Tests.Composing [Test] public void PackageActionCollectionBuilderWorks() { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); + var container = RegisterFactory.Create(); + + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - container.RegisterCollectionBuilder() + composition.WithCollectionBuilder() .Add(() => TypeLoader.GetPackageActions()); + Current.Factory = composition.CreateFactory(); + var actions = Current.PackageActions; Assert.AreEqual(2, actions.Count()); // order is unspecified, but both must be there - bool hasAction1 = actions.ElementAt(0) is PackageAction1 || actions.ElementAt(1) is PackageAction1; - bool hasAction2 = actions.ElementAt(0) is PackageAction2 || actions.ElementAt(1) is PackageAction2; + var hasAction1 = actions.ElementAt(0) is PackageAction1 || actions.ElementAt(1) is PackageAction1; + var hasAction2 = actions.ElementAt(0) is PackageAction2 || actions.ElementAt(1) is PackageAction2; Assert.IsTrue(hasAction1); Assert.IsTrue(hasAction2); } diff --git a/src/Umbraco.Tests/Composing/TypeFinderTests.cs b/src/Umbraco.Tests/Composing/TypeFinderTests.cs index 3bddd1b0d6..49c807b19f 100644 --- a/src/Umbraco.Tests/Composing/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeFinderTests.cs @@ -93,7 +93,7 @@ namespace Umbraco.Tests.Composing Assert.AreEqual(22, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] } - private static ProfilingLogger GetTestProfilingLogger() + private static IProfilingLogger GetTestProfilingLogger() { var logger = new DebugDiagnosticsLogger(); var profiler = new TestProfiler(); diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 75edcd6404..5148c7eb1b 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -9,6 +9,7 @@ using umbraco; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; @@ -27,7 +28,7 @@ namespace Umbraco.Tests.Composing public void Initialize() { // this ensures it's reset - _typeLoader = new TypeLoader(NullCacheProvider.Instance, SettingsForTests.GenerateMockGlobalSettings(), new ProfilingLogger(Mock.Of(), Mock.Of())); + _typeLoader = new TypeLoader(NullCacheProvider.Instance, LocalTempStorage.Default, new ProfilingLogger(Mock.Of(), Mock.Of())); foreach (var file in Directory.GetFiles(IOHelper.MapPath("~/App_Data/TEMP/TypesCache"))) File.Delete(file); diff --git a/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs b/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs index 34fb2add8b..d1470756f0 100644 --- a/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs +++ b/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs @@ -2,6 +2,7 @@ using System.Web.Routing; using Moq; using NUnit.Framework; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Tests.TestHelpers; @@ -50,7 +51,7 @@ namespace Umbraco.Tests.Configurations SettingsForTests.ConfigureSettings(globalSettingsMock.Object); SystemDirectories.Root = rootPath; - Assert.AreEqual(outcome, UmbracoConfig.For.GlobalSettings().GetUmbracoMvcArea()); + Assert.AreEqual(outcome, Current.Config.Global().GetUmbracoMvcArea()); } [TestCase("/umbraco/editContent.aspx")] diff --git a/src/Umbraco.Tests/CoreThings/TryConvertToTests.cs b/src/Umbraco.Tests/CoreThings/TryConvertToTests.cs index 2e187d85a5..c7741deb04 100644 --- a/src/Umbraco.Tests/CoreThings/TryConvertToTests.cs +++ b/src/Umbraco.Tests/CoreThings/TryConvertToTests.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; -using Umbraco.Core.Composing; using Umbraco.Tests.Testing; namespace Umbraco.Tests.CoreThings @@ -18,7 +17,7 @@ namespace Umbraco.Tests.CoreThings var settings = SettingsForTests.GetDefaultUmbracoSettings(); // fixme - base should do it! - Container.RegisterSingleton(_ => new DefaultShortStringHelper(settings)); + Composition.RegisterUnique(_ => new DefaultShortStringHelper(settings)); } [Test] diff --git a/src/Umbraco.Tests/CoreThings/UdiTests.cs b/src/Umbraco.Tests/CoreThings/UdiTests.cs index 62aa56bd14..2b4ace8810 100644 --- a/src/Umbraco.Tests/CoreThings/UdiTests.cs +++ b/src/Umbraco.Tests/CoreThings/UdiTests.cs @@ -2,13 +2,13 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using LightInject; using Moq; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Deploy; using Umbraco.Core.Logging; using Umbraco.Core.Serialization; @@ -23,11 +23,11 @@ namespace Umbraco.Tests.CoreThings public void SetUp() { // fixme - bad in a unit test - but Udi has a static ctor that wants it?! - var container = new Mock(); + var container = new Mock(); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); - container.Setup(x => x.GetInstance(typeof (TypeLoader))).Returns( - new TypeLoader(NullCacheProvider.Instance, globalSettings, new ProfilingLogger(Mock.Of(), Mock.Of()))); - Current.Container = container.Object; + container.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( + new TypeLoader(NullCacheProvider.Instance, LocalTempStorage.Default, new ProfilingLogger(Mock.Of(), Mock.Of()))); + Current.Factory = container.Object; Udi.ResetUdiTypes(); } diff --git a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs index 182f1a98f5..7508395c64 100644 --- a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; using System.Text; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Tests.TestHelpers; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Web; @@ -407,19 +407,19 @@ namespace Umbraco.Tests.FrontEnd private void SetUpDependencyContainer() { // fixme - bad in a unit test - but Udi has a static ctor that wants it?! - var container = new Mock(); + var container = new Mock(); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); container .Setup(x => x.GetInstance(typeof(TypeLoader))) .Returns(new TypeLoader( NullCacheProvider.Instance, - globalSettings, + LocalTempStorage.Default, new ProfilingLogger(Mock.Of(), Mock.Of()) ) ); - Current.Container = container.Object; + Current.Factory = container.Object; } } } diff --git a/src/Umbraco.Tests/IO/FileSystemsTests.cs b/src/Umbraco.Tests/IO/FileSystemsTests.cs index 49b8af6d18..116c53f5e4 100644 --- a/src/Umbraco.Tests/IO/FileSystemsTests.cs +++ b/src/Umbraco.Tests/IO/FileSystemsTests.cs @@ -1,16 +1,18 @@ using System; using System.IO; using System.Text; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Components; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Composing; +using Umbraco.Core.Composing.Composers; using Umbraco.Core.IO; using Umbraco.Core.IO.MediaPathSchemes; using Umbraco.Core.Logging; using Umbraco.Core.Services; +using Umbraco.Tests.Components; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.IO @@ -18,7 +20,8 @@ namespace Umbraco.Tests.IO [TestFixture] public class FileSystemsTests { - private ServiceContainer _container; + private IRegister _register; + private IFactory _factory; [SetUp] public void Setup() @@ -27,13 +30,18 @@ namespace Umbraco.Tests.IO var config = SettingsForTests.GetDefaultUmbracoSettings(); SettingsForTests.ConfigureSettings(config); - _container = new ServiceContainer(); - _container.ConfigureUmbracoCore(); - _container.Register(_ => Mock.Of()); - _container.Register(); - _container.Register(_ => Mock.Of()); - _container.Register(_ => Mock.Of()); - _container.RegisterSingleton(); + _register = RegisterFactory.Create(); + + var composition = new Composition(_register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + + composition.Register(_ => Mock.Of()); + composition.Register(_ => Mock.Of()); + composition.Register(_ => Mock.Of()); + composition.RegisterUnique(); + + composition.ComposeFileSystems(); + + _factory = composition.CreateFactory(); // make sure we start clean // because some tests will create corrupt or weird filesystems @@ -47,31 +55,37 @@ namespace Umbraco.Tests.IO FileSystems.Reset(); Current.Reset(); - _container.Dispose(); + _register.DisposeIfDisposable(); } - private FileSystems FileSystems => _container.GetInstance(); + private FileSystems FileSystems => _factory.GetInstance(); [Test] - public void Can_Get_Base_File_System() + public void Can_Get_MediaFileSystem() { - var fileSystem = FileSystems.GetUnderlyingFileSystemProvider("media"); - + var fileSystem = _factory.GetInstance(); Assert.NotNull(fileSystem); } [Test] - public void Can_Get_Typed_File_System() + public void Can_Get_IMediaFileSystem() { - var fileSystem = FileSystems.GetFileSystemProvider(); - + var fileSystem = _factory.GetInstance(); Assert.NotNull(fileSystem); } [Test] - public void Media_Fs_Safe_Delete() + public void IMediaFileSystem_Is_Singleton() { - var fs = FileSystems.GetFileSystemProvider(); + var fileSystem1 = _factory.GetInstance(); + var fileSystem2 = _factory.GetInstance(); + Assert.AreSame(fileSystem1, fileSystem2); + } + + [Test] + public void Can_Delete_MediaFiles() + { + var fs = _factory.GetInstance(); var ms = new MemoryStream(Encoding.UTF8.GetBytes("test")); var virtPath = fs.GetMediaPath("file.txt", Guid.NewGuid(), Guid.NewGuid()); fs.AddFile(virtPath, ms); @@ -93,52 +107,42 @@ namespace Umbraco.Tests.IO Assert.IsTrue(Directory.Exists(physPath)); } - public void Singleton_Typed_File_System() - { - var fs1 = FileSystems.GetFileSystemProvider(); - var fs2 = FileSystems.GetFileSystemProvider(); - Assert.AreSame(fs1, fs2); + // fixme - don't make sense anymore + /* + [Test] + public void Cannot_Get_InvalidFileSystem() + { + // throws because InvalidTypedFileSystem does not have the proper attribute with an alias + Assert.Throws(() => FileSystems.GetFileSystem()); } [Test] - public void Exception_Thrown_On_Invalid_Typed_File_System() - { - Assert.Throws(() => FileSystems.GetFileSystemProvider()); - } - - [Test] - public void Exception_Thrown_On_NonConfigured_Typed_File_System() + public void Cannot_Get_NonConfiguredFileSystem() { // note: we need to reset the manager between tests else the Accept_Fallback test would corrupt that one - Assert.Throws(() => FileSystems.GetFileSystemProvider()); + // throws because NonConfiguredFileSystem has the proper attribute with an alias, + // but then the container cannot find an IFileSystem implementation for that alias + Assert.Throws(() => FileSystems.GetFileSystem()); + + // all we'd need to pass is to register something like: + //_container.Register("noconfig", factory => new PhysicalFileSystem("~/foo")); } - [Test] - public void Accept_Fallback_On_NonConfigured_Typed_File_System() + internal class InvalidFileSystem : FileSystemWrapper { - var fs = FileSystems.GetFileSystemProvider(() => new PhysicalFileSystem("~/App_Data/foo")); - - Assert.NotNull(fs); - } - - /// - /// Used in unit tests, for a typed file system we need to inherit from FileSystemWrapper and they MUST have a ctor - /// that only accepts a base IFileSystem object - /// - internal class InvalidTypedFileSystem : FileSystemWrapper - { - public InvalidTypedFileSystem(IFileSystem wrapped, string invalidParam) - : base(wrapped) + public InvalidFileSystem(IFileSystem innerFileSystem) + : base(innerFileSystem) { } } - [FileSystemProvider("noconfig")] - internal class NonConfiguredTypeFileSystem : FileSystemWrapper + [InnerFileSystem("noconfig")] + internal class NonConfiguredFileSystem : FileSystemWrapper { - public NonConfiguredTypeFileSystem(IFileSystem wrapped) - : base(wrapped) + public NonConfiguredFileSystem(IFileSystem innerFileSystem) + : base(innerFileSystem) { } } + */ } } diff --git a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs index 90fdf3810d..262b026cae 100644 --- a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs +++ b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Scoping; @@ -25,7 +27,7 @@ namespace Umbraco.Tests.IO { SafeCallContext.Clear(); ClearFiles(); - ShadowFileSystems.ResetId(); + FileSystems.ResetShadowId(); } [TearDown] @@ -33,13 +35,13 @@ namespace Umbraco.Tests.IO { SafeCallContext.Clear(); ClearFiles(); - ShadowFileSystems.ResetId(); + FileSystems.ResetShadowId(); } private static void ClearFiles() { TestHelper.DeleteDirectory(IOHelper.MapPath("FileSysTests")); - TestHelper.DeleteDirectory(IOHelper.MapPath("App_Data")); + TestHelper.DeleteDirectory(IOHelper.MapPath("App_Data/TEMP/ShadowFs")); } private static string NormPath(string path) @@ -373,74 +375,92 @@ namespace Umbraco.Tests.IO Assert.IsFalse(File.Exists(path + "/ShadowTests/sub/sub/f2.txt")); } + class FS : FileSystemWrapper + { + public FS(IFileSystem innerFileSystem) + : base(innerFileSystem) + { } + } + [Test] public void ShadowScopeComplete() { var logger = Mock.Of(); var path = IOHelper.MapPath("FileSysTests"); - var appdata = IOHelper.MapPath("App_Data"); + var shadowfs = IOHelper.MapPath("App_Data/TEMP/ShadowFs"); Directory.CreateDirectory(path); - Directory.CreateDirectory(appdata); + Directory.CreateDirectory(shadowfs); var scopedFileSystems = false; - var fs = new PhysicalFileSystem(path, "ignore"); - var sw = new ShadowWrapper(fs, "shadow", () => scopedFileSystems); - var swa = new[] { sw }; + var phy = new PhysicalFileSystem(path, "ignore"); + + var container = Mock.Of(); + var fileSystems = new FileSystems(container, logger) { IsScoped = () => scopedFileSystems }; + var fs = fileSystems.GetFileSystem(phy); + var sw = (ShadowWrapper) fs.InnerFileSystem; using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f1.txt", ms); - Assert.IsTrue(fs.FileExists("sub/f1.txt")); + Assert.IsTrue(phy.FileExists("sub/f1.txt")); Guid id; // explicit shadow without scope does not work sw.Shadow(id = Guid.NewGuid()); - Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); - Assert.IsTrue(fs.FileExists("sub/f2.txt")); + Assert.IsTrue(phy.FileExists("sub/f2.txt")); sw.UnShadow(true); - Assert.IsTrue(fs.FileExists("sub/f2.txt")); - Assert.IsFalse(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + Assert.IsTrue(phy.FileExists("sub/f2.txt")); + Assert.IsFalse(Directory.Exists(shadowfs + "/" + id)); // shadow with scope but no complete does not complete scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(id = Guid.NewGuid(), swa, logger); - Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f3.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f3.txt")); - Assert.AreEqual(1, Directory.GetDirectories(appdata + "/TEMP/ShadowFs").Length); + Assert.IsFalse(phy.FileExists("sub/f3.txt")); + var dirs = Directory.GetDirectories(shadowfs); + Assert.AreEqual(1, dirs.Length); + Assert.AreEqual((shadowfs + "/" + id).Replace('\\', '/'), dirs[0].Replace('\\', '/')); + dirs = Directory.GetDirectories(dirs[0]); + var typedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/typed")); + Assert.IsNotNull(typedDir); + dirs = Directory.GetDirectories(typedDir); + var scopedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/Umbraco.Tests.IO.ShadowFileSystemTests+FS")); // this is where files go + Assert.IsNotNull(scopedDir); scope.Dispose(); scopedFileSystems = false; - Assert.IsFalse(fs.FileExists("sub/f3.txt")); - TestHelper.TryAssert(() => Assert.IsFalse(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id))); + Assert.IsFalse(phy.FileExists("sub/f3.txt")); + TestHelper.TryAssert(() => Assert.IsFalse(Directory.Exists(shadowfs + "/" + id))); // shadow with scope and complete does complete scopedFileSystems = true; // pretend we have a scope - scope = new ShadowFileSystems(id = Guid.NewGuid(), swa, logger); - Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f4.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f4.txt")); - Assert.AreEqual(1, Directory.GetDirectories(appdata + "/TEMP/ShadowFs").Length); + Assert.IsFalse(phy.FileExists("sub/f4.txt")); + Assert.AreEqual(1, Directory.GetDirectories(shadowfs).Length); scope.Complete(); scope.Dispose(); scopedFileSystems = false; - TestHelper.TryAssert(() => Assert.AreEqual(0, Directory.GetDirectories(appdata + "/TEMP/ShadowFs").Length)); - Assert.IsTrue(fs.FileExists("sub/f4.txt")); - Assert.IsFalse(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + TestHelper.TryAssert(() => Assert.AreEqual(0, Directory.GetDirectories(shadowfs).Length)); + Assert.IsTrue(phy.FileExists("sub/f4.txt")); + Assert.IsFalse(Directory.Exists(shadowfs + "/" + id)); // test scope for "another thread" scopedFileSystems = true; // pretend we have a scope - scope = new ShadowFileSystems(id = Guid.NewGuid(), swa, logger); - Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f5.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f5.txt")); + Assert.IsFalse(phy.FileExists("sub/f5.txt")); // pretend we're another thread w/out scope scopedFileSystems = false; @@ -448,12 +468,12 @@ namespace Umbraco.Tests.IO sw.AddFile("sub/f6.txt", ms); scopedFileSystems = true; // pretend we have a scope - Assert.IsTrue(fs.FileExists("sub/f6.txt")); // other thread has written out to fs + Assert.IsTrue(phy.FileExists("sub/f6.txt")); // other thread has written out to fs scope.Complete(); scope.Dispose(); scopedFileSystems = false; - Assert.IsTrue(fs.FileExists("sub/f5.txt")); - Assert.IsFalse(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + Assert.IsTrue(phy.FileExists("sub/f5.txt")); + Assert.IsFalse(Directory.Exists(shadowfs + "/" + id)); } [Test] @@ -462,27 +482,30 @@ namespace Umbraco.Tests.IO var logger = Mock.Of(); var path = IOHelper.MapPath("FileSysTests"); - var appdata = IOHelper.MapPath("App_Data"); + var shadowfs = IOHelper.MapPath("App_Data/TEMP/ShadowFs"); Directory.CreateDirectory(path); var scopedFileSystems = false; - var fs = new PhysicalFileSystem(path, "ignore"); - var sw = new ShadowWrapper(fs, "shadow", () => scopedFileSystems); - var swa = new[] { sw }; + var phy = new PhysicalFileSystem(path, "ignore"); + + var container = Mock.Of(); + var fileSystems = new FileSystems(container, logger) { IsScoped = () => scopedFileSystems }; + var fs = fileSystems.GetFileSystem( phy); + var sw = (ShadowWrapper) fs.InnerFileSystem; using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f1.txt", ms); - Assert.IsTrue(fs.FileExists("sub/f1.txt")); + Assert.IsTrue(phy.FileExists("sub/f1.txt")); Guid id; scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(id = Guid.NewGuid(), swa, logger); - Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f2.txt")); + Assert.IsFalse(phy.FileExists("sub/f2.txt")); // pretend we're another thread w/out scope scopedFileSystems = false; @@ -490,15 +513,15 @@ namespace Umbraco.Tests.IO sw.AddFile("sub/f2.txt", ms); scopedFileSystems = true; // pretend we have a scope - Assert.IsTrue(fs.FileExists("sub/f2.txt")); // other thread has written out to fs + Assert.IsTrue(phy.FileExists("sub/f2.txt")); // other thread has written out to fs scope.Complete(); scope.Dispose(); scopedFileSystems = false; - Assert.IsTrue(fs.FileExists("sub/f2.txt")); - TestHelper.TryAssert(() => Assert.IsFalse(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id))); + Assert.IsTrue(phy.FileExists("sub/f2.txt")); + TestHelper.TryAssert(() => Assert.IsFalse(Directory.Exists(shadowfs + "/" + id))); string text; - using (var s = fs.OpenFile("sub/f2.txt")) + using (var s = phy.OpenFile("sub/f2.txt")) using (var r = new StreamReader(s)) text = r.ReadToEnd(); @@ -512,27 +535,30 @@ namespace Umbraco.Tests.IO var logger = Mock.Of(); var path = IOHelper.MapPath("FileSysTests"); - var appdata = IOHelper.MapPath("App_Data"); + var shadowfs = IOHelper.MapPath("App_Data/TEMP/ShadowFs"); Directory.CreateDirectory(path); var scopedFileSystems = false; - var fs = new PhysicalFileSystem(path, "ignore"); - var sw = new ShadowWrapper(fs, "shadow", () => scopedFileSystems); - var swa = new[] { sw }; + var phy = new PhysicalFileSystem(path, "ignore"); + + var container = Mock.Of(); + var fileSystems = new FileSystems(container, logger) { IsScoped = () => scopedFileSystems }; + var fs = fileSystems.GetFileSystem( phy); + var sw = (ShadowWrapper)fs.InnerFileSystem; using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f1.txt", ms); - Assert.IsTrue(fs.FileExists("sub/f1.txt")); + Assert.IsTrue(phy.FileExists("sub/f1.txt")); Guid id; scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(id = Guid.NewGuid(), swa, logger); - Assert.IsTrue(Directory.Exists(appdata + "/TEMP/ShadowFs/" + id)); + var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid()); + Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f2.txt")); + Assert.IsFalse(phy.FileExists("sub/f2.txt")); // pretend we're another thread w/out scope scopedFileSystems = false; @@ -540,11 +566,11 @@ namespace Umbraco.Tests.IO sw.AddFile("sub/f2.txt/f2.txt", ms); scopedFileSystems = true; // pretend we have a scope - Assert.IsTrue(fs.FileExists("sub/f2.txt/f2.txt")); // other thread has written out to fs + Assert.IsTrue(phy.FileExists("sub/f2.txt/f2.txt")); // other thread has written out to fs using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f3.txt", ms); - Assert.IsFalse(fs.FileExists("sub/f3.txt")); + Assert.IsFalse(phy.FileExists("sub/f3.txt")); scope.Complete(); @@ -570,7 +596,7 @@ namespace Umbraco.Tests.IO } // still, the rest of the changes has been applied ok - Assert.IsTrue(fs.FileExists("sub/f3.txt")); + Assert.IsTrue(phy.FileExists("sub/f3.txt")); } [Test] diff --git a/src/Umbraco.Tests/Integration/ContentEventsTests.cs b/src/Umbraco.Tests/Integration/ContentEventsTests.cs index 2aee582acc..7b22d282f0 100644 --- a/src/Umbraco.Tests/Integration/ContentEventsTests.cs +++ b/src/Umbraco.Tests/Integration/ContentEventsTests.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -20,6 +19,7 @@ using static Umbraco.Tests.Cache.DistributedCache.DistributedCacheTests; namespace Umbraco.Tests.Integration { [TestFixture] + [Category("Slow")] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class ContentEventsTests : TestWithSomeContentBase { @@ -50,10 +50,10 @@ namespace Umbraco.Tests.Integration { base.Compose(); - Container.Register(_ => new TestServerRegistrar()); // localhost-only - Container.Register(new PerContainerLifetime()); + Composition.Register(_ => new TestServerRegistrar()); // localhost-only + Composition.RegisterUnique(); - Container.RegisterCollectionBuilder() + Composition.WithCollectionBuilder() .Add() .Add() .Add(); diff --git a/src/Umbraco.Tests/Issues/U9560.cs b/src/Umbraco.Tests/Issues/U9560.cs index c5748e21e0..e422cbc86b 100644 --- a/src/Umbraco.Tests/Issues/U9560.cs +++ b/src/Umbraco.Tests/Issues/U9560.cs @@ -3,7 +3,8 @@ using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Tests.Testing; -using LightInject; +using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Issues @@ -26,7 +27,7 @@ namespace Umbraco.Tests.Issues var aliasName = string.Empty; // read fields, same as what we do with PetaPoco Fetch - using (var db = Container.GetInstance().CreateDatabase()) + using (var db = Factory.GetInstance().CreateDatabase()) { db.OpenSharedConnection(); try @@ -54,7 +55,7 @@ namespace Umbraco.Tests.Issues Assert.AreEqual("Alias", aliasName); // try differently - using (var db = Container.GetInstance().CreateDatabase()) + using (var db = Factory.GetInstance().CreateDatabase()) { db.OpenSharedConnection(); try diff --git a/src/Umbraco.Tests/Macros/MacroTests.cs b/src/Umbraco.Tests/Macros/MacroTests.cs index 2cb7b1aa4e..3ab56052e0 100644 --- a/src/Umbraco.Tests/Macros/MacroTests.cs +++ b/src/Umbraco.Tests/Macros/MacroTests.cs @@ -4,6 +4,7 @@ using System.Web.UI.WebControls; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; @@ -26,7 +27,7 @@ namespace Umbraco.Tests.Macros new IsolatedRuntimeCache(type => new ObjectCacheRuntimeCacheProvider())); //Current.ApplicationContext = new ApplicationContext(cacheHelper, new ProfilingLogger(Mock.Of(), Mock.Of())); - UmbracoConfig.For.SetUmbracoSettings(SettingsForTests.GetDefaultUmbracoSettings()); + Current.Config.SetUmbracoConfig(SettingsForTests.GetDefaultUmbracoSettings()); } [TestCase("123", "IntProp", typeof(int))] diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs index 19aaf79581..ce3d1d705c 100644 --- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs @@ -2,7 +2,6 @@ using System.Linq; using Moq; using System.Text; -using LightInject; using NUnit.Framework; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -14,27 +13,25 @@ using Umbraco.Core.Manifest; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.Validators; using Umbraco.Core.Services; -using Umbraco.Web.ContentApps; namespace Umbraco.Tests.Manifest { [TestFixture] public class ManifestParserTests { - private ManifestParser _parser; [SetUp] public void Setup() { Current.Reset(); - var container = Mock.Of(); - Current.Container = container; + var factory = Mock.Of(); + Current.Factory = factory; - var serviceContext = new ServiceContext( + var serviceContext = ServiceContext.CreatePartial( localizedTextService: Mock.Of()); - Mock.Get(container) + Mock.Get(factory) .Setup(x => x.GetInstance(It.IsAny())) .Returns(x => { diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs index 2a5b76e1bd..f1f46133c3 100644 --- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs +++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs @@ -3,17 +3,9 @@ using System.Collections.Specialized; using System.Configuration.Provider; using System.Security.Cryptography; using System.Web.Security; -using LightInject; using Moq; using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; -using Umbraco.Core.Scoping; using Umbraco.Core.Security; -using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; namespace Umbraco.Tests.Membership diff --git a/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs b/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs index 9c0560351b..47c854333a 100644 --- a/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs +++ b/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs @@ -1,7 +1,4 @@ using System; -using System.Configuration; -using System.Linq; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -11,8 +8,6 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Sync; using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Cache.DistributedCache; -using Umbraco.Tests.TestHelpers.Stubs; namespace Umbraco.Tests.Misc @@ -32,7 +27,7 @@ namespace Umbraco.Tests.Misc [Test] public void NoApplicationUrlByDefault() { - var state = new RuntimeState(Mock.Of(), new Lazy(Mock.Of), new Lazy(Mock.Of), Mock.Of(), Mock.Of()); + var state = new RuntimeState(Mock.Of(), Mock.Of(), Mock.Of(), new Lazy(), new Lazy()); Assert.IsNull(state.ApplicationUrl); } @@ -51,10 +46,7 @@ namespace Umbraco.Tests.Misc var registrar = new Mock(); registrar.Setup(x => x.GetCurrentServerUmbracoApplicationUrl()).Returns("http://server1.com/umbraco"); - var state = new RuntimeState( - Mock.Of(), - new Lazy(() => registrar.Object), - new Lazy(Mock.Of), settings, globalConfig.Object); + var state = new RuntimeState(Mock.Of(), settings, globalConfig.Object, new Lazy(), new Lazy(() => registrar.Object)); state.EnsureApplicationUrl(); @@ -75,9 +67,9 @@ namespace Umbraco.Tests.Misc ApplicationUrlHelper.ApplicationUrlProvider = request => "http://server1.com/umbraco"; - - var state = new RuntimeState(Mock.Of(), new Lazy(Mock.Of), new Lazy(Mock.Of), settings, globalConfig.Object); + + var state = new RuntimeState(Mock.Of(), settings, globalConfig.Object, new Lazy(), new Lazy(() => Mock.Of())); state.EnsureApplicationUrl(); @@ -101,7 +93,7 @@ namespace Umbraco.Tests.Misc // still NOT set Assert.IsNull(url); } - + [Test] public void SetApplicationUrlFromStSettingsNoSsl() { @@ -112,8 +104,8 @@ namespace Umbraco.Tests.Misc var globalConfig = Mock.Get(SettingsForTests.GenerateMockGlobalSettings()); globalConfig.Setup(x => x.UseHttps).Returns(false); - - + + var url = ApplicationUrlHelper.TryGetApplicationUrl(settings, Mock.Of(), globalConfig.Object, Mock.Of()); Assert.AreEqual("http://mycoolhost.com/umbraco", url); @@ -129,8 +121,8 @@ namespace Umbraco.Tests.Misc var globalConfig = Mock.Get(SettingsForTests.GenerateMockGlobalSettings()); globalConfig.Setup(x => x.UseHttps).Returns(true); - - + + var url = ApplicationUrlHelper.TryGetApplicationUrl(settings, Mock.Of(), globalConfig.Object, Mock.Of()); Assert.AreEqual("https://mycoolhost.com/umbraco", url); @@ -146,13 +138,11 @@ namespace Umbraco.Tests.Misc var globalConfig = Mock.Get(SettingsForTests.GenerateMockGlobalSettings()); globalConfig.Setup(x => x.UseHttps).Returns(true); - + var url = ApplicationUrlHelper.TryGetApplicationUrl(settings, Mock.Of(), globalConfig.Object, Mock.Of()); Assert.AreEqual("httpx://whatever.com/umbraco", url); } - - } } diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index d31fcbf744..c893b71df5 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -5,9 +5,12 @@ using System.Globalization; using System.Linq; using System.Threading; using Moq; +using Umbraco.Core; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Composing.Composers; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -38,10 +41,11 @@ namespace Umbraco.Tests.Models { base.Compose(); - Container.Register(_ => Mock.Of()); - Container.Register(); - Container.Register(_ => Mock.Of()); - Container.Register(_ => Mock.Of()); + Composition.Register(_ => Mock.Of()); + Composition.ComposeFileSystems(); + + Composition.Register(_ => Mock.Of()); + Composition.Register(_ => Mock.Of()); } [Test] @@ -51,7 +55,7 @@ namespace Umbraco.Tests.Models var content = new Content("content", -1, contentType) { Id = 1, VersionId = 1 }; const string langFr = "fr-FR"; - + contentType.Variations = ContentVariation.Culture; Assert.IsFalse(content.IsPropertyDirty("CultureInfos")); //hasn't been changed @@ -69,7 +73,7 @@ namespace Umbraco.Tests.Models Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date content.SetCultureName("name-fr", langFr); - Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); + Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); Assert.IsTrue(content.IsPropertyDirty("CultureInfos")); //it's true now since we've updated a name } @@ -100,7 +104,7 @@ namespace Umbraco.Tests.Models Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date content.SetCultureName("name-fr", langFr); content.PublishCulture(langFr); //we've set the name, now we're publishing it - Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); + Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); Assert.IsTrue(content.IsPropertyDirty("PublishCultureInfos")); //it's true now since we've updated a name } @@ -206,7 +210,7 @@ namespace Umbraco.Tests.Models Assert.AreNotSame(content.Properties, clone.Properties); } - private static ProfilingLogger GetTestProfilingLogger() + private static IProfilingLogger GetTestProfilingLogger() { var logger = new DebugDiagnosticsLogger(); var profiler = new TestProfiler(); @@ -305,7 +309,7 @@ namespace Umbraco.Tests.Models content.UpdateDate = DateTime.Now; content.WriterId = 23; - + // Act var clone = (Content)content.DeepClone(); @@ -402,7 +406,7 @@ namespace Umbraco.Tests.Models content.SetCultureName("Hello", "en-US"); content.SetCultureName("World", "es-ES"); content.PublishCulture("en-US"); - + var i = 200; foreach (var property in content.Properties) { @@ -420,7 +424,7 @@ namespace Umbraco.Tests.Models { Id = 88 }; - + content.Trashed = true; content.UpdateDate = DateTime.Now; content.WriterId = 23; diff --git a/src/Umbraco.Tests/Models/ContentTypeTests.cs b/src/Umbraco.Tests/Models/ContentTypeTests.cs index 6ef28d8290..d9e65ba6c6 100644 --- a/src/Umbraco.Tests/Models/ContentTypeTests.cs +++ b/src/Umbraco.Tests/Models/ContentTypeTests.cs @@ -93,7 +93,7 @@ namespace Umbraco.Tests.Models } } - private static ProfilingLogger GetTestProfilingLogger() + private static IProfilingLogger GetTestProfilingLogger() { var logger = new DebugDiagnosticsLogger(); var profiler = new TestProfiler(); diff --git a/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs b/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs index 23a9eafe76..1f7c5624bf 100644 --- a/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/AutoMapperTests.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using AutoMapper; using NUnit.Framework; @@ -9,7 +8,6 @@ using Umbraco.Core.Manifest; using Umbraco.Core.PropertyEditors; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; -using LightInject; namespace Umbraco.Tests.Models.Mapping { @@ -22,16 +20,16 @@ namespace Umbraco.Tests.Models.Mapping base.Compose(); var manifestBuilder = new ManifestParser( - CacheHelper.CreateDisabledCacheHelper().RuntimeCache, + CacheHelper.Disabled.RuntimeCache, new ManifestValueValidatorCollection(Enumerable.Empty()), - Logger) + Composition.Logger) { Path = TestHelper.CurrentAssemblyDirectory }; - Container.Register(_ => manifestBuilder); + Composition.RegisterUnique(_ => manifestBuilder); Func> typeListProducerList = Enumerable.Empty; - Container.GetInstance() + Composition.WithCollectionBuilder() .Clear() .Add(typeListProducerList); } @@ -39,7 +37,7 @@ namespace Umbraco.Tests.Models.Mapping [Test] public void AssertConfigurationIsValid() { - var profiles = Container.GetAllInstances().ToArray(); + var profiles = Factory.GetAllInstances().ToArray(); var config = new MapperConfiguration(cfg => { diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs index 76e618ea26..b2d1440010 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; using System.Linq; using AutoMapper; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -55,13 +53,13 @@ namespace Umbraco.Tests.Models.Mapping var dataEditors = new DataEditorCollection(editors); _editorsMock = new Mock(dataEditors); _editorsMock.Setup(x => x[It.IsAny()]).Returns(editors[0]); - Container.RegisterSingleton(f => _editorsMock.Object); + Composition.RegisterUnique(f => _editorsMock.Object); - Container.RegisterSingleton(_ => _contentTypeService.Object); - Container.RegisterSingleton(_ => _contentService.Object); - Container.RegisterSingleton(_ => _dataTypeService.Object); - Container.RegisterSingleton(_ => _entityService.Object); - Container.RegisterSingleton(_ => _fileService.Object); + Composition.RegisterUnique(_ => _contentTypeService.Object); + Composition.RegisterUnique(_ => _contentService.Object); + Composition.RegisterUnique(_ => _dataTypeService.Object); + Composition.RegisterUnique(_ => _entityService.Object); + Composition.RegisterUnique(_ => _fileService.Object); } [Test] diff --git a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs index 03a59584a3..cab9de9204 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs @@ -12,7 +12,6 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Core.Composing; using Umbraco.Tests.Testing; using Current = Umbraco.Web.Composing.Current; @@ -26,7 +25,7 @@ namespace Umbraco.Tests.Models.Mapping { base.Compose(); - Container.RegisterSingleton(f => Mock.Of()); + Composition.RegisterUnique(f => Mock.Of()); } [DataEditor("Test.Test", "Test", "~/Test.html")] @@ -158,7 +157,7 @@ namespace Umbraco.Tests.Models.Mapping AssertBasicProperty(invariantContent, p); AssertDisplayProperty(invariantContent, p); } - + Assert.AreEqual(content.PropertyGroups.Count(), invariantContent.Tabs.Count()); } diff --git a/src/Umbraco.Tests/Models/MediaXmlTest.cs b/src/Umbraco.Tests/Models/MediaXmlTest.cs index 492803ce17..1a56fac4eb 100644 --- a/src/Umbraco.Tests/Models/MediaXmlTest.cs +++ b/src/Umbraco.Tests/Models/MediaXmlTest.cs @@ -29,7 +29,12 @@ namespace Umbraco.Tests.Models // reference, so static ctor runs, so event handlers register // and then, this will reset the width, height... because the file does not exist, of course ;-( - var ignored = new FileUploadPropertyEditor(Mock.Of(), new MediaFileSystem(Mock.Of()), Mock.Of()); + var logger = Mock.Of(); + var scheme = Mock.Of(); + var config = Mock.Of(); + + var mediaFileSystem = new MediaFileSystem(Mock.Of(), config, scheme, logger); + var ignored = new FileUploadPropertyEditor(Mock.Of(), mediaFileSystem, config); var media = MockedMedia.CreateMediaImage(mediaType, -1); media.WriterId = -1; // else it's zero and that's not a user and it breaks the tests diff --git a/src/Umbraco.Tests/Models/VariationTests.cs b/src/Umbraco.Tests/Models/VariationTests.cs index e6f4e53d26..f5ba7f5915 100644 --- a/src/Umbraco.Tests/Models/VariationTests.cs +++ b/src/Umbraco.Tests/Models/VariationTests.cs @@ -1,5 +1,4 @@ using System; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -27,8 +26,8 @@ namespace Umbraco.Tests.Models // need to be able to retrieve them all... Current.Reset(); - var container = Mock.Of(); - Current.Container = container; + var factory = Mock.Of(); + Current.Factory = factory; var dataEditors = new DataEditorCollection(new IDataEditor[] { @@ -46,11 +45,11 @@ namespace Umbraco.Tests.Models .Setup(x => x.GetDataType(It.IsAny())) .Returns(x => dataType); - var serviceContext = new ServiceContext( + var serviceContext = ServiceContext.CreatePartial( dataTypeService: dataTypeService, localizedTextService: Mock.Of()); - Mock.Get(container) + Mock.Get(factory) .Setup(x => x.GetInstance(It.IsAny())) .Returns(x => { diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index 788ee597dd..32689fe192 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; +using Umbraco.Web.Security; namespace Umbraco.Tests.Persistence { @@ -33,7 +34,7 @@ namespace Umbraco.Tests.Persistence _sqlCeSyntaxProvider = new SqlCeSyntaxProvider(); _sqlSyntaxProviders = new[] { (ISqlSyntaxProvider) _sqlCeSyntaxProvider }; _logger = Mock.Of(); - _databaseFactory = new UmbracoDatabaseFactory(_sqlSyntaxProviders, _logger, Mock.Of()); + _databaseFactory = new UmbracoDatabaseFactory(_logger, new Lazy(() => Mock.Of())); } [TearDown] @@ -76,7 +77,7 @@ namespace Umbraco.Tests.Persistence } // re-create the database factory and database context with proper connection string - _databaseFactory = new UmbracoDatabaseFactory(connString, Constants.DbProviderNames.SqlCe, _sqlSyntaxProviders, _logger, Mock.Of()); + _databaseFactory = new UmbracoDatabaseFactory(connString, Constants.DbProviderNames.SqlCe, _logger, new Lazy(() => Mock.Of())); // create application context //var appCtx = new ApplicationContext( @@ -88,9 +89,11 @@ namespace Umbraco.Tests.Persistence // create the umbraco database DatabaseSchemaCreator schemaHelper; using (var database = _databaseFactory.CreateDatabase()) + using (var transaction = database.GetTransaction()) { schemaHelper = new DatabaseSchemaCreator(database, _logger); schemaHelper.InitializeDatabaseSchema(); + transaction.Complete(); } var umbracoNodeTable = schemaHelper.TableExists("umbracoNode"); diff --git a/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs b/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs index bff43d5ea8..6aba8187f2 100644 --- a/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs +++ b/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs @@ -19,8 +19,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling { const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=x;password=umbraco"; const string providerName = Constants.DbProviderNames.SqlServer; - var sqlSyntax = new[] { new SqlServerSyntaxProvider(new Lazy(() => null)) }; - var factory = new UmbracoDatabaseFactory(connectionString, providerName, sqlSyntax, Mock.Of(), Mock.Of()); + var factory = new UmbracoDatabaseFactory(connectionString, providerName, Mock.Of(), new Lazy(() => Mock.Of())); using (var database = factory.CreateDatabase()) { @@ -34,8 +33,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling { const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco"; const string providerName = Constants.DbProviderNames.SqlServer; - var sqlSyntax = new[] { new SqlServerSyntaxProvider(new Lazy(() => null)) }; - var factory = new UmbracoDatabaseFactory(connectionString, providerName, sqlSyntax, Mock.Of(), Mock.Of()); + var factory = new UmbracoDatabaseFactory(connectionString, providerName, Mock.Of(), new Lazy(() => Mock.Of())); using (var database = factory.CreateDatabase()) { diff --git a/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs index eb85656ee4..de970c900d 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs @@ -1,6 +1,5 @@ using System.Linq; using NUnit.Framework; -using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -26,7 +25,7 @@ namespace Umbraco.Tests.Persistence.Repositories var sp = TestObjects.GetScopeProvider(Logger); using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor) sp, Logger); repo.Save(new AuditItem(-1, AuditType.System, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), "This is a System audit trail")); var dtos = scope.Database.Fetch("WHERE id > -1"); @@ -42,7 +41,7 @@ namespace Umbraco.Tests.Persistence.Repositories var sp = TestObjects.GetScopeProvider(Logger); using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor) sp, Logger); for (var i = 0; i < 100; i++) { @@ -55,7 +54,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor) sp, Logger); var page = repo.GetPagedResultsByQuery(sp.SqlContext.Query(), 0, 10, out var total, Direction.Descending, null, null); @@ -70,7 +69,7 @@ namespace Umbraco.Tests.Persistence.Repositories var sp = TestObjects.GetScopeProvider(Logger); using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor)sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor)sp, Logger); for (var i = 0; i < 100; i++) { @@ -83,7 +82,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor)sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor)sp, Logger); var query = sp.SqlContext.Query().Where(x => x.UserId == -1); @@ -113,7 +112,7 @@ namespace Umbraco.Tests.Persistence.Repositories var sp = TestObjects.GetScopeProvider(Logger); using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor) sp, Logger); for (var i = 0; i < 100; i++) { @@ -126,7 +125,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor) sp, Logger); var page = repo.GetPagedResultsByQuery(sp.SqlContext.Query(), 0, 9, out var total, Direction.Descending, new[] {AuditType.Publish}, null) @@ -144,7 +143,7 @@ namespace Umbraco.Tests.Persistence.Repositories var sp = TestObjects.GetScopeProvider(Logger); using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor) sp, Logger); for (var i = 0; i < 100; i++) { @@ -157,7 +156,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger); + var repo = new AuditRepository((IScopeAccessor) sp, Logger); var page = repo.GetPagedResultsByQuery(sp.SqlContext.Query(), 0, 8, out var total, Direction.Descending, null, sp.SqlContext.Query().Where(item => item.Comment == "Content created")) diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 6a282169fb..aeaf76967f 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -35,8 +35,8 @@ namespace Umbraco.Tests.Persistence.Repositories private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out ContentTypeRepository contentTypeRepository) { - var cacheHelper = CacheHelper.CreateDisabledCacheHelper(); - var templateRepository = new TemplateRepository(scopeAccessor, cacheHelper, Logger, Mock.Of(), Mock.Of(), Mock.Of()); + var cacheHelper = CacheHelper.Disabled; + var templateRepository = new TemplateRepository(scopeAccessor, cacheHelper, Logger, Mock.Of(), TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(scopeAccessor, cacheHelper, Logger); contentTypeRepository = new ContentTypeRepository(scopeAccessor, cacheHelper, Logger, templateRepository); var languageRepository = new LanguageRepository(scopeAccessor, cacheHelper, Logger); @@ -46,20 +46,20 @@ namespace Umbraco.Tests.Persistence.Repositories private ContentTypeRepository CreateRepository(IScopeAccessor scopeAccessor) { - var templateRepository = new TemplateRepository(scopeAccessor, CacheHelper.CreateDisabledCacheHelper(), Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var contentTypeRepository = new ContentTypeRepository(scopeAccessor, CacheHelper.CreateDisabledCacheHelper(), Logger, templateRepository); + var templateRepository = new TemplateRepository(scopeAccessor, CacheHelper.Disabled, Logger, Mock.Of(), TestObjects.GetFileSystemsMock()); + var contentTypeRepository = new ContentTypeRepository(scopeAccessor, CacheHelper.Disabled, Logger, templateRepository); return contentTypeRepository; } private MediaTypeRepository CreateMediaTypeRepository(IScopeAccessor scopeAccessor) { - var contentTypeRepository = new MediaTypeRepository(scopeAccessor, CacheHelper.CreateDisabledCacheHelper(), Logger); + var contentTypeRepository = new MediaTypeRepository(scopeAccessor, CacheHelper.Disabled, Logger); return contentTypeRepository; } private EntityContainerRepository CreateContainerRepository(IScopeAccessor scopeAccessor, Guid containerEntityType) { - return new EntityContainerRepository(scopeAccessor, CacheHelper.CreateDisabledCacheHelper(), Logger, containerEntityType); + return new EntityContainerRepository(scopeAccessor, CacheHelper.Disabled, Logger, containerEntityType); } //TODO Add test to verify SetDefaultTemplates updates both AllowedTemplates and DefaultTemplate(id). @@ -71,7 +71,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var templateRepo = new TemplateRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Logger, Mock.Of(), Mock.Of(), Mock.Of()); + var templateRepo = new TemplateRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger, Mock.Of(), TestObjects.GetFileSystemsMock()); var repository = CreateRepository((IScopeAccessor) provider); var templates = new[] { diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index 41daa05022..ce871d0d0a 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -6,7 +6,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; -using LightInject; +using Umbraco.Core.Composing; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; @@ -20,12 +20,12 @@ namespace Umbraco.Tests.Persistence.Repositories { private IDataTypeRepository CreateRepository() { - return Container.GetInstance(); + return Factory.GetInstance(); } private EntityContainerRepository CreateContainerRepository(IScopeAccessor scopeAccessor) { - return new EntityContainerRepository(scopeAccessor, CacheHelper.CreateDisabledCacheHelper(), Logger, Constants.ObjectTypes.DataTypeContainer); + return new EntityContainerRepository(scopeAccessor, CacheHelper.Disabled, Logger, Constants.ObjectTypes.DataTypeContainer); } [Test] diff --git a/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs index 8e8306095f..155ea1905c 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs @@ -6,8 +6,8 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; -using LightInject; -using Umbraco.Core.Scoping; +using Umbraco.Core; +using Umbraco.Core.Composing; namespace Umbraco.Tests.Persistence.Repositories { @@ -24,7 +24,7 @@ namespace Umbraco.Tests.Persistence.Repositories private IDictionaryRepository CreateRepository() { - return Container.GetInstance(); + return Factory.GetInstance(); } [Test] @@ -230,7 +230,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var languageRepository = Container.GetInstance(); + var languageRepository = Factory.GetInstance(); var repository = CreateRepository(); var language = languageRepository.Get(1); diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index 9f84d9faf5..1f0036d874 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -64,7 +64,7 @@ namespace Umbraco.Tests.Persistence.Repositories { cacheHelper = cacheHelper ?? CacheHelper; - templateRepository = new TemplateRepository(scopeAccessor, cacheHelper, Logger, Mock.Of(), Mock.Of(), Mock.Of()); + templateRepository = new TemplateRepository(scopeAccessor, cacheHelper, Logger, Mock.Of(), TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(scopeAccessor, cacheHelper, Logger); contentTypeRepository = new ContentTypeRepository(scopeAccessor, cacheHelper, Logger, templateRepository); var languageRepository = new LanguageRepository(scopeAccessor, cacheHelper, Logger); @@ -670,7 +670,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void AliasRegexTest() { - var regex = new SqlServerSyntaxProvider(new Lazy(() => null)).AliasRegex; + var regex = new SqlServerSyntaxProvider().AliasRegex; Assert.AreEqual(@"(\[\w+]\.\[\w+])\s+AS\s+(\[\w+])", regex.ToString()); const string sql = "SELECT [table].[column1] AS [alias1], [table].[column2] AS [alias2] FROM [table];"; var matches = regex.Matches(sql); diff --git a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs index e88b6e3f44..a5402f964e 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs @@ -22,12 +22,12 @@ namespace Umbraco.Tests.Persistence.Repositories private DomainRepository CreateRepository(IScopeProvider provider, out ContentTypeRepository contentTypeRepository, out DocumentRepository documentRepository, out LanguageRepository languageRepository) { var accessor = (IScopeAccessor) provider; - var templateRepository = new TemplateRepository(accessor, DisabledCache, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepository = new TagRepository(accessor, DisabledCache, Logger); - contentTypeRepository = new ContentTypeRepository(accessor, DisabledCache, Logger, templateRepository); - languageRepository = new LanguageRepository(accessor, DisabledCache, Logger); - documentRepository = new DocumentRepository(accessor, DisabledCache, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); - var domainRepository = new DomainRepository(accessor, DisabledCache, Logger); + var templateRepository = new TemplateRepository(accessor, Core.Cache.CacheHelper.Disabled, Logger, Mock.Of(), TestObjects.GetFileSystemsMock()); + var tagRepository = new TagRepository(accessor, Core.Cache.CacheHelper.Disabled, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.CacheHelper.Disabled, Logger, templateRepository); + languageRepository = new LanguageRepository(accessor, Core.Cache.CacheHelper.Disabled, Logger); + documentRepository = new DocumentRepository(accessor, Core.Cache.CacheHelper.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); + var domainRepository = new DomainRepository(accessor, Core.Cache.CacheHelper.Disabled, Logger); return domainRepository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs index a063d2e387..2f91c602af 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs @@ -26,7 +26,7 @@ namespace Umbraco.Tests.Persistence.Repositories private LanguageRepository CreateRepository(IScopeProvider provider) { - return new LanguageRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + return new LanguageRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); } [Test] diff --git a/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs index 5ae25d629f..e21debfdfd 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs @@ -35,7 +35,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); var macro = new Macro("test1", "Test", "~/views/macropartials/test.cshtml", MacroTypes.PartialView); ; @@ -52,7 +52,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); var macro = repository.Get(1); macro.Alias = "test2"; @@ -69,7 +69,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); // Assert Assert.That(repository, Is.Not.Null); @@ -83,7 +83,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); // Act var macro = repository.Get(1); @@ -111,7 +111,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); // Act var macros = repository.GetMany(); @@ -129,7 +129,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); // Act var query = scope.SqlContext.Query().Where(x => x.Alias.ToUpper() == "TEST1"); @@ -147,7 +147,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); // Act var query = scope.SqlContext.Query().Where(x => x.Name.StartsWith("Test")); @@ -165,7 +165,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); // Act var macro = new Macro("test", "Test", "~/views/macropartials/test.cshtml", MacroTypes.PartialView); @@ -186,7 +186,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); // Act var macro = repository.Get(2); @@ -221,7 +221,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); // Act var macro = repository.Get(3); @@ -242,7 +242,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); // Act var exists = repository.Exists(3); @@ -261,7 +261,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); var macro = repository.Get(1); macro.Properties.Add(new MacroProperty("new1", "New1", 3, "test")); @@ -287,7 +287,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); var macro = new Macro("newmacro", "A new macro", "~/views/macropartials/test1.cshtml", MacroTypes.PartialView); macro.Properties.Add(new MacroProperty("blah1", "New1", 4, "test.editor")); @@ -312,7 +312,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); var macro = new Macro("newmacro", "A new macro", "~/views/macropartials/test1.cshtml", MacroTypes.PartialView); macro.Properties.Add(new MacroProperty("blah1", "New1", 4, "test.editor")); @@ -336,7 +336,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); var macro = new Macro("newmacro", "A new macro", "~/views/macropartials/test1.cshtml", MacroTypes.PartialView); var prop1 = new MacroProperty("blah1", "New1", 4, "test.editor"); @@ -367,7 +367,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); var macro = repository.Get(1); macro.Properties.Add(new MacroProperty("new1", "New1", 3, "test")); @@ -394,7 +394,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); var macro = repository.Get(1); macro.Properties.Add(new MacroProperty("new1", "New1", 3, "test")); @@ -422,7 +422,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); repository.Save(new Macro("test1", "Test1", "~/views/macropartials/test1.cshtml", MacroTypes.PartialView)); repository.Save(new Macro("test2", "Test2", "~/views/macropartials/test2.cshtml", MacroTypes.PartialView)); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index 85f49306d2..ae4308db55 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -2,6 +2,7 @@ using System.Linq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; @@ -20,12 +21,12 @@ namespace Umbraco.Tests.Persistence.Repositories { private MediaTypeRepository CreateRepository(IScopeProvider provider) { - return new MediaTypeRepository((IScopeAccessor) provider, DisabledCache, Logger); + return new MediaTypeRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger); } private EntityContainerRepository CreateContainerRepository(IScopeProvider provider) { - return new EntityContainerRepository((IScopeAccessor) provider, DisabledCache, Logger, Constants.ObjectTypes.MediaTypeContainer); + return new EntityContainerRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger, Constants.ObjectTypes.MediaTypeContainer); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 38ecc6c8ae..18bfc4fcea 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -6,6 +6,7 @@ using Moq; using NPoco; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -28,10 +29,10 @@ namespace Umbraco.Tests.Persistence.Repositories private MemberRepository CreateRepository(IScopeProvider provider, out MemberTypeRepository memberTypeRepository, out MemberGroupRepository memberGroupRepository) { var accessor = (IScopeAccessor) provider; - memberTypeRepository = new MemberTypeRepository(accessor, DisabledCache, Logger); - memberGroupRepository = new MemberGroupRepository(accessor, DisabledCache, Logger); - var tagRepo = new TagRepository(accessor, DisabledCache, Logger); - var repository = new MemberRepository(accessor, DisabledCache, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of()); + memberTypeRepository = new MemberTypeRepository(accessor, CacheHelper.Disabled, Logger); + memberGroupRepository = new MemberGroupRepository(accessor, CacheHelper.Disabled, Logger); + var tagRepo = new TagRepository(accessor, CacheHelper.Disabled, Logger); + var repository = new MemberRepository(accessor, CacheHelper.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of()); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs index e3d15613c2..87d146f9f4 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -22,7 +22,7 @@ namespace Umbraco.Tests.Persistence.Repositories { private MemberTypeRepository CreateRepository(IScopeProvider provider) { - return new MemberTypeRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + return new MemberTypeRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); } [Test] diff --git a/src/Umbraco.Tests/Persistence/Repositories/PartialViewRepositoryTests.cs b/src/Umbraco.Tests/Persistence/Repositories/PartialViewRepositoryTests.cs index cf307d2ea9..9c326b3ddc 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PartialViewRepositoryTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PartialViewRepositoryTests.cs @@ -1,12 +1,10 @@ using System.Linq; +using Moq; using NUnit.Framework; using Umbraco.Core.IO; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Composing; using Umbraco.Core.Persistence.Repositories.Implement; -using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -29,7 +27,7 @@ namespace Umbraco.Tests.Persistence.Repositories { base.Compose(); - Container.RegisterSingleton(f => new DataEditorCollection(Enumerable.Empty())); + Composition.RegisterUnique(f => new DataEditorCollection(Enumerable.Empty())); } [Test] @@ -37,10 +35,13 @@ namespace Umbraco.Tests.Persistence.Repositories { // unless noted otherwise, no changes / 7.2.8 + var fileSystems = Mock.Of(); + Mock.Get(fileSystems).Setup(x => x.PartialViewsFileSystem).Returns(_fileSystem); + var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new PartialViewRepository(_fileSystem); + var repository = new PartialViewRepository(fileSystems); var partialView = new PartialView(PartialViewType.PartialView, "test-path-1.cshtml") { Content = "// partialView" }; repository.Save(partialView); diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs index 39b3b0f797..e76d794e69 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -307,7 +307,7 @@ namespace Umbraco.Tests.Persistence.Repositories private DocumentRepository CreateRepository(IScopeProvider provider, out ContentTypeRepository contentTypeRepository) { var accessor = (IScopeAccessor) provider; - var templateRepository = new TemplateRepository(accessor, CacheHelper, Logger, Mock.Of(), Mock.Of(), Mock.Of()); + var templateRepository = new TemplateRepository(accessor, CacheHelper, Logger, Mock.Of(), TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, CacheHelper, Logger); contentTypeRepository = new ContentTypeRepository(accessor, CacheHelper, Logger, templateRepository); var languageRepository = new LanguageRepository(accessor, CacheHelper, Logger); diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs index 9ffcfa4442..697951d021 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs @@ -30,8 +30,8 @@ namespace Umbraco.Tests.Persistence.Repositories private RelationRepository CreateRepository(IScopeProvider provider, out RelationTypeRepository relationTypeRepository) { var accessor = (IScopeAccessor) provider; - relationTypeRepository = new RelationTypeRepository(accessor, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); - var repository = new RelationRepository(accessor, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), relationTypeRepository); + relationTypeRepository = new RelationTypeRepository(accessor, CacheHelper.Disabled, Mock.Of()); + var repository = new RelationRepository(accessor, Mock.Of(), relationTypeRepository); return repository; } @@ -266,12 +266,11 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var relationTypeRepository = new RelationTypeRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); - var relationRepository = new RelationRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), relationTypeRepository); + var relationTypeRepository = new RelationTypeRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); + var relationRepository = new RelationRepository((IScopeAccessor) provider, Mock.Of(), relationTypeRepository); relationTypeRepository.Save(relateContent); - relationTypeRepository.Save(relateContentType); - + relationTypeRepository.Save(relateContentType); //Create and Save ContentType "umbTextpage" -> (NodeDto.NodeIdSeed) ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs index ce59f17ea1..26b2fe48c2 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.Persistence.Repositories private RelationTypeRepository CreateRepository(IScopeProvider provider) { - return new RelationTypeRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + return new RelationTypeRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); } @@ -232,7 +232,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new RelationTypeRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new RelationTypeRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); repository.Save(relateContent);//Id 2 repository.Save(relateContentType);//Id 3 diff --git a/src/Umbraco.Tests/Persistence/Repositories/ScriptRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ScriptRepositoryTest.cs index 755682a913..36c1bbdfb4 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ScriptRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ScriptRepositoryTest.cs @@ -4,13 +4,10 @@ using System.Text; using Moq; using NUnit.Framework; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -20,13 +17,16 @@ namespace Umbraco.Tests.Persistence.Repositories [UmbracoTest(WithApplication = true, Database = UmbracoTestOptions.Database.NewEmptyPerFixture)] public class ScriptRepositoryTest : TestWithDatabaseBase { + private IFileSystems _fileSystems; private IFileSystem _fileSystem; public override void SetUp() { base.SetUp(); + _fileSystems = Mock.Of(); _fileSystem = new PhysicalFileSystem(SystemDirectories.Scripts); + Mock.Get(_fileSystems).Setup(x => x.ScriptsFileSystem).Returns(_fileSystem); using (var stream = CreateStream("Umbraco.Sys.registerNamespace(\"Umbraco.Utils\");")) { _fileSystem.AddFile("test-script.js", stream); @@ -37,7 +37,7 @@ namespace Umbraco.Tests.Persistence.Repositories { base.Compose(); - Container.RegisterSingleton(f => new DataEditorCollection(Enumerable.Empty())); + Composition.RegisterUnique(f => new DataEditorCollection(Enumerable.Empty())); } [Test] @@ -47,9 +47,8 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - // Act - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); // Assert Assert.That(repository, Is.Not.Null); @@ -63,12 +62,12 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); // Act var script = new Script("test-add-script.js") { Content = "/// " }; repository.Save(script); - + //Assert Assert.That(_fileSystem.FileExists("test-add-script.js"), Is.True); @@ -82,16 +81,16 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); // Act var script = new Script("test-updated-script.js") { Content = "/// " }; repository.Save(script); - + script.Content = "/// "; repository.Save(script); - + var scriptUpdated = repository.Get("test-updated-script.js"); @@ -108,12 +107,12 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); // Act var script = repository.Get("test-script.js"); repository.Delete(script); - + // Assert @@ -128,7 +127,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); // Act var exists = repository.Get("test-script.js"); @@ -147,7 +146,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); var script = new Script("test-script1.js") { Content = "/// " }; repository.Save(script); @@ -155,7 +154,7 @@ namespace Umbraco.Tests.Persistence.Repositories repository.Save(script2); var script3 = new Script("test-script3.js") { Content = "/// " }; repository.Save(script3); - + // Act var scripts = repository.GetMany(); @@ -175,7 +174,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); var script = new Script("test-script1.js") { Content = "/// " }; repository.Save(script); @@ -183,7 +182,7 @@ namespace Umbraco.Tests.Persistence.Repositories repository.Save(script2); var script3 = new Script("test-script3.js") { Content = "/// " }; repository.Save(script3); - + // Act var scripts = repository.GetMany("test-script1.js", "test-script2.js"); @@ -203,7 +202,7 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); // Act var exists = repository.Exists("test-script.js"); @@ -222,17 +221,17 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); var script = new Script("test-move-script.js") { Content = content }; repository.Save(script); - + // Act script = repository.Get("test-move-script.js"); script.Path = "moved/test-move-script.js"; repository.Save(script); - + var existsOld = repository.Exists("test-move-script.js"); var existsNew = repository.Exists("moved/test-move-script.js"); @@ -255,11 +254,11 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = TestObjects.GetScopeProvider(Logger); using (var scope = ScopeProvider.CreateScope()) { - var repository = new ScriptRepository(_fileSystem, Mock.Of()); + var repository = new ScriptRepository(_fileSystems, Mock.Of()); var script = new Script("test-path-1.js") { Content = "// script" }; repository.Save(script); - + Assert.IsTrue(_fileSystem.FileExists("test-path-1.js")); Assert.AreEqual("test-path-1.js", script.Path); Assert.AreEqual("/scripts/test-path-1.js", script.VirtualPath); @@ -267,14 +266,14 @@ namespace Umbraco.Tests.Persistence.Repositories //ensure you can prefix the same path as the root path name script = new Script("scripts/path-2/test-path-2.js") { Content = "// script" }; repository.Save(script); - + Assert.IsTrue(_fileSystem.FileExists("scripts/path-2/test-path-2.js")); Assert.AreEqual("scripts\\path-2\\test-path-2.js", script.Path); Assert.AreEqual("/scripts/scripts/path-2/test-path-2.js", script.VirtualPath); script = new Script("path-2/test-path-2.js") { Content = "// script" }; repository.Save(script); - + Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-2.js")); Assert.AreEqual("path-2\\test-path-2.js", script.Path); // fixed in 7.3 - 7.2.8 does not update the path Assert.AreEqual("/scripts/path-2/test-path-2.js", script.VirtualPath); @@ -286,7 +285,7 @@ namespace Umbraco.Tests.Persistence.Repositories script = new Script("path-2\\test-path-3.js") { Content = "// script" }; repository.Save(script); - + Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-3.js")); Assert.AreEqual("path-2\\test-path-3.js", script.Path); Assert.AreEqual("/scripts/path-2/test-path-3.js", script.VirtualPath); @@ -328,11 +327,11 @@ namespace Umbraco.Tests.Persistence.Repositories base.TearDown(); //Delete all files - Purge((PhysicalFileSystem) _fileSystem, ""); - _fileSystem = null; + Purge(_fileSystems.ScriptsFileSystem, ""); + _fileSystems = null; } - private void Purge(PhysicalFileSystem fs, string path) + private void Purge(IFileSystem fs, string path) { var files = fs.GetFiles(path, "*.js"); foreach (var file in files) diff --git a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs index dd0bc36ff3..6fae1d4749 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using System.Text; +using Moq; using NUnit.Framework; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -15,13 +16,16 @@ namespace Umbraco.Tests.Persistence.Repositories [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] public class StylesheetRepositoryTest : TestWithDatabaseBase { + private IFileSystems _fileSystems; private IFileSystem _fileSystem; public override void SetUp() { base.SetUp(); + _fileSystems = Mock.Of(); _fileSystem = new PhysicalFileSystem(SystemDirectories.Css); + Mock.Get(_fileSystems).Setup(x => x.StylesheetsFileSystem).Returns(_fileSystem); var stream = CreateStream("body {background:#EE7600; color:#FFF;}"); _fileSystem.AddFile("styles.css", stream); } @@ -30,11 +34,10 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Instantiate_Repository() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { // Act - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); // Assert @@ -46,15 +49,14 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); // Act var stylesheet = new Stylesheet("test-add.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + //Assert Assert.That(_fileSystem.FileExists("test-add.css"), Is.True); @@ -65,20 +67,19 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); // Act var stylesheet = new Stylesheet("test-update.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + var stylesheetUpdate = repository.Get("test-update.css"); stylesheetUpdate.Content = "body { color:#000; }"; repository.Save(stylesheetUpdate); - + var stylesheetUpdated = repository.Get("test-update.css"); @@ -93,20 +94,19 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_With_Property() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); // Act var stylesheet = new Stylesheet("test-update.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + stylesheet.AddProperty(new StylesheetProperty("Test", "p", "font-size:2em;")); repository.Save(stylesheet); - + //re-get stylesheet = repository.Get(stylesheet.Name); @@ -121,15 +121,14 @@ namespace Umbraco.Tests.Persistence.Repositories public void Throws_When_Adding_Duplicate_Properties() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); // Act var stylesheet = new Stylesheet("test-update.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + stylesheet.AddProperty(new StylesheetProperty("Test", "p", "font-size:2em;")); @@ -141,18 +140,17 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); // Act var stylesheet = new Stylesheet("test-delete.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + repository.Delete(stylesheet); - + //Assert Assert.That(_fileSystem.FileExists("test-delete.css"), Is.False); @@ -163,10 +161,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); // Act var stylesheet = repository.Get("styles.css"); @@ -183,14 +180,13 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); var stylesheet = new Stylesheet("styles-v2.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + // Act var stylesheets = repository.GetMany(); @@ -207,14 +203,13 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_With_Params() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); var stylesheet = new Stylesheet("styles-v2.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + // Act var stylesheets = repository.GetMany("styles-v2.css", "styles.css"); @@ -231,10 +226,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Exists() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); // Act var exists = repository.Exists("styles.css"); @@ -249,21 +243,20 @@ namespace Umbraco.Tests.Persistence.Repositories { // unless noted otherwise, no changes / 7.2.8 - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystem); + var repository = new StylesheetRepository(_fileSystems); var stylesheet = new Stylesheet("test-path-1.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + Assert.IsTrue(_fileSystem.FileExists("test-path-1.css")); Assert.AreEqual("test-path-1.css", stylesheet.Path); Assert.AreEqual("/css/test-path-1.css", stylesheet.VirtualPath); stylesheet = new Stylesheet("path-2/test-path-2.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-2.css")); Assert.AreEqual("path-2\\test-path-2.css", stylesheet.Path); // fixed in 7.3 - 7.2.8 does not update the path Assert.AreEqual("/css/path-2/test-path-2.css", stylesheet.VirtualPath); @@ -275,7 +268,7 @@ namespace Umbraco.Tests.Persistence.Repositories stylesheet = new Stylesheet("path-2\\test-path-3.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); - + Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-3.css")); Assert.AreEqual("path-2\\test-path-3.css", stylesheet.Path); Assert.AreEqual("/css/path-2/test-path-3.css", stylesheet.VirtualPath); diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index d53d680e94..78eb736007 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -1,6 +1,7 @@ using System.Linq; using Moq; using NUnit.Framework; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -946,26 +947,26 @@ namespace Umbraco.Tests.Persistence.Repositories private TagRepository CreateRepository(IScopeProvider provider) { - return new TagRepository((IScopeAccessor) provider, DisabledCache, Logger); + return new TagRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger); } private DocumentRepository CreateContentRepository(IScopeProvider provider, out ContentTypeRepository contentTypeRepository) { var accessor = (IScopeAccessor) provider; - var templateRepository = new TemplateRepository(accessor, DisabledCache, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepository = new TagRepository(accessor, DisabledCache, Logger); - contentTypeRepository = new ContentTypeRepository(accessor, DisabledCache, Logger, templateRepository); - var languageRepository = new LanguageRepository(accessor, DisabledCache, Logger); - var repository = new DocumentRepository(accessor, DisabledCache, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); + var templateRepository = new TemplateRepository(accessor, CacheHelper.Disabled, Logger, Mock.Of(), TestObjects.GetFileSystemsMock()); + var tagRepository = new TagRepository(accessor, CacheHelper.Disabled, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, CacheHelper.Disabled, Logger, templateRepository); + var languageRepository = new LanguageRepository(accessor, CacheHelper.Disabled, Logger); + var repository = new DocumentRepository(accessor, CacheHelper.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); return repository; } private MediaRepository CreateMediaRepository(IScopeProvider provider, out MediaTypeRepository mediaTypeRepository) { var accessor = (IScopeAccessor) provider; - var tagRepository = new TagRepository(accessor, DisabledCache, Logger); - mediaTypeRepository = new MediaTypeRepository(accessor, DisabledCache, Logger); - var repository = new MediaRepository(accessor, DisabledCache, Logger, mediaTypeRepository, tagRepository, Mock.Of(), Mock.Of()); + var tagRepository = new TagRepository(accessor, CacheHelper.Disabled, Logger); + mediaTypeRepository = new MediaTypeRepository(accessor, CacheHelper.Disabled, Logger); + var repository = new MediaRepository(accessor, CacheHelper.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of(), Mock.Of()); return repository; } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index 3ef769adbc..3ec9d572bf 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -6,6 +6,7 @@ using System.Text; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -22,47 +23,46 @@ namespace Umbraco.Tests.Persistence.Repositories [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class TemplateRepositoryTest : TestWithDatabaseBase { - private IFileSystem _masterPageFileSystem; - private IFileSystem _viewsFileSystem; + private IFileSystems _fileSystems; private ITemplateRepository CreateRepository(IScopeProvider provider, ITemplatesSection templatesSection = null) { - return new TemplateRepository((IScopeAccessor) provider, DisabledCache, Logger, + return new TemplateRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger, templatesSection ?? Mock.Of(t => t.DefaultRenderingEngine == RenderingEngine.Mvc), - _masterPageFileSystem, _viewsFileSystem); + _fileSystems); } public override void SetUp() { base.SetUp(); - _masterPageFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages); - _viewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews); + _fileSystems = Mock.Of(); + var masterPageFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages); + Mock.Get(_fileSystems).Setup(x => x.MasterPagesFileSystem).Returns(masterPageFileSystem); + var viewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews); + Mock.Get(_fileSystems).Setup(x => x.MvcViewsFileSystem).Returns(viewsFileSystem); } [Test] public void Can_Instantiate_Repository() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); // Assert Assert.That(repository, Is.Not.Null); } - } [Test] public void Can_Perform_Add_MasterPage_Detect_Content() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); // Act var template = new Template("test", "test") @@ -70,32 +70,28 @@ namespace Umbraco.Tests.Persistence.Repositories Content = @"<%@ Master Language=""C#"" %>" }; repository.Save(template); - //Assert Assert.That(repository.Get("test"), Is.Not.Null); - Assert.That(_masterPageFileSystem.FileExists("test.master"), Is.True); + Assert.That(_fileSystems.MasterPagesFileSystem.FileExists("test.master"), Is.True); } - } [Test] public void Can_Perform_Add_MasterPage_With_Default_Content() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider, Mock.Of(x => x.DefaultRenderingEngine == RenderingEngine.WebForms)); + var repository = CreateRepository(ScopeProvider, Mock.Of(x => x.DefaultRenderingEngine == RenderingEngine.WebForms)); // Act var template = new Template("test", "test"); repository.Save(template); - //Assert Assert.That(repository.Get("test"), Is.Not.Null); - Assert.That(_masterPageFileSystem.FileExists("test.master"), Is.True); + Assert.That(_fileSystems.MasterPagesFileSystem.FileExists("test.master"), Is.True); Assert.AreEqual(@"<%@ Master Language=""C#"" MasterPageFile=""~/umbraco/masterpages/default.master"" AutoEventWireup=""true"" %> @@ -103,47 +99,41 @@ namespace Umbraco.Tests.Persistence.Repositories ".StripWhitespace(), template.Content.StripWhitespace()); } - } [Test] public void Can_Perform_Add_MasterPage_With_Default_Content_With_Parent() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider, Mock.Of(x => x.DefaultRenderingEngine == RenderingEngine.WebForms)); + var repository = CreateRepository(ScopeProvider, Mock.Of(x => x.DefaultRenderingEngine == RenderingEngine.WebForms)); //NOTE: This has to be persisted first var template = new Template("test", "test"); repository.Save(template); - // Act var template2 = new Template("test2", "test2"); template2.SetMasterTemplate(template); repository.Save(template2); - //Assert Assert.That(repository.Get("test2"), Is.Not.Null); - Assert.That(_masterPageFileSystem.FileExists("test2.master"), Is.True); + Assert.That(_fileSystems.MasterPagesFileSystem.FileExists("test2.master"), Is.True); Assert.AreEqual(@"<%@ Master Language=""C#"" MasterPageFile=""~/masterpages/test.master"" AutoEventWireup=""true"" %> ".StripWhitespace(), template2.Content.StripWhitespace()); } - } [Test] public void Can_Perform_Add_View() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); // Act var template = new Template("test", "test"); @@ -152,19 +142,17 @@ namespace Umbraco.Tests.Persistence.Repositories //Assert Assert.That(repository.Get("test"), Is.Not.Null); - Assert.That(_viewsFileSystem.FileExists("test.cshtml"), Is.True); + Assert.That(_fileSystems.MvcViewsFileSystem.FileExists("test.cshtml"), Is.True); } - } [Test] public void Can_Perform_Add_View_With_Default_Content() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); // Act var template = new Template("test", "test") @@ -172,41 +160,36 @@ namespace Umbraco.Tests.Persistence.Repositories Content = ViewHelper.GetDefaultFileContent() }; repository.Save(template); - //Assert Assert.That(repository.Get("test"), Is.Not.Null); - Assert.That(_viewsFileSystem.FileExists("test.cshtml"), Is.True); + Assert.That(_fileSystems.MvcViewsFileSystem.FileExists("test.cshtml"), Is.True); Assert.AreEqual( @"@inherits Umbraco.Web.Mvc.UmbracoViewPage @{ Layout = null;}".StripWhitespace(), template.Content.StripWhitespace()); } - } [Test] public void Can_Perform_Add_View_With_Default_Content_With_Parent() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); //NOTE: This has to be persisted first var template = new Template("test", "test"); repository.Save(template); - // Act var template2 = new Template("test2", "test2"); template2.SetMasterTemplate(template); repository.Save(template2); - //Assert Assert.That(repository.Get("test2"), Is.Not.Null); - Assert.That(_viewsFileSystem.FileExists("test2.cshtml"), Is.True); + Assert.That(_fileSystems.MvcViewsFileSystem.FileExists("test2.cshtml"), Is.True); Assert.AreEqual( "@inherits Umbraco.Web.Mvc.UmbracoViewPage @{ Layout = \"test.cshtml\";}".StripWhitespace(), template2.Content.StripWhitespace()); @@ -217,10 +200,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_Unique_Alias() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); // Act var template = new Template("test", "test") @@ -228,29 +210,25 @@ namespace Umbraco.Tests.Persistence.Repositories Content = ViewHelper.GetDefaultFileContent() }; repository.Save(template); - var template2 = new Template("test", "test") { Content = ViewHelper.GetDefaultFileContent() }; repository.Save(template2); - //Assert Assert.AreEqual("test1", template2.Alias); } - } [Test] public void Can_Perform_Update_Unique_Alias() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); // Act var template = new Template("test", "test") @@ -258,35 +236,30 @@ namespace Umbraco.Tests.Persistence.Repositories Content = ViewHelper.GetDefaultFileContent() }; repository.Save(template); - var template2 = new Template("test1", "test1") { Content = ViewHelper.GetDefaultFileContent() }; repository.Save(template2); - template.Alias = "test1"; repository.Save(template); - //Assert Assert.AreEqual("test11", template.Alias); - Assert.That(_viewsFileSystem.FileExists("test11.cshtml"), Is.True); - Assert.That(_viewsFileSystem.FileExists("test.cshtml"), Is.False); + Assert.That(_fileSystems.MvcViewsFileSystem.FileExists("test11.cshtml"), Is.True); + Assert.That(_fileSystems.MvcViewsFileSystem.FileExists("test.cshtml"), Is.False); } - } [Test] public void Can_Perform_Update_MasterPage() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); // Act var template = new Template("test", "test") @@ -294,30 +267,25 @@ namespace Umbraco.Tests.Persistence.Repositories Content = @"<%@ Master Language=""C#"" %>" }; repository.Save(template); - template.Content = @"<%@ Master Language=""VB"" %>"; repository.Save(template); - var updated = repository.Get("test"); // Assert - Assert.That(_masterPageFileSystem.FileExists("test.master"), Is.True); + Assert.That(_fileSystems.MasterPagesFileSystem.FileExists("test.master"), Is.True); Assert.That(updated.Content, Is.EqualTo(@"<%@ Master Language=""VB"" %>")); } - - } [Test] public void Can_Perform_Update_View() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); // Act var template = new Template("test", "test") @@ -325,16 +293,14 @@ namespace Umbraco.Tests.Persistence.Repositories Content = ViewHelper.GetDefaultFileContent() }; repository.Save(template); - template.Content += ""; repository.Save(template); - var updated = repository.Get("test"); // Assert - Assert.That(_viewsFileSystem.FileExists("test.cshtml"), Is.True); + Assert.That(_fileSystems.MvcViewsFileSystem.FileExists("test.cshtml"), Is.True); Assert.That(updated.Content, Is.EqualTo(ViewHelper.GetDefaultFileContent() + "")); } } @@ -343,27 +309,24 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_MasterPage() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); var template = new Template("test", "test") { Content = @"<%@ Master Language=""C#"" %>" }; repository.Save(template); - // Act var templates = repository.Get("test"); - Assert.That(_masterPageFileSystem.FileExists("test.master"), Is.True); + Assert.That(_fileSystems.MasterPagesFileSystem.FileExists("test.master"), Is.True); repository.Delete(templates); - // Assert Assert.IsNull(repository.Get("test")); - Assert.That(_masterPageFileSystem.FileExists("test.master"), Is.False); + Assert.That(_fileSystems.MasterPagesFileSystem.FileExists("test.master"), Is.False); } } @@ -371,27 +334,24 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_View() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); var template = new Template("test", "test") { Content = ViewHelper.GetDefaultFileContent() }; repository.Save(template); - // Act var templates = repository.Get("test"); - Assert.That(_viewsFileSystem.FileExists("test.cshtml"), Is.True); + Assert.That(_fileSystems.MvcViewsFileSystem.FileExists("test.cshtml"), Is.True); repository.Delete(templates); - // Assert Assert.IsNull(repository.Get("test")); - Assert.That(_viewsFileSystem.FileExists("test.cshtml"), Is.False); + Assert.That(_fileSystems.MvcViewsFileSystem.FileExists("test.cshtml"), Is.False); } } @@ -399,39 +359,33 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_When_Assigned_To_Doc() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var templateRepository = CreateRepository(provider); + var templateRepository = CreateRepository(ScopeProvider); - var tagRepository = new TagRepository((IScopeAccessor) provider, DisabledCache, Logger); - var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) provider, DisabledCache, Logger, templateRepository); - var languageRepository = new LanguageRepository((IScopeAccessor) provider, DisabledCache, Logger); - var contentRepo = new DocumentRepository((IScopeAccessor) provider, DisabledCache, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); + var tagRepository = new TagRepository((IScopeAccessor) ScopeProvider, CacheHelper.Disabled, Logger); + var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, CacheHelper.Disabled, Logger, templateRepository); + var languageRepository = new LanguageRepository((IScopeAccessor) ScopeProvider, CacheHelper.Disabled, Logger); + var contentRepo = new DocumentRepository((IScopeAccessor) ScopeProvider, CacheHelper.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); // else, FK violation on contentType! contentTypeRepository.Save(contentType); var textpage = MockedContent.CreateSimpleContent(contentType); contentRepo.Save(textpage); - - var template = new Template("test", "test") { Content = @"<%@ Master Language=""C#"" %>" }; templateRepository.Save(template); - textpage.Template = template; contentRepo.Save(textpage); - // Act var templates = templateRepository.Get("test"); templateRepository.Delete(templates); - // Assert Assert.IsNull(templateRepository.Get("test")); @@ -442,10 +396,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_On_Nested_Templates() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); var parent = new Template("parent", "parent") { @@ -466,12 +419,10 @@ namespace Umbraco.Tests.Persistence.Repositories repository.Save(parent); repository.Save(child); repository.Save(baby); - // Act var templates = repository.Get("parent"); repository.Delete(templates); - // Assert Assert.IsNull(repository.Get("test")); @@ -482,10 +433,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_All() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); var created = CreateHierarchy(repository).ToArray(); @@ -510,10 +460,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_Children() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); var created = CreateHierarchy(repository).ToArray(); @@ -521,7 +470,6 @@ namespace Umbraco.Tests.Persistence.Repositories var childrenById = repository.GetChildren(created[1].Id); var childrenByAlias = repository.GetChildren(created[1].Alias); - // Assert Assert.AreEqual(2, childrenById.Count()); Assert.AreEqual(2, childrenById.DistinctBy(x => x.Id).Count()); @@ -534,17 +482,15 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_Children_At_Root() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); CreateHierarchy(repository).ToArray(); // Act var children = repository.GetChildren(-1); - // Assert Assert.AreEqual(1, children.Count()); Assert.AreEqual(1, children.DistinctBy(x => x.Id).Count()); @@ -555,18 +501,15 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_Descendants() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); - + var repository = CreateRepository(ScopeProvider); var created = CreateHierarchy(repository).ToArray(); // Act var descendantsById = repository.GetDescendants(created[1].Id); var descendantsByAlias = repository.GetDescendants(created[1].Alias); - // Assert Assert.AreEqual(3, descendantsById.Count()); Assert.AreEqual(3, descendantsById.DistinctBy(x => x.Id).Count()); @@ -580,10 +523,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Path_Is_Set_Correctly_On_Creation() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); var parent = new Template("parent", "parent"); var child1 = new Template("child1", "child1"); @@ -612,7 +554,6 @@ namespace Umbraco.Tests.Persistence.Repositories baby2.MasterTemplateAlias = toddler4.Alias; baby2.MasterTemplateId = new Lazy(() => toddler4.Id); - // Act repository.Save(parent); repository.Save(child1); @@ -623,7 +564,6 @@ namespace Umbraco.Tests.Persistence.Repositories repository.Save(toddler4); repository.Save(baby1); repository.Save(baby2); - // Assert Assert.AreEqual(string.Format("-1,{0}", parent.Id), parent.Path); @@ -643,10 +583,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Path_Is_Set_Correctly_On_Update() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); var parent = new Template("parent", "parent"); var child1 = new Template("child1", "child1"); @@ -668,12 +607,10 @@ namespace Umbraco.Tests.Persistence.Repositories repository.Save(child2); repository.Save(toddler1); repository.Save(toddler2); - //Act toddler2.SetMasterTemplate(child2); repository.Save(toddler2); - //Assert Assert.AreEqual($"-1,{parent.Id},{child2.Id},{toddler2.Id}", toddler2.Path); @@ -684,10 +621,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Path_Is_Set_Correctly_On_Update_With_Master_Template_Removal() { // Arrange - var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = ScopeProvider.CreateScope()) + using (ScopeProvider.CreateScope()) { - var repository = CreateRepository(provider); + var repository = CreateRepository(ScopeProvider); var parent = new Template("parent", "parent"); var child1 = new Template("child1", "child1"); @@ -697,12 +633,10 @@ namespace Umbraco.Tests.Persistence.Repositories repository.Save(parent); repository.Save(child1); - //Act child1.SetMasterTemplate(null); repository.Save(child1); - //Assert Assert.AreEqual($"-1,{child1.Id}", child1.Path); @@ -714,21 +648,18 @@ namespace Umbraco.Tests.Persistence.Repositories { base.TearDown(); - _masterPageFileSystem = null; - _viewsFileSystem = null; + _fileSystems = null; + //Delete all files var fsMaster = new PhysicalFileSystem(SystemDirectories.Masterpages); var masterPages = fsMaster.GetFiles("", "*.master"); foreach (var file in masterPages) - { fsMaster.DeleteFile(file); - } + var fsViews = new PhysicalFileSystem(SystemDirectories.MvcViews); - var views = fsMaster.GetFiles("", "*.cshtml"); + var views = fsViews.GetFiles("", "*.cshtml"); foreach (var file in views) - { - fsMaster.DeleteFile(file); - } + fsViews.DeleteFile(file); } protected Stream CreateStream(string contents = null) diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs index aeec6065df..f0fb8cff88 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserGroupRepositoryTest.cs @@ -19,7 +19,7 @@ namespace Umbraco.Tests.Persistence.Repositories { private UserGroupRepository CreateRepository(IScopeProvider provider) { - return new UserGroupRepository((IScopeAccessor) provider, Core.Cache.CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + return new UserGroupRepository((IScopeAccessor) provider, Core.Cache.CacheHelper.Disabled, Mock.Of()); } [Test] @@ -131,7 +131,7 @@ namespace Umbraco.Tests.Persistence.Repositories var id = userGroup.Id; - var repository2 = new UserGroupRepository((IScopeAccessor) provider, Core.Cache.CacheHelper.CreateDisabledCacheHelper(), Logger); + var repository2 = new UserGroupRepository((IScopeAccessor) provider, Core.Cache.CacheHelper.Disabled, Logger); repository2.Delete(userGroup); scope.Complete(); diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index e778f987eb..847972cc50 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -41,7 +41,7 @@ namespace Umbraco.Tests.Persistence.Repositories private DocumentRepository CreateContentRepository(IScopeProvider provider, out IContentTypeRepository contentTypeRepository, out ITemplateRepository templateRepository) { var accessor = (IScopeAccessor) provider; - templateRepository = new TemplateRepository(accessor, CacheHelper, Logger, Mock.Of(), Mock.Of(), Mock.Of()); + templateRepository = new TemplateRepository(accessor, CacheHelper, Logger, Mock.Of(), TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, CacheHelper, Logger); contentTypeRepository = new ContentTypeRepository(accessor, CacheHelper, Logger, templateRepository); var languageRepository = new LanguageRepository(accessor, CacheHelper, Logger); @@ -52,14 +52,14 @@ namespace Umbraco.Tests.Persistence.Repositories private UserRepository CreateRepository(IScopeProvider provider) { var accessor = (IScopeAccessor) provider; - var repository = new UserRepository(accessor, CacheHelper.CreateDisabledCacheHelper(), Logger, Mappers, TestObjects.GetGlobalSettings()); + var repository = new UserRepository(accessor, CacheHelper.Disabled, Logger, Mappers, TestObjects.GetGlobalSettings()); return repository; } private UserGroupRepository CreateUserGroupRepository(IScopeProvider provider) { var accessor = (IScopeAccessor) provider; - return new UserGroupRepository(accessor, CacheHelper.CreateDisabledCacheHelper(), Logger); + return new UserGroupRepository(accessor, CacheHelper.Disabled, Logger); } [Test] @@ -207,7 +207,7 @@ namespace Umbraco.Tests.Persistence.Repositories var id = user.Id; - var repository2 = new UserRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Logger, Mock.Of(),TestObjects.GetGlobalSettings()); + var repository2 = new UserRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger, Mock.Of(),TestObjects.GetGlobalSettings()); repository2.Delete(user); diff --git a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs index df842dc43c..557de9eb11 100644 --- a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs +++ b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs @@ -74,7 +74,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, [Test] public void Format_SqlServer_NonClusteredIndexDefinition_AddsNonClusteredDirective() { - var sqlSyntax = new SqlServerSyntaxProvider(new Lazy(() => null)); + var sqlSyntax = new SqlServerSyntaxProvider(); var indexDefinition = CreateIndexDefinition(); indexDefinition.IndexType = IndexTypes.NonClustered; @@ -86,7 +86,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, [Test] public void Format_SqlServer_NonClusteredIndexDefinition_UsingIsClusteredFalse_AddsClusteredDirective() { - var sqlSyntax = new SqlServerSyntaxProvider(new Lazy(() => null)); + var sqlSyntax = new SqlServerSyntaxProvider(); var indexDefinition = CreateIndexDefinition(); indexDefinition.IndexType = IndexTypes.Clustered; @@ -99,7 +99,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, public void CreateIndexBuilder_SqlServer_NonClustered_CreatesNonClusteredIndex() { var logger = Mock.Of(); - var sqlSyntax = new SqlServerSyntaxProvider(new Lazy(() => null)); + var sqlSyntax = new SqlServerSyntaxProvider(); var db = new TestDatabase(DatabaseType.SqlServer2005, sqlSyntax); var context = new MigrationContext(db, logger); @@ -120,7 +120,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, public void CreateIndexBuilder_SqlServer_Unique_CreatesUniqueNonClusteredIndex() { var logger = Mock.Of(); - var sqlSyntax = new SqlServerSyntaxProvider(new Lazy(() => null)); + var sqlSyntax = new SqlServerSyntaxProvider(); var db = new TestDatabase(DatabaseType.SqlServer2005, sqlSyntax); var context = new MigrationContext(db, logger); @@ -141,7 +141,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, public void CreateIndexBuilder_SqlServer_Unique_CreatesUniqueNonClusteredIndex_Multi_Columnn() { var logger = Mock.Of(); - var sqlSyntax = new SqlServerSyntaxProvider(new Lazy(() => null)); + var sqlSyntax = new SqlServerSyntaxProvider(); var db = new TestDatabase(DatabaseType.SqlServer2005, sqlSyntax); var context = new MigrationContext(db, logger); @@ -162,7 +162,7 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, public void CreateIndexBuilder_SqlServer_Clustered_CreatesClusteredIndex() { var logger = Mock.Of(); - var sqlSyntax = new SqlServerSyntaxProvider(new Lazy(() => null)); + var sqlSyntax = new SqlServerSyntaxProvider(); var db = new TestDatabase(DatabaseType.SqlServer2005, sqlSyntax); var context = new MigrationContext(db, logger); diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index 1f6b569a0e..c55da764e2 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -1,21 +1,23 @@ using System; using System.Globalization; -using LightInject; using Moq; using Newtonsoft.Json; using NUnit.Framework; using Newtonsoft.Json.Linq; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; -using Umbraco.Core.IO.MediaPathSchemes; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Services; +using Umbraco.Tests.Components; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Models; using Umbraco.Web; @@ -68,14 +70,18 @@ namespace Umbraco.Tests.PropertyEditors { try { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); - container.RegisterCollectionBuilder(); + var container = RegisterFactory.Create(); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - container.Register(f => Mock.Of()); - container.Register(f => Mock.Of()); - container.RegisterSingleton(); - var mediaFileSystem = new MediaFileSystem(Mock.Of()); + composition.WithCollectionBuilder(); + + Current.Factory = composition.CreateFactory(); + + var logger = Mock.Of(); + var scheme = Mock.Of(); + var config = Mock.Of(); + + var mediaFileSystem = new MediaFileSystem(Mock.Of(), config, scheme, logger); var dataTypeService = new TestObjects.TestDataTypeService( new DataType(new ImageCropperPropertyEditor(Mock.Of(), mediaFileSystem, Mock.Of(), Mock.Of())) { Id = 1 }); diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs index f7d09bd2c0..ea5fbcaa06 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs @@ -1,14 +1,16 @@ using System; using System.Threading; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Strings; +using Umbraco.Tests.Components; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.PropertyEditors @@ -22,10 +24,13 @@ namespace Umbraco.Tests.PropertyEditors //normalize culture Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture; - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); - container.Register(_ + var register = RegisterFactory.Create(); + var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + + register.Register(_ => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefaultUmbracoSettings()))); + + Current.Factory = composition.CreateFactory(); } [TearDown] diff --git a/src/Umbraco.Tests/Published/ConvertersTests.cs b/src/Umbraco.Tests/Published/ConvertersTests.cs index edc4face17..eb0f1a9368 100644 --- a/src/Umbraco.Tests/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests/Published/ConvertersTests.cs @@ -1,15 +1,18 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Tests.Components; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; @@ -172,10 +175,11 @@ namespace Umbraco.Tests.Published public void SimpleConverter3Test() { Current.Reset(); - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); + var register = RegisterFactory.Create(); - Current.Container.RegisterCollectionBuilder() + var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + + composition.WithCollectionBuilder() .Append() .Append(); @@ -184,7 +188,9 @@ namespace Umbraco.Tests.Published typeof (PublishedSnapshotTestObjects.TestElementModel1), typeof (PublishedSnapshotTestObjects.TestElementModel2), typeof (PublishedSnapshotTestObjects.TestContentModel1), typeof (PublishedSnapshotTestObjects.TestContentModel2), }); - Current.Container.Register(f => factory); + register.Register(f => factory); + + Current.Factory = composition.CreateFactory(); var cacheMock = new Mock(); var cacheContent = new Dictionary(); @@ -193,9 +199,9 @@ namespace Umbraco.Tests.Published publishedSnapshotMock.Setup(x => x.Content).Returns(cacheMock.Object); var publishedSnapshotAccessorMock = new Mock(); publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot).Returns(publishedSnapshotMock.Object); - Current.Container.Register(f => publishedSnapshotAccessorMock.Object); + register.Register(f => publishedSnapshotAccessorMock.Object); - var converters = Current.Container.GetInstance(); + var converters = Current.Factory.GetInstance(); var dataTypeService = new TestObjects.TestDataTypeService( new DataType(new VoidEditor(Mock.Of())) { Id = 1 }, diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index 492f1f7dc0..d12699efb7 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -4,6 +4,7 @@ using System.Data; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.Logging; @@ -35,7 +36,7 @@ namespace Umbraco.Tests.PublishedContent // use any local db files, does not rely on any database) - and tests variations SettingsForTests.ConfigureSettings(SettingsForTests.GenerateMockUmbracoSettings()); - var globalSettings = UmbracoConfig.For.GlobalSettings(); + var globalSettings = Current.Config.Global(); // create a content node kit var kit = new ContentNodeKit @@ -101,7 +102,7 @@ namespace Umbraco.Tests.PublishedContent Mock.Get(dataTypeService).Setup(x => x.GetAll()).Returns(dataTypes); // create a service context - var serviceContext = new ServiceContext( + var serviceContext = ServiceContext.CreatePartial( dataTypeService : dataTypeService, memberTypeService: Mock.Of(), memberService: Mock.Of(), diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index 4b431d18e6..7a96f670e6 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -2,13 +2,10 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Reflection; using Moq; using NUnit.Framework; -using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Tests.Testing; using Umbraco.Web; @@ -23,12 +20,12 @@ namespace Umbraco.Tests.PublishedContent { base.Compose(); - Container.RegisterSingleton(_ => GetServiceContext()); + Composition.RegisterUnique(_ => GetServiceContext()); } protected ServiceContext GetServiceContext() { - var serviceContext = TestObjects.GetServiceContextMock(Container); + var serviceContext = TestObjects.GetServiceContextMock(Factory); MockLocalizationService(serviceContext); return serviceContext; } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index 2732fb465a..c5b8e21870 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; +using Umbraco.Core; using Umbraco.Tests.Testing; namespace Umbraco.Tests.PublishedContent diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs index 85432768f0..6b280832da 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using System.Collections.ObjectModel; using System.Web.Routing; using Moq; using Umbraco.Core.Models.PublishedContent; @@ -10,7 +9,8 @@ using Umbraco.Web.Routing; using Umbraco.Web.Security; using Umbraco.Core.Composing; using Current = Umbraco.Core.Composing.Current; -using LightInject; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -37,12 +37,12 @@ namespace Umbraco.Tests.PublishedContent { base.Compose(); - Container.RegisterSingleton(f => new PublishedModelFactory(f.GetInstance().GetTypes())); + Composition.RegisterUnique(f => new PublishedModelFactory(f.GetInstance().GetTypes())); } - protected override TypeLoader CreateTypeLoader(IServiceFactory f) + protected override TypeLoader CreateTypeLoader(IRuntimeCacheProvider runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - var pluginManager = base.CreateTypeLoader(f); + var pluginManager = base.CreateTypeLoader(runtimeCache, globalSettings, logger); // this is so the model factory looks into the test assembly pluginManager.AssembliesToScan = pluginManager.AssembliesToScan diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs index b1fbc43f08..d18c6b6668 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs @@ -1,14 +1,12 @@ using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Tests.TestHelpers; -using LightInject; using Moq; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Services; using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.PublishedContent @@ -25,10 +23,8 @@ namespace Umbraco.Tests.PublishedContent // fixme - what about the if (PropertyValueConvertersResolver.HasCurrent == false) ?? // can we risk double - registering and then, what happens? - var builder = Container.TryGetInstance() - ?? Container.RegisterCollectionBuilder(); - - builder.Clear() + Composition.WithCollectionBuilder() + .Clear() .Append() .Append() .Append(); @@ -38,7 +34,7 @@ namespace Umbraco.Tests.PublishedContent { base.Initialize(); - var converters = Container.GetInstance(); + var converters = Factory.GetInstance(); var dataTypeService = new TestObjects.TestDataTypeService( new DataType(new RichTextPropertyEditor(Mock.Of())) { Id = 1 }); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index ed7affa79e..914956dce1 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -9,8 +9,9 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Core.Composing; -using LightInject; using Moq; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -32,9 +33,9 @@ namespace Umbraco.Tests.PublishedContent { base.Compose(); - Container.RegisterSingleton(f => new PublishedModelFactory(f.GetInstance().GetTypes())); - Container.RegisterSingleton(); - Container.RegisterSingleton(); + Composition.RegisterUnique(f => new PublishedModelFactory(f.GetInstance().GetTypes())); + Composition.RegisterUnique(); + Composition.RegisterUnique(); var logger = Mock.Of(); var dataTypeService = new TestObjects.TestDataTypeService( @@ -44,14 +45,14 @@ namespace Umbraco.Tests.PublishedContent new DataType(new IntegerPropertyEditor(logger)) { Id = 1003 }, new DataType(new TextboxPropertyEditor(logger)) { Id = 1004 }, new DataType(new MediaPickerPropertyEditor(logger)) { Id = 1005 }); - Container.RegisterSingleton(f => dataTypeService); + Composition.RegisterUnique(f => dataTypeService); } protected override void Initialize() { base.Initialize(); - var factory = Container.GetInstance() as PublishedContentTypeFactory; + var factory = Factory.GetInstance() as PublishedContentTypeFactory; // need to specify a custom callback for unit tests // AutoPublishedContentTypes generates properties automatically @@ -72,9 +73,9 @@ namespace Umbraco.Tests.PublishedContent ContentTypesCache.GetPublishedContentTypeByAlias = alias => type; } - protected override TypeLoader CreateTypeLoader(IServiceFactory f) + protected override TypeLoader CreateTypeLoader(IRuntimeCacheProvider runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - var pluginManager = base.CreateTypeLoader(f); + var pluginManager = base.CreateTypeLoader(runtimeCache, globalSettings, logger); // this is so the model factory looks into the test assembly pluginManager.AssembliesToScan = pluginManager.AssembliesToScan @@ -453,7 +454,7 @@ namespace Umbraco.Tests.PublishedContent public void FirstChildAsT() { var doc = GetNode(1046); // has child nodes - + var model = doc.FirstChild(x => true); // predicate Assert.IsNotNull(model); @@ -817,7 +818,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void FragmentProperty() { - var factory = Container.GetInstance() as PublishedContentTypeFactory; + var factory = Factory.GetInstance() as PublishedContentTypeFactory; var pt = factory.CreatePropertyType("detached", 1003); var ct = factory.CreateContentType(0, "alias", new[] { pt }); @@ -836,7 +837,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Fragment2() { - var factory = Container.GetInstance() as PublishedContentTypeFactory; + var factory = Factory.GetInstance() as PublishedContentTypeFactory; var pt1 = factory.CreatePropertyType("legend", 1004); var pt2 = factory.CreatePropertyType("image", 1005); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs index 309ce1c0fb..d88df766e3 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs @@ -1,7 +1,6 @@ using System.Web; using System.Xml.Linq; using System.Xml.XPath; -using Lucene.Net.Store; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; @@ -20,9 +19,8 @@ using Umbraco.Core.Strings; using Umbraco.Examine; using Current = Umbraco.Web.Composing.Current; using Umbraco.Tests.Testing; -using LightInject; +using Umbraco.Core.Composing; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; using Umbraco.Core.PropertyEditors; namespace Umbraco.Tests.PublishedContent @@ -42,7 +40,7 @@ namespace Umbraco.Tests.PublishedContent { base.Compose(); - Container.GetInstance() + Composition.WithCollectionBuilder() .Clear() .Append(); } @@ -113,7 +111,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Ensure_Children_Sorted_With_Examine() { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockMediaService()); + var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, @@ -142,7 +140,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Do_Not_Find_In_Recycle_Bin() { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockMediaService()); + var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, @@ -190,7 +188,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Children_With_Examine() { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockMediaService()); + var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, @@ -218,7 +216,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Descendants_With_Examine() { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockMediaService()); + var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, @@ -246,7 +244,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void DescendantsOrSelf_With_Examine() { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockMediaService()); + var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, @@ -274,7 +272,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Ancestors_With_Examine() { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockMediaService()); + var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) @@ -300,7 +298,7 @@ namespace Umbraco.Tests.PublishedContent [Test] public void AncestorsOrSelf_With_Examine() { - var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockMediaService()); + var rebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, diff --git a/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs index 224b4a7934..72848753e5 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs @@ -1,8 +1,9 @@ using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Routing; -using LightInject; namespace Umbraco.Tests.Routing { @@ -17,7 +18,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext(urlAsString); var publishedRouter = CreatePublishedRouter(); var frequest = publishedRouter.CreateRequest(umbracoContext); - var lookup = new ContentFinderByIdPath(Container.GetInstance().WebRouting, Logger); + var lookup = new ContentFinderByIdPath(Factory.GetInstance().WebRouting, Logger); var result = lookup.TryFindContent(frequest); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs index 5080ab339d..dab0f15917 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs @@ -1,12 +1,10 @@ using Moq; using NUnit.Framework; -using LightInject; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Routing; using Umbraco.Core.Models; using Umbraco.Tests.Testing; using Current = Umbraco.Web.Composing.Current; -using Umbraco.Core.Configuration.UmbracoSettings; namespace Umbraco.Tests.Routing { diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs index 8b591dfb84..e4002c0da1 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs @@ -2,6 +2,7 @@ using System.Globalization; using Moq; using NUnit.Framework; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; @@ -36,7 +37,7 @@ namespace Umbraco.Tests.Routing var frequest = publishedRouter.CreateRequest(umbracoContext); var lookup = new ContentFinderByUrl(Logger); - Assert.IsTrue(UmbracoConfig.For.GlobalSettings().HideTopLevelNodeFromPath); + Assert.IsTrue(Current.Config.Global().HideTopLevelNodeFromPath); // fixme debugging - going further down, the routes cache is NOT empty?! if (urlString == "/home/sub1") @@ -66,13 +67,13 @@ namespace Umbraco.Tests.Routing var globalSettingsMock = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container globalSettingsMock.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); SettingsForTests.ConfigureSettings(globalSettingsMock.Object); - + var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettingsMock.Object); var publishedRouter = CreatePublishedRouter(); var frequest = publishedRouter.CreateRequest(umbracoContext); var lookup = new ContentFinderByUrl(Logger); - Assert.IsFalse(UmbracoConfig.For.GlobalSettings().HideTopLevelNodeFromPath); + Assert.IsFalse(Current.Config.Global().HideTopLevelNodeFromPath); var result = lookup.TryFindContent(frequest); @@ -97,7 +98,7 @@ namespace Umbraco.Tests.Routing var publishedRouter = CreatePublishedRouter(); var frequest = publishedRouter.CreateRequest(umbracoContext); var lookup = new ContentFinderByUrl(Logger); - + var result = lookup.TryFindContent(frequest); Assert.IsTrue(result); @@ -156,7 +157,7 @@ namespace Umbraco.Tests.Routing var frequest = publishedRouter.CreateRequest(umbracoContext); frequest.Domain = new DomainAndUri(new Domain(1, "mysite/æøå", -1, CultureInfo.CurrentCulture, false), new Uri("http://mysite/æøå")); var lookup = new ContentFinderByUrl(Logger); - + var result = lookup.TryFindContent(frequest); Assert.IsTrue(result); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs index 5787d3e613..92aed90168 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs @@ -129,7 +129,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.ConfigureSettings(globalSettingsMock.Object); var umbracoContext = GetUmbracoContext(url, globalSettings:globalSettingsMock.Object); - var publishedRouter = CreatePublishedRouter(Container); + var publishedRouter = CreatePublishedRouter(Factory); var frequest = publishedRouter.CreateRequest(umbracoContext); // must lookup domain else lookup by url fails @@ -172,7 +172,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.ConfigureSettings(globalSettingsMock.Object); var umbracoContext = GetUmbracoContext(url, globalSettings:globalSettingsMock.Object); - var publishedRouter = CreatePublishedRouter(Container); + var publishedRouter = CreatePublishedRouter(Factory); var frequest = publishedRouter.CreateRequest(umbracoContext); // must lookup domain else lookup by url fails diff --git a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs index 987fa0a869..471fa27f82 100644 --- a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs +++ b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs @@ -1,9 +1,10 @@ -using System; -using Moq; +using Moq; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Routing; +using Umbraco.Core; +using Umbraco.Core.Composing; namespace Umbraco.Tests.Routing { @@ -14,7 +15,7 @@ namespace Umbraco.Tests.Routing { base.Compose(); - Container.Register(); + Composition.Register(); } private void SetDomains1() @@ -269,7 +270,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings.Object); - var publishedRouter = CreatePublishedRouter(Container); + var publishedRouter = CreatePublishedRouter(Factory); var frequest = publishedRouter.CreateRequest(umbracoContext); // lookup domain @@ -319,7 +320,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings.Object); - var publishedRouter = CreatePublishedRouter(Container); + var publishedRouter = CreatePublishedRouter(Factory); var frequest = publishedRouter.CreateRequest(umbracoContext); // lookup domain @@ -374,7 +375,7 @@ namespace Umbraco.Tests.Routing globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings.Object); - var publishedRouter = CreatePublishedRouter(Container); + var publishedRouter = CreatePublishedRouter(Factory); var frequest = publishedRouter.CreateRequest(umbracoContext); // lookup domain diff --git a/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs b/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs index a2a627227e..6079369df7 100644 --- a/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs +++ b/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs @@ -59,7 +59,7 @@ namespace Umbraco.Tests.Routing content.Path = "-1,1046"; var umbContext = GetUmbracoContext("http://localhost:8000"); - var publishedRouter = CreatePublishedRouter(Container, + var publishedRouter = CreatePublishedRouter(Factory, contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(Logger) })); var urls = content.GetContentUrls(publishedRouter, umbContext, @@ -79,10 +79,10 @@ namespace Umbraco.Tests.Routing content.Id = 1046; //fixme: we are using this ID only because it's built into the test XML published cache content.Path = "-1,1046"; content.Published = true; - + var umbContext = GetUmbracoContext("http://localhost:8000", urlProviders: new []{ new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, TestObjects.GetGlobalSettings(), new SiteDomainHelper()) }); - var publishedRouter = CreatePublishedRouter(Container, + var publishedRouter = CreatePublishedRouter(Factory, contentFinders:new ContentFinderCollection(new[]{new ContentFinderByUrl(Logger) })); var urls = content.GetContentUrls(publishedRouter, umbContext, @@ -112,7 +112,7 @@ namespace Umbraco.Tests.Routing var umbContext = GetUmbracoContext("http://localhost:8000", urlProviders: new[] { new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, TestObjects.GetGlobalSettings(), new SiteDomainHelper()) }); - var publishedRouter = CreatePublishedRouter(Container, + var publishedRouter = CreatePublishedRouter(Factory, contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(Logger) })); var urls = child.GetContentUrls(publishedRouter, umbContext, diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index b277194063..84f4f5dbd7 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -2,10 +2,10 @@ using System.Linq; using System.Web.Mvc; using System.Web.Routing; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; @@ -13,11 +13,12 @@ using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; using Umbraco.Web.Models; using Umbraco.Web.Mvc; -using Umbraco.Web.Routing; using Umbraco.Web.WebApi; using Umbraco.Core.Strings; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; using Umbraco.Tests.PublishedContent; using Umbraco.Tests.Testing; using Umbraco.Tests.Testing.Objects.Accessors; @@ -47,17 +48,8 @@ namespace Umbraco.Tests.Routing : base(umbracoApplication) { } - public override void Boot(ServiceContainer container) - { - // do it before anything else - this is the only place where it's possible - var logger = Mock.Of(); - container.RegisterInstance(logger); - var profiler = Mock.Of(); - container.RegisterInstance(profiler); - container.RegisterInstance(new ProfilingLogger(logger, profiler)); - - base.Boot(container); - } + protected override ILogger GetLogger() => Mock.Of(); + protected override IProfiler GetProfiler() => Mock.Of(); } protected override void Compose() @@ -67,13 +59,13 @@ namespace Umbraco.Tests.Routing // set the default RenderMvcController Current.DefaultRenderMvcControllerType = typeof(RenderMvcController); // fixme WRONG! - var surfaceControllerTypes = new SurfaceControllerTypeCollection(Current.TypeLoader.GetSurfaceControllers()); - Container.RegisterInstance(surfaceControllerTypes); + var surfaceControllerTypes = new SurfaceControllerTypeCollection(Composition.TypeLoader.GetSurfaceControllers()); + Composition.RegisterUnique(surfaceControllerTypes); - var umbracoApiControllerTypes = new UmbracoApiControllerTypeCollection(Current.TypeLoader.GetUmbracoApiControllers()); - Container.RegisterInstance(umbracoApiControllerTypes); + var umbracoApiControllerTypes = new UmbracoApiControllerTypeCollection(Composition.TypeLoader.GetUmbracoApiControllers()); + Composition.RegisterUnique(umbracoApiControllerTypes); - Container.RegisterSingleton(_ => new DefaultShortStringHelper(SettingsForTests.GetDefaultUmbracoSettings())); + Composition.RegisterUnique(_ => new DefaultShortStringHelper(SettingsForTests.GetDefaultUmbracoSettings())); } public override void TearDown() @@ -181,6 +173,11 @@ namespace Umbraco.Tests.Routing /// public class CustomDocumentController : RenderMvcController { + public CustomDocumentController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ServiceContext services, CacheHelper applicationCache, ILogger logger, IProfilingLogger profilingLogger) + : base(globalSettings, umbracoContext, services, applicationCache, logger, profilingLogger) + { + } + public ActionResult HomePage(ContentModel model) { return View(); diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs index 69533c3c77..c87a02f43a 100644 --- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs @@ -4,13 +4,17 @@ using System.Threading; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Configuration; +using Umbraco.Core.Composing; using Umbraco.Tests.TestHelpers; using Umbraco.Web; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Sync; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; namespace Umbraco.Tests.Routing { @@ -18,23 +22,34 @@ namespace Umbraco.Tests.Routing [Apartment(ApartmentState.STA)] public class UmbracoModuleTests : BaseWebTest { - private UmbracoModule _module; + private UmbracoInjectedModule _module; public override void SetUp() { base.SetUp(); + // fixme - be able to get the UmbracoModule from the container. any reason settings were from testobjects? //create the module - _module = new UmbracoModule - { - GlobalSettings = TestObjects.GetGlobalSettings(), - Logger = Mock.Of() - }; - var runtime = new RuntimeState(_module.Logger, new Lazy(), new Lazy(), Mock.Of(), _module.GlobalSettings); + var logger = Mock.Of(); + var globalSettings = TestObjects.GetGlobalSettings(); + var runtime = new RuntimeState(logger, Mock.Of(), globalSettings, + new Lazy(), new Lazy()); + + _module = new UmbracoInjectedModule + ( + globalSettings, + Mock.Of(), + Factory.GetInstance(), + Factory.GetInstance(), + new UrlProviderCollection(new IUrlProvider[0]), + runtime, + logger, + null, // fixme - PublishedRouter complexities... + Mock.Of() + ); - _module.Runtime = runtime; runtime.Level = RuntimeLevel.Run; - + //SettingsForTests.ReservedPaths = "~/umbraco,~/install/"; //SettingsForTests.ReservedUrls = "~/config/splashes/booting.aspx,~/install/default.aspx,~/config/splashes/noNodes.aspx,~/VSEnterpriseHelper.axd"; diff --git a/src/Umbraco.Tests/Routing/UrlProviderTests.cs b/src/Umbraco.Tests/Routing/UrlProviderTests.cs index 2f1e4e3476..6f29a312ae 100644 --- a/src/Umbraco.Tests/Routing/UrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/UrlProviderTests.cs @@ -4,6 +4,8 @@ using System.Globalization; using System.Linq; using Moq; using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; @@ -23,7 +25,7 @@ namespace Umbraco.Tests.Routing protected override void Compose() { base.Compose(); - Container.Register(); + Composition.Register(); } private IUmbracoSettingsSection _umbracoSettings; diff --git a/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs b/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs index 6965c4dce9..7566c5372d 100644 --- a/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs +++ b/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs @@ -2,6 +2,7 @@ using System.Linq; using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -33,13 +34,13 @@ namespace Umbraco.Tests.Routing { base.Compose(); - Container.RegisterSingleton(_ => GetServiceContext()); + Composition.RegisterUnique(_ => GetServiceContext()); } protected ServiceContext GetServiceContext() { // get the mocked service context to get the mocked domain service - var serviceContext = TestObjects.GetServiceContextMock(Container); + var serviceContext = TestObjects.GetServiceContextMock(Factory); //setup mock domain service var domainService = Mock.Get(serviceContext.DomainService); diff --git a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs index c7ce42e4bc..e26877481a 100644 --- a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs @@ -3,12 +3,13 @@ using System.Collections.Generic; using System.Linq; using Moq; using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.Routing; -using Umbraco.Core.Composing; namespace Umbraco.Tests.Routing { @@ -19,8 +20,8 @@ namespace Umbraco.Tests.Routing { base.Compose(); - Container.RegisterSingleton(_ => Mock.Of()); - Container.Register(); + Composition.RegisterUnique(_ => Mock.Of()); + Composition.Register(); } void SetDomains1() diff --git a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs index 6b2737a3d1..13ec434608 100644 --- a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs +++ b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs @@ -1,12 +1,13 @@ using System; using Moq; using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; using Umbraco.Web.PublishedCache.XmlPublishedCache; using Umbraco.Web.Routing; using Umbraco.Core.Services; -using Umbraco.Core.Composing; namespace Umbraco.Tests.Routing { @@ -21,8 +22,8 @@ namespace Umbraco.Tests.Routing protected override void Compose() { base.Compose(); - Container.RegisterSingleton(_ => Mock.Of()); - Container.Register(); + Composition.RegisterUnique(_ => Mock.Of()); + Composition.Register(); } [Test] diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs old mode 100755 new mode 100644 index 9a8164356a..ca70ac5b6a --- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs @@ -1,16 +1,19 @@ using System; using System.Collections.Generic; +using System.Data; using System.Web.Hosting; using Examine; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Components; using Umbraco.Core.Composing; +using Umbraco.Core.Events; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Runtime; +using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; @@ -18,13 +21,18 @@ using Umbraco.Web; namespace Umbraco.Tests.Runtimes { [TestFixture] - [Ignore("cannot work until we refactor IUmbracoDatabaseFactory vs UmbracoDatabaseFactory")] public class CoreRuntimeTests { [SetUp] public void SetUp() { TestComponent.Reset(); + + // cannot boot runtime without some configuration + var umbracoSettings = SettingsForTests.GenerateMockUmbracoSettings(); + var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); + SettingsForTests.ConfigureSettings(umbracoSettings); + SettingsForTests.ConfigureSettings(globalSettings); } public void TearDown() @@ -39,12 +47,28 @@ namespace Umbraco.Tests.Runtimes { app.HandleApplicationStart(app, new EventArgs()); + var e = app.Runtime.State.BootFailedException; + var m = ""; + switch (e) + { + case null: + m = ""; + break; + case BootFailedException bfe when bfe.InnerException != null: + m = "BootFailed: " + bfe.InnerException.GetType() + " " + bfe.InnerException.Message + " " + bfe.InnerException.StackTrace; + break; + default: + m = e.GetType() + " " + e.Message + " " + e.StackTrace; + break; + } + + Assert.AreNotEqual(RuntimeLevel.BootFailed, app.Runtime.State.Level, m); + Assert.IsTrue(TestComposer.Ctored); + Assert.IsTrue(TestComposer.Composed); Assert.IsTrue(TestComponent.Ctored); - Assert.IsTrue(TestComponent.Composed); - Assert.IsTrue(TestComponent.Initialized1); - Assert.IsTrue(TestComponent.Initialized2); Assert.IsNotNull(TestComponent.ProfilingLogger); - Assert.IsInstanceOf(TestComponent.ProfilingLogger.Logger); + Assert.IsInstanceOf(TestComponent.ProfilingLogger); + Assert.IsInstanceOf(((ProfilingLogger) TestComponent.ProfilingLogger).Logger); // note: components are NOT disposed after boot @@ -58,40 +82,23 @@ namespace Umbraco.Tests.Runtimes // test application public class TestUmbracoApplication : UmbracoApplicationBase { + public IRuntime Runtime { get; private set; } + protected override IRuntime GetRuntime() { - return new TestRuntime(); + return Runtime = new TestRuntime(); } } // test runtime public class TestRuntime : CoreRuntime { - // the application's logger is created by the application - // through GetLogger, that custom application can override - protected override ILogger GetLogger() - { - //return Mock.Of(); - return new DebugDiagnosticsLogger(); - } - - public override void Compose(ServiceContainer container) - { - base.Compose(container); - - // the application's profiler and profiling logger are - // registered by CoreRuntime.Compose() but can be - // overriden afterwards - they haven't been resolved yet - container.RegisterSingleton(_ => new TestProfiler()); - container.RegisterSingleton(factory => new ProfilingLogger(factory.GetInstance(), factory.GetInstance())); - - // must override the database factory - container.RegisterSingleton(_ => GetDatabaseFactory()); - } + protected override ILogger GetLogger() => new DebugDiagnosticsLogger(); + protected override IProfiler GetProfiler() => new TestProfiler(); // must override the database factory // else BootFailedException because U cannot connect to the configured db - private static IUmbracoDatabaseFactory GetDatabaseFactory() + protected internal override IUmbracoDatabaseFactory GetDatabaseFactory() { var mock = new Mock(); mock.Setup(x => x.Configured).Returns(true); @@ -99,19 +106,43 @@ namespace Umbraco.Tests.Runtimes return mock.Object; } + // fixme so how the f* should we do it now? + /* // pretend we have the proper migration // else BootFailedException because our mock IUmbracoDatabaseFactory does not provide databases - protected override bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) + protected override bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory) { return true; } + */ - private MainDom _mainDom; - - public override void Boot(ServiceContainer container) + // because we don't even have the core runtime component, + // there are a few required stuff that we need to compose + public override void Compose(Composition composition) { - base.Boot(container); - _mainDom = container.GetInstance(); + base.Compose(composition); + + var scopeProvider = Mock.Of(); + Mock.Get(scopeProvider) + .Setup(x => x.CreateScope( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(Mock.Of()); + + composition.RegisterUnique(scopeProvider); + } + + private IMainDom _mainDom; + + public override IFactory Boot(IRegister container) + { + var factory = base.Boot(container); + _mainDom = factory.GetInstance(); + return factory; } public override void Terminate() @@ -123,62 +154,65 @@ namespace Umbraco.Tests.Runtimes // runs with only one single component // UmbracoCoreComponent will be force-added too // and that's it - protected override IEnumerable GetComponentTypes() + protected override IEnumerable GetComposerTypes(TypeLoader typeLoader) { - return new[] { typeof(TestComponent) }; + return new[] { typeof(TestComposer) }; } } - public class TestComponent : UmbracoComponentBase + public class TestComposer : IComposer { // test flags public static bool Ctored; public static bool Composed; - public static bool Initialized1; - public static bool Initialized2; - public static bool Terminated; - public static ProfilingLogger ProfilingLogger; public static void Reset() { - Ctored = Composed = Initialized1 = Initialized2 = Terminated = false; - ProfilingLogger = null; + Ctored = Composed = false; } - public TestComponent() + public TestComposer() { Ctored = true; } - public override void Compose(Composition composition) + public void Compose(Composition composition) { - base.Compose(composition); - - composition.Container.Register(factory => SettingsForTests.GetDefaultUmbracoSettings()); - composition.Container.RegisterSingleton(); + composition.Register(factory => SettingsForTests.GetDefaultUmbracoSettings()); + composition.RegisterUnique(); + composition.Components().Append(); Composed = true; } + } - public void Initialize() + public class TestComponent : IComponent, IDisposable + { + // test flags + public static bool Ctored; + public static bool Initialized1; + public static bool Initialized2; + public static bool Terminated; + public static IProfilingLogger ProfilingLogger; + + public bool Disposed; + + public static void Reset() { - Initialized1 = true; + Ctored = Initialized1 = Initialized2 = Terminated = false; + ProfilingLogger = null; } - public void Initialize(ILogger logger) - { - Initialized2 = true; - } - - public void Initialize(ProfilingLogger proflog) + public TestComponent(IProfilingLogger proflog) { + Ctored = true; ProfilingLogger = proflog; } - public override void Terminate() + public void Dispose() { - base.Terminate(); + Disposed = true; Terminated = true; } } diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs new file mode 100644 index 0000000000..c24ca6f351 --- /dev/null +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -0,0 +1,399 @@ +using System; +using System.Configuration; +using System.Data.SqlServerCe; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Web; +using Examine; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Runtime; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services; +using Umbraco.Core.Sync; +using Umbraco.Tests.Composing; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing.Objects.Accessors; +using Umbraco.Web; +using Umbraco.Web.Cache; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; +using Umbraco.Web.Runtime; +using File = System.IO.File; + +namespace Umbraco.Tests.Runtimes +{ + [TestFixture] + public class StandaloneTests + { + [Test] + [Explicit("This test must be run manually")] + public void StandaloneTest() + { + IFactory factory = null; + + // clear + foreach (var file in Directory.GetFiles(Path.Combine(IOHelper.MapPath("~/App_Data")), "NuCache.*")) + File.Delete(file); + + // settings + // reset the current version to 0.0.0, clear connection strings + ConfigurationManager.AppSettings["umbracoConfigurationStatus"] = ""; + // fixme we need a better management of settings here (and, true config files?) + + // create the very basic and essential things we need + var logger = new ConsoleLogger(); + var profiler = new LogProfiler(logger); + var profilingLogger = new ProfilingLogger(logger, profiler); + var appCaches = new CacheHelper(); // fixme has HttpRuntime stuff? + var databaseFactory = new UmbracoDatabaseFactory(logger, new Lazy(() => factory.GetInstance())); + var typeLoader = new TypeLoader(appCaches.RuntimeCache, LocalTempStorage.Default, profilingLogger); + var mainDom = new SimpleMainDom(); + var runtimeState = new RuntimeState(logger, null, null, new Lazy(() => mainDom), new Lazy(() => factory.GetInstance())); + + // create the register and the composition + var register = RegisterFactory.Create(); + var composition = new Composition(register, typeLoader, profilingLogger, runtimeState); + composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState); + + // create the core runtime and have it compose itself + var coreRuntime = new CoreRuntime(); + coreRuntime.Compose(composition); + + // determine actual runtime level + runtimeState.DetermineRuntimeLevel(databaseFactory, logger); + Console.WriteLine(runtimeState.Level); + // going to be Install BUT we want to force components to be there (nucache etc) + runtimeState.Level = RuntimeLevel.Run; + + var composerTypes = typeLoader.GetTypes() // all of them + .Where(x => !x.FullName.StartsWith("Umbraco.Tests.")) // exclude test components + .Where(x => x != typeof(WebRuntimeComposer)); // exclude web runtime + var composers = new Composers(composition, composerTypes, profilingLogger); + composers.Compose(); + + // must registers stuff that WebRuntimeComponent would register otherwise + // fixme UmbracoContext creates a snapshot that it does not register with the accessor + // and so, we have to use the UmbracoContextPublishedSnapshotAccessor + // the UmbracoContext does not know about the accessor + // else that would be a catch-22 where they both know about each other? + //composition.Register(Lifetime.Singleton); + composition.Register(Lifetime.Singleton); + composition.Register(Lifetime.Singleton); + composition.Register(Lifetime.Singleton); + composition.Register(Lifetime.Singleton); + composition.Register(_ => Mock.Of(), Lifetime.Singleton); + composition.RegisterUnique(f => new DistributedCache()); + composition.WithCollectionBuilder().Append(); + composition.RegisterUnique(); + composition.RegisterUnique(f => ExamineManager.Instance); + + // initialize some components only/individually + composition.WithCollectionBuilder() + .Clear() + .Append(); + + // create and register the factory + Current.Factory = factory = composition.CreateFactory(); + + // instantiate and initialize components + var components = factory.GetInstance(); + + // do stuff + Console.WriteLine(runtimeState.Level); + + // install + if (true || runtimeState.Level == RuntimeLevel.Install) + { + var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var file = Path.Combine(path, "Umbraco.sdf"); + if (File.Exists(file)) + File.Delete(file); + + // create the database file + // databaseBuilder.ConfigureEmbeddedDatabaseConnection() can do it too, + // but then it wants to write the connection string to web.config = bad + using (var engine = new SqlCeEngine("Data Source=|DataDirectory|\\Umbraco.sdf;Flush Interval=1;")) + { + engine.CreateDatabase(); + } + + //var databaseBuilder = factory.GetInstance(); + //databaseFactory.Configure(DatabaseBuilder.EmbeddedDatabaseConnectionString, Constants.DbProviderNames.SqlCe); + //databaseBuilder.CreateDatabaseSchemaAndData(); + + databaseFactory.Configure(DatabaseBuilder.EmbeddedDatabaseConnectionString, Constants.DbProviderNames.SqlCe); + + var scopeProvider = factory.GetInstance(); + using (var scope = scopeProvider.CreateScope()) + { + var creator = new DatabaseSchemaCreator(scope.Database, logger); + creator.InitializeDatabaseSchema(); + scope.Complete(); + } + } + + // done installing + runtimeState.Level = RuntimeLevel.Run; + + var globalSettings = SettingsForTests.GetDefaultGlobalSettings(); + SettingsForTests.ConfigureSettings(globalSettings); + var umbracoSettings = SettingsForTests.GetDefaultUmbracoSettings(); + SettingsForTests.ConfigureSettings(umbracoSettings); + + // instantiate to register events + // should be done by Initialize? + // should we invoke Initialize? + _ = factory.GetInstance(); + + // at that point, Umbraco can run! + // though, we probably still need to figure out what depends on HttpContext... + var contentService = factory.GetInstance(); + var content = contentService.GetById(1234); + Assert.IsNull(content); + + // create a document type and a document + var contentType = new ContentType(-1) { Alias = "ctype", Name = "ctype" }; + factory.GetInstance().Save(contentType); + content = new Content("test", -1, contentType); + contentService.Save(content); + + // assert that it is possible to get the document back + content = contentService.GetById(content.Id); + Assert.IsNotNull(content); + Assert.AreEqual("test", content.Name); + + // need an UmbracoCOntext to access the cache + // fixme - not exactly pretty, should not depend on HttpContext + var httpContext = Mock.Of(); + var withUmbracoContext = UmbracoContext.EnsureContext(httpContext); + var umbracoContext = Umbraco.Web.Composing.Current.UmbracoContext; + + // assert that there is no published document + var pcontent = umbracoContext.ContentCache.GetById(content.Id); + Assert.IsNull(pcontent); + + // but a draft document + pcontent = umbracoContext.ContentCache.GetById(true, content.Id); + Assert.IsNotNull(pcontent); + Assert.AreEqual("test", pcontent.Name); + Assert.IsTrue(pcontent.IsDraft()); + + // no published url + Assert.AreEqual("#", pcontent.GetUrl()); + + // now publish the document + make some unpublished changes + contentService.SaveAndPublish(content); + content.Name = "testx"; + contentService.Save(content); + + // assert that snapshot has been updated and there is now a published document + pcontent = umbracoContext.ContentCache.GetById(content.Id); + Assert.IsNotNull(pcontent); + Assert.AreEqual("test", pcontent.Name); + Assert.IsFalse(pcontent.IsDraft()); + + // but the url is the published one - no draft url + Assert.AreEqual("/test/", pcontent.GetUrl()); + + // and also an updated draft document + pcontent = umbracoContext.ContentCache.GetById(true, content.Id); + Assert.IsNotNull(pcontent); + Assert.AreEqual("testx", pcontent.Name); + Assert.IsTrue(pcontent.IsDraft()); + + // and the published document has a url + Assert.AreEqual("/test/", pcontent.GetUrl()); + + withUmbracoContext.Dispose(); + mainDom.Stop(); + components.Terminate(); + + // exit! + } + + [Test] + [Explicit("This test must be run manually")] + public void ValidateComposition() + { + // this is almost what CoreRuntime does, without + // - managing MainDom + // - configuring for unhandled exceptions, assembly resolution, application root path + // - testing for database, and for upgrades (runtime level) + // - assigning the factory to Current.Factory + + // create the very basic and essential things we need + var logger = new ConsoleLogger(); + var profiler = Mock.Of(); + var profilingLogger = new ProfilingLogger(logger, profiler); + var appCaches = CacheHelper.Disabled; + var databaseFactory = Mock.Of(); + var typeLoader = new TypeLoader(appCaches.RuntimeCache, LocalTempStorage.Default, profilingLogger); + var runtimeState = Mock.Of(); + Mock.Get(runtimeState).Setup(x => x.Level).Returns(RuntimeLevel.Run); + var mainDom = Mock.Of(); + Mock.Get(mainDom).Setup(x => x.IsMainDom).Returns(true); + + // create the register and the composition + var register = RegisterFactory.Create(); + var composition = new Composition(register, typeLoader, profilingLogger, runtimeState); + composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState); + + // create the core runtime and have it compose itself + var coreRuntime = new CoreRuntime(); + coreRuntime.Compose(composition); + + // get the components + // all of them? + var composerTypes = typeLoader.GetTypes(); + // filtered? + //var componentTypes = typeLoader.GetTypes() + // .Where(x => !x.FullName.StartsWith("Umbraco.Web")); + // single? + //var componentTypes = new[] { typeof(CoreRuntimeComponent) }; + var composers = new Composers(composition, composerTypes, profilingLogger); + + // get components to compose themselves + composers.Compose(); + + // create the factory + var factory = composition.CreateFactory(); + + // at that point Umbraco is fully composed + // but nothing is initialized (no maindom, nothing - beware!) + // to actually *run* Umbraco standalone, better use a StandaloneRuntime + // that would inherit from CoreRuntime and ensure everything starts + + // get components to initialize themselves + //components.Initialize(factory); + + // and then, validate + var lightInjectContainer = (LightInject.ServiceContainer) factory.Concrete; + var results = lightInjectContainer.Validate().ToList(); + foreach (var resultGroup in results.GroupBy(x => x.Severity).OrderBy(x => x.Key)) + { + Console.WriteLine($"{resultGroup.Key}: {resultGroup.Count()}"); + } + + foreach (var resultGroup in results.GroupBy(x => x.Severity).OrderBy(x => x.Key)) + foreach (var result in resultGroup) + { + Console.WriteLine(); + Console.Write(ToText(result)); + } + + Assert.AreEqual(0, results.Count); + } + + private static string ToText(ValidationResult result) + { + var text = new StringBuilder(); + + text.AppendLine($"{result.Severity}: {WordWrap(result.Message, 120)}"); + var target = result.ValidationTarget; + text.Append("\tsvce: "); + text.Append(target.ServiceName); + text.Append(target.DeclaringService.ServiceType); + if (!target.DeclaringService.ServiceName.IsNullOrWhiteSpace()) + { + text.Append(" '"); + text.Append(target.DeclaringService.ServiceName); + text.Append("'"); + } + + text.Append(" ("); + if (target.DeclaringService.Lifetime == null) + text.Append("Transient"); + else + text.Append(target.DeclaringService.Lifetime.ToString().TrimStart("LightInject.").TrimEnd("Lifetime")); + text.AppendLine(")"); + text.Append("\timpl: "); + text.Append(target.DeclaringService.ImplementingType); + text.AppendLine(); + text.Append("\tparm: "); + text.Append(target.Parameter); + text.AppendLine(); + + return text.ToString(); + } + + private static string WordWrap(string text, int width) + { + int pos, next; + var sb = new StringBuilder(); + var nl = Environment.NewLine; + + // Lucidity check + if (width < 1) + return text; + + // Parse each line of text + for (pos = 0; pos < text.Length; pos = next) + { + // Find end of line + var eol = text.IndexOf(nl, pos, StringComparison.Ordinal); + + if (eol == -1) + next = eol = text.Length; + else + next = eol + nl.Length; + + // Copy this line of text, breaking into smaller lines as needed + if (eol > pos) + { + do + { + var len = eol - pos; + + if (len > width) + len = BreakLine(text, pos, width); + + if (pos > 0) + sb.Append("\t\t"); + sb.Append(text, pos, len); + sb.Append(nl); + + // Trim whitespace following break + pos += len; + + while (pos < eol && char.IsWhiteSpace(text[pos])) + pos++; + + } while (eol > pos); + } + else sb.Append(nl); // Empty line + } + + return sb.ToString(); + } + + private static int BreakLine(string text, int pos, int max) + { + // Find last whitespace in line + var i = max - 1; + while (i >= 0 && !char.IsWhiteSpace(text[pos + i])) + i--; + if (i < 0) + return max; // No whitespace found; break at maximum length + // Find start of whitespace + while (i >= 0 && char.IsWhiteSpace(text[pos + i])) + i--; + // Return length of text before whitespace + return i + 1; + } + } +} diff --git a/src/Umbraco.Tests/Runtimes/WebRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs similarity index 92% rename from src/Umbraco.Tests/Runtimes/WebRuntimeTests.cs rename to src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs index b9f8f6576b..97688a7113 100644 --- a/src/Umbraco.Tests/Runtimes/WebRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs @@ -2,14 +2,13 @@ using System.Web.Mvc; using NUnit.Framework; using Umbraco.Core.Profiling; -using Umbraco.Web; using Umbraco.Web.Mvc; using Umbraco.Web.Runtime; namespace Umbraco.Tests.Runtimes { [TestFixture] - public class WebRuntimeTests + public class WebRuntimeComponentTests { [Test] public void WrapViewEngines_HasEngines_WrapsAll() @@ -43,7 +42,6 @@ namespace Umbraco.Tests.Runtimes Assert.That(((ProfilingViewEngine)engines[1]).Inner, Is.InstanceOf()); } - [Test] public void WrapViewEngines_HasProfiledEngine_AddsSameInstance() { @@ -61,10 +59,7 @@ namespace Umbraco.Tests.Runtimes [Test] public void WrapViewEngines_CollectionIsNull_DoesNotThrow() { - IList engines = null; - Assert.DoesNotThrow(() => WebRuntimeComponent.WrapViewEngines(engines)); - Assert.That(engines, Is.Null); + Assert.DoesNotThrow(() => WebRuntimeComponent.WrapViewEngines(null)); } - } } diff --git a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs index 59cf30c0fb..3664717af7 100644 --- a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs +++ b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs @@ -13,6 +13,7 @@ namespace Umbraco.Tests.Scheduling { [TestFixture] [Timeout(30000)] + [Category("Slow")] public class BackgroundTaskRunnerTests { private ILogger _logger; diff --git a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs index 4d49747004..e74125adfa 100644 --- a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs @@ -1,10 +1,10 @@ using System; -using System.Collections.Generic; using System.Linq; -using LightInject; using Moq; using NUnit.Framework; -using Umbraco.Core.Collections; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.IO; @@ -13,8 +13,10 @@ using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Services; +using Umbraco.Tests.Components; namespace Umbraco.Tests.Scoping { @@ -31,12 +33,17 @@ namespace Umbraco.Tests.Scoping DoThing2 = null; DoThing3 = null; - Current.Container = new ServiceContainer(); + var register = RegisterFactory.Create(); - _testObjects = new TestObjects(Current.Container); - Current.Container.RegisterSingleton(f => Current.Container); - Current.Container.RegisterSingleton(factory => new FileSystems(factory.TryGetInstance())); - Current.Container.RegisterCollectionBuilder(); + var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + + _testObjects = new TestObjects(register); + + composition.RegisterUnique(factory => new FileSystems(factory, factory.TryGetInstance())); + composition.WithCollectionBuilder(); + + Current.Reset(); + Current.Factory = composition.CreateFactory(); SettingsForTests.Reset(); // ensure we have configuration } diff --git a/src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs b/src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs index ae16ba484f..0ef0e9362b 100644 --- a/src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeFileSystemsTests.cs @@ -4,8 +4,8 @@ using System.Text; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Composing.Composers; using Umbraco.Core.IO; -using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -23,11 +23,21 @@ namespace Umbraco.Tests.Scoping ClearFiles(); } + protected override void ComposeApplication(bool withApplication) + { + base.ComposeApplication(withApplication); + + if (!withApplication) return; + + // re-register with actual media fs + Composition.ComposeFileSystems(); + } + public override void TearDown() { base.TearDown(); SafeCallContext.Clear(); - ShadowFileSystems.ResetId(); + FileSystems.ResetShadowId(); ClearFiles(); } @@ -35,7 +45,7 @@ namespace Umbraco.Tests.Scoping { TestHelper.DeleteDirectory(IOHelper.MapPath("media")); TestHelper.DeleteDirectory(IOHelper.MapPath("FileSysTests")); - TestHelper.DeleteDirectory(IOHelper.MapPath("App_Data")); + TestHelper.DeleteDirectory(IOHelper.MapPath("App_Data/TEMP/ShadowFs")); } [TestCase(true)] @@ -43,7 +53,7 @@ namespace Umbraco.Tests.Scoping public void CreateMediaTest(bool complete) { var physMediaFileSystem = new PhysicalFileSystem(IOHelper.MapPath("media"), "ignore"); - var mediaFileSystem = Current.FileSystems.MediaFileSystem; + var mediaFileSystem = Current.MediaFileSystem; Assert.IsFalse(physMediaFileSystem.FileExists("f1.txt")); @@ -62,12 +72,12 @@ namespace Umbraco.Tests.Scoping if (complete) { - Assert.IsTrue(Current.FileSystems.MediaFileSystem.FileExists("f1.txt")); + Assert.IsTrue(Current.MediaFileSystem.FileExists("f1.txt")); Assert.IsTrue(physMediaFileSystem.FileExists("f1.txt")); } else { - Assert.IsFalse(Current.FileSystems.MediaFileSystem.FileExists("f1.txt")); + Assert.IsFalse(Current.MediaFileSystem.FileExists("f1.txt")); Assert.IsFalse(physMediaFileSystem.FileExists("f1.txt")); } } @@ -76,7 +86,7 @@ namespace Umbraco.Tests.Scoping public void MultiThread() { var physMediaFileSystem = new PhysicalFileSystem(IOHelper.MapPath("media"), "ignore"); - var mediaFileSystem = Current.FileSystems.MediaFileSystem; + var mediaFileSystem = Current.MediaFileSystem; var scopeProvider = ScopeProvider; using (var scope = scopeProvider.CreateScope(scopeFileSystems: true)) diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 211bdc3cdb..b273ee9526 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Web.Routing; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -15,7 +14,6 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; @@ -47,10 +45,10 @@ namespace Umbraco.Tests.Scoping // but then, it requires a lot of plumbing ;( // fixme - and we cannot inject a DistributedCache yet // so doing all this mess - Container.RegisterSingleton(); - Container.RegisterSingleton(f => Mock.Of()); - Container.RegisterCollectionBuilder() - .Add(f => f.TryGetInstance().GetCacheRefreshers()); + Composition.RegisterUnique(); + Composition.RegisterUnique(f => Mock.Of()); + Composition.WithCollectionBuilder() + .Add(() => Composition.TypeLoader.GetCacheRefreshers()); } public override void TearDown() @@ -97,7 +95,7 @@ namespace Umbraco.Tests.Scoping documentRepository, mediaRepository, memberRepository, DefaultCultureAccessor, new DatabaseDataSource(), - Container.GetInstance(), new SiteDomainHelper()); + Factory.GetInstance(), new SiteDomainHelper()); } protected UmbracoContext GetUmbracoContextNu(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, IUmbracoSettingsSection umbracoSettings = null, IEnumerable urlProviders = null) diff --git a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs index 9ace5860e1..3a0cb00ea9 100644 --- a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs @@ -10,7 +10,6 @@ using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; using Umbraco.Web.Cache; -using LightInject; using Moq; using Umbraco.Core.Events; using Umbraco.Core.Logging; @@ -32,22 +31,20 @@ namespace Umbraco.Tests.Scoping // but then, it requires a lot of plumbing ;( // fixme - and we cannot inject a DistributedCache yet // so doing all this mess - Container.RegisterSingleton(); - Container.RegisterSingleton(f => Mock.Of()); - Container.RegisterCollectionBuilder() - .Add(f => f.TryGetInstance().GetCacheRefreshers()); + Composition.RegisterUnique(); + Composition.RegisterUnique(f => Mock.Of()); + Composition.WithCollectionBuilder() + .Add(() => Composition.TypeLoader.GetCacheRefreshers()); } - protected override void ComposeCacheHelper() + protected override CacheHelper GetCacheHelper() { // this is what's created core web runtime - var cacheHelper = new CacheHelper( + return new CacheHelper( new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()), new StaticCacheProvider(), NullCacheProvider.Instance, new IsolatedRuntimeCache(type => new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); - Container.RegisterSingleton(f => cacheHelper); - Container.RegisterSingleton(f => f.GetInstance().RuntimeCache); } [TearDown] diff --git a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs index 1afbf5cb12..99a5c57058 100644 --- a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Xml; using Moq; using NUnit.Framework; -using LightInject; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Events; @@ -34,10 +33,10 @@ namespace Umbraco.Tests.Scoping // but then, it requires a lot of plumbing ;( // fixme - and we cannot inject a DistributedCache yet // so doing all this mess - Container.RegisterSingleton(); - Container.RegisterSingleton(f => Mock.Of()); - Container.RegisterCollectionBuilder() - .Add(f => f.TryGetInstance().GetCacheRefreshers()); + Composition.RegisterUnique(); + Composition.RegisterUnique(f => Mock.Of()); + Composition.WithCollectionBuilder() + .Add(() => Composition.TypeLoader.GetCacheRefreshers()); } [TearDown] @@ -65,7 +64,7 @@ namespace Umbraco.Tests.Scoping // xmlStore.Xml - the actual main xml document // publishedContentCache.GetXml() - the captured xml - private static XmlStore XmlStore => (Current.Container.GetInstance() as PublishedSnapshotService).XmlStore; + private static XmlStore XmlStore => (Current.Factory.GetInstance() as PublishedSnapshotService).XmlStore; private static XmlDocument XmlMaster => XmlStore.Xml; private static XmlDocument XmlInContext => ((PublishedContentCache) Umbraco.Web.Composing.Current.UmbracoContext.ContentCache).GetXml(false); diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index 322b79429e..3aefa88a50 100644 --- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs +++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs @@ -5,6 +5,8 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -34,7 +36,7 @@ namespace Umbraco.Tests.Services protected override void Compose() { base.Compose(); - Container.Register(); + Composition.Register(); } [Test] @@ -43,7 +45,7 @@ namespace Umbraco.Tests.Services Assert.IsInstanceOf(Current.Profiler); } - private static ProfilingLogger GetTestProfilingLogger() + private static IProfilingLogger GetTestProfilingLogger() { var logger = new DebugDiagnosticsLogger(); var profiler = new TestProfiler(); @@ -161,11 +163,11 @@ namespace Umbraco.Tests.Services var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var tRepository = new TemplateRepository((IScopeAccessor) provider, DisabledCache, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepo = new TagRepository((IScopeAccessor) provider, DisabledCache, Logger); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, DisabledCache, Logger, tRepository); - var languageRepository = new LanguageRepository((IScopeAccessor) provider, DisabledCache, Logger); - var repository = new DocumentRepository((IScopeAccessor) provider, DisabledCache, Logger, ctRepository, tRepository, tagRepo, languageRepository, Mock.Of()); + var tRepository = new TemplateRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger, Mock.Of(), TestObjects.GetFileSystemsMock()); + var tagRepo = new TagRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger, tRepository); + var languageRepository = new LanguageRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger); + var repository = new DocumentRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, Mock.Of()); // Act Stopwatch watch = Stopwatch.StartNew(); @@ -194,11 +196,11 @@ namespace Umbraco.Tests.Services var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var tRepository = new TemplateRepository((IScopeAccessor) provider, DisabledCache, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepo = new TagRepository((IScopeAccessor) provider, DisabledCache, Logger); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, DisabledCache, Logger, tRepository); - var languageRepository = new LanguageRepository((IScopeAccessor) provider, DisabledCache, Logger); - var repository = new DocumentRepository((IScopeAccessor) provider, DisabledCache, Logger, ctRepository, tRepository, tagRepo, languageRepository, Mock.Of()); + var tRepository = new TemplateRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger, Mock.Of(), TestObjects.GetFileSystemsMock()); + var tagRepo = new TagRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger, tRepository); + var languageRepository = new LanguageRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger); + var repository = new DocumentRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, Mock.Of()); // Act Stopwatch watch = Stopwatch.StartNew(); @@ -225,11 +227,11 @@ namespace Umbraco.Tests.Services var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var tRepository = new TemplateRepository((IScopeAccessor) provider, DisabledCache, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepo = new TagRepository((IScopeAccessor) provider, DisabledCache, Logger); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, DisabledCache, Logger, tRepository); - var languageRepository = new LanguageRepository((IScopeAccessor) provider, DisabledCache, Logger); - var repository = new DocumentRepository((IScopeAccessor) provider, DisabledCache, Logger, ctRepository, tRepository, tagRepo, languageRepository, Mock.Of()); + var tRepository = new TemplateRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger, Mock.Of(), TestObjects.GetFileSystemsMock()); + var tagRepo = new TagRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger, tRepository); + var languageRepository = new LanguageRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger); + var repository = new DocumentRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, Mock.Of()); // Act var contents = repository.GetMany(); @@ -259,11 +261,11 @@ namespace Umbraco.Tests.Services var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var tRepository = new TemplateRepository((IScopeAccessor) provider, DisabledCache, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepo = new TagRepository((IScopeAccessor) provider, DisabledCache, Logger); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, DisabledCache, Logger, tRepository); - var languageRepository = new LanguageRepository((IScopeAccessor) provider, DisabledCache, Logger); - var repository = new DocumentRepository((IScopeAccessor) provider, DisabledCache, Logger, ctRepository, tRepository, tagRepo, languageRepository, Mock.Of()); + var tRepository = new TemplateRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger, Mock.Of(), TestObjects.GetFileSystemsMock()); + var tagRepo = new TagRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger, tRepository); + var languageRepository = new LanguageRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger); + var repository = new DocumentRepository((IScopeAccessor) provider, CacheHelper.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, Mock.Of()); // Act var contents = repository.GetMany(); diff --git a/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs b/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs index 8f66e98b76..62a3526af5 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs @@ -1,6 +1,6 @@ using System; using System.Linq; -using LightInject; +using Umbraco.Core.Composing; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; @@ -36,7 +36,7 @@ namespace Umbraco.Tests.Services base.Compose(); // fixme - do it differently - Container.Register(factory => factory.GetInstance().TextService); + Composition.Register(factory => factory.GetInstance().TextService); } [Test] diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index d3ebf703a5..8fba16662c 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -7,10 +7,8 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; -using LightInject; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Core.Events; @@ -22,6 +20,8 @@ using Umbraco.Core.Services.Implement; using Umbraco.Tests.Testing; using System.Reflection; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; namespace Umbraco.Tests.Services { @@ -31,6 +31,7 @@ namespace Umbraco.Tests.Services /// as well as configuration. /// [TestFixture] + [Category("Slow")] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true, WithApplication = true, @@ -56,8 +57,7 @@ namespace Umbraco.Tests.Services { base.Compose(); - // fixme - do it differently - Container.Register(factory => factory.GetInstance().TextService); + Composition.RegisterUnique(factory => Mock.Of()); } /// @@ -222,7 +222,7 @@ namespace Umbraco.Tests.Services c.ContentSchedule.Add(now.AddSeconds(5), null); //release in 5 seconds var r = ServiceContext.ContentService.Save(c); Assert.IsTrue(r.Success, r.Result.ToString()); - } + } else { c.ContentSchedule.Add(null, now.AddSeconds(5)); //expire in 5 seconds @@ -258,7 +258,7 @@ namespace Umbraco.Tests.Services variant.Add(c); } - + var runSched = ServiceContext.ContentService.PerformScheduledPublish( now.AddMinutes(1)).ToList(); //process anything scheduled before a minute from now @@ -742,7 +742,7 @@ namespace Umbraco.Tests.Services public void Can_Unpublish_Content_Variation() { // Arrange - + var langUk = new Language("en-GB") { IsDefault = true }; var langFr = new Language("fr-FR"); @@ -1066,7 +1066,7 @@ namespace Umbraco.Tests.Services foreach (var x in descendants) Console.WriteLine(" ".Substring(0, x.Level) + x.Id); } - + Console.WriteLine(); // publish parent & its branch @@ -1420,7 +1420,7 @@ namespace Umbraco.Tests.Services var descendants = new List(); while(page * pageSize < total) descendants.AddRange(contentService.GetPagedDescendants(content.Id, page++, pageSize, out total)); - + Assert.AreNotEqual(-20, content.ParentId); Assert.IsFalse(content.Trashed); Assert.AreEqual(3, descendants.Count); @@ -2989,11 +2989,11 @@ namespace Umbraco.Tests.Services private DocumentRepository CreateRepository(IScopeProvider provider, out ContentTypeRepository contentTypeRepository) { var accessor = (IScopeAccessor) provider; - var templateRepository = new TemplateRepository(accessor, DisabledCache, Logger, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepository = new TagRepository(accessor, DisabledCache, Logger); - contentTypeRepository = new ContentTypeRepository(accessor, DisabledCache, Logger, templateRepository); - var languageRepository = new LanguageRepository(accessor, DisabledCache, Logger); - var repository = new DocumentRepository(accessor, DisabledCache, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); + var templateRepository = new TemplateRepository(accessor, CacheHelper.Disabled, Logger, Mock.Of(), TestObjects.GetFileSystemsMock()); + var tagRepository = new TagRepository(accessor, CacheHelper.Disabled, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, CacheHelper.Disabled, Logger, templateRepository); + var languageRepository = new LanguageRepository(accessor, CacheHelper.Disabled, Logger); + var repository = new DocumentRepository(accessor, CacheHelper.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, Mock.Of()); return repository; } } diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index 8ea4856861..b1a8fa26a8 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -17,6 +17,7 @@ using Umbraco.Tests.Scoping; namespace Umbraco.Tests.Services { [TestFixture] + [Category("Slow")] [Apartment(ApartmentState.STA)] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)] public class ContentTypeServiceTests : TestWithSomeContentBase diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index c28d4f7955..94d2126bd2 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -35,10 +34,10 @@ namespace Umbraco.Tests.Services base.Compose(); // pfew - see note in ScopedNuCacheTests? - Container.RegisterSingleton(); - Container.RegisterSingleton(f => Mock.Of()); - Container.RegisterCollectionBuilder() - .Add(f => f.TryGetInstance().GetCacheRefreshers()); + Composition.RegisterUnique(); + Composition.RegisterUnique(f => Mock.Of()); + Composition.WithCollectionBuilder() + .Add(() => Composition.TypeLoader.GetCacheRefreshers()); } protected override IPublishedSnapshotService CreatePublishedSnapshotService() @@ -50,7 +49,7 @@ namespace Umbraco.Tests.Services var contentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), Mock.Of()); //var documentRepository = Mock.Of(); - var documentRepository = Container.GetInstance(); + var documentRepository = Factory.GetInstance(); var mediaRepository = Mock.Of(); var memberRepository = Mock.Of(); @@ -68,7 +67,7 @@ namespace Umbraco.Tests.Services documentRepository, mediaRepository, memberRepository, DefaultCultureAccessor, new DatabaseDataSource(), - Container.GetInstance(), new SiteDomainHelper()); + Factory.GetInstance(), new SiteDomainHelper()); } public class LocalServerMessenger : ServerMessengerBase @@ -770,4 +769,4 @@ namespace Umbraco.Tests.Services "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs index b33ff83c4a..8e67aa4e1f 100644 --- a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs +++ b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Xml.Linq; @@ -7,15 +6,16 @@ using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Composing.Composers; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.PropertyEditors; using Umbraco.Tests.Testing; -using LightInject; namespace Umbraco.Tests.Services.Importing { [TestFixture] + [Category("Slow")] [Apartment(ApartmentState.STA)] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class PackageImportTests : TestWithSomeContentBase @@ -49,11 +49,21 @@ namespace Umbraco.Tests.Services.Importing // pollute everything, they are ignored by the type finder and explicitely // added to the editors collection - Container.GetInstance() + Composition.WithCollectionBuilder() .Add() .Add(); } + protected override void ComposeApplication(bool withApplication) + { + base.ComposeApplication(withApplication); + + if (!withApplication) return; + + // re-register with actual media fs + Composition.ComposeFileSystems(); + } + [Test] public void PackagingService_Can_Import_uBlogsy_ContentTypes_And_Verify_Structure() { diff --git a/src/Umbraco.Tests/Services/MacroServiceTests.cs b/src/Umbraco.Tests/Services/MacroServiceTests.cs index 6539a37114..52cc8ab0d1 100644 --- a/src/Umbraco.Tests/Services/MacroServiceTests.cs +++ b/src/Umbraco.Tests/Services/MacroServiceTests.cs @@ -26,7 +26,7 @@ namespace Umbraco.Tests.Services var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { - var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.CreateDisabledCacheHelper(), Mock.Of()); + var repository = new MacroRepository((IScopeAccessor) provider, CacheHelper.Disabled, Mock.Of()); repository.Save(new Macro("test1", "Test1", "~/views/macropartials/test1.cshtml", MacroTypes.PartialView)); repository.Save(new Macro("test2", "Test2", "~/views/macropartials/test2.cshtml", MacroTypes.PartialView)); diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 078336262f..13cde1c659 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -24,6 +24,7 @@ using Umbraco.Web.Security.Providers; namespace Umbraco.Tests.Services { [TestFixture] + [Category("Slow")] [Apartment(ApartmentState.STA)] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true, WithApplication = true)] public class MemberServiceTests : TestWithSomeContentBase diff --git a/src/Umbraco.Tests/Services/PerformanceTests.cs b/src/Umbraco.Tests/Services/PerformanceTests.cs index 900a466a1d..09743b350f 100644 --- a/src/Umbraco.Tests/Services/PerformanceTests.cs +++ b/src/Umbraco.Tests/Services/PerformanceTests.cs @@ -57,7 +57,7 @@ namespace Umbraco.Tests.Services base.TearDown(); } - private static ProfilingLogger GetTestProfilingLogger() + private static IProfilingLogger GetTestProfilingLogger() { var logger = new DebugDiagnosticsLogger(); var profiler = new TestProfiler(); diff --git a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs index 36e5874e14..0ceaffa916 100644 --- a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs +++ b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs @@ -1,15 +1,13 @@ using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -71,7 +69,7 @@ namespace Umbraco.Tests.Strings })); // fixme - move to a "compose" thing? - Container.RegisterSingleton(f => _helper); + Composition.RegisterUnique(f => _helper); } private static readonly Regex FrenchElisionsRegex = new Regex("\\b(c|d|j|l|m|n|qu|s|t)('|\u8217)", RegexOptions.Compiled | RegexOptions.IgnoreCase); diff --git a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs index 81dec809c8..f0db3991b8 100644 --- a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs @@ -1,15 +1,11 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.Linq; -using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Composing; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Strings; -using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; namespace Umbraco.Tests.Strings @@ -17,12 +13,10 @@ namespace Umbraco.Tests.Strings [TestFixture] public class StringExtensionsTests : UmbracoTestBase { - public override void SetUp() + protected override void Compose() { - base.SetUp(); - - // fixme - in "compose"? - Container.RegisterSingleton(_ => new MockShortStringHelper()); + base.Compose(); + Composition.RegisterUnique(_ => new MockShortStringHelper()); } [Test] @@ -75,6 +69,17 @@ namespace Umbraco.Tests.Strings Assert.AreEqual(stripped, result); } + [TestCase("'+alert(1234)+'", "+alert1234+")] + [TestCase("'+alert(56+78)+'", "+alert56+78+")] + [TestCase("{{file}}", "file")] + [TestCase("'+alert('hello')+'", "+alerthello+")] + [TestCase("Test", "Test")] + public void Clean_From_XSS(string input, string result) + { + var cleaned = input.CleanForXss(); + Assert.AreEqual(cleaned, result); + } + [TestCase("This is a string to encrypt")] [TestCase("This is a string to encrypt\nThis is a second line")] [TestCase(" White space is preserved ")] diff --git a/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs b/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs index 340f0ea79f..795d79ced1 100644 --- a/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs +++ b/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs @@ -6,9 +6,6 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; @@ -19,12 +16,11 @@ namespace Umbraco.Tests.Templates public class TemplateRepositoryTests { private readonly Mock _cacheMock = new Mock(); - private readonly Mock _viewFileSystemMock = new Mock(); - private readonly Mock _masterpageFileSystemMock = new Mock(); private readonly Mock _templateConfigMock = new Mock(); + private readonly IFileSystems _fileSystems = Mock.Of(); private TemplateRepository _templateRepository; - private readonly TestObjects TestObjects = new TestObjects(null); + private readonly TestObjects _testObjects = new TestObjects(null); [SetUp] public void Setup() @@ -33,12 +29,16 @@ namespace Umbraco.Tests.Templates var accessorMock = new Mock(); var scopeMock = new Mock(); - var database = TestObjects.GetUmbracoSqlCeDatabase(logger); + var database = _testObjects.GetUmbracoSqlCeDatabase(logger); scopeMock.Setup(x => x.Database).Returns(database); accessorMock.Setup(x => x.AmbientScope).Returns(scopeMock.Object); - _templateRepository = new TemplateRepository(accessorMock.Object, _cacheMock.Object, logger, _templateConfigMock.Object, _masterpageFileSystemMock.Object, _viewFileSystemMock.Object); + var mvcFs = Mock.Of(); + var masterFs = Mock.Of(); + Mock.Get(_fileSystems).Setup(x => x.MvcViewsFileSystem).Returns(mvcFs); + Mock.Get(_fileSystems).Setup(x => x.MasterPagesFileSystem).Returns(masterFs); + _templateRepository = new TemplateRepository(accessorMock.Object, _cacheMock.Object, logger, _templateConfigMock.Object, _fileSystems); } [Test] @@ -53,7 +53,8 @@ namespace Umbraco.Tests.Templates templateMock.Setup(x => x.Content).Returns(""); // but MVC View already exists - _viewFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(true); + Mock.Get(_fileSystems.MvcViewsFileSystem) + .Setup(x => x.FileExists(It.IsAny())).Returns(true); var res = _templateRepository.DetermineTemplateRenderingEngine(templateMock.Object); Assert.AreEqual(RenderingEngine.Mvc, res); @@ -71,7 +72,8 @@ namespace Umbraco.Tests.Templates templateMock.Setup(x => x.Content).Returns(""); // MVC View doesn't exist - _viewFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(false); + Mock.Get(_fileSystems.MvcViewsFileSystem) + .Setup(x => x.FileExists(It.IsAny())).Returns(false); var res = _templateRepository.DetermineTemplateRenderingEngine(templateMock.Object); Assert.AreEqual(RenderingEngine.WebForms, res); @@ -87,8 +89,10 @@ namespace Umbraco.Tests.Templates templateMock.Setup(x => x.Alias).Returns("Something"); // but masterpage already exists - _viewFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(false); - _masterpageFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(true); + Mock.Get(_fileSystems.MvcViewsFileSystem) + .Setup(x => x.FileExists(It.IsAny())).Returns(false); + Mock.Get(_fileSystems.MasterPagesFileSystem) + .Setup(x => x.FileExists(It.IsAny())).Returns(true); var res = _templateRepository.DetermineTemplateRenderingEngine(templateMock.Object); Assert.AreEqual(RenderingEngine.WebForms, res); @@ -104,8 +108,10 @@ namespace Umbraco.Tests.Templates templateMock.Setup(x => x.Alias).Returns("Something"); // but MVC View already exists - _viewFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(true); - _masterpageFileSystemMock.Setup(x => x.FileExists(It.IsAny())).Returns(false); + Mock.Get(_fileSystems.MvcViewsFileSystem) + .Setup(x => x.FileExists(It.IsAny())).Returns(true); + Mock.Get(_fileSystems.MasterPagesFileSystem) + .Setup(x => x.FileExists(It.IsAny())).Returns(false); var res = _templateRepository.DetermineTemplateRenderingEngine(templateMock.Object); Assert.AreEqual(RenderingEngine.Mvc, res); diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index bc29139918..7fd2f0f18b 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -1,15 +1,16 @@ -using LightInject; -using Moq; +using Moq; using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Components; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Profiling; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Persistence; +using Umbraco.Tests.Components; namespace Umbraco.Tests.TestHelpers { @@ -34,22 +35,27 @@ namespace Umbraco.Tests.TestHelpers var sqlSyntax = new SqlCeSyntaxProvider(); - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); - - container.RegisterSingleton(factory => Mock.Of()); - container.RegisterSingleton(factory => Mock.Of()); + var container = RegisterFactory.Create(); var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var pluginManager = new TypeLoader(NullCacheProvider.Instance, - SettingsForTests.GenerateMockGlobalSettings(), + var typeLoader = new TypeLoader(NullCacheProvider.Instance, + LocalTempStorage.Default, logger, false); - container.RegisterInstance(pluginManager); - container.RegisterCollectionBuilder() - .Add(() => Current.TypeLoader.GetAssignedMapperTypes()); - Mappers = container.GetInstance(); + var composition = new Composition(container, typeLoader, Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + + composition.RegisterUnique(_ => Mock.Of()); + composition.RegisterUnique(_ => Mock.Of()); + + composition.RegisterUnique(typeLoader); + + composition.WithCollectionBuilder() + .Add(() => composition.TypeLoader.GetAssignedMapperTypes()); + + var factory = Current.Factory = composition.CreateFactory(); + + Mappers = factory.GetInstance(); var pocoMappers = new NPoco.MapperCollection { new PocoMapper() }; var pocoDataFactory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, pocoMappers).Init()); diff --git a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs index 5eea6bcf72..f7e3744600 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs @@ -1,10 +1,9 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Threading; -using LightInject; using Moq; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; @@ -28,8 +27,8 @@ namespace Umbraco.Tests.TestHelpers { base.Compose(); - Container.RegisterSingleton(); - Container.RegisterSingleton(); + Composition.RegisterUnique(); + Composition.RegisterUnique(); } protected override void Initialize() @@ -86,19 +85,19 @@ namespace Umbraco.Tests.TestHelpers "; } - internal PublishedRouter CreatePublishedRouter(IServiceContainer container = null, ContentFinderCollection contentFinders = null) + internal PublishedRouter CreatePublishedRouter(IFactory container = null, ContentFinderCollection contentFinders = null) { return CreatePublishedRouter(TestObjects.GetUmbracoSettings().WebRouting, container, contentFinders); } - internal static PublishedRouter CreatePublishedRouter(IWebRoutingSection webRoutingSection, IServiceContainer container = null, ContentFinderCollection contentFinders = null) + internal static PublishedRouter CreatePublishedRouter(IWebRoutingSection webRoutingSection, IFactory container = null, ContentFinderCollection contentFinders = null) { return new PublishedRouter( webRoutingSection, contentFinders ?? new ContentFinderCollection(Enumerable.Empty()), new TestLastChanceFinder(), new TestVariationContextAccessor(), - container?.TryGetInstance() ?? new ServiceContext(), + container?.TryGetInstance() ?? ServiceContext.CreatePartial(), new ProfilingLogger(Mock.Of(), Mock.Of())); } } diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index 7152622b07..9e2a2156ee 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -9,26 +8,20 @@ using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using System.Web.Security; using Moq; -using Semver; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Dictionary; -using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Security; using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; using Umbraco.Web.WebApi; -using LightInject; -using System.Globalization; +using Umbraco.Core.Logging; using Umbraco.Tests.Testing.Objects.Accessors; namespace Umbraco.Tests.TestHelpers.ControllerTesting @@ -58,7 +51,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting var mockedDataTypeService = Mock.Of(); var mockedContentTypeService = Mock.Of(); - var serviceContext = new ServiceContext( + var serviceContext = ServiceContext.CreatePartial( userService: mockedUserService, contentService: mockedContentService, mediaService: mockedMediaService, @@ -141,10 +134,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting var publishedSnapshotService = new Mock(); publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(publishedSnapshot.Object); - //var umbracoContextAccessor = new TestUmbracoContextAccessor(); - //Umbraco.Web.Composing.Current.UmbracoContextAccessor = umbracoContextAccessor; var umbracoContextAccessor = Umbraco.Web.Composing.Current.UmbracoContextAccessor; - Current.Container.Register(factory => umbracoContextAccessor.UmbracoContext); // but really, should we inject this?! var umbCtx = UmbracoContext.EnsureContext( umbracoContextAccessor, @@ -161,7 +151,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting urlHelper.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(UrlInfo.Url("/hello/world/1234")); - var membershipHelper = new MembershipHelper(umbCtx, Mock.Of(), Mock.Of()); + var membershipHelper = new MembershipHelper(new TestUmbracoContextAccessor(umbCtx), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), null, Mock.Of(), Mock.Of()); var umbHelper = new UmbracoHelper(umbCtx, Mock.Of(), diff --git a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs index d38cd8af6f..5f08478658 100644 --- a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs +++ b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs @@ -3,6 +3,7 @@ using System.IO; using System.Configuration; using Moq; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; @@ -13,7 +14,7 @@ namespace Umbraco.Tests.TestHelpers { public static void ConfigureSettings(IGlobalSettings settings) { - UmbracoConfig.For.SetGlobalConfig(settings); + Current.Config.SetGlobalConfig(settings); } // umbracoSettings @@ -24,7 +25,7 @@ namespace Umbraco.Tests.TestHelpers /// public static void ConfigureSettings(IUmbracoSettingsSection settings) { - UmbracoConfig.For.SetUmbracoSettings(settings); + Current.Config.SetUmbracoConfig(settings); } public static IGlobalSettings GenerateMockGlobalSettings() @@ -103,7 +104,7 @@ namespace Umbraco.Tests.TestHelpers // SaveSetting("umbracoConfigurationStatus"); //} - + // reset & defaults diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs index 00e1a363b8..36c5961b9f 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs @@ -47,7 +47,8 @@ namespace Umbraco.Tests.TestHelpers.Stubs var allParams = ctor.GetParameters().ToArray(); foreach (var parameter in allParams) { - var found = possibleParams.SingleOrDefault(x => x.GetType() == parameter.ParameterType); + var found = possibleParams.SingleOrDefault(x => x.GetType() == parameter.ParameterType) + ?? Current.Factory.GetInstance(parameter.ParameterType); if (found != null) args.Add(found); } if (args.Count == allParams.Length) diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs index 167d6730ed..8c230f98d0 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs @@ -3,18 +3,18 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Linq; +using System.Linq.Expressions; using System.Web; -using LightInject; using Moq; using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Events; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; using Umbraco.Web.PublishedCache; @@ -53,34 +53,37 @@ namespace Umbraco.Tests.TestHelpers /// Gets a mocked service context built with mocked services. /// /// A ServiceContext. - public ServiceContext GetServiceContextMock(IServiceFactory container = null) + public ServiceContext GetServiceContextMock(IFactory container = null) { - return new ServiceContext( - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService(), - MockService()); + // fixme - else some tests break - figure it out + container = null; + + return ServiceContext.CreatePartial( + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container), + MockService(container)); } - private T MockService(IServiceFactory container = null) + private T MockService(IFactory container = null) where T : class { return container?.TryGetInstance() ?? new Mock().Object; @@ -139,6 +142,26 @@ namespace Umbraco.Tests.TestHelpers return SettingsForTests.GetDefaultGlobalSettings(); } + public IFileSystems GetFileSystemsMock() + { + var fileSystems = Mock.Of(); + + MockFs(fileSystems, x => x.MasterPagesFileSystem); + MockFs(fileSystems, x => x.MacroPartialsFileSystem); + MockFs(fileSystems, x => x.MvcViewsFileSystem); + MockFs(fileSystems, x => x.PartialViewsFileSystem); + MockFs(fileSystems, x => x.ScriptsFileSystem); + MockFs(fileSystems, x => x.StylesheetsFileSystem); + + return fileSystems; + } + + private void MockFs(IFileSystems fileSystems, Expression> fileSystem) + { + var fs = Mock.Of(); + Mock.Get(fileSystems).Setup(fileSystem).Returns(fs); + } + #region Inner classes private class MockDbConnection : DbConnection diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 4529c4f1ef..cb36e6ca5f 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -1,9 +1,7 @@ using System; -using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; -using LightInject; using Moq; using NPoco; using Umbraco.Core; @@ -32,27 +30,11 @@ namespace Umbraco.Tests.TestHelpers /// internal partial class TestObjects { - private readonly IServiceContainer _container; + private readonly IRegister _register; - public TestObjects(IServiceContainer container) + public TestObjects(IRegister register) { - _container = container; - } - - /// - /// Gets the default ISqlSyntaxProvider objects. - /// - /// A logger. - /// A (lazy) scope provider. - /// The default ISqlSyntaxProvider objects. - public IEnumerable GetDefaultSqlSyntaxProviders(ILogger logger, Lazy lazyScopeProvider = null) - { - return new ISqlSyntaxProvider[] - { - new MySqlSyntaxProvider(logger), - new SqlCeSyntaxProvider(), - new SqlServerSyntaxProvider(lazyScopeProvider ?? new Lazy(() => null)) - }; + _register = register; } /// @@ -79,13 +61,13 @@ namespace Umbraco.Tests.TestHelpers /// that can begin a transaction. public UmbracoDatabase GetUmbracoSqlServerDatabase(ILogger logger) { - var syntax = new SqlServerSyntaxProvider(new Lazy(() => null)); // do NOT try to get the server's version! + var syntax = new SqlServerSyntaxProvider(); // do NOT try to get the server's version! var connection = GetDbConnection(); var sqlContext = new SqlContext(syntax, DatabaseType.SqlServer2008, Mock.Of()); return new UmbracoDatabase(connection, sqlContext, logger); } - public void RegisterServices(IServiceContainer container) + public void RegisterServices(IRegister register) { } /// @@ -109,9 +91,9 @@ namespace Umbraco.Tests.TestHelpers IGlobalSettings globalSettings, IUmbracoSettingsSection umbracoSettings, IEventMessagesFactory eventMessagesFactory, - IEnumerable urlSegmentProviders, + UrlSegmentProviderCollection urlSegmentProviders, TypeLoader typeLoader, - IServiceFactory container = null) + IFactory factory = null) { if (scopeProvider == null) throw new ArgumentNullException(nameof(scopeProvider)); if (scopeAccessor == null) throw new ArgumentNullException(nameof(scopeAccessor)); @@ -119,14 +101,17 @@ namespace Umbraco.Tests.TestHelpers if (logger == null) throw new ArgumentNullException(nameof(logger)); if (eventMessagesFactory == null) throw new ArgumentNullException(nameof(eventMessagesFactory)); - var mediaFileSystem = new MediaFileSystem(Mock.Of()); + var scheme = Mock.Of(); + var config = Mock.Of(); - var externalLoginService = GetLazyService(container, c => new ExternalLoginService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var publicAccessService = GetLazyService(container, c => new PublicAccessService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var domainService = GetLazyService(container, c => new DomainService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var auditService = GetLazyService(container, c => new AuditService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); + var mediaFileSystem = new MediaFileSystem(Mock.Of(), config, scheme, logger); - var localizedTextService = GetLazyService(container, c => new LocalizedTextService( + var externalLoginService = GetLazyService(factory, c => new ExternalLoginService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var publicAccessService = GetLazyService(factory, c => new PublicAccessService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var domainService = GetLazyService(factory, c => new DomainService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var auditService = GetLazyService(factory, c => new AuditService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); + + var localizedTextService = GetLazyService(factory, c => new LocalizedTextService( new Lazy(() => { var mainLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Umbraco + "/config/lang/")); @@ -161,34 +146,34 @@ namespace Umbraco.Tests.TestHelpers var runtimeState = Mock.Of(); var idkMap = new IdkMap(scopeProvider); - var localizationService = GetLazyService(container, c => new LocalizationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c))); - var userService = GetLazyService(container, c => new UserService(scopeProvider, logger, eventMessagesFactory, runtimeState, GetRepo(c), GetRepo(c),globalSettings)); - var dataTypeService = GetLazyService(container, c => new DataTypeService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var contentService = GetLazyService(container, c => new ContentService(scopeProvider, logger, eventMessagesFactory, mediaFileSystem, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var notificationService = GetLazyService(container, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, localizationService.Value, logger, GetRepo(c), globalSettings, umbracoSettings.Content)); - var serverRegistrationService = GetLazyService(container, c => new ServerRegistrationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var memberGroupService = GetLazyService(container, c => new MemberGroupService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var memberService = GetLazyService(container, c => new MemberService(scopeProvider, logger, eventMessagesFactory, memberGroupService.Value, mediaFileSystem, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var mediaService = GetLazyService(container, c => new MediaService(scopeProvider, mediaFileSystem, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var contentTypeService = GetLazyService(container, c => new ContentTypeService(scopeProvider, logger, eventMessagesFactory, contentService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var mediaTypeService = GetLazyService(container, c => new MediaTypeService(scopeProvider, logger, eventMessagesFactory, mediaService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var fileService = GetLazyService(container, c => new FileService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var localizationService = GetLazyService(factory, c => new LocalizationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c))); + var userService = GetLazyService(factory, c => new UserService(scopeProvider, logger, eventMessagesFactory, runtimeState, GetRepo(c), GetRepo(c),globalSettings)); + var dataTypeService = GetLazyService(factory, c => new DataTypeService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var contentService = GetLazyService(factory, c => new ContentService(scopeProvider, logger, eventMessagesFactory, mediaFileSystem, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var notificationService = GetLazyService(factory, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, localizationService.Value, logger, GetRepo(c), globalSettings, umbracoSettings.Content)); + var serverRegistrationService = GetLazyService(factory, c => new ServerRegistrationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var memberGroupService = GetLazyService(factory, c => new MemberGroupService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var memberService = GetLazyService(factory, c => new MemberService(scopeProvider, logger, eventMessagesFactory, memberGroupService.Value, mediaFileSystem, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var mediaService = GetLazyService(factory, c => new MediaService(scopeProvider, mediaFileSystem, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var contentTypeService = GetLazyService(factory, c => new ContentTypeService(scopeProvider, logger, eventMessagesFactory, contentService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var mediaTypeService = GetLazyService(factory, c => new MediaTypeService(scopeProvider, logger, eventMessagesFactory, mediaService.Value, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var fileService = GetLazyService(factory, c => new FileService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); - var memberTypeService = GetLazyService(container, c => new MemberTypeService(scopeProvider, logger, eventMessagesFactory, memberService.Value, GetRepo(c), GetRepo(c), GetRepo(c))); - var entityService = GetLazyService(container, c => new EntityService( + var memberTypeService = GetLazyService(factory, c => new MemberTypeService(scopeProvider, logger, eventMessagesFactory, memberService.Value, GetRepo(c), GetRepo(c), GetRepo(c))); + var entityService = GetLazyService(factory, c => new EntityService( scopeProvider, logger, eventMessagesFactory, contentService.Value, contentTypeService.Value, mediaService.Value, mediaTypeService.Value, dataTypeService.Value, memberService.Value, memberTypeService.Value, idkMap, GetRepo(c))); - var macroService = GetLazyService(container, c => new MacroService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); - var packagingService = GetLazyService(container, c => new PackagingService(logger, contentService.Value, contentTypeService.Value, mediaService.Value, macroService.Value, dataTypeService.Value, fileService.Value, localizationService.Value, entityService.Value, userService.Value, scopeProvider, urlSegmentProviders, GetRepo(c), GetRepo(c), new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())))); - var relationService = GetLazyService(container, c => new RelationService(scopeProvider, logger, eventMessagesFactory, entityService.Value, GetRepo(c), GetRepo(c))); - var treeService = GetLazyService(container, c => new ApplicationTreeService(logger, cache, typeLoader)); - var tagService = GetLazyService(container, c => new TagService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var sectionService = GetLazyService(container, c => new SectionService(userService.Value, treeService.Value, scopeProvider, cache)); - var redirectUrlService = GetLazyService(container, c => new RedirectUrlService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); - var consentService = GetLazyService(container, c => new ConsentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var macroService = GetLazyService(factory, c => new MacroService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); + var packagingService = GetLazyService(factory, c => new PackagingService(logger, contentService.Value, contentTypeService.Value, mediaService.Value, macroService.Value, dataTypeService.Value, fileService.Value, localizationService.Value, entityService.Value, userService.Value, scopeProvider, urlSegmentProviders, GetRepo(c), GetRepo(c), new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())))); + var relationService = GetLazyService(factory, c => new RelationService(scopeProvider, logger, eventMessagesFactory, entityService.Value, GetRepo(c), GetRepo(c))); + var treeService = GetLazyService(factory, c => new ApplicationTreeService(logger, cache, typeLoader)); + var tagService = GetLazyService(factory, c => new TagService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var sectionService = GetLazyService(factory, c => new SectionService(userService.Value, treeService.Value, scopeProvider, cache)); + var redirectUrlService = GetLazyService(factory, c => new RedirectUrlService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var consentService = GetLazyService(factory, c => new ConsentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); return new ServiceContext( publicAccessService, @@ -220,13 +205,13 @@ namespace Umbraco.Tests.TestHelpers consentService); } - private Lazy GetLazyService(IServiceFactory container, Func ctor) + private Lazy GetLazyService(IFactory container, Func ctor) where T : class { return new Lazy(() => container?.TryGetInstance() ?? ctor(container)); } - private T GetRepo(IServiceFactory container) + private T GetRepo(IFactory container) where T : class, IRepository { return container?.TryGetInstance() ?? Mock.Of(); @@ -239,11 +224,11 @@ namespace Umbraco.Tests.TestHelpers //var mappersBuilder = new MapperCollectionBuilder(Current.Container); // fixme //mappersBuilder.AddCore(); //var mappers = mappersBuilder.CreateCollection(); - var mappers = Current.Container.GetInstance(); - databaseFactory = new UmbracoDatabaseFactory(Constants.System.UmbracoConnectionName, GetDefaultSqlSyntaxProviders(logger), logger, mappers); + var mappers = Current.Factory.GetInstance(); + databaseFactory = new UmbracoDatabaseFactory(Constants.System.UmbracoConnectionName, logger, new Lazy(() => mappers)); } - fileSystems = fileSystems ?? new FileSystems(logger); + fileSystems = fileSystems ?? new FileSystems(Current.Factory, logger); var scopeProvider = new ScopeProvider(databaseFactory, fileSystems, logger); return scopeProvider; } diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index 6b52137542..2daf86796b 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -26,9 +26,7 @@ using File = System.IO.File; using Umbraco.Core.Composing; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Scoping; -using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing; -using LightInject; using Umbraco.Core.Migrations.Install; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence.Repositories; @@ -49,13 +47,9 @@ namespace Umbraco.Tests.TestHelpers [UmbracoTest(WithApplication = true)] public abstract class TestWithDatabaseBase : UmbracoTestBase { - private CacheHelper _disabledCacheHelper; - private string _databasePath; private static byte[] _databaseBytes; - protected CacheHelper DisabledCache => _disabledCacheHelper ?? (_disabledCacheHelper = CacheHelper.CreateDisabledCacheHelper()); - protected PublishedContentTypeCache ContentTypesCache { get; private set; } protected override ISqlSyntaxProvider SqlSyntax => GetSyntaxProvider(); @@ -64,7 +58,7 @@ namespace Umbraco.Tests.TestHelpers internal ScopeProvider ScopeProvider => Current.ScopeProvider as ScopeProvider; - protected ISqlContext SqlContext => Container.GetInstance(); + protected ISqlContext SqlContext => Factory.GetInstance(); public override void SetUp() { @@ -78,23 +72,22 @@ namespace Umbraco.Tests.TestHelpers { base.Compose(); - Container.Register(); - Container.Register(factory => PublishedSnapshotService); - Container.Register(factory => DefaultCultureAccessor); + Composition.Register(); + Composition.Register(factory => PublishedSnapshotService); + Composition.Register(factory => DefaultCultureAccessor); - Container.GetInstance() + Composition.WithCollectionBuilder() .Clear() - .Add(f => f.GetInstance().GetDataEditors()); + .Add(() => Composition.TypeLoader.GetDataEditors()); - Container.RegisterSingleton(f => + Composition.RegisterUnique(f => { if (Options.Database == UmbracoTestOptions.Database.None) return TestObjects.GetDatabaseFactoryMock(); - var sqlSyntaxProviders = new[] { new SqlCeSyntaxProvider() }; var logger = f.GetInstance(); var mappers = f.GetInstance(); - var factory = new UmbracoDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), sqlSyntaxProviders, logger, mappers); + var factory = new UmbracoDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), logger, new Lazy(() => mappers)); factory.ResetForTests(); return factory; }); @@ -108,7 +101,7 @@ namespace Umbraco.Tests.TestHelpers public override void TearDown() { - var profilingLogger = Container.TryGetInstance(); + var profilingLogger = Factory.TryGetInstance(); var timer = profilingLogger?.TraceDuration("teardown"); // fixme move that one up try { @@ -257,10 +250,10 @@ namespace Umbraco.Tests.TestHelpers var cache = NullCacheProvider.Instance; ContentTypesCache = new PublishedContentTypeCache( - Container.GetInstance(), - Container.GetInstance(), - Container.GetInstance(), - Container.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), Logger); // testing=true so XmlStore will not use the file nor the database @@ -269,13 +262,13 @@ namespace Umbraco.Tests.TestHelpers var variationContextAccessor = new TestVariationContextAccessor(); var service = new PublishedSnapshotService( ServiceContext, - Container.GetInstance(), + Factory.GetInstance(), ScopeProvider, cache, publishedSnapshotAccessor, variationContextAccessor, - Container.GetInstance(), Container.GetInstance(), Container.GetInstance(), + Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), DefaultCultureAccessor, Logger, - Container.GetInstance(), new SiteDomainHelper(), + Factory.GetInstance(), new SiteDomainHelper(), ContentTypesCache, null, true, Options.PublishedRepositoryEvents); @@ -376,11 +369,11 @@ namespace Umbraco.Tests.TestHelpers var umbracoContext = new UmbracoContext( httpContext, service, - new WebSecurity(httpContext, Container.GetInstance(), - Container.GetInstance()), - umbracoSettings ?? Container.GetInstance(), + new WebSecurity(httpContext, Factory.GetInstance(), + Factory.GetInstance()), + umbracoSettings ?? Factory.GetInstance(), urlProviders ?? Enumerable.Empty(), - globalSettings ?? Container.GetInstance(), + globalSettings ?? Factory.GetInstance(), new TestVariationContextAccessor()); if (setSingleton) diff --git a/src/Umbraco.Tests/Testing/Objects/Accessors/TestUmbracoContextAccessor.cs b/src/Umbraco.Tests/Testing/Objects/Accessors/TestUmbracoContextAccessor.cs index da93218907..4f3b801af9 100644 --- a/src/Umbraco.Tests/Testing/Objects/Accessors/TestUmbracoContextAccessor.cs +++ b/src/Umbraco.Tests/Testing/Objects/Accessors/TestUmbracoContextAccessor.cs @@ -5,5 +5,14 @@ namespace Umbraco.Tests.Testing.Objects.Accessors public class TestUmbracoContextAccessor : IUmbracoContextAccessor { public UmbracoContext UmbracoContext { get; set; } + + public TestUmbracoContextAccessor() + { + } + + public TestUmbracoContextAccessor(UmbracoContext umbracoContext) + { + UmbracoContext = umbracoContext; + } } } diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index 5b93de0c09..c413cb0e94 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -6,7 +6,9 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Dictionary; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; @@ -51,11 +53,11 @@ namespace Umbraco.Tests.Testing.TestingTests var umbracoContext = TestObjects.GetUmbracoContextMock(); // unless we can inject them in MembershipHelper, we need need this - Container.Register(_ => Mock.Of()); - Container.Register(_ => Mock.Of()); - Container.Register(_ => Mock.Of()); - Container.Register(_ => CacheHelper.CreateDisabledCacheHelper()); - Container.Register(); + Composition.Register(_ => Mock.Of()); + Composition.Register(_ => Mock.Of()); + Composition.Register(_ => Mock.Of()); + Composition.Register(_ => CacheHelper.Disabled); + Composition.Register(); // ReSharper disable once UnusedVariable var helper = new UmbracoHelper(umbracoContext, @@ -63,8 +65,8 @@ namespace Umbraco.Tests.Testing.TestingTests Mock.Of(), Mock.Of(), Mock.Of(), - new MembershipHelper(umbracoContext, Mock.Of(), Mock.Of()), - new ServiceContext()); + new MembershipHelper(new TestUmbracoContextAccessor(umbracoContext), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), null, Mock.Of(), Mock.Of()), + ServiceContext.CreatePartial()); Assert.Pass(); } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index c7c493c64d..3f5240d04c 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -4,15 +4,15 @@ using System.Linq; using System.Reflection; using AutoMapper; using Examine; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Components; using Umbraco.Core.Composing; -using Umbraco.Core.Composing.CompositionRoots; +using Umbraco.Core.Composing.Composers; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.IO.MediaPathSchemes; @@ -22,7 +22,6 @@ using Umbraco.Core.Manifest; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.PropertyEditors; @@ -30,16 +29,15 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; +using Umbraco.Tests.Components; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; using Umbraco.Web.Services; -using Umbraco.Examine; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web.Actions; -using Umbraco.Web.Composing.CompositionRoots; +using Umbraco.Web.Composing.Composers; using Umbraco.Web.ContentApps; - using Current = Umbraco.Core.Composing.Current; using Umbraco.Web.Routing; @@ -79,7 +77,9 @@ namespace Umbraco.Tests.Testing // test feature, and no test "base" class should be. only actual test feature classes // should be marked with that attribute. - protected ServiceContainer Container { get; private set; } + protected Composition Composition { get; private set; } + + protected IFactory Factory { get; private set; } protected UmbracoTestAttribute Options { get; private set; } @@ -95,17 +95,17 @@ namespace Umbraco.Tests.Testing #region Accessors - protected ILogger Logger => Container.GetInstance(); + protected ILogger Logger => Factory.GetInstance(); - protected IProfiler Profiler => Container.GetInstance(); + protected IProfiler Profiler => Factory.GetInstance(); - protected virtual ProfilingLogger ProfilingLogger => Container.GetInstance(); + protected virtual IProfilingLogger ProfilingLogger => Factory.GetInstance(); - protected CacheHelper CacheHelper => Container.GetInstance(); + protected CacheHelper CacheHelper => Factory.GetInstance(); - protected virtual ISqlSyntaxProvider SqlSyntax => Container.GetInstance(); + protected virtual ISqlSyntaxProvider SqlSyntax => Factory.GetInstance(); - protected IMapperCollection Mappers => Container.GetInstance(); + protected IMapperCollection Mappers => Factory.GetInstance(); #endregion @@ -118,24 +118,37 @@ namespace Umbraco.Tests.Testing // but hey, never know, better avoid garbage-in Reset(); - Container = new ServiceContainer(); - Container.ConfigureUmbracoCore(); - - TestObjects = new TestObjects(Container); - // get/merge the attributes marking the method and/or the classes Options = TestOptionAttributeBase.GetTestOptions(); + // fixme - align to runtimes & components - don't redo everything here + + var (logger, profiler) = GetLoggers(Options.Logger); + var proflogger = new ProfilingLogger(logger, profiler); + var cacheHelper = GetCacheHelper(); + var globalSettings = SettingsForTests.GetDefaultGlobalSettings(); + var typeLoader = GetTypeLoader(cacheHelper.RuntimeCache, globalSettings, proflogger, Options.TypeLoader); + + var register = RegisterFactory.Create(); + + Composition = new Composition(register, typeLoader, proflogger, ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + + Composition.RegisterUnique(typeLoader); + Composition.RegisterUnique(logger); + Composition.RegisterUnique(profiler); + Composition.RegisterUnique(proflogger); + Composition.RegisterUnique(cacheHelper); + Composition.RegisterUnique(cacheHelper.RuntimeCache); + + TestObjects = new TestObjects(register); Compose(); + Current.Factory = Factory = Composition.CreateFactory(); Initialize(); } protected virtual void Compose() { - ComposeLogging(Options.Logger); - ComposeCacheHelper(); ComposeAutoMapper(Options.AutoMapper); - ComposeTypeLoader(Options.TypeLoader); ComposeDatabase(Options.Database); ComposeApplication(Options.WithApplication); @@ -144,8 +157,7 @@ namespace Umbraco.Tests.Testing ComposeWtf(); // not sure really - var composition = new Composition(Container, RuntimeLevel.Run); - Compose(composition); + Compose(Composition); } protected virtual void Compose(Composition composition) @@ -161,41 +173,49 @@ namespace Umbraco.Tests.Testing #region Compose - protected virtual void ComposeLogging(UmbracoTestOptions.Logger option) + protected virtual (ILogger, IProfiler) GetLoggers(UmbracoTestOptions.Logger option) { - if (option == UmbracoTestOptions.Logger.Mock) + ILogger logger; + IProfiler profiler; + + switch (option) { - Container.RegisterSingleton(f => Mock.Of()); - Container.RegisterSingleton(f => Mock.Of()); - } - else if (option == UmbracoTestOptions.Logger.Serilog) - { - Container.RegisterSingleton(f => new SerilogLogger(new FileInfo(TestHelper.MapPathForTest("~/unit-test.config")))); - Container.RegisterSingleton(f => new LogProfiler(f.GetInstance())); - } - else if (option == UmbracoTestOptions.Logger.Console) - { - Container.RegisterSingleton(f => new ConsoleLogger()); - Container.RegisterSingleton(f => new LogProfiler(f.GetInstance())); + case UmbracoTestOptions.Logger.Mock: + logger = Mock.Of(); + profiler = Mock.Of(); + break; + case UmbracoTestOptions.Logger.Serilog: + logger = new SerilogLogger(new FileInfo(TestHelper.MapPathForTest("~/unit-test.config"))); + profiler = new LogProfiler(logger); + break; + case UmbracoTestOptions.Logger.Console: + logger = new ConsoleLogger(); + profiler = new LogProfiler(logger); + break; + default: + throw new NotSupportedException($"Logger option {option} is not supported."); } - Container.RegisterSingleton(f => new ProfilingLogger(f.GetInstance(), f.GetInstance())); + return (logger, profiler); + } + + protected virtual CacheHelper GetCacheHelper() + { + return CacheHelper.Disabled; } protected virtual void ComposeWeb() { - //TODO: Should we 'just' register the WebRuntimeComponent? - // imported from TestWithSettingsBase // which was inherited by TestWithApplicationBase so pretty much used everywhere Umbraco.Web.Composing.Current.UmbracoContextAccessor = new TestUmbracoContextAccessor(); // web - Container.Register(_ => Umbraco.Web.Composing.Current.UmbracoContextAccessor); - Container.RegisterSingleton(); - Container.RegisterCollectionBuilder(); - Container.Register(); - Container.Register(); + Composition.RegisterUnique(_ => Umbraco.Web.Composing.Current.UmbracoContextAccessor); + Composition.RegisterUnique(); + Composition.WithCollectionBuilder(); + Composition.RegisterUnique(); + Composition.RegisterUnique(); } protected virtual void ComposeWtf() @@ -203,59 +223,52 @@ namespace Umbraco.Tests.Testing // what else? var runtimeStateMock = new Mock(); runtimeStateMock.Setup(x => x.Level).Returns(RuntimeLevel.Run); - Container.RegisterSingleton(f => runtimeStateMock.Object); + Composition.RegisterUnique(f => runtimeStateMock.Object); // ah... - Container.RegisterCollectionBuilder(); - Container.RegisterCollectionBuilder(); - Container.RegisterSingleton(); + Composition.WithCollectionBuilder(); + Composition.WithCollectionBuilder(); + Composition.RegisterUnique(); - Container.RegisterSingleton(); + Composition.RegisterUnique(); // register empty content apps collection - Container.RegisterCollectionBuilder(); - } - - protected virtual void ComposeCacheHelper() - { - Container.RegisterSingleton(f => CacheHelper.CreateDisabledCacheHelper()); - Container.RegisterSingleton(f => f.GetInstance().RuntimeCache); + Composition.WithCollectionBuilder(); } protected virtual void ComposeAutoMapper(bool configure) { if (configure == false) return; - Container.RegisterFrom(); - Container.RegisterFrom(); + Composition + .ComposeCoreMappingProfiles() + .ComposeWebMappingProfiles(); } - protected virtual void ComposeTypeLoader(UmbracoTestOptions.TypeLoader typeLoader) + protected virtual TypeLoader GetTypeLoader(IRuntimeCacheProvider runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger, UmbracoTestOptions.TypeLoader option) { - Container.RegisterSingleton(f => + switch (option) { - switch (typeLoader) - { - case UmbracoTestOptions.TypeLoader.Default: - return _commonTypeLoader ?? (_commonTypeLoader = CreateCommonTypeLoader(f)); - case UmbracoTestOptions.TypeLoader.PerFixture: - return _featureTypeLoader ?? (_featureTypeLoader = CreateTypeLoader(f)); - case UmbracoTestOptions.TypeLoader.PerTest: - return CreateTypeLoader(f); - default: - throw new ArgumentOutOfRangeException(nameof(typeLoader)); - } - }); + case UmbracoTestOptions.TypeLoader.Default: + return _commonTypeLoader ?? (_commonTypeLoader = CreateCommonTypeLoader(runtimeCache, globalSettings, logger)); + case UmbracoTestOptions.TypeLoader.PerFixture: + return _featureTypeLoader ?? (_featureTypeLoader = CreateTypeLoader(runtimeCache, globalSettings, logger)); + case UmbracoTestOptions.TypeLoader.PerTest: + return CreateTypeLoader(runtimeCache, globalSettings, logger); + default: + throw new ArgumentOutOfRangeException(nameof(option)); + } } - protected virtual TypeLoader CreateTypeLoader(IServiceFactory f) + protected virtual TypeLoader CreateTypeLoader(IRuntimeCacheProvider runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - return CreateCommonTypeLoader(f); + return CreateCommonTypeLoader(runtimeCache, globalSettings, logger); } - private static TypeLoader CreateCommonTypeLoader(IServiceFactory f) + // common to all tests = cannot be overriden + private static TypeLoader CreateCommonTypeLoader(IRuntimeCacheProvider runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - return new TypeLoader(f.GetInstance().RuntimeCache, f.GetInstance(), f.GetInstance(), false) + return new TypeLoader(runtimeCache, globalSettings.LocalTempStorageLocation, logger, false) { AssembliesToScan = new[] { @@ -272,7 +285,6 @@ namespace Umbraco.Tests.Testing // create the file // create the schema - } protected virtual void ComposeApplication(bool withApplication) @@ -286,66 +298,61 @@ namespace Umbraco.Tests.Testing SettingsForTests.ConfigureSettings(globalSettings); // default Datalayer/Repositories/SQL/Database/etc... - Container.RegisterFrom(); + Composition.ComposeRepositories(); // register basic stuff that might need to be there for some container resolvers to work - Container.RegisterSingleton(factory => umbracoSettings); - Container.RegisterSingleton(factory => globalSettings); - Container.RegisterSingleton(factory => umbracoSettings.Content); - Container.RegisterSingleton(factory => umbracoSettings.Templates); - Container.RegisterSingleton(factory => umbracoSettings.WebRouting); - Container.Register(factory => new MediaFileSystem(Mock.Of())); - Container.RegisterSingleton(factory => ExamineManager.Instance); + Composition.RegisterUnique(factory => umbracoSettings); + Composition.RegisterUnique(factory => globalSettings); + Composition.RegisterUnique(factory => umbracoSettings.Content); + Composition.RegisterUnique(factory => umbracoSettings.Templates); + Composition.RegisterUnique(factory => umbracoSettings.WebRouting); - // replace some stuff - Container.RegisterSingleton(factory => Mock.Of(), "ScriptFileSystem"); - Container.RegisterSingleton(factory => Mock.Of(), "PartialViewFileSystem"); - Container.RegisterSingleton(factory => Mock.Of(), "PartialViewMacroFileSystem"); - Container.RegisterSingleton(factory => Mock.Of(), "StylesheetFileSystem"); + Composition.RegisterUnique(factory => ExamineManager.Instance); - // need real file systems here as templates content is on-disk only - //Container.RegisterSingleton(factory => Mock.Of(), "MasterpageFileSystem"); - //Container.RegisterSingleton(factory => Mock.Of(), "ViewFileSystem"); - Container.RegisterSingleton(factory => new PhysicalFileSystem("Views", "/views"), "ViewFileSystem"); - Container.RegisterSingleton(factory => new PhysicalFileSystem("MasterPages", "/masterpages"), "MasterpageFileSystem"); - Container.RegisterSingleton(factory => new PhysicalFileSystem("Xslt", "/xslt"), "XsltFileSystem"); + // register filesystems + Composition.RegisterUnique(factory => TestObjects.GetFileSystemsMock()); + + var logger = Mock.Of(); + var scheme = Mock.Of(); + var config = Mock.Of(); + + var mediaFileSystem = new MediaFileSystem(Mock.Of(), config, scheme, logger); + Composition.RegisterUnique(factory => mediaFileSystem); // no factory (noop) - Container.RegisterSingleton(); + Composition.RegisterUnique(); // register application stuff (database factory & context, services...) - Container.RegisterCollectionBuilder() - .AddCore(); + Composition.WithCollectionBuilder() + .AddCoreMappers(); - Container.RegisterSingleton(_ => new TransientEventMessagesFactory()); - var sqlSyntaxProviders = TestObjects.GetDefaultSqlSyntaxProviders(Logger); - Container.RegisterSingleton(_ => sqlSyntaxProviders.OfType().First()); - Container.RegisterSingleton(f => new UmbracoDatabaseFactory( + Composition.RegisterUnique(_ => new TransientEventMessagesFactory()); + Composition.RegisterUnique(f => new UmbracoDatabaseFactory( Constants.System.UmbracoConnectionName, - sqlSyntaxProviders, Logger, - Mock.Of())); - Container.RegisterSingleton(f => f.TryGetInstance().SqlContext); + new Lazy(Mock.Of))); + Composition.RegisterUnique(f => f.TryGetInstance().SqlContext); - Container.RegisterCollectionBuilder(); // empty - Container.RegisterSingleton(factory => new FileSystems(factory.TryGetInstance())); - Container.RegisterSingleton(factory + Composition.WithCollectionBuilder(); // empty + + Composition.RegisterUnique(factory => TestObjects.GetScopeProvider(factory.TryGetInstance(), factory.TryGetInstance(), factory.TryGetInstance())); - Container.RegisterSingleton(factory => (IScopeAccessor) factory.GetInstance()); + Composition.RegisterUnique(factory => (IScopeAccessor) factory.GetInstance()); + + Composition.ComposeServices(); - Container.RegisterFrom(); // composition root is doing weird things, fix - Container.RegisterSingleton(); - Container.RegisterSingleton(); + Composition.RegisterUnique(); + Composition.RegisterUnique(); // somehow property editor ends up wanting this - Container.RegisterCollectionBuilder(); - Container.RegisterSingleton(); + Composition.WithCollectionBuilder(); + Composition.RegisterUnique(); // note - don't register collections, use builders - Container.RegisterCollectionBuilder(); - Container.RegisterSingleton(); - Container.RegisterSingleton(); + Composition.WithCollectionBuilder(); + Composition.RegisterUnique(); + Composition.RegisterUnique(); } #endregion @@ -358,7 +365,7 @@ namespace Umbraco.Tests.Testing Mapper.Initialize(configuration => { - var profiles = Container.GetAllInstances(); + var profiles = Factory.GetAllInstances(); foreach (var profile in profiles) configuration.AddProfile(profile); }); @@ -395,9 +402,9 @@ namespace Umbraco.Tests.Testing // reset and dispose scopes // ensures we don't leak an opened database connection // which would lock eg SqlCe .sdf files - if (Container?.TryGetInstance() is ScopeProvider scopeProvider) + if (Factory?.TryGetInstance() is ScopeProvider scopeProvider) { - Core.Scoping.Scope scope; + Scope scope; while ((scope = scopeProvider.AmbientScope) != null) { scope.Reset(); @@ -405,10 +412,7 @@ namespace Umbraco.Tests.Testing } } - Current.Reset(); - - Container?.Dispose(); - Container = null; + Current.Reset(); // disposes the factory // reset all other static things that should not be static ;( UriUtility.ResetAppDomainAppVirtualPath(); diff --git a/src/Umbraco.Tests/Testing/UmbracoTestOptions.cs b/src/Umbraco.Tests/Testing/UmbracoTestOptions.cs index 2d62f104d1..da3ffccc55 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestOptions.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestOptions.cs @@ -28,11 +28,11 @@ public enum TypeLoader { - // the default, global plugin manager for tests + // the default, global type loader for tests Default, - // create one plugin manager for the feature + // create one type loader for the feature PerFixture, - // create one plugin manager for each test + // create one type loader for each test PerTest } } diff --git a/src/Umbraco.Tests/TreesAndSections/SectionTests.cs b/src/Umbraco.Tests/TreesAndSections/SectionTests.cs index e35bd5fd45..1f4a01fd3d 100644 --- a/src/Umbraco.Tests/TreesAndSections/SectionTests.cs +++ b/src/Umbraco.Tests/TreesAndSections/SectionTests.cs @@ -3,8 +3,7 @@ using NUnit.Framework; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using System; -using System.Linq; -using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Tests.Testing; using Umbraco.Web.Services; @@ -21,7 +20,7 @@ namespace Umbraco.Tests.TreesAndSections protected override void Compose() { base.Compose(); - Container.Register(); + Composition.RegisterUnique(); } public override void SetUp() diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index e3e4dec9ce..6cfd53be00 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -116,6 +116,8 @@ + + @@ -133,6 +135,7 @@ + @@ -279,7 +282,7 @@ - + diff --git a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs index 5ca195849b..5be3fe2e7e 100644 --- a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs @@ -18,8 +18,8 @@ namespace Umbraco.Tests.UmbracoExamine _profilingLogger = new ProfilingLogger(logger, new LogProfiler(logger)); } - private ProfilingLogger _profilingLogger; - protected override ProfilingLogger ProfilingLogger => _profilingLogger; + private IProfilingLogger _profilingLogger; + protected override IProfilingLogger ProfilingLogger => _profilingLogger; /// /// sets up resolvers before resolution is frozen @@ -28,7 +28,7 @@ namespace Umbraco.Tests.UmbracoExamine { base.Compose(); - Container.RegisterSingleton(_ => new DefaultShortStringHelper(SettingsForTests.GetDefaultUmbracoSettings())); + Composition.RegisterUnique(_ => new DefaultShortStringHelper(SettingsForTests.GetDefaultUmbracoSettings())); } } } diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index 4435a5e829..fbbf2042ca 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -44,7 +44,7 @@ namespace Umbraco.Tests.UmbracoExamine public static MediaIndexPopulator GetMediaIndexRebuilder(PropertyEditorCollection propertyEditors, IMediaService mediaService) { - var mediaValueSetBuilder = new MediaValueSetBuilder(propertyEditors, new[] { new DefaultUrlSegmentProvider() }, GetMockUserService()); + var mediaValueSetBuilder = new MediaValueSetBuilder(propertyEditors, new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), GetMockUserService()); var mediaIndexDataSource = new MediaIndexPopulator(null, mediaService, mediaValueSetBuilder); return mediaIndexDataSource; } @@ -147,7 +147,7 @@ namespace Umbraco.Tests.UmbracoExamine } public static UmbracoContentIndex GetUmbracoIndexer( - ProfilingLogger profilingLogger, + IProfilingLogger profilingLogger, Directory luceneDir, Analyzer analyzer = null, ILocalizationService languageService = null, @@ -161,7 +161,7 @@ namespace Umbraco.Tests.UmbracoExamine if (validator == null) validator = new ContentValueSetValidator(true); - + var i = new UmbracoContentIndex( "testIndexer", new UmbracoFieldDefinitionCollection(), diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs index 1bc51b6173..ba6a83adff 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs @@ -4,12 +4,11 @@ using Examine; using Examine.LuceneEngine.Providers; using Lucene.Net.Index; using Lucene.Net.Search; -using Lucene.Net.Store; using NUnit.Framework; using Umbraco.Tests.Testing; using Umbraco.Examine; +using Umbraco.Core.Composing; using Umbraco.Core.PropertyEditors; -using LightInject; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Core.Models; using Newtonsoft.Json; @@ -26,11 +25,10 @@ namespace Umbraco.Tests.UmbracoExamine [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class IndexTest : ExamineBaseTest { - [Test] public void Index_Property_Data_With_Value_Indexer() { - var contentValueSetBuilder = IndexInitializer.GetContentValueSetBuilder(Container.GetInstance(), false); + var contentValueSetBuilder = IndexInitializer.GetContentValueSetBuilder(Factory.GetInstance(), false); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, @@ -122,8 +120,8 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Rebuild_Index() { - var contentRebuilder = IndexInitializer.GetContentIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); - var mediaRebuilder = IndexInitializer.GetMediaIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockMediaService()); + var contentRebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); + var mediaRebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockMediaService()); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, @@ -150,7 +148,7 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Index_Protected_Content_Not_Indexed() { - var rebuilder = IndexInitializer.GetContentIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); + var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); using (var luceneDir = new RandomIdRamDirectory()) @@ -275,7 +273,7 @@ namespace Umbraco.Tests.UmbracoExamine [Test] public void Index_Reindex_Content() { - var rebuilder = IndexInitializer.GetContentIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); + var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir, validator: new ContentValueSetValidator(false))) @@ -316,7 +314,7 @@ namespace Umbraco.Tests.UmbracoExamine public void Index_Delete_Index_Item_Ensure_Heirarchy_Removed() { - var rebuilder = IndexInitializer.GetContentIndexRebuilder(Container.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); + var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false); using (var luceneDir = new RandomIdRamDirectory()) using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir)) diff --git a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs index 747bab3c6d..7aa36f16e3 100644 --- a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs +++ b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using LightInject; using Examine; using Examine.Search; using NUnit.Framework; @@ -11,6 +10,7 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Services; using Umbraco.Tests.Testing; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Composing; using Umbraco.Examine; namespace Umbraco.Tests.UmbracoExamine @@ -53,7 +53,7 @@ namespace Umbraco.Tests.UmbracoExamine == allRecs); - var propertyEditors = Container.GetInstance(); + var propertyEditors = Factory.GetInstance(); var rebuilder = IndexInitializer.GetContentIndexRebuilder(propertyEditors, contentService, ScopeProvider.SqlContext, true); using (var luceneDir = new RandomIdRamDirectory()) @@ -62,7 +62,7 @@ namespace Umbraco.Tests.UmbracoExamine { indexer.CreateIndex(); rebuilder.Populate(indexer); - + var searcher = indexer.GetSearcher(); var numberSortedCriteria = searcher.CreateQuery() diff --git a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs index 9bd00f3d96..7bd47bed66 100644 --- a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs @@ -81,14 +81,14 @@ namespace Umbraco.Tests.Web.Controllers var textService = new Mock(); textService.Setup(x => x.Localize(It.IsAny(), It.IsAny(), It.IsAny>())).Returns("text"); - Container.RegisterSingleton(f => Mock.Of()); - Container.RegisterSingleton(f => userServiceMock.Object); - Container.RegisterSingleton(f => entityService.Object); - Container.RegisterSingleton(f => dataTypeService.Object); - Container.RegisterSingleton(f => langService.Object); - Container.RegisterSingleton(f => textService.Object); - Container.RegisterSingleton(f => Mock.Of()); - Container.RegisterSingleton(f => new UmbracoApiControllerTypeCollection(new[] { typeof(ContentTreeController) })); + Composition.RegisterUnique(f => Mock.Of()); + Composition.RegisterUnique(f => userServiceMock.Object); + Composition.RegisterUnique(f => entityService.Object); + Composition.RegisterUnique(f => dataTypeService.Object); + Composition.RegisterUnique(f => langService.Object); + Composition.RegisterUnique(f => textService.Object); + Composition.RegisterUnique(f => Mock.Of()); + Composition.RegisterUnique(f => new UmbracoApiControllerTypeCollection(new[] { typeof(ContentTreeController) })); } private MultipartFormDataContent GetMultiPartRequestContent(string json) @@ -213,7 +213,6 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var usersController = new ContentController(propertyEditorCollection); - Container.InjectProperties(usersController); return usersController; } @@ -240,7 +239,6 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var usersController = new ContentController(propertyEditorCollection); - Container.InjectProperties(usersController); return usersController; } @@ -272,7 +270,6 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var usersController = new ContentController(propertyEditorCollection); - Container.InjectProperties(usersController); return usersController; } @@ -310,7 +307,6 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var usersController = new ContentController(propertyEditorCollection); - Container.InjectProperties(usersController); return usersController; } @@ -342,7 +338,6 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var usersController = new ContentController(propertyEditorCollection); - Container.InjectProperties(usersController); return usersController; } @@ -378,7 +373,6 @@ namespace Umbraco.Tests.Web.Controllers var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); var usersController = new ContentController(propertyEditorCollection); - Container.InjectProperties(usersController); return usersController; } diff --git a/src/Umbraco.Tests/Web/Controllers/PluginControllerAreaTests.cs b/src/Umbraco.Tests/Web/Controllers/PluginControllerAreaTests.cs index 669c607aea..01525f12da 100644 --- a/src/Umbraco.Tests/Web/Controllers/PluginControllerAreaTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/PluginControllerAreaTests.cs @@ -54,8 +54,8 @@ namespace Umbraco.Tests.Web.Controllers public class Plugin1Controller : PluginController { public Plugin1Controller(UmbracoContext umbracoContext) + : base(umbracoContext, null, null, null, null, null) { - UmbracoContext = umbracoContext; } } @@ -63,8 +63,8 @@ namespace Umbraco.Tests.Web.Controllers public class Plugin2Controller : PluginController { public Plugin2Controller(UmbracoContext umbracoContext) + : base(umbracoContext, null, null, null, null, null) { - UmbracoContext = umbracoContext; } } @@ -72,16 +72,16 @@ namespace Umbraco.Tests.Web.Controllers public class Plugin3Controller : PluginController { public Plugin3Controller(UmbracoContext umbracoContext) + : base(umbracoContext, null, null, null, null, null) { - UmbracoContext = umbracoContext; } } public class Plugin4Controller : PluginController { public Plugin4Controller(UmbracoContext umbracoContext) + : base(umbracoContext, null, null, null, null, null) { - UmbracoContext = umbracoContext; } } diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index 6604e25aa2..857e922ac9 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -6,6 +6,7 @@ using System.Web.Http; using Moq; using Newtonsoft.Json; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -37,12 +38,12 @@ namespace Umbraco.Tests.Web.Controllers // replace the true IUserService implementation with a mock // so that each test can configure the service to their liking - Container.RegisterSingleton(f => Mock.Of()); + Composition.RegisterUnique(f => Mock.Of()); // kill the true IEntityService too - Container.RegisterSingleton(f => Mock.Of()); - - Container.RegisterSingleton(); + Composition.RegisterUnique(f => Mock.Of()); + + Composition.RegisterUnique(); } [Test] @@ -68,7 +69,6 @@ namespace Umbraco.Tests.Web.Controllers .Returns((int id) => id == 1234 ? new User(1234, "Test", "test@test.com", "test@test.com", "", new List(), new int[0], new int[0]) : null); var usersController = new UsersController(); - Container.InjectProperties(usersController); return usersController; } @@ -125,7 +125,6 @@ namespace Umbraco.Tests.Web.Controllers ApiController Factory(HttpRequestMessage message, UmbracoHelper helper) { var usersController = new UsersController(); - Container.InjectProperties(usersController); return usersController; } @@ -153,7 +152,6 @@ namespace Umbraco.Tests.Web.Controllers .Returns(() => users); var usersController = new UsersController(); - Container.InjectProperties(usersController); return usersController; } diff --git a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs index f4c15f7c19..c6609f38c9 100644 --- a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs @@ -9,6 +9,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Profiling; @@ -17,12 +18,12 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; -using Umbraco.Web.Composing; using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; +using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Tests.Web.Mvc { @@ -33,6 +34,7 @@ namespace Umbraco.Tests.Web.Mvc public void SetUp() { Current.UmbracoContextAccessor = new TestUmbracoContextAccessor(); + Core.Composing.Current.Factory = Mock.Of(); } [TearDown] @@ -156,7 +158,8 @@ namespace Umbraco.Tests.Web.Mvc } public class MatchesDefaultIndexController : RenderMvcController - { } + { + } public class MatchesOverriddenIndexController : RenderMvcController { diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs index dce975d0c4..35e8f0a937 100644 --- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs @@ -7,8 +7,10 @@ using System.Web.Security; using Moq; using NUnit.Framework; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Dictionary; +using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; @@ -16,11 +18,11 @@ using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; -using Umbraco.Web.Composing; using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; +using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Tests.Web.Mvc { @@ -49,7 +51,7 @@ namespace Umbraco.Tests.Web.Mvc new TestVariationContextAccessor(), true); - var ctrl = new TestSurfaceController { UmbracoContext = umbracoContext }; + var ctrl = new TestSurfaceController(umbracoContext); var result = ctrl.Index(); @@ -71,7 +73,7 @@ namespace Umbraco.Tests.Web.Mvc new TestVariationContextAccessor(), true); - var ctrl = new TestSurfaceController { UmbracoContext = umbCtx }; + var ctrl = new TestSurfaceController(umbCtx); Assert.IsNotNull(ctrl.UmbracoContext); } @@ -91,9 +93,8 @@ namespace Umbraco.Tests.Web.Mvc new TestVariationContextAccessor(), true); - var controller = new TestSurfaceController { UmbracoContext = umbracoContext }; - Container.Register(_ => umbracoContext); - Container.InjectProperties(controller); + var controller = new TestSurfaceController(umbracoContext); + Composition.Register(_ => umbracoContext); Assert.IsNotNull(controller.Umbraco); } @@ -131,10 +132,10 @@ namespace Umbraco.Tests.Web.Mvc Mock.Of(), Mock.Of(), Mock.Of(), - new MembershipHelper(umbracoContext, Mock.Of(), Mock.Of()), - new ServiceContext()); + new MembershipHelper(new TestUmbracoContextAccessor(umbracoContext), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), null, Mock.Of(), Mock.Of()), + ServiceContext.CreatePartial()); - var ctrl = new TestSurfaceController { UmbracoContext = umbracoContext, Umbraco = helper }; + var ctrl = new TestSurfaceController(umbracoContext, helper); var result = ctrl.GetContent(2) as PublishedContentResult; Assert.IsNotNull(result); @@ -173,7 +174,7 @@ namespace Umbraco.Tests.Web.Mvc var routeData = new RouteData(); routeData.DataTokens.Add(Core.Constants.Web.UmbracoRouteDefinitionDataToken, routeDefinition); - var ctrl = new TestSurfaceController { UmbracoContext = umbracoContext, Umbraco = new UmbracoHelper() }; + var ctrl = new TestSurfaceController(umbracoContext, new UmbracoHelper()); ctrl.ControllerContext = new ControllerContext(contextBase, routeData, ctrl); var result = ctrl.GetContentFromCurrentPage() as PublishedContentResult; @@ -183,6 +184,15 @@ namespace Umbraco.Tests.Web.Mvc public class TestSurfaceController : SurfaceController { + public TestSurfaceController(UmbracoContext ctx, UmbracoHelper helper = null) + : base(ctx, null, ServiceContext.CreatePartial(), Mock.Of(), null, null) + { + if (helper != null) + { + Umbraco = helper; + } + } + public ActionResult Index() { return View(); diff --git a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs index a4b210dbef..0ad3bff109 100644 --- a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs @@ -3,18 +3,13 @@ using System.Globalization; using System.Linq; using System.Web.Mvc; using System.Web.Routing; -using LightInject; using Moq; using NUnit.Framework; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Persistence; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index d98afa6bcc..7b640fae95 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Web; -using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -35,20 +34,20 @@ namespace Umbraco.Tests.Web // should not depend on more than IdkMap maybe - fix this! var entityService = new Mock(); entityService.Setup(x => x.GetId(It.IsAny(), It.IsAny())).Returns(Attempt.Fail()); - var serviceContext = new ServiceContext(entityService: entityService.Object); + var serviceContext = ServiceContext.CreatePartial(entityService: entityService.Object); // fixme - bad in a unit test - but Udi has a static ctor that wants it?! - var container = new Mock(); + var container = new Mock(); container.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( - new TypeLoader(NullCacheProvider.Instance, SettingsForTests.GenerateMockGlobalSettings(), new ProfilingLogger(Mock.Of(), Mock.Of()))); + new TypeLoader(NullCacheProvider.Instance, LocalTempStorage.Default, new ProfilingLogger(Mock.Of(), Mock.Of()))); container.Setup(x => x.GetInstance(typeof (ServiceContext))).Returns(serviceContext); - Current.Container = container.Object; + Current.Factory = container.Object; Umbraco.Web.Composing.Current.UmbracoContextAccessor = new TestUmbracoContextAccessor(); Udi.ResetUdiTypes(); - UmbracoConfig.For.SetUmbracoSettings(SettingsForTests.GetDefaultUmbracoSettings()); + Current.Config.SetUmbracoConfig(SettingsForTests.GetDefaultUmbracoSettings()); } [TearDown] @@ -82,7 +81,7 @@ namespace Umbraco.Tests.Web .Returns((UmbracoContext umbCtx, IPublishedContent content, UrlProviderMode mode, string culture, Uri url) => UrlInfo.Url("/my-test-url")); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); - + var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var publishedContent = Mock.Of(); Mock.Get(publishedContent).Setup(x => x.Id).Returns(1234); diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 960c4f5147..cb47a77915 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -937,7 +937,7 @@ }, "ansi-colors": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { @@ -955,7 +955,7 @@ }, "ansi-escapes": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, @@ -1216,7 +1216,7 @@ "arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=", "dev": true }, "asap": { @@ -1269,7 +1269,7 @@ "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=", "dev": true }, "asynckit": { @@ -2379,7 +2379,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -2502,7 +2502,7 @@ "core-js": { "version": "2.5.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "integrity": "sha1-+XJgj/DOrWi4QaFqky0LGDeRgU4=", "dev": true }, "core-util-is": { @@ -3425,7 +3425,7 @@ "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "integrity": "sha1-XNAfwQFiG0LEzX9dGmYkNxbT850=", "dev": true, "requires": { "esutils": "^2.0.2" @@ -3953,7 +3953,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -3989,7 +3989,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -4153,7 +4153,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true, "optional": true } @@ -4248,7 +4248,7 @@ "eslint-scope": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "integrity": "sha1-UL8wcekzi83EMzF5Sgy1M/ATYXI=", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -4258,13 +4258,13 @@ "eslint-utils": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "integrity": "sha1-moUbqJ7nxGA0b5fPiTnHKYgn5RI=", "dev": true }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "integrity": "sha1-PzGA+y4pEBdxastMnW1bXDSmqB0=", "dev": true }, "espree": { @@ -4287,7 +4287,7 @@ "esquery": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", "dev": true, "requires": { "estraverse": "^4.0.0" @@ -4296,7 +4296,7 @@ "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", "dev": true, "requires": { "estraverse": "^4.1.0" @@ -4356,13 +4356,13 @@ "eventemitter3": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", - "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", + "integrity": "sha1-CQtNbNvWRe0Qv3UNS1QHlC17oWM=", "dev": true }, "exec-buffer": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", - "integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==", + "integrity": "sha1-sWhtvZBMfPmC5lLB9aebHlVzCCs=", "dev": true, "optional": true, "requires": { @@ -4571,7 +4571,7 @@ "fill-range": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha1-6x53OrsFbc2N8r/favWbizqTZWU=", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", "dev": true, "requires": { "is-number": "^2.1.0", @@ -5118,7 +5118,7 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", "dev": true }, "fs-extra": { @@ -5204,12 +5204,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5224,17 +5226,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5351,7 +5356,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5363,6 +5369,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5377,6 +5384,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5384,12 +5392,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5408,6 +5418,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5488,7 +5499,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5500,6 +5512,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5621,6 +5634,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6449,7 +6463,7 @@ "gulp-eslint": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-5.0.0.tgz", - "integrity": "sha512-9GUqCqh85C7rP9120cpxXuZz2ayq3BZc85pCTuPJS03VQYxne0aWPIXWx6LSvsGPa3uRqtSO537vaugOh+5cXg==", + "integrity": "sha1-KiaECV93Syz3kxAmIHjFbMehK1I=", "dev": true, "requires": { "eslint": "^5.0.1", @@ -7307,7 +7321,7 @@ "has-binary2": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=", "dev": true, "requires": { "isarray": "2.0.1" @@ -7458,7 +7472,7 @@ "http-proxy": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", - "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "integrity": "sha1-etOElGWPhGBeL220Q230EPTlvpo=", "dev": true, "requires": { "eventemitter3": "^3.0.0", @@ -8056,7 +8070,7 @@ "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", "dev": true }, "is-retry-allowed": { @@ -8261,7 +8275,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -8388,7 +8402,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -8996,7 +9010,7 @@ "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", "dev": true }, "lpad-align": { @@ -9021,7 +9035,7 @@ "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", "dev": true, "requires": { "pify": "^3.0.0" @@ -9219,7 +9233,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", "dev": true }, "minimatch": { @@ -12735,7 +12749,7 @@ "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=", "dev": true }, "posix-character-classes": { @@ -13225,7 +13239,7 @@ "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=", "dev": true }, "qs": { @@ -13421,7 +13435,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -13919,7 +13933,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=", "dev": true }, "sax": { @@ -14106,7 +14120,7 @@ "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=", "dev": true }, "shebang-command": { @@ -14390,7 +14404,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -14670,7 +14684,7 @@ "stream-consume": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.1.tgz", - "integrity": "sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg==", + "integrity": "sha1-0721mMK9CugrjKx6xQsRB6eZbEg=", "dev": true }, "stream-shift": { @@ -14682,7 +14696,7 @@ "streamroller": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", - "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", + "integrity": "sha1-odG3z4PTmvsNYwSaWsv5NJO99ks=", "dev": true, "requires": { "date-format": "^1.2.0", @@ -14709,7 +14723,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -14724,7 +14738,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -14741,7 +14755,7 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -14931,7 +14945,7 @@ "strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, "requires": { "escape-string-regexp": "^1.0.2" @@ -15068,7 +15082,7 @@ "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", "dev": true, "requires": { "once": "^1.4.0" @@ -15283,7 +15297,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", "dev": true, "requires": { "os-tmpdir": "~1.0.2" @@ -15318,7 +15332,7 @@ "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", "dev": true }, "to-fast-properties": { @@ -15489,7 +15503,7 @@ "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=", "dev": true }, "unc-path-regex": { @@ -15651,13 +15665,13 @@ "upath": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "integrity": "sha1-NSVll+RqWB20eT0M5H+prr/J+r0=", "dev": true }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", "dev": true, "requires": { "punycode": "^2.1.0" @@ -16092,7 +16106,7 @@ "ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=", "dev": true, "requires": { "async-limiter": "~1.0.0", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js index 7578ade867..6e75973ae7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -136,7 +136,7 @@ angular.module('umbraco.directives') return; } - angularHelper.safeApply(scope, attrs.onOutsideClick); + scope.$apply(attrs.onOutsideClick); } diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/content/redirecturls.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/content/redirecturls.html index 24b32815e5..837c24cb0c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/content/redirecturls.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/content/redirecturls.html @@ -54,9 +54,10 @@
-
Original URL
+
Culture
+
Original URL
-
Redirected To
+
Redirected To
@@ -64,8 +65,10 @@
- -
+
+ {{redirectUrl.culture ||'*'}} +
+ @@ -73,7 +76,7 @@
-
+
diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 8c521207f5..316ac15a6e 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -94,9 +94,9 @@ - - - + + + all @@ -107,7 +107,7 @@ - 8.0.0-alpha.24 + 8.0.0-alpha.26 diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml index 4659674c59..8c684562cf 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml @@ -1,6 +1,7 @@ @using Umbraco.Core @using ClientDependency.Core @using ClientDependency.Core.Mvc +@using Umbraco.Core.Composing @using Umbraco.Core.IO @using Umbraco.Web @using Umbraco.Core.Configuration @@ -76,7 +77,7 @@
- @@ -112,15 +113,15 @@ view="ysodOverlay.view"> - - @Html.BareMinimumServerVariablesScript(Url, Url.Action("ExternalLogin", "BackOffice", new { area = ViewBag.UmbracoPath }), Model.Features, UmbracoConfig.For.GlobalSettings()) + @Html.BareMinimumServerVariablesScript(Url, Url.Action("ExternalLogin", "BackOffice", new { area = ViewBag.UmbracoPath }), Model.Features, Current.Config.Global())