From c70d82d32bd22d95c02b14b1e78d7a060e86338a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 7 Nov 2019 14:10:06 +0100 Subject: [PATCH 01/36] AB3649 - Moved ManifestSection --- .../Manifest/ManifestSection.cs | 0 src/Umbraco.Core/Umbraco.Core.csproj | 1 - 2 files changed, 1 deletion(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Manifest/ManifestSection.cs (100%) diff --git a/src/Umbraco.Core/Manifest/ManifestSection.cs b/src/Umbraco.Abstractions/Manifest/ManifestSection.cs similarity index 100% rename from src/Umbraco.Core/Manifest/ManifestSection.cs rename to src/Umbraco.Abstractions/Manifest/ManifestSection.cs diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 085a830fbf..b6084e695a 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -392,7 +392,6 @@ - From 6398f1e2166fb42acdd8528580a403c16595eb30 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Nov 2019 14:26:06 +1100 Subject: [PATCH 02/36] Creates ITypeFinder interface and simplifies type finder with TypeFinderExtensions, moves GetTypeByName to TypeHelper using assembly name parsing instead of scanning every assembly. --- .../Cache/FastDictionaryAppCache.cs | 6 +- .../Cache/FastDictionaryAppCacheBase.cs | 4 +- .../Cache/ObjectCacheAppCache.cs | 4 +- .../Composing/TypeHelper.cs | 61 +++ src/Umbraco.Core/Composing/ITypeFinder.cs | 50 ++ src/Umbraco.Core/Composing/TypeFinder.cs | 412 ++++++--------- .../Composing/TypeFinderExtensions.cs | 45 ++ src/Umbraco.Core/Composing/TypeLoader.cs | 19 +- src/Umbraco.Core/Runtime/CoreRuntime.cs | 5 +- src/Umbraco.Core/Umbraco.Core.csproj | 5 +- src/Umbraco.Examine/LuceneIndexCreator.cs | 2 +- .../Components/ComponentTests.cs | 3 +- .../Composing/ComposingTestBase.cs | 3 +- .../Composing/CompositionTests.cs | 3 +- .../Composing/TypeFinderTests.cs | 472 +----------------- .../Composing/TypeLoaderTests.cs | 3 +- src/Umbraco.Tests/CoreThings/UdiTests.cs | 3 +- .../Published/PropertyCacheLevelTests.cs | 28 +- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 6 +- .../TestHelpers/BaseUsingSqlCeSyntax.cs | 3 +- .../Stubs/TestControllerFactory.cs | 4 +- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 3 +- .../Web/TemplateUtilitiesTests.cs | 3 +- src/Umbraco.Tests/Web/UmbracoHelperTests.cs | 3 +- src/Umbraco.Web/Editors/EntityController.cs | 1 + 25 files changed, 377 insertions(+), 774 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Cache/FastDictionaryAppCache.cs (97%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Cache/FastDictionaryAppCacheBase.cs (98%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Cache/ObjectCacheAppCache.cs (99%) create mode 100644 src/Umbraco.Core/Composing/ITypeFinder.cs create mode 100644 src/Umbraco.Core/Composing/TypeFinderExtensions.cs diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs similarity index 97% rename from src/Umbraco.Core/Cache/FastDictionaryAppCache.cs rename to src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs index b38f36a7d8..3bd29c3f5f 100644 --- a/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs @@ -10,13 +10,15 @@ namespace Umbraco.Core.Cache /// /// Implements a fast on top of a concurrent dictionary. /// - internal class FastDictionaryAppCache : IAppCache + public class FastDictionaryAppCache : IAppCache { /// /// Gets the internal items dictionary, for tests only! /// internal readonly ConcurrentDictionary> Items = new ConcurrentDictionary>(); + public int Count => Items.Count; + /// public object Get(string cacheKey) { @@ -76,7 +78,7 @@ namespace Umbraco.Core.Cache /// public void ClearOfType(string typeName) { - var type = TypeFinder.GetTypeByName(typeName); + var type = TypeHelper.GetTypeByName(typeName); if (type == null) return; var isInterface = type.IsInterface; diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs similarity index 98% rename from src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs rename to src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs index eb84423f32..b49ecea671 100644 --- a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs +++ b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Cache /// /// Provides a base class to fast, dictionary-based implementations. /// - internal abstract class FastDictionaryAppCacheBase : IAppCache + public abstract class FastDictionaryAppCacheBase : IAppCache { // prefix cache keys so we know which one are ours protected const string CacheItemPrefix = "umbrtmche"; @@ -116,7 +116,7 @@ namespace Umbraco.Core.Cache /// public virtual void ClearOfType(string typeName) { - var type = TypeFinder.GetTypeByName(typeName); + var type = TypeHelper.GetTypeByName(typeName); if (type == null) return; var isInterface = type.IsInterface; try diff --git a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs b/src/Umbraco.Abstractions/Cache/ObjectCacheAppCache.cs similarity index 99% rename from src/Umbraco.Core/Cache/ObjectCacheAppCache.cs rename to src/Umbraco.Abstractions/Cache/ObjectCacheAppCache.cs index ad7f3d6bf7..7d5081baa5 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/ObjectCacheAppCache.cs @@ -29,7 +29,7 @@ namespace Umbraco.Core.Cache /// /// Gets the internal memory cache, for tests only! /// - internal ObjectCache MemoryCache { get; private set; } + public ObjectCache MemoryCache { get; private set; } /// public object Get(string key) @@ -178,7 +178,7 @@ namespace Umbraco.Core.Cache /// public virtual void ClearOfType(string typeName) { - var type = TypeFinder.GetTypeByName(typeName); + var type = TypeHelper.GetTypeByName(typeName); if (type == null) return; var isInterface = type.IsInterface; try diff --git a/src/Umbraco.Abstractions/Composing/TypeHelper.cs b/src/Umbraco.Abstractions/Composing/TypeHelper.cs index 40a1adc118..768709c44c 100644 --- a/src/Umbraco.Abstractions/Composing/TypeHelper.cs +++ b/src/Umbraco.Abstractions/Composing/TypeHelper.cs @@ -8,6 +8,7 @@ using System.Reflection; namespace Umbraco.Core.Composing { + /// /// A utility class for type checking, this provides internal caching so that calls to these methods will be faster /// than doing a manual type check in c# @@ -21,6 +22,66 @@ namespace Umbraco.Core.Composing private static readonly Assembly[] EmptyAssemblies = new Assembly[0]; + /// + /// Returns a Type for the string type name + /// + /// + /// + public static Type GetTypeByName(string name) + { + var type = Type.GetType(name); + if (type != null) return type; + + // now try fall back procedures. null may be returned because Type.GetName only returns types that have already been loaded in the appdomain + // and this type might not be there yet, so we need to parse the type name and then try to load in via assembly. + + var typeName = TypeName.Parse(name); + try + { + var assembly = Assembly.Load(typeName.AssemblyName); + type = assembly.GetType(typeName.Name); + return type; + } + catch (NotSupportedException) + { + throw; + } + catch (Exception) + { + return null; + } + } + + /// + /// Used to parse a fully qualified type name + /// + /// + /// Does not support generics. + /// This is a simple utility class and is not an exhaustive type name parser (which doesn't exist in .net strangely) + /// + private class TypeName + { + private TypeName(string name, AssemblyName assemblyName) + { + Name = name; + AssemblyName = assemblyName; + } + + public static TypeName Parse(string name) + { + if (name.Contains("[[")) + throw new NotSupportedException($"{nameof(TypeName)} does not support generic types"); + + var index = name.IndexOf(','); + return index > 0 + ? new TypeName(name.Substring(0, index).Trim(), new AssemblyName(name.Substring(index + 1).Trim())) + : new TypeName(name, null); + } + + public string Name { get; } + public AssemblyName AssemblyName { get; } + } + /// /// Based on a type we'll check if it is IEnumerable{T} (or similar) and if so we'll return a List{T}, this will also deal with array types and return List{T} for those too. /// If it cannot be done, null is returned. diff --git a/src/Umbraco.Core/Composing/ITypeFinder.cs b/src/Umbraco.Core/Composing/ITypeFinder.cs new file mode 100644 index 0000000000..844719b782 --- /dev/null +++ b/src/Umbraco.Core/Composing/ITypeFinder.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + public interface ITypeFinder + { + /// + /// Return a list of found local Assemblies that Umbraco should scan for type finding + /// + /// + IEnumerable AssembliesToScan { get; } + + /// + /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute + /// + /// + /// + /// + /// + /// + IEnumerable FindClassesOfTypeWithAttribute( + Type assignTypeFrom, + Type attributeType, + IEnumerable assemblies = null, + bool onlyConcreteClasses = true); + + /// + /// Returns all types found of in the assemblies specified of type T + /// + /// + /// + /// + /// + IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable assemblies = null, bool onlyConcreteClasses = true); + + /// + /// Finds any classes with the attribute. + /// + /// The attribute type + /// The assemblies. + /// if set to true only concrete classes. + /// + IEnumerable FindClassesWithAttribute( + Type attributeType, + IEnumerable assemblies, + bool onlyConcreteClasses); + } +} diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 5ad1e43580..a1b1bf2fef 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -6,11 +6,9 @@ using System.Linq; using System.Reflection; using System.Security; using System.Text; -using System.Web; using System.Web.Compilation; -using System.Web.Hosting; -using Umbraco.Core.Composing; using Umbraco.Core.IO; +using Umbraco.Core.Logging; namespace Umbraco.Core.Composing { @@ -18,14 +16,107 @@ namespace Umbraco.Core.Composing /// A utility class to find all classes of a certain type by reflection in the current bin folder /// of the web application. /// - public static class TypeFinder + public class TypeFinder : ITypeFinder { - private static volatile HashSet _localFilteredAssemblyCache; - private static readonly object LocalFilteredAssemblyCacheLocker = new object(); - private static readonly List NotifiedLoadExceptionAssemblies = new List(); - private static string[] _assembliesAcceptingLoadExceptions; + private readonly ILogger _logger; - private static string[] AssembliesAcceptingLoadExceptions + public TypeFinder(ILogger logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _allAssemblies = new Lazy>(() => + { + HashSet assemblies = null; + try + { + var isHosted = IOHelper.IsHosted; + + try + { + if (isHosted) + { + assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); + } + } + catch (InvalidOperationException e) + { + if (e.InnerException is SecurityException == false) + throw; + } + + if (assemblies == null) + { + //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have + // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. + var binFolder = IOHelper.GetRootDirectoryBinFolder(); + var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + //var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; + //var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + assemblies = new HashSet(); + foreach (var a in binAssemblyFiles) + { + try + { + var assName = AssemblyName.GetAssemblyName(a); + var ass = Assembly.Load(assName); + assemblies.Add(ass); + } + catch (Exception e) + { + if (e is SecurityException || e is BadImageFormatException) + { + //swallow these exceptions + } + else + { + throw; + } + } + } + } + + //Since we are only loading in the /bin assemblies above, we will also load in anything that's already loaded (which will include gac items) + foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + { + assemblies.Add(a); + } + + //here we are trying to get the App_Code assembly + var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported + var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); + //check if the folder exists and if there are any files in it with the supported file extensions + if (appCodeFolder.Exists && fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any())) + { + try + { + var appCodeAssembly = Assembly.Load("App_Code"); + if (assemblies.Contains(appCodeAssembly) == false) // BuildManager will find App_Code already + assemblies.Add(appCodeAssembly); + } + catch (FileNotFoundException ex) + { + //this will occur if it cannot load the assembly + _logger.Error(typeof(TypeFinder), ex, "Could not load assembly App_Code"); + } + } + } + catch (InvalidOperationException e) + { + if (e.InnerException is SecurityException == false) + throw; + } + + return assemblies; + }); + } + + //Lazy access to the all assemblies list + private readonly Lazy> _allAssemblies; + private volatile HashSet _localFilteredAssemblyCache; + private readonly object _localFilteredAssemblyCacheLocker = new object(); + private readonly List _notifiedLoadExceptionAssemblies = new List(); + private string[] _assembliesAcceptingLoadExceptions; + + private string[] AssembliesAcceptingLoadExceptions { get { @@ -39,7 +130,7 @@ namespace Umbraco.Core.Composing } } - private static bool AcceptsLoadExceptions(Assembly a) + private bool AcceptsLoadExceptions(Assembly a) { if (AssembliesAcceptingLoadExceptions.Length == 0) return false; @@ -67,118 +158,25 @@ namespace Umbraco.Core.Composing /// http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app /// http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl /// - internal static HashSet GetAllAssemblies() + private IEnumerable GetAllAssemblies() { - return AllAssemblies.Value; + return _allAssemblies.Value; } - //Lazy access to the all assemblies list - private static readonly Lazy> AllAssemblies = new Lazy>(() => + /// + public IEnumerable AssembliesToScan { - HashSet assemblies = null; - try + get { - var isHosted = IOHelper.IsHosted; - - try + lock (_localFilteredAssemblyCacheLocker) { - if (isHosted) - { - assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); - } - } - catch (InvalidOperationException e) - { - if (e.InnerException is SecurityException == false) - throw; - } + if (_localFilteredAssemblyCache != null) + return _localFilteredAssemblyCache; - if (assemblies == null) - { - //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have - // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. - var binFolder = IOHelper.GetRootDirectoryBinFolder(); - var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - //var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; - //var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - assemblies = new HashSet(); - foreach (var a in binAssemblyFiles) - { - try - { - var assName = AssemblyName.GetAssemblyName(a); - var ass = Assembly.Load(assName); - assemblies.Add(ass); - } - catch (Exception e) - { - if (e is SecurityException || e is BadImageFormatException) - { - //swallow these exceptions - } - else - { - throw; - } - } - } - } - - //if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies. - if (assemblies.Any() == false) - { - foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) - { - assemblies.Add(a); - } - } - - //here we are trying to get the App_Code assembly - var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported - var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); - //check if the folder exists and if there are any files in it with the supported file extensions - if (appCodeFolder.Exists && fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any())) - { - try - { - var appCodeAssembly = Assembly.Load("App_Code"); - if (assemblies.Contains(appCodeAssembly) == false) // BuildManager will find App_Code already - assemblies.Add(appCodeAssembly); - } - catch (FileNotFoundException ex) - { - //this will occur if it cannot load the assembly - Current.Logger.Error(typeof(TypeFinder), ex, "Could not load assembly App_Code"); - } - } - } - catch (InvalidOperationException e) - { - if (e.InnerException is SecurityException == false) - throw; - } - - return assemblies; - }); - - /// - /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan - /// and excluding the ones passed in and excluding the exclusion list filter, the results of this are - /// cached for performance reasons. - /// - /// - /// - internal static HashSet GetAssembliesWithKnownExclusions( - IEnumerable excludeFromResults = null) - { - lock (LocalFilteredAssemblyCacheLocker) - { - if (_localFilteredAssemblyCache != null) + var assemblies = GetFilteredAssemblies(null, KnownAssemblyExclusionFilter); + _localFilteredAssemblyCache = new HashSet(assemblies); return _localFilteredAssemblyCache; - - var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); - _localFilteredAssemblyCache = new HashSet(assemblies); - return _localFilteredAssemblyCache; + } } } @@ -188,7 +186,7 @@ namespace Umbraco.Core.Composing /// /// /// - private static IEnumerable GetFilteredAssemblies( + private IEnumerable GetFilteredAssemblies( IEnumerable excludeFromResults = null, string[] exclusionFilter = null) { @@ -210,7 +208,7 @@ namespace Umbraco.Core.Composing /// NOTE the comma vs period... comma delimits the name in an Assembly FullName property so if it ends with comma then its an exact name match /// NOTE this means that "foo." will NOT exclude "foo.dll" but only "foo.*.dll" /// - internal static readonly string[] KnownAssemblyExclusionFilter = { + private static readonly string[] KnownAssemblyExclusionFilter = { "Antlr3.", "AutoMapper,", "AutoMapper.", @@ -261,115 +259,40 @@ namespace Umbraco.Core.Composing }; /// - /// Finds any classes derived from the type T that contain the attribute TAttribute + /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute /// - /// - /// - /// - public static IEnumerable FindClassesOfTypeWithAttribute() - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(GetAssembliesWithKnownExclusions(), true); - } - - /// - /// Finds any classes derived from the type T that contain the attribute TAttribute - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfTypeWithAttribute(IEnumerable assemblies) - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(assemblies, true); - } - - /// - /// Finds any classes derived from the type T that contain the attribute TAttribute - /// - /// - /// + /// + /// /// /// /// - public static IEnumerable FindClassesOfTypeWithAttribute( - IEnumerable assemblies, - bool onlyConcreteClasses) - where TAttribute : Attribute + public IEnumerable FindClassesOfTypeWithAttribute( + Type assignTypeFrom, + Type attributeType, + IEnumerable assemblies = null, + bool onlyConcreteClasses = true) { - return FindClassesOfTypeWithAttribute(typeof(T), assemblies, onlyConcreteClasses); + var assemblyList = (assemblies ?? AssembliesToScan).ToList(); + + return GetClassesWithBaseType(assignTypeFrom, assemblyList, onlyConcreteClasses, + //the additional filter will ensure that any found types also have the attribute applied. + t => t.GetCustomAttributes(attributeType, false).Any()); } /// - /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute + /// Returns all types found of in the assemblies specified of type T /// - /// /// /// /// /// - public static IEnumerable FindClassesOfTypeWithAttribute( - Type assignTypeFrom, - IEnumerable assemblies, - bool onlyConcreteClasses) - where TAttribute : Attribute + public IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable assemblies = null, bool onlyConcreteClasses = true) { - if (assemblies == null) throw new ArgumentNullException(nameof(assemblies)); + var assemblyList = (assemblies ?? AssembliesToScan).ToList(); - return GetClassesWithBaseType(assignTypeFrom, assemblies, onlyConcreteClasses, - //the additional filter will ensure that any found types also have the attribute applied. - t => t.GetCustomAttributes(false).Any()); + return GetClassesWithBaseType(assignTypeFrom, assemblyList, onlyConcreteClasses); } - - /// - /// Searches all filtered local assemblies specified for classes of the type passed in. - /// - /// - /// - public static IEnumerable FindClassesOfType() - { - return FindClassesOfType(GetAssembliesWithKnownExclusions(), true); - } - - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfType(IEnumerable assemblies, bool onlyConcreteClasses) - { - if (assemblies == null) throw new ArgumentNullException(nameof(assemblies)); - - return GetClassesWithBaseType(typeof(T), assemblies, onlyConcreteClasses); - } - - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - public static IEnumerable FindClassesOfType(IEnumerable assemblies) - { - return FindClassesOfType(assemblies, true); - } - - /// - /// Finds the classes with attribute. - /// - /// - /// The assemblies. - /// if set to true only concrete classes. - /// - public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies, bool onlyConcreteClasses) - where T : Attribute - { - return FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); - } - + /// /// Finds any classes with the attribute. /// @@ -377,40 +300,19 @@ namespace Umbraco.Core.Composing /// The assemblies. /// if set to true only concrete classes. /// - public static IEnumerable FindClassesWithAttribute( + public IEnumerable FindClassesWithAttribute( Type attributeType, - IEnumerable assemblies, - bool onlyConcreteClasses) + IEnumerable assemblies = null, + bool onlyConcreteClasses = true) { - return GetClassesWithAttribute(attributeType, assemblies, onlyConcreteClasses); - } + var assemblyList = (assemblies ?? AssembliesToScan).ToList(); - /// - /// Finds the classes with attribute. - /// - /// - /// The assemblies. - /// - public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies) - where T : Attribute - { - return FindClassesWithAttribute(assemblies, true); - } - - /// - /// Finds the classes with attribute in filtered local assemblies - /// - /// - /// - public static IEnumerable FindClassesWithAttribute() - where T : Attribute - { - return FindClassesWithAttribute(GetAssembliesWithKnownExclusions()); + return GetClassesWithAttribute(attributeType, assemblyList, onlyConcreteClasses); } #region Private methods - private static IEnumerable GetClassesWithAttribute( + private IEnumerable GetClassesWithAttribute( Type attributeType, IEnumerable assemblies, bool onlyConcreteClasses) @@ -441,7 +343,7 @@ namespace Umbraco.Core.Composing } catch (TypeLoadException ex) { - Current.Logger.Error(typeof(TypeFinder), ex, "Could not query types on {Assembly} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly); + _logger.Error(typeof(TypeFinder), ex, "Could not query types on {Assembly} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly); continue; } @@ -477,7 +379,7 @@ namespace Umbraco.Core.Composing /// /// An additional filter to apply for what types will actually be included in the return value /// - private static IEnumerable GetClassesWithBaseType( + private IEnumerable GetClassesWithBaseType( Type baseType, IEnumerable assemblies, bool onlyConcreteClasses, @@ -507,7 +409,7 @@ namespace Umbraco.Core.Composing } catch (TypeLoadException ex) { - Current.Logger.Error(typeof(TypeFinder), ex, "Could not query types on {Assembly} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly); + _logger.Error(typeof(TypeFinder), ex, "Could not query types on {Assembly} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly); continue; } @@ -533,7 +435,7 @@ namespace Umbraco.Core.Composing return types; } - internal static IEnumerable GetTypesWithFormattedException(Assembly a) + private IEnumerable GetTypesWithFormattedException(Assembly a) { //if the assembly is dynamic, do not try to scan it if (a.IsDynamic) @@ -569,12 +471,12 @@ namespace Umbraco.Core.Composing if (AcceptsLoadExceptions(a) == false) throw ex; // log a warning, and return what we can - lock (NotifiedLoadExceptionAssemblies) + lock (_notifiedLoadExceptionAssemblies) { - if (NotifiedLoadExceptionAssemblies.Contains(a.FullName) == false) + if (_notifiedLoadExceptionAssemblies.Contains(a.FullName) == false) { - NotifiedLoadExceptionAssemblies.Add(a.FullName); - Current.Logger.Warn(typeof (TypeFinder), ex, "Could not load all types from {TypeName}.", a.GetName().Name); + _notifiedLoadExceptionAssemblies.Add(a.FullName); + _logger.Warn(typeof (TypeFinder), ex, "Could not load all types from {TypeName}.", a.GetName().Name); } } return rex.Types.WhereNotNull().ToArray(); @@ -595,8 +497,7 @@ namespace Umbraco.Core.Composing sb.Append(". "); sb.Append(loaderException.GetType().FullName); - var tloadex = loaderException as TypeLoadException; - if (tloadex != null) + if (loaderException is TypeLoadException tloadex) { sb.Append(" on "); sb.Append(tloadex.TypeName); @@ -609,24 +510,5 @@ namespace Umbraco.Core.Composing #endregion - public static Type GetTypeByName(string typeName) - { - var type = BuildManager.GetType(typeName, false); - if (type != null) return type; - - // TODO: This isn't very elegant, and will have issues since the AppDomain.CurrentDomain - // doesn't actualy load in all assemblies, only the types that have been referenced so far. - // However, in a web context, the BuildManager will have executed which will force all assemblies - // to be loaded so it's fine for now. - // It could be fairly easy to parse the typeName to get the assembly name and then do Assembly.Load and - // find the type from there. - - //now try fall back procedures. - type = Type.GetType(typeName); - if (type != null) return type; - return AppDomain.CurrentDomain.GetAssemblies() - .Select(x => x.GetType(typeName)) - .FirstOrDefault(x => x != null); - } } } diff --git a/src/Umbraco.Core/Composing/TypeFinderExtensions.cs b/src/Umbraco.Core/Composing/TypeFinderExtensions.cs new file mode 100644 index 0000000000..e364790556 --- /dev/null +++ b/src/Umbraco.Core/Composing/TypeFinderExtensions.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + public static class TypeFinderExtensions + { + /// + /// Finds any classes derived from the type T that contain the attribute TAttribute + /// + /// + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfTypeWithAttribute(this ITypeFinder typeFinder, IEnumerable assemblies = null, bool onlyConcreteClasses = true) + where TAttribute : Attribute + => typeFinder.FindClassesOfTypeWithAttribute(typeof(T), typeof(TAttribute), assemblies, onlyConcreteClasses); + + /// + /// Returns all types found of in the assemblies specified of type T + /// + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfType(this ITypeFinder typeFinder, IEnumerable assemblies = null, bool onlyConcreteClasses = true) + => typeFinder.FindClassesOfType(typeof(T), assemblies, onlyConcreteClasses); + + /// + /// Finds the classes with attribute. + /// + /// + /// + /// The assemblies. + /// if set to true only concrete classes. + /// + public static IEnumerable FindClassesWithAttribute(this ITypeFinder typeFinder, IEnumerable assemblies = null, bool onlyConcreteClasses = true) + where T : Attribute + => typeFinder.FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); + } +} diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index fe7a561eca..daa95ede49 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Composing /// Provides methods to find and instantiate types. /// /// - /// This class should be used to get all types, the class should never be used directly. + /// This class should be used to get all types, the class should never be used directly. /// In most cases this class is not used directly but through extension methods that retrieve specific types. /// This class caches the types it knows to avoid excessive assembly scanning and shorten startup times, relying /// on a hash of the DLLs in the ~/bin folder to check for cache expiration. @@ -48,22 +48,25 @@ namespace Umbraco.Core.Composing /// /// Initializes a new instance of the class. /// + /// /// The application runtime cache. /// Files storage location. /// A profiling logger. - public TypeLoader(IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger) - : this(runtimeCache, localTempPath, logger, true) + public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger) + : this(typeFinder, runtimeCache, localTempPath, logger, true) { } /// /// Initializes a new instance of the class. /// + /// /// The application runtime cache. /// Files storage location. /// A profiling logger. /// Whether to detect changes using hashes. - internal TypeLoader(IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, bool detectChanges) + internal TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, bool detectChanges) { + TypeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); _runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache)); _localTempPath = localTempPath; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -105,6 +108,12 @@ namespace Umbraco.Core.Composing internal TypeLoader() { } + /// + /// Returns the underlying + /// + // ReSharper disable once MemberCanBePrivate.Global + public ITypeFinder TypeFinder { get; } + /// /// Gets or sets the set of assemblies to scan. /// @@ -117,7 +126,7 @@ namespace Umbraco.Core.Composing // internal for tests internal IEnumerable AssembliesToScan { - get => _assemblies ?? (_assemblies = TypeFinder.GetAssembliesWithKnownExclusions()); + get => _assemblies ?? (_assemblies = TypeFinder.AssembliesToScan); set => _assemblies = value; } diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 5b069641c4..8ccb740cc2 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -111,8 +111,9 @@ namespace Umbraco.Core.Runtime // configs var configs = GetConfigs(); - // type loader - var typeLoader = new TypeLoader(appCaches.RuntimeCache, configs.Global().LocalTempPath, ProfilingLogger); + // type finder/loader + var typeFinder = new TypeFinder(Logger); + var typeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, configs.Global().LocalTempPath, ProfilingLogger); // runtime state // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance' diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5f18cb3d95..87f20eb070 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -125,11 +125,8 @@ --> - - - @@ -142,7 +139,9 @@ + + diff --git a/src/Umbraco.Examine/LuceneIndexCreator.cs b/src/Umbraco.Examine/LuceneIndexCreator.cs index 806d0edc7a..97a929c9c8 100644 --- a/src/Umbraco.Examine/LuceneIndexCreator.cs +++ b/src/Umbraco.Examine/LuceneIndexCreator.cs @@ -38,7 +38,7 @@ namespace Umbraco.Examine if (!configuredDirectoryFactory.IsNullOrWhiteSpace()) { //this should be a fully qualified type - var factoryType = TypeFinder.GetTypeByName(configuredDirectoryFactory); + var factoryType = TypeHelper.GetTypeByName(configuredDirectoryFactory); if (factoryType == null) throw new NullReferenceException("No directory type found for value: " + configuredDirectoryFactory); var directoryFactory = (IDirectoryFactory)Activator.CreateInstance(factoryType); return directoryFactory.CreateDirectory(dirInfo); diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 2ba94d8c78..2bd0d5d471 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -361,7 +361,8 @@ namespace Umbraco.Tests.Components [Test] public void AllComposers() { - var typeLoader = new TypeLoader(AppCaches.Disabled.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of()); + var typeLoader = new TypeLoader(typeFinder, AppCaches.Disabled.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), Mock.Of()); var register = MockRegister(); var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); diff --git a/src/Umbraco.Tests/Composing/ComposingTestBase.cs b/src/Umbraco.Tests/Composing/ComposingTestBase.cs index ef364afd38..4b5be30cd9 100644 --- a/src/Umbraco.Tests/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests/Composing/ComposingTestBase.cs @@ -22,7 +22,8 @@ namespace Umbraco.Tests.Composing { ProfilingLogger = new ProfilingLogger(Mock.Of(), Mock.Of()); - TypeLoader = new TypeLoader(NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), ProfilingLogger, detectChanges: false) + var typeFinder = new TypeFinder(Mock.Of()); + TypeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), ProfilingLogger, detectChanges: false) { AssembliesToScan = AssembliesToScan }; diff --git a/src/Umbraco.Tests/Composing/CompositionTests.cs b/src/Umbraco.Tests/Composing/CompositionTests.cs index 33855a8bfb..9668d7619a 100644 --- a/src/Umbraco.Tests/Composing/CompositionTests.cs +++ b/src/Umbraco.Tests/Composing/CompositionTests.cs @@ -36,7 +36,8 @@ namespace Umbraco.Tests.Composing .Returns(() => factoryFactory?.Invoke(mockedFactory)); var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var typeLoader = new TypeLoader(Mock.Of(), IOHelper.MapPath("~/App_Data/TEMP"), logger); + var typeFinder = new TypeFinder(Mock.Of()); + var typeLoader = new TypeLoader(typeFinder, Mock.Of(), IOHelper.MapPath("~/App_Data/TEMP"), logger); var composition = new Composition(mockedRegister, typeLoader, logger, Mock.Of()); // create the factory, ensure it is the mocked factory diff --git a/src/Umbraco.Tests/Composing/TypeFinderTests.cs b/src/Umbraco.Tests/Composing/TypeFinderTests.cs index ca622e9288..fd070dee2a 100644 --- a/src/Umbraco.Tests/Composing/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeFinderTests.cs @@ -10,6 +10,7 @@ using System.Text; using System.Threading; using System.Web; using System.Web.Compilation; +using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Composing; @@ -56,39 +57,19 @@ namespace Umbraco.Tests.Composing [Test] public void Find_Class_Of_Type_With_Attribute() { - - var typesFound = TypeFinder.FindClassesOfTypeWithAttribute(_assemblies); + var typeFinder = new TypeFinder(GetTestProfilingLogger()); + var typesFound = typeFinder.FindClassesOfTypeWithAttribute(_assemblies); Assert.AreEqual(2, typesFound.Count()); } - //[Test] - //public void Find_Classes_Of_Type() - //{ - // var typesFound = TypeFinder.FindClassesOfType(_assemblies); - // var originalTypesFound = TypeFinderOriginal.FindClassesOfType(_assemblies); - - // foreach (var type in typesFound) - // Console.WriteLine(type); - // Console.WriteLine(); - // foreach (var type in originalTypesFound) - // Console.WriteLine(type); - - // // 6 classes in _assemblies implement IApplicationEventHandler - // Assert.AreEqual(6, typesFound.Count()); - - // // however, - // // Umbraco.Core.Profiling.WebProfiler is internal and is not returned by TypeFinderOriginal, - // // that's a known issue of the legacy type finder, so we have to tweak the count here. - // Assert.AreEqual(5, originalTypesFound.Count()); - //} - [Test] public void Find_Classes_With_Attribute() { - var typesFound = TypeFinder.FindClassesWithAttribute(_assemblies); + var typeFinder = new TypeFinder(GetTestProfilingLogger()); + var typesFound = typeFinder.FindClassesWithAttribute(_assemblies); Assert.AreEqual(0, typesFound.Count()); // 0 classes in _assemblies are marked with [Tree] - typesFound = TypeFinder.FindClassesWithAttribute(new[] { typeof (UmbracoContext).Assembly }); + typesFound = typeFinder.FindClassesWithAttribute(new[] { typeof (UmbracoContext).Assembly }); Assert.AreEqual(22, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] } @@ -122,447 +103,6 @@ namespace Umbraco.Tests.Composing } - //USED FOR THE ABOVE TESTS - // see this issue for details: http://issues.umbraco.org/issue/U4-1187 - internal static class TypeFinderOriginal - { - - private static readonly ConcurrentBag LocalFilteredAssemblyCache = new ConcurrentBag(); - private static readonly ReaderWriterLockSlim LocalFilteredAssemblyCacheLocker = new ReaderWriterLockSlim(); - private static ReadOnlyCollection _allAssemblies = null; - private static ReadOnlyCollection _binFolderAssemblies = null; - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); - - /// - /// lazily load a reference to all assemblies and only local assemblies. - /// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder - /// - /// - /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been - /// loaded in the CLR, not all assemblies. - /// See these threads: - /// http://issues.umbraco.org/issue/U5-198 - /// http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app - /// http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl - /// - internal static IEnumerable GetAllAssemblies() - { - if (_allAssemblies == null) - { - using (new WriteLock(Locker)) - { - List assemblies = null; - try - { - var isHosted = HttpContext.Current != null; - - try - { - if (isHosted) - { - assemblies = new List(BuildManager.GetReferencedAssemblies().Cast()); - } - } - catch (InvalidOperationException e) - { - if (!(e.InnerException is SecurityException)) - throw; - } - - - if (assemblies == null) - { - //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have - // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. - var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; - var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - assemblies = new List(); - foreach (var a in binAssemblyFiles) - { - try - { - var assName = AssemblyName.GetAssemblyName(a); - var ass = Assembly.Load(assName); - assemblies.Add(ass); - } - catch (Exception e) - { - if (e is SecurityException || e is BadImageFormatException) - { - //swallow these exceptions - } - else - { - throw; - } - } - } - } - - //if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies. - if (!assemblies.Any()) - { - assemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies().ToList()); - } - - //here we are trying to get the App_Code assembly - var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported - var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); - //check if the folder exists and if there are any files in it with the supported file extensions - if (appCodeFolder.Exists && (fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any()))) - { - var appCodeAssembly = Assembly.Load("App_Code"); - if (!assemblies.Contains(appCodeAssembly)) // BuildManager will find App_Code already - assemblies.Add(appCodeAssembly); - } - - //now set the _allAssemblies - _allAssemblies = new ReadOnlyCollection(assemblies); - - } - catch (InvalidOperationException e) - { - if (!(e.InnerException is SecurityException)) - throw; - - _binFolderAssemblies = _allAssemblies; - } - } - } - - return _allAssemblies; - } - - /// - /// Returns only assemblies found in the bin folder that have been loaded into the app domain. - /// - /// - /// - /// This will be used if we implement App_Plugins from Umbraco v5 but currently it is not used. - /// - internal static IEnumerable GetBinAssemblies() - { - - if (_binFolderAssemblies == null) - { - using (new WriteLock(Locker)) - { - var assemblies = GetAssembliesWithKnownExclusions().ToArray(); - var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; - var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - var domainAssemblyNames = binAssemblyFiles.Select(AssemblyName.GetAssemblyName); - var safeDomainAssemblies = new List(); - var binFolderAssemblies = new List(); - - foreach (var a in assemblies) - { - try - { - //do a test to see if its queryable in med trust - var assemblyFile = a.GetAssemblyFile(); - safeDomainAssemblies.Add(a); - } - catch (SecurityException) - { - //we will just ignore this because this will fail - //in medium trust for system assemblies, we get an exception but we just want to continue until we get to - //an assembly that is ok. - } - } - - foreach (var assemblyName in domainAssemblyNames) - { - try - { - var foundAssembly = safeDomainAssemblies.FirstOrDefault(a => a.GetAssemblyFile() == assemblyName.GetAssemblyFile()); - if (foundAssembly != null) - { - binFolderAssemblies.Add(foundAssembly); - } - } - catch (SecurityException) - { - //we will just ignore this because if we are trying to do a call to: - // AssemblyName.ReferenceMatchesDefinition(a.GetName(), assemblyName))) - //in medium trust for system assemblies, we get an exception but we just want to continue until we get to - //an assembly that is ok. - } - } - - _binFolderAssemblies = new ReadOnlyCollection(binFolderAssemblies); - } - } - return _binFolderAssemblies; - } - - /// - /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan - /// and exluding the ones passed in and excluding the exclusion list filter, the results of this are - /// cached for perforance reasons. - /// - /// - /// - internal static IEnumerable GetAssembliesWithKnownExclusions( - IEnumerable excludeFromResults = null) - { - if (LocalFilteredAssemblyCache.Any()) return LocalFilteredAssemblyCache; - using (new WriteLock(LocalFilteredAssemblyCacheLocker)) - { - var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); - foreach (var assembly in assemblies) LocalFilteredAssemblyCache.Add(assembly); - } - return LocalFilteredAssemblyCache; - } - - /// - /// Return a list of found local Assemblies and exluding the ones passed in and excluding the exclusion list filter - /// - /// - /// - /// - private static IEnumerable GetFilteredAssemblies( - IEnumerable excludeFromResults = null, - string[] exclusionFilter = null) - { - if (excludeFromResults == null) - excludeFromResults = new List(); - if (exclusionFilter == null) - exclusionFilter = new string[] { }; - - return GetAllAssemblies() - .Where(x => !excludeFromResults.Contains(x) - && !x.GlobalAssemblyCache - && !exclusionFilter.Any(f => x.FullName.StartsWith(f))); - } - - /// - /// this is our assembly filter to filter out known types that def dont contain types we'd like to find or plugins - /// - /// - /// NOTE the comma vs period... comma delimits the name in an Assembly FullName property so if it ends with comma then its an exact name match - /// - internal static readonly string[] KnownAssemblyExclusionFilter = new[] - { - "mscorlib,", - "System.", - "Antlr3.", - "Autofac.", - "Autofac,", - "Castle.", - "ClientDependency.", - "DataAnnotationsExtensions.", - "DataAnnotationsExtensions,", - "Dynamic,", - "HtmlDiff,", - "Iesi.Collections,", - "Microsoft.", - "Newtonsoft.", - "NHibernate.", - "NHibernate,", - "NuGet.", - "RouteDebugger,", - "SqlCE4Umbraco,", - "umbraco.datalayer,", - "umbraco.interfaces,", - "umbraco.providers,", - "Umbraco.Web.UI,", - "Lucene.", - "Examine,", - "Examine.", - "ServiceStack.", - "MySql.", - "HtmlAgilityPack.", - "TidyNet.", - "ICSharpCode.", - "CookComputing.", - /* Mono */ - "MonoDevelop.NUnit", - "Serilog." - }; - - public static IEnumerable FindClassesOfTypeWithAttribute() - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(GetAssembliesWithKnownExclusions(), true); - } - - public static IEnumerable FindClassesOfTypeWithAttribute(IEnumerable assemblies) - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(assemblies, true); - } - - public static IEnumerable FindClassesOfTypeWithAttribute(IEnumerable assemblies, bool onlyConcreteClasses) - where TAttribute : Attribute - { - if (assemblies == null) throw new ArgumentNullException("assemblies"); - - var l = new List(); - foreach (var a in assemblies) - { - var types = from t in GetTypesWithFormattedException(a) - where !t.IsInterface - && typeof(T).IsAssignableFrom(t) - && t.GetCustomAttributes(false).Any() - && (!onlyConcreteClasses || (t.IsClass && !t.IsAbstract)) - select t; - l.AddRange(types); - } - - return l; - } - - /// - /// Searches all filtered local assemblies specified for classes of the type passed in. - /// - /// - /// - public static IEnumerable FindClassesOfType() - { - return FindClassesOfType(GetAssembliesWithKnownExclusions(), true); - } - - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfType(IEnumerable assemblies, bool onlyConcreteClasses) - { - if (assemblies == null) throw new ArgumentNullException("assemblies"); - - return GetAssignablesFromType(assemblies, onlyConcreteClasses); - } - - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - public static IEnumerable FindClassesOfType(IEnumerable assemblies) - { - return FindClassesOfType(assemblies, true); - } - - /// - /// Finds the classes with attribute. - /// - /// - /// The assemblies. - /// if set to true only concrete classes. - /// - public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies, bool onlyConcreteClasses) - where T : Attribute - { - return FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); - } - - /// - /// Finds the classes with attribute. - /// - /// The attribute type - /// The assemblies. - /// if set to true only concrete classes. - /// - public static IEnumerable FindClassesWithAttribute(Type type, IEnumerable assemblies, bool onlyConcreteClasses) - { - if (assemblies == null) throw new ArgumentNullException("assemblies"); - if (!TypeHelper.IsTypeAssignableFrom(type)) - throw new ArgumentException("The type specified: " + type + " is not an Attribute type"); - - var l = new List(); - foreach (var a in assemblies) - { - var types = from t in GetTypesWithFormattedException(a) - where !t.IsInterface && t.GetCustomAttributes(type, false).Any() && (!onlyConcreteClasses || (t.IsClass && !t.IsAbstract)) - select t; - l.AddRange(types); - } - - return l; - } - - /// - /// Finds the classes with attribute. - /// - /// - /// The assemblies. - /// - public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies) - where T : Attribute - { - return FindClassesWithAttribute(assemblies, true); - } - - /// - /// Finds the classes with attribute in filtered local assemblies - /// - /// - /// - public static IEnumerable FindClassesWithAttribute() - where T : Attribute - { - return FindClassesWithAttribute(GetAssembliesWithKnownExclusions()); - } - - - #region Private methods - - /// - /// Gets a collection of assignables of type T from a collection of assemblies - /// - /// - /// - /// - /// - private static IEnumerable GetAssignablesFromType(IEnumerable assemblies, bool onlyConcreteClasses) - { - return GetTypes(typeof(T), assemblies, onlyConcreteClasses); - } - - private static IEnumerable GetTypes(Type assignTypeFrom, IEnumerable assemblies, bool onlyConcreteClasses) - { - var l = new List(); - foreach (var a in assemblies) - { - var types = from t in GetTypesWithFormattedException(a) - where !t.IsInterface && assignTypeFrom.IsAssignableFrom(t) && (!onlyConcreteClasses || (t.IsClass && !t.IsAbstract)) - select t; - l.AddRange(types); - } - return l; - } - - private static IEnumerable GetTypesWithFormattedException(Assembly a) - { - //if the assembly is dynamic, do not try to scan it - if (a.IsDynamic) - return Enumerable.Empty(); - - try - { - return a.GetExportedTypes(); - } - catch (ReflectionTypeLoadException ex) - { - var sb = new StringBuilder(); - sb.AppendLine("Could not load types from assembly " + a.FullName + ", errors:"); - foreach (var loaderException in ex.LoaderExceptions.WhereNotNull()) - { - sb.AppendLine("Exception: " + loaderException.ToString()); - } - throw new ReflectionTypeLoadException(ex.Types, ex.LoaderExceptions, sb.ToString()); - } - } - - #endregion - - - - } } diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 7459ae848b..b135405615 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -27,7 +27,8 @@ namespace Umbraco.Tests.Composing public void Initialize() { // this ensures it's reset - _typeLoader = new TypeLoader(NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()), false); + var typeFinder = new TypeFinder(Mock.Of()); + _typeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()), false); // for testing, we'll specify which assemblies are scanned for the PluginTypeResolver // TODO: Should probably update this so it only searches this assembly and add custom types to be found diff --git a/src/Umbraco.Tests/CoreThings/UdiTests.cs b/src/Umbraco.Tests/CoreThings/UdiTests.cs index 2770803bea..a6b87ad57b 100644 --- a/src/Umbraco.Tests/CoreThings/UdiTests.cs +++ b/src/Umbraco.Tests/CoreThings/UdiTests.cs @@ -26,8 +26,9 @@ namespace Umbraco.Tests.CoreThings // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var container = new Mock(); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); + var typeFinder = new TypeFinder(Mock.Of()); container.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( - new TypeLoader(NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()))); + new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()))); Current.Factory = container.Object; Udi.ResetUdiTypes(); diff --git a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs index 9db539d142..2f009d147e 100644 --- a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs +++ b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs @@ -144,39 +144,39 @@ namespace Umbraco.Tests.Published Assert.AreEqual(1, converter.SourceConverts); Assert.AreEqual(1, converter.InterConverts); - Assert.AreEqual(elementsCount1, elementsCache.Items.Count); - Assert.AreEqual(snapshotCount1, snapshotCache.Items.Count); + Assert.AreEqual(elementsCount1, elementsCache.Count); + Assert.AreEqual(snapshotCount1, snapshotCache.Count); Assert.AreEqual(1234, set1.Value("prop1")); Assert.AreEqual(1, converter.SourceConverts); Assert.AreEqual(interConverts, converter.InterConverts); - Assert.AreEqual(elementsCount2, elementsCache.Items.Count); - Assert.AreEqual(snapshotCount2, snapshotCache.Items.Count); + Assert.AreEqual(elementsCount2, elementsCache.Count); + Assert.AreEqual(snapshotCount2, snapshotCache.Count); var oldSnapshotCache = snapshotCache; - snapshotCache.Items.Clear(); + snapshotCache.Clear(); Assert.AreEqual(1234, set1.Value("prop1")); Assert.AreEqual(1, converter.SourceConverts); - Assert.AreEqual(elementsCount2, elementsCache.Items.Count); - Assert.AreEqual(snapshotCount2, snapshotCache.Items.Count); - Assert.AreEqual(snapshotCount2, oldSnapshotCache.Items.Count); + Assert.AreEqual(elementsCount2, elementsCache.Count); + Assert.AreEqual(snapshotCount2, snapshotCache.Count); + Assert.AreEqual(snapshotCount2, oldSnapshotCache.Count); - Assert.AreEqual((interConverts == 1 ? 1 : 3) + snapshotCache.Items.Count, converter.InterConverts); + Assert.AreEqual((interConverts == 1 ? 1 : 3) + snapshotCache.Count, converter.InterConverts); var oldElementsCache = elementsCache; - elementsCache.Items.Clear(); + elementsCache.Clear(); Assert.AreEqual(1234, set1.Value("prop1")); Assert.AreEqual(1, converter.SourceConverts); - Assert.AreEqual(elementsCount2, elementsCache.Items.Count); - Assert.AreEqual(elementsCount2, oldElementsCache.Items.Count); - Assert.AreEqual(snapshotCount2, snapshotCache.Items.Count); + Assert.AreEqual(elementsCount2, elementsCache.Count); + Assert.AreEqual(elementsCount2, oldElementsCache.Count); + Assert.AreEqual(snapshotCount2, snapshotCache.Count); - Assert.AreEqual((interConverts == 1 ? 1 : 4) + snapshotCache.Items.Count + elementsCache.Items.Count, converter.InterConverts); + Assert.AreEqual((interConverts == 1 ? 1 : 4) + snapshotCache.Count + elementsCache.Count, converter.InterConverts); } [Test] diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 1f1500137f..a205478246 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -61,7 +61,8 @@ namespace Umbraco.Tests.Runtimes var profilingLogger = new ProfilingLogger(logger, profiler); var appCaches = AppCaches.Disabled; var databaseFactory = new UmbracoDatabaseFactory(logger, new Lazy(() => factory.GetInstance())); - var typeLoader = new TypeLoader(appCaches.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), profilingLogger); + var typeFinder = new TypeFinder(logger); + var typeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), profilingLogger); var mainDom = new SimpleMainDom(); var runtimeState = new RuntimeState(logger, null, null, new Lazy(() => mainDom), new Lazy(() => factory.GetInstance())); @@ -249,7 +250,8 @@ namespace Umbraco.Tests.Runtimes var profilingLogger = new ProfilingLogger(logger, profiler); var appCaches = AppCaches.Disabled; var databaseFactory = Mock.Of(); - var typeLoader = new TypeLoader(appCaches.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), profilingLogger); + var typeFinder = new TypeFinder(Mock.Of()); + var typeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), profilingLogger); var runtimeState = Mock.Of(); Mock.Get(runtimeState).Setup(x => x.Level).Returns(RuntimeLevel.Run); var mainDom = Mock.Of(); diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index f62effcb62..a2f08604b5 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -37,7 +37,8 @@ namespace Umbraco.Tests.TestHelpers var container = RegisterFactory.Create(); var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var typeLoader = new TypeLoader(NoAppCache.Instance, + var typeFinder = new TypeFinder(Mock.Of()); + var typeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), logger, false); diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs index e27fe17bbe..273f7a996e 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Web.Mvc; using System.Web.Routing; using System.Web.SessionState; +using Moq; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Composing; @@ -38,7 +39,8 @@ namespace Umbraco.Tests.TestHelpers.Stubs { if (_factory != null) return _factory(requestContext); - var types = TypeFinder.FindClassesOfType(new[] { Assembly.GetExecutingAssembly() }); + var typeFinder = new TypeFinder(Mock.Of()); + var types = typeFinder.FindClassesOfType(new[] { Assembly.GetExecutingAssembly() }); var controllerTypes = types.Where(x => x.Name.Equals(controllerName + "Controller", StringComparison.InvariantCultureIgnoreCase)); var t = controllerTypes.SingleOrDefault(); diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index b9fd0f6640..4a911680ab 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -288,7 +288,8 @@ namespace Umbraco.Tests.Testing // common to all tests = cannot be overriden private static TypeLoader CreateCommonTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - return new TypeLoader(runtimeCache, globalSettings.LocalTempPath, logger, false) + var typeFinder = new TypeFinder(Mock.Of()); + return new TypeLoader(typeFinder, runtimeCache, globalSettings.LocalTempPath, logger, false) { AssembliesToScan = new[] { diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index 3a5405548b..907c5efdd2 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -39,8 +39,9 @@ namespace Umbraco.Tests.Web // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var factory = new Mock(); + var typeFinder = new TypeFinder(Mock.Of()); factory.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( - new TypeLoader(NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()))); + new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()))); factory.Setup(x => x.GetInstance(typeof (ServiceContext))).Returns(serviceContext); var settings = SettingsForTests.GetDefaultUmbracoSettings(); diff --git a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs index 26d85f60cf..c1147ed5a7 100644 --- a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs @@ -260,10 +260,11 @@ namespace Umbraco.Tests.Web // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var container = new Mock(); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); - + var typeFinder = new TypeFinder(Mock.Of()); container .Setup(x => x.GetInstance(typeof(TypeLoader))) .Returns(new TypeLoader( + typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 7113abd6eb..aa8f735cf2 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -486,6 +486,7 @@ namespace Umbraco.Web.Editors /// /// /// + /// /// public PagedResult GetPagedChildren( string id, From 74d12a4d62493c3b252eaa1e3520e02f75aecd5d Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Nov 2019 14:51:20 +1100 Subject: [PATCH 03/36] Moves more cache items to the abstractions project, decouples the HttpRequestAppCache from System.Web --- .../Cache/CacheRefresherCollectionBuilder.cs | 0 .../Cache/FastDictionaryAppCache.cs | 38 +++++------ .../Cache/HttpRequestAppCache.cs | 67 +++++++++---------- src/Umbraco.Core/Umbraco.Core.csproj | 2 - .../Cache/HttpRequestAppCacheTests.cs | 2 +- src/Umbraco.Web/Runtime/WebRuntime.cs | 2 +- 6 files changed, 51 insertions(+), 60 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Cache/CacheRefresherCollectionBuilder.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Cache/HttpRequestAppCache.cs (61%) diff --git a/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs b/src/Umbraco.Abstractions/Cache/CacheRefresherCollectionBuilder.cs similarity index 100% rename from src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs rename to src/Umbraco.Abstractions/Cache/CacheRefresherCollectionBuilder.cs diff --git a/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs index 3bd29c3f5f..35ba4d08ca 100644 --- a/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs @@ -15,21 +15,21 @@ namespace Umbraco.Core.Cache /// /// Gets the internal items dictionary, for tests only! /// - internal readonly ConcurrentDictionary> Items = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> _items = new ConcurrentDictionary>(); - public int Count => Items.Count; + public int Count => _items.Count; /// public object Get(string cacheKey) { - Items.TryGetValue(cacheKey, out var result); // else null + _items.TryGetValue(cacheKey, out var result); // else null return result == null ? null : SafeLazy.GetSafeLazyValue(result); // return exceptions as null } /// public object Get(string cacheKey, Func getCacheItem) { - var result = Items.GetOrAdd(cacheKey, k => SafeLazy.GetSafeLazy(getCacheItem)); + var result = _items.GetOrAdd(cacheKey, k => SafeLazy.GetSafeLazy(getCacheItem)); var value = result.Value; // will not throw (safe lazy) if (!(value is SafeLazy.ExceptionHolder eh)) @@ -39,7 +39,7 @@ namespace Umbraco.Core.Cache // which would trick with GetSafeLazyValue, we need to remove by ourselves, // in order NOT to cache exceptions - Items.TryRemove(cacheKey, out result); + _items.TryRemove(cacheKey, out result); eh.Exception.Throw(); // throw once! return null; // never reached } @@ -47,7 +47,7 @@ namespace Umbraco.Core.Cache /// public IEnumerable SearchByKey(string keyStartsWith) { - return Items + return _items .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)) .Select(kvp => SafeLazy.GetSafeLazyValue(kvp.Value)) .Where(x => x != null); @@ -57,7 +57,7 @@ namespace Umbraco.Core.Cache public IEnumerable SearchByRegex(string regex) { var compiled = new Regex(regex, RegexOptions.Compiled); - return Items + return _items .Where(kvp => compiled.IsMatch(kvp.Key)) .Select(kvp => SafeLazy.GetSafeLazyValue(kvp.Value)) .Where(x => x != null); @@ -66,13 +66,13 @@ namespace Umbraco.Core.Cache /// public void Clear() { - Items.Clear(); + _items.Clear(); } /// public void Clear(string key) { - Items.TryRemove(key, out _); + _items.TryRemove(key, out _); } /// @@ -82,7 +82,7 @@ namespace Umbraco.Core.Cache if (type == null) return; var isInterface = type.IsInterface; - foreach (var kvp in Items + foreach (var kvp in _items .Where(x => { // entry.Value is Lazy and not null, its value may be null @@ -94,7 +94,7 @@ namespace Umbraco.Core.Cache // otherwise remove exact types (not inherited types) return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type)); })) - Items.TryRemove(kvp.Key, out _); + _items.TryRemove(kvp.Key, out _); } /// @@ -103,7 +103,7 @@ namespace Umbraco.Core.Cache var typeOfT = typeof(T); var isInterface = typeOfT.IsInterface; - foreach (var kvp in Items + foreach (var kvp in _items .Where(x => { // entry.Value is Lazy and not null, its value may be null @@ -116,7 +116,7 @@ namespace Umbraco.Core.Cache // otherwise remove exact types (not inherited types) return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT)); })) - Items.TryRemove(kvp.Key, out _); + _items.TryRemove(kvp.Key, out _); } /// @@ -125,7 +125,7 @@ namespace Umbraco.Core.Cache var typeOfT = typeof(T); var isInterface = typeOfT.IsInterface; - foreach (var kvp in Items + foreach (var kvp in _items .Where(x => { // entry.Value is Lazy and not null, its value may be null @@ -141,24 +141,24 @@ namespace Umbraco.Core.Cache // run predicate on the 'public key' part only, ie without prefix && predicate(x.Key, (T)value); })) - Items.TryRemove(kvp.Key, out _); + _items.TryRemove(kvp.Key, out _); } /// public void ClearByKey(string keyStartsWith) { - foreach (var ikvp in Items + foreach (var ikvp in _items .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith))) - Items.TryRemove(ikvp.Key, out _); + _items.TryRemove(ikvp.Key, out _); } /// public void ClearByRegex(string regex) { var compiled = new Regex(regex, RegexOptions.Compiled); - foreach (var ikvp in Items + foreach (var ikvp in _items .Where(kvp => compiled.IsMatch(kvp.Key))) - Items.TryRemove(ikvp.Key, out _); + _items.TryRemove(ikvp.Key, out _); } } } diff --git a/src/Umbraco.Core/Cache/HttpRequestAppCache.cs b/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs similarity index 61% rename from src/Umbraco.Core/Cache/HttpRequestAppCache.cs rename to src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs index 018726538b..4fad382cc8 100644 --- a/src/Umbraco.Core/Cache/HttpRequestAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs @@ -15,37 +15,29 @@ namespace Umbraco.Core.Cache /// or no Items...) then this cache acts as a pass-through and does not cache /// anything. /// - internal class HttpRequestAppCache : FastDictionaryAppCacheBase + public class HttpRequestAppCache : FastDictionaryAppCacheBase { - private readonly HttpContextBase _context; - /// /// Initializes a new instance of the class with a context, for unit tests! /// - public HttpRequestAppCache(HttpContextBase context) + public HttpRequestAppCache(Func requestItems) { - _context = context; + ContextItems = requestItems; } - /// - /// Initializes a new instance of the class. - /// - /// - /// Will use HttpContext.Current. - /// TODO: https://github.com/umbraco/Umbraco-CMS/issues/4239 - use IHttpContextAccessor NOT HttpContext.Current - /// - public HttpRequestAppCache() - { } + private Func ContextItems { get; } - private IDictionary ContextItems => _context?.Items ?? HttpContext.Current?.Items; - - private bool HasContextItems => _context?.Items != null || HttpContext.Current != null; + private bool TryGetContextItems(out IDictionary items) + { + items = ContextItems?.Invoke(); + return items != null; + } /// public override object Get(string key, Func factory) { //no place to cache so just return the callback result - if (HasContextItems == false) return factory(); + if (!TryGetContextItems(out var items)) return factory(); key = GetCacheKey(key); @@ -54,7 +46,7 @@ namespace Umbraco.Core.Cache try { EnterWriteLock(); - result = ContextItems[key] as Lazy; // null if key not found + result = items[key] as Lazy; // null if key not found // cannot create value within the lock, so if result.IsValueCreated is false, just // do nothing here - means that if creation throws, a race condition could cause @@ -63,7 +55,7 @@ namespace Umbraco.Core.Cache if (result == null || SafeLazy.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null { result = SafeLazy.GetSafeLazy(factory); - ContextItems[key] = result; + items[key] = result; } } finally @@ -89,22 +81,22 @@ namespace Umbraco.Core.Cache { const string prefix = CacheItemPrefix + "-"; - if (HasContextItems == false) return Enumerable.Empty(); + if (!TryGetContextItems(out var items)) return Enumerable.Empty(); - return ContextItems.Cast() + return items.Cast() .Where(x => x.Key is string s && s.StartsWith(prefix)); } protected override void RemoveEntry(string key) { - if (HasContextItems == false) return; + if (!TryGetContextItems(out var items)) return; - ContextItems.Remove(key); + items.Remove(key); } protected override object GetEntry(string key) { - return HasContextItems ? ContextItems[key] : null; + return !TryGetContextItems(out var items) ? null : items[key]; } #endregion @@ -117,26 +109,27 @@ namespace Umbraco.Core.Cache protected override void EnterWriteLock() { - if (HasContextItems) - { - // note: cannot keep 'entered' as a class variable here, - // since there is one per request - so storing it within - // ContextItems - which is locked, so this should be safe + if (!TryGetContextItems(out var items)) return; - var entered = false; - Monitor.Enter(ContextItems.SyncRoot, ref entered); - ContextItems[ContextItemsLockKey] = entered; - } + // note: cannot keep 'entered' as a class variable here, + // since there is one per request - so storing it within + // ContextItems - which is locked, so this should be safe + + var entered = false; + Monitor.Enter(items.SyncRoot, ref entered); + items[ContextItemsLockKey] = entered; } protected override void ExitReadLock() => ExitWriteLock(); protected override void ExitWriteLock() { - var entered = (bool?) ContextItems[ContextItemsLockKey] ?? false; + if (!TryGetContextItems(out var items)) return; + + var entered = (bool?)items[ContextItemsLockKey] ?? false; if (entered) - Monitor.Exit(ContextItems.SyncRoot); - ContextItems.Remove(ContextItemsLockKey); + Monitor.Exit(items.SyncRoot); + items.Remove(ContextItemsLockKey); } #endregion diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 87f20eb070..76460493c8 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -126,9 +126,7 @@ --> - - diff --git a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs index 042830e059..81a784fc01 100644 --- a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs @@ -14,7 +14,7 @@ namespace Umbraco.Tests.Cache { base.Setup(); _ctx = new FakeHttpContextFactory("http://localhost/test"); - _appCache = new HttpRequestAppCache(_ctx.HttpContext); + _appCache = new HttpRequestAppCache(() => _ctx.HttpContext.Items); } internal override IAppCache AppCache diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index 13cd717fd1..afbaa36cb4 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -63,7 +63,7 @@ namespace Umbraco.Web.Runtime // all entities are cached properly (cloned in and cloned out) new DeepCloneAppCache(new WebCachingAppCache(HttpRuntime.Cache)), // we need request based cache when running in web-based context - new HttpRequestAppCache(), + new HttpRequestAppCache(() => HttpContext.Current?.Items), new IsolatedCaches(type => // we need to have the dep clone runtime cache provider to ensure // all entities are cached properly (cloned in and cloned out) From db216e43b7708d69345687a7c454195d9a67341b Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Nov 2019 15:00:15 +1100 Subject: [PATCH 04/36] Moves the WebCachingAppCache to the web project --- src/Umbraco.Core/Umbraco.Core.csproj | 1 - src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs | 1 + src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs | 1 + src/{Umbraco.Core => Umbraco.Web}/Cache/WebCachingAppCache.cs | 4 +++- src/Umbraco.Web/Runtime/WebRuntime.cs | 1 + src/Umbraco.Web/Umbraco.Web.csproj | 1 + 6 files changed, 7 insertions(+), 2 deletions(-) rename src/{Umbraco.Core => Umbraco.Web}/Cache/WebCachingAppCache.cs (99%) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 76460493c8..bfffc037ce 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -127,7 +127,6 @@ - diff --git a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs index 13fabe5ca2..c17d29a88f 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Collections; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Tests.Collections; +using Umbraco.Web.Cache; namespace Umbraco.Tests.Cache { diff --git a/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs b/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs index e732ae5766..84a946036a 100644 --- a/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Web; using NUnit.Framework; using Umbraco.Core.Cache; +using Umbraco.Web.Cache; namespace Umbraco.Tests.Cache { diff --git a/src/Umbraco.Core/Cache/WebCachingAppCache.cs b/src/Umbraco.Web/Cache/WebCachingAppCache.cs similarity index 99% rename from src/Umbraco.Core/Cache/WebCachingAppCache.cs rename to src/Umbraco.Web/Cache/WebCachingAppCache.cs index 362df8f603..3d169699f0 100644 --- a/src/Umbraco.Core/Cache/WebCachingAppCache.cs +++ b/src/Umbraco.Web/Cache/WebCachingAppCache.cs @@ -4,8 +4,10 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Web.Caching; +using Umbraco.Core; +using Umbraco.Core.Cache; -namespace Umbraco.Core.Cache +namespace Umbraco.Web.Cache { /// /// Implements on top of a . diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index afbaa36cb4..09446e1dab 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -4,6 +4,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Runtime; +using Umbraco.Web.Cache; using Umbraco.Web.Logging; namespace Umbraco.Web.Runtime diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 317786b970..7097b0e6f5 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -131,6 +131,7 @@ + From c812e3aec92103f9a2f35386e2cbd3a7e541d0dc Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Nov 2019 15:31:38 +1100 Subject: [PATCH 05/36] fixing tests, reverting change to TypeHelper, just leaving it as is but with caching --- .../Composing/TypeHelper.cs | 56 +++---------------- 1 file changed, 8 insertions(+), 48 deletions(-) diff --git a/src/Umbraco.Abstractions/Composing/TypeHelper.cs b/src/Umbraco.Abstractions/Composing/TypeHelper.cs index 768709c44c..1752521b5d 100644 --- a/src/Umbraco.Abstractions/Composing/TypeHelper.cs +++ b/src/Umbraco.Abstractions/Composing/TypeHelper.cs @@ -19,6 +19,8 @@ namespace Umbraco.Core.Composing = new ConcurrentDictionary, PropertyInfo[]>(); private static readonly ConcurrentDictionary GetFieldsCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary TypeNamesCache + = new ConcurrentDictionary(); private static readonly Assembly[] EmptyAssemblies = new Assembly[0]; @@ -29,57 +31,15 @@ namespace Umbraco.Core.Composing /// public static Type GetTypeByName(string name) { + // First try using the basic functionality var type = Type.GetType(name); if (type != null) return type; - // now try fall back procedures. null may be returned because Type.GetName only returns types that have already been loaded in the appdomain - // and this type might not be there yet, so we need to parse the type name and then try to load in via assembly. - - var typeName = TypeName.Parse(name); - try - { - var assembly = Assembly.Load(typeName.AssemblyName); - type = assembly.GetType(typeName.Name); - return type; - } - catch (NotSupportedException) - { - throw; - } - catch (Exception) - { - return null; - } - } - - /// - /// Used to parse a fully qualified type name - /// - /// - /// Does not support generics. - /// This is a simple utility class and is not an exhaustive type name parser (which doesn't exist in .net strangely) - /// - private class TypeName - { - private TypeName(string name, AssemblyName assemblyName) - { - Name = name; - AssemblyName = assemblyName; - } - - public static TypeName Parse(string name) - { - if (name.Contains("[[")) - throw new NotSupportedException($"{nameof(TypeName)} does not support generic types"); - - var index = name.IndexOf(','); - return index > 0 - ? new TypeName(name.Substring(0, index).Trim(), new AssemblyName(name.Substring(index + 1).Trim())) - : new TypeName(name, null); - } - - public string Name { get; } - public AssemblyName AssemblyName { get; } + // It didn't parse, so try loading from each already loaded assembly and cache it + return TypeNamesCache.GetOrAdd(name, s => + AppDomain.CurrentDomain.GetAssemblies() + .Select(x => x.GetType(s)) + .FirstOrDefault(x => x != null)); } /// From 118d50671f02433982b72a2b6b2e6327969df21b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 8 Nov 2019 10:57:24 +0100 Subject: [PATCH 06/36] AB3649 - Moved Manifest stuff --- .../Manifest/ManifestContentAppDefinition.cs | 8 +------- .../Models/EntityContainer.cs | 0 .../IManifestValueValidator.cs | 0 .../PropertyEditors/IValueFormatValidator.cs | 0 .../IValueRequiredValidator.cs | 0 .../PropertyEditors/IValueValidator.cs | 0 .../ManifestValueValidatorCollection.cs | 0 ...ManifestValueValidatorCollectionBuilder.cs | 9 +++++++++ .../Validators/DecimalValidator.cs | 2 +- .../Validators/EmailValidator.cs | 2 +- .../Validators/IntegerValidator.cs | 2 +- .../Umbraco.Abstractions.csproj | 1 + src/Umbraco.Core/Manifest/IManifestFilter.cs | 1 + .../Manifest/ManifestContentAppFactory.cs | 7 +++++-- src/Umbraco.Core/Manifest/ManifestParser.cs | 17 ++++++++++------- ...ManifestValueValidatorCollectionBuilder.cs | 9 --------- .../Validators/RegexValidator.cs | 2 +- .../Validators/RequiredValidator.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 19 ++++--------------- .../Manifest/ManifestContentAppTests.cs | 3 ++- .../Manifest/ManifestParserTests.cs | 2 +- .../ContentAppFactoryCollectionBuilder.cs | 3 ++- 22 files changed, 41 insertions(+), 48 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Manifest/ManifestContentAppDefinition.cs (92%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Models/EntityContainer.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/PropertyEditors/IManifestValueValidator.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/PropertyEditors/IValueFormatValidator.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/PropertyEditors/IValueRequiredValidator.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/PropertyEditors/IValueValidator.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/PropertyEditors/ManifestValueValidatorCollection.cs (100%) create mode 100644 src/Umbraco.Abstractions/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs rename src/{Umbraco.Core => Umbraco.Abstractions}/PropertyEditors/Validators/DecimalValidator.cs (92%) rename src/{Umbraco.Core => Umbraco.Abstractions}/PropertyEditors/Validators/EmailValidator.cs (92%) rename src/{Umbraco.Core => Umbraco.Abstractions}/PropertyEditors/Validators/IntegerValidator.cs (92%) delete mode 100644 src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Abstractions/Manifest/ManifestContentAppDefinition.cs similarity index 92% rename from src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs rename to src/Umbraco.Abstractions/Manifest/ManifestContentAppDefinition.cs index 2aafcd8b74..35293a6377 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Abstractions/Manifest/ManifestContentAppDefinition.cs @@ -1,7 +1,5 @@ using System; using System.Runtime.Serialization; -using Umbraco.Core.Composing; -using Umbraco.Core.IO; namespace Umbraco.Core.Manifest { @@ -65,11 +63,7 @@ namespace Umbraco.Core.Manifest /// Gets or sets the view for rendering the content app. /// [DataMember(Name = "view")] - public string View - { - get => _view; - set => _view = Current.IOHelper.ResolveVirtualUrl(value); - } + public string View { get; set; } /// /// Gets or sets the list of 'show' conditions for the content app. diff --git a/src/Umbraco.Core/Models/EntityContainer.cs b/src/Umbraco.Abstractions/Models/EntityContainer.cs similarity index 100% rename from src/Umbraco.Core/Models/EntityContainer.cs rename to src/Umbraco.Abstractions/Models/EntityContainer.cs diff --git a/src/Umbraco.Core/PropertyEditors/IManifestValueValidator.cs b/src/Umbraco.Abstractions/PropertyEditors/IManifestValueValidator.cs similarity index 100% rename from src/Umbraco.Core/PropertyEditors/IManifestValueValidator.cs rename to src/Umbraco.Abstractions/PropertyEditors/IManifestValueValidator.cs diff --git a/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs b/src/Umbraco.Abstractions/PropertyEditors/IValueFormatValidator.cs similarity index 100% rename from src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs rename to src/Umbraco.Abstractions/PropertyEditors/IValueFormatValidator.cs diff --git a/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs b/src/Umbraco.Abstractions/PropertyEditors/IValueRequiredValidator.cs similarity index 100% rename from src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs rename to src/Umbraco.Abstractions/PropertyEditors/IValueRequiredValidator.cs diff --git a/src/Umbraco.Core/PropertyEditors/IValueValidator.cs b/src/Umbraco.Abstractions/PropertyEditors/IValueValidator.cs similarity index 100% rename from src/Umbraco.Core/PropertyEditors/IValueValidator.cs rename to src/Umbraco.Abstractions/PropertyEditors/IValueValidator.cs diff --git a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollection.cs b/src/Umbraco.Abstractions/PropertyEditors/ManifestValueValidatorCollection.cs similarity index 100% rename from src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollection.cs rename to src/Umbraco.Abstractions/PropertyEditors/ManifestValueValidatorCollection.cs diff --git a/src/Umbraco.Abstractions/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs b/src/Umbraco.Abstractions/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs new file mode 100644 index 0000000000..0ebda864f6 --- /dev/null +++ b/src/Umbraco.Abstractions/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.PropertyEditors +{ + public class ManifestValueValidatorCollectionBuilder : LazyCollectionBuilderBase + { + protected override ManifestValueValidatorCollectionBuilder This => this; + } +} diff --git a/src/Umbraco.Core/PropertyEditors/Validators/DecimalValidator.cs b/src/Umbraco.Abstractions/PropertyEditors/Validators/DecimalValidator.cs similarity index 92% rename from src/Umbraco.Core/PropertyEditors/Validators/DecimalValidator.cs rename to src/Umbraco.Abstractions/PropertyEditors/Validators/DecimalValidator.cs index 86db995566..f464044923 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/DecimalValidator.cs +++ b/src/Umbraco.Abstractions/PropertyEditors/Validators/DecimalValidator.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.PropertyEditors.Validators /// /// A validator that validates that the value is a valid decimal /// - internal sealed class DecimalValidator : IManifestValueValidator + public sealed class DecimalValidator : IManifestValueValidator { /// public string ValidationName => "Decimal"; diff --git a/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs b/src/Umbraco.Abstractions/PropertyEditors/Validators/EmailValidator.cs similarity index 92% rename from src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs rename to src/Umbraco.Abstractions/PropertyEditors/Validators/EmailValidator.cs index 4df11e4f60..8fb6d0c31b 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs +++ b/src/Umbraco.Abstractions/PropertyEditors/Validators/EmailValidator.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.PropertyEditors.Validators /// /// A validator that validates an email address /// - internal sealed class EmailValidator : IManifestValueValidator + public sealed class EmailValidator : IManifestValueValidator { /// public string ValidationName => "Email"; diff --git a/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs b/src/Umbraco.Abstractions/PropertyEditors/Validators/IntegerValidator.cs similarity index 92% rename from src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs rename to src/Umbraco.Abstractions/PropertyEditors/Validators/IntegerValidator.cs index 335ddf7724..5274ff484b 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs +++ b/src/Umbraco.Abstractions/PropertyEditors/Validators/IntegerValidator.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.PropertyEditors.Validators /// /// A validator that validates that the value is a valid integer /// - internal sealed class IntegerValidator : IManifestValueValidator + public sealed class IntegerValidator : IManifestValueValidator { /// public string ValidationName => "Integer"; diff --git a/src/Umbraco.Abstractions/Umbraco.Abstractions.csproj b/src/Umbraco.Abstractions/Umbraco.Abstractions.csproj index a5e5a94520..b3a1b4fa25 100644 --- a/src/Umbraco.Abstractions/Umbraco.Abstractions.csproj +++ b/src/Umbraco.Abstractions/Umbraco.Abstractions.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Umbraco.Core/Manifest/IManifestFilter.cs b/src/Umbraco.Core/Manifest/IManifestFilter.cs index 505f13d385..88e00a3966 100644 --- a/src/Umbraco.Core/Manifest/IManifestFilter.cs +++ b/src/Umbraco.Core/Manifest/IManifestFilter.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; + namespace Umbraco.Core.Manifest { /// diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs index 1c50a4b895..e2c3ee48fa 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.Models.Membership; @@ -31,10 +32,12 @@ namespace Umbraco.Core.Manifest public class ManifestContentAppFactory : IContentAppFactory { private readonly ManifestContentAppDefinition _definition; + private readonly IIOHelper _ioHelper; - public ManifestContentAppFactory(ManifestContentAppDefinition definition) + public ManifestContentAppFactory(ManifestContentAppDefinition definition, IIOHelper ioHelper) { _definition = definition; + _ioHelper = ioHelper; } private ContentApp _app; @@ -132,7 +135,7 @@ namespace Umbraco.Core.Manifest Alias = _definition.Alias, Name = _definition.Name, Icon = _definition.Icon, - View = _definition.View, + View = _ioHelper.ResolveVirtualUrl(_definition.View), Weight = _definition.Weight }); } diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index cac904a520..7f2a1825b4 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Text; using Newtonsoft.Json; using Umbraco.Core.Cache; -using Umbraco.Core.Composing; using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -22,6 +21,7 @@ namespace Umbraco.Core.Manifest private readonly IAppPolicyCache _cache; private readonly ILogger _logger; + private readonly IIOHelper _ioHelper; private readonly ManifestValueValidatorCollection _validators; private readonly ManifestFilterCollection _filters; @@ -30,28 +30,31 @@ namespace Umbraco.Core.Manifest /// /// Initializes a new instance of the class. /// - public ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ManifestFilterCollection filters, ILogger logger) - : this(appCaches, validators, filters, "~/App_Plugins", logger) + public ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ManifestFilterCollection filters, ILogger logger, IIOHelper ioHelper) + : this(appCaches, validators, filters, "~/App_Plugins", logger, ioHelper) { } /// /// Initializes a new instance of the class. /// - private ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ManifestFilterCollection filters, string path, ILogger logger) + private ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ManifestFilterCollection filters, string path, ILogger logger, IIOHelper ioHelper) { if (appCaches == null) throw new ArgumentNullException(nameof(appCaches)); _cache = appCaches.RuntimeCache; + _ioHelper = ioHelper; _validators = validators ?? throw new ArgumentNullException(nameof(validators)); _filters = filters ?? throw new ArgumentNullException(nameof(filters)); if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullOrEmptyException(nameof(path)); + Path = path; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } public string Path { get => _path; - set => _path = value.StartsWith("~/") ? Current.IOHelper.MapPath(value) : value; + set => _path = value.StartsWith("~/") ? _ioHelper.MapPath(value) : value; } /// @@ -166,9 +169,9 @@ namespace Umbraco.Core.Manifest // scripts and stylesheets are raw string, must process here for (var i = 0; i < manifest.Scripts.Length; i++) - manifest.Scripts[i] = Current.IOHelper.ResolveVirtualUrl(manifest.Scripts[i]); + manifest.Scripts[i] = _ioHelper.ResolveVirtualUrl(manifest.Scripts[i]); for (var i = 0; i < manifest.Stylesheets.Length; i++) - manifest.Stylesheets[i] = Current.IOHelper.ResolveVirtualUrl(manifest.Stylesheets[i]); + manifest.Stylesheets[i] = _ioHelper.ResolveVirtualUrl(manifest.Stylesheets[i]); // add property editors that are also parameter editors, to the parameter editors list // (the manifest format is kinda legacy) diff --git a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs deleted file mode 100644 index 8f7c68c813..0000000000 --- a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Umbraco.Core.Composing; - -namespace Umbraco.Core.PropertyEditors -{ - internal class ManifestValueValidatorCollectionBuilder : LazyCollectionBuilderBase - { - protected override ManifestValueValidatorCollectionBuilder This => this; - } -} diff --git a/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs index e405fa3a3e..4036adba42 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.PropertyEditors.Validators /// /// A validator that validates that the value against a regular expression. /// - internal sealed class RegexValidator : IValueFormatValidator, IManifestValueValidator + public sealed class RegexValidator : IValueFormatValidator, IManifestValueValidator { private readonly ILocalizedTextService _textService; private string _regex; diff --git a/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs index c51f572817..e83997b170 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.PropertyEditors.Validators /// /// A validator that validates that the value is not null or empty (if it is a string) /// - internal sealed class RequiredValidator : IValueRequiredValidator, IManifestValueValidator + public sealed class RequiredValidator : IValueRequiredValidator, IManifestValueValidator { private readonly ILocalizedTextService _textService; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 68a6260fe3..f22a29075f 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -193,6 +193,7 @@ + @@ -271,6 +272,9 @@ + + + @@ -285,7 +289,6 @@ - @@ -390,7 +393,6 @@ - @@ -483,9 +485,6 @@ - - - @@ -560,7 +559,6 @@ - @@ -582,7 +580,6 @@ - @@ -1002,22 +999,14 @@ - - - - - - - - diff --git a/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs b/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs index 1e02f562e3..c949e0bb67 100644 --- a/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using Umbraco.Core.Manifest; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Web.Composing; namespace Umbraco.Tests.Manifest { @@ -67,7 +68,7 @@ namespace Umbraco.Tests.Manifest private void AssertDefinition(object source, bool expected, string[] show, IReadOnlyUserGroup[] groups) { var definition = JsonConvert.DeserializeObject("{" + (show.Length == 0 ? "" : " \"show\": [" + string.Join(",", show.Select(x => "\"" + x + "\"")) + "] ") + "}"); - var factory = new ManifestContentAppFactory(definition); + var factory = new ManifestContentAppFactory(definition, Current.IOHelper); var app = factory.GetContentAppFor(source, groups); if (expected) Assert.IsNotNull(app); diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs index 1c90f68d62..0fcb7ac9e0 100644 --- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs @@ -44,7 +44,7 @@ namespace Umbraco.Tests.Manifest new RequiredValidator(Mock.Of()), new RegexValidator(Mock.Of(), null) }; - _parser = new ManifestParser(AppCaches.Disabled, new ManifestValueValidatorCollection(validators), new ManifestFilterCollection(Array.Empty()), Mock.Of()); + _parser = new ManifestParser(AppCaches.Disabled, new ManifestValueValidatorCollection(validators), new ManifestFilterCollection(Array.Empty()), Mock.Of(), Current.IOHelper); } [Test] diff --git a/src/Umbraco.Web/ContentApps/ContentAppFactoryCollectionBuilder.cs b/src/Umbraco.Web/ContentApps/ContentAppFactoryCollectionBuilder.cs index 170b9169ef..4e86b407b5 100644 --- a/src/Umbraco.Web/ContentApps/ContentAppFactoryCollectionBuilder.cs +++ b/src/Umbraco.Web/ContentApps/ContentAppFactoryCollectionBuilder.cs @@ -2,6 +2,7 @@ using System.Linq; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.Models.ContentEditing; @@ -28,7 +29,7 @@ namespace Umbraco.Web.ContentApps // its dependencies too, and that can create cycles or other oddities var manifestParser = factory.GetInstance(); - return base.CreateItems(factory).Concat(manifestParser.Manifest.ContentApps.Select(x => new ManifestContentAppFactory(x))); + return base.CreateItems(factory).Concat(manifestParser.Manifest.ContentApps.Select(x => new ManifestContentAppFactory(x, Current.IOHelper))); } } } From ed82bd05c66d8098a1c02d4dc67966e5e607ebe9 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 8 Nov 2019 14:27:27 +0100 Subject: [PATCH 07/36] AB3649 - Abstracted IPropertyType, IProperty and IPropertyValue --- src/Umbraco.Abstractions/Models/IProperty.cs | 100 ++++++++++++++++++ .../Models/IPropertyType.cs | 43 ++++++++ .../Models/IPropertyValue.cs | 34 ++++++ .../ConfigurationFieldAttribute.cs | 0 .../PropertyEditors/GridEditor.cs | 35 +++--- .../PropertyEditors/IConfigurationEditor.cs | 0 .../CompositionExtensions_Essentials.cs | 2 + .../ContentVariationExtensions.cs | 12 +-- src/Umbraco.Core/IO/IMediaFileSystem.cs | 4 +- src/Umbraco.Core/IO/MediaFileSystem.cs | 4 +- .../Manifest/DataEditorConverter.cs | 26 ++++- .../Manifest/ManifestDashboard.cs | 8 +- src/Umbraco.Core/Manifest/ManifestParser.cs | 15 ++- src/Umbraco.Core/Models/IDataValueEditor.cs | 8 +- src/Umbraco.Core/Models/Property.cs | 36 +++---- src/Umbraco.Core/Models/PropertyType.cs | 6 +- .../ConfigurationFieldsExtensions.cs | 34 ------ .../PropertyEditors/DataValueEditor.cs | 15 +-- .../Services/PropertyValidationService.cs | 6 +- src/Umbraco.Core/Umbraco.Core.csproj | 19 ++-- .../Umbraco.ModelsBuilder.Embedded.csproj | 4 +- src/Umbraco.Tests/App.config | 4 + src/Umbraco.Tests/Models/PropertyTypeTests.cs | 10 +- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 1 + src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 +- .../PropertyEditors/DateValueEditor.cs | 2 +- .../PropertyEditors/GridPropertyEditor.cs | 2 +- .../ImageCropperPropertyValueEditor.cs | 4 +- .../MultiUrlPickerValueEditor.cs | 2 +- .../MultipleTextStringPropertyEditor.cs | 2 +- .../PropertyEditors/MultipleValueEditor.cs | 2 +- .../NestedContentPropertyEditor.cs | 4 +- .../PropertyEditors/RichTextPropertyEditor.cs | 2 +- .../PropertyEditors/TextOnlyValueEditor.cs | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 3 +- 36 files changed, 311 insertions(+), 146 deletions(-) create mode 100644 src/Umbraco.Abstractions/Models/IProperty.cs create mode 100644 src/Umbraco.Abstractions/Models/IPropertyType.cs create mode 100644 src/Umbraco.Abstractions/Models/IPropertyValue.cs rename src/{Umbraco.Core => Umbraco.Abstractions}/PropertyEditors/ConfigurationFieldAttribute.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/PropertyEditors/GridEditor.cs (68%) rename src/{Umbraco.Core => Umbraco.Abstractions}/PropertyEditors/IConfigurationEditor.cs (100%) delete mode 100644 src/Umbraco.Core/PropertyEditors/ConfigurationFieldsExtensions.cs diff --git a/src/Umbraco.Abstractions/Models/IProperty.cs b/src/Umbraco.Abstractions/Models/IProperty.cs new file mode 100644 index 0000000000..308f4ae851 --- /dev/null +++ b/src/Umbraco.Abstractions/Models/IProperty.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + public interface IProperty + { + /// + /// Returns the PropertyType, which this Property is based on + /// + IPropertyType PropertyType { get; } + + /// + /// Gets the list of values. + /// + IReadOnlyCollection Values { get; set; } + + /// + /// Returns the Alias of the PropertyType, which this Property is based on + /// + string Alias { get; } + + /// + int Id { get; set; } + + /// + Guid Key { get; set; } + + /// + DateTime CreateDate { get; set; } + + /// + DateTime UpdateDate { get; set; } + + /// + DateTime? DeleteDate { get; set; } // no change tracking - not persisted + + /// + bool HasIdentity { get; } + + /// + /// Gets the value. + /// + object GetValue(string culture = null, string segment = null, bool published = false); + + /// + /// Sets a value. + /// + void SetValue(object value, string culture = null, string segment = null); + + /// + /// Resets the entity identity. + /// + void ResetIdentity(); + + bool Equals(EntityBase other); + bool Equals(object obj); + int GetHashCode(); + object DeepClone(); + + /// + bool IsDirty(); + + /// + bool IsPropertyDirty(string propertyName); + + /// + IEnumerable GetDirtyProperties(); + + /// + /// Saves dirty properties so they can be checked with WasDirty. + void ResetDirtyProperties(); + + /// + bool WasDirty(); + + /// + bool WasPropertyDirty(string propertyName); + + /// + void ResetWereDirtyProperties(); + + /// + void ResetDirtyProperties(bool rememberDirty); + + /// + IEnumerable GetWereDirtyProperties(); + + /// + /// Disables change tracking. + /// + void DisableChangeTracking(); + + /// + /// Enables change tracking. + /// + void EnableChangeTracking(); + } +} diff --git a/src/Umbraco.Abstractions/Models/IPropertyType.cs b/src/Umbraco.Abstractions/Models/IPropertyType.cs new file mode 100644 index 0000000000..4447a858ab --- /dev/null +++ b/src/Umbraco.Abstractions/Models/IPropertyType.cs @@ -0,0 +1,43 @@ +using System; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + public interface IPropertyType : IEntity + { + [DataMember] + string Name { get; } + [DataMember] + string Alias { get; } + [DataMember] + string Description { get; } + [DataMember] + int DataTypeId { get; } + [DataMember] + Guid DataTypeKey { get; } + [DataMember] + string PropertyEditorAlias { get; } + [DataMember] + ValueStorageType ValueStorageType { get; } + [DataMember] + Lazy PropertyGroupId { get; } + [DataMember] + bool Mandatory { get; } + [DataMember] + int SortOrder { get; } + [DataMember] + string ValidationRegExp { get; } + + bool SupportsPublishing { get; } + + ContentVariation Variations { get; } + + + + bool SupportsVariation(string culture, string segment, bool wildcards = false); + object ConvertAssignedValue(object value); + + + } +} diff --git a/src/Umbraco.Abstractions/Models/IPropertyValue.cs b/src/Umbraco.Abstractions/Models/IPropertyValue.cs new file mode 100644 index 0000000000..abc459a72f --- /dev/null +++ b/src/Umbraco.Abstractions/Models/IPropertyValue.cs @@ -0,0 +1,34 @@ +namespace Umbraco.Core.Models +{ + public interface IPropertyValue + { + /// + /// Gets or sets the culture of the property. + /// + /// The culture is either null (invariant) or a non-empty string. If the property is + /// set with an empty or whitespace value, its value is converted to null. + string Culture { get; set; } + + /// + /// Gets or sets the segment of the property. + /// + /// The segment is either null (neutral) or a non-empty string. If the property is + /// set with an empty or whitespace value, its value is converted to null. + string Segment { get; set; } + + /// + /// Gets or sets the edited value of the property. + /// + object EditedValue { get; set; } + + /// + /// Gets or sets the published value of the property. + /// + object PublishedValue { get; set; } + + /// + /// Clones the property value. + /// + IPropertyValue Clone(); + } +} diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationFieldAttribute.cs b/src/Umbraco.Abstractions/PropertyEditors/ConfigurationFieldAttribute.cs similarity index 100% rename from src/Umbraco.Core/PropertyEditors/ConfigurationFieldAttribute.cs rename to src/Umbraco.Abstractions/PropertyEditors/ConfigurationFieldAttribute.cs diff --git a/src/Umbraco.Core/PropertyEditors/GridEditor.cs b/src/Umbraco.Abstractions/PropertyEditors/GridEditor.cs similarity index 68% rename from src/Umbraco.Core/PropertyEditors/GridEditor.cs rename to src/Umbraco.Abstractions/PropertyEditors/GridEditor.cs index 388e79675c..7af72cf5ea 100644 --- a/src/Umbraco.Core/PropertyEditors/GridEditor.cs +++ b/src/Umbraco.Abstractions/PropertyEditors/GridEditor.cs @@ -1,48 +1,37 @@ using System.Collections.Generic; -using Newtonsoft.Json; -using Umbraco.Core.Composing; +using System.Runtime.Serialization; using Umbraco.Core.Configuration.Grid; -using Umbraco.Core.IO; namespace Umbraco.Core.PropertyEditors { + + [DataContract] public class GridEditor : IGridEditorConfig { - private string _view; - private string _render; - public GridEditor() { Config = new Dictionary(); } - [JsonProperty("name", Required = Required.Always)] + [DataMember(Name = "name", IsRequired = true)] public string Name { get; set; } - [JsonProperty("nameTemplate")] + [DataMember(Name = "nameTemplate")] public string NameTemplate { get; set; } - [JsonProperty("alias", Required = Required.Always)] + [DataMember(Name = "alias", IsRequired = true)] public string Alias { get; set; } - [JsonProperty("view", Required = Required.Always)] - public string View - { - get => _view; - set => _view = Current.IOHelper.ResolveVirtualUrl(value); - } + [DataMember(Name = "view", IsRequired = true)] + public string View{ get; set; } - [JsonProperty("render")] - public string Render - { - get => _render; - set => _render = Current.IOHelper.ResolveVirtualUrl(value); - } + [DataMember(Name = "render")] + public string Render { get; set; } - [JsonProperty("icon", Required = Required.Always)] + [DataMember(Name = "icon", IsRequired = true)] public string Icon { get; set; } - [JsonProperty("config")] + [DataMember(Name = "config")] public IDictionary Config { get; set; } protected bool Equals(GridEditor other) diff --git a/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs b/src/Umbraco.Abstractions/PropertyEditors/IConfigurationEditor.cs similarity index 100% rename from src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs rename to src/Umbraco.Abstractions/PropertyEditors/IConfigurationEditor.cs diff --git a/src/Umbraco.Core/CompositionExtensions_Essentials.cs b/src/Umbraco.Core/CompositionExtensions_Essentials.cs index b85479716c..34c9492072 100644 --- a/src/Umbraco.Core/CompositionExtensions_Essentials.cs +++ b/src/Umbraco.Core/CompositionExtensions_Essentials.cs @@ -1,5 +1,6 @@ using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; @@ -30,6 +31,7 @@ namespace Umbraco.Core composition.RegisterUnique(factory => factory.GetInstance().SqlContext); composition.RegisterUnique(typeLoader); composition.RegisterUnique(state); + composition.RegisterUnique(); } } } diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index 5b157307ab..bdcf300f3f 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -50,18 +50,18 @@ namespace Umbraco.Core /// Determines whether the property type varies by culture. /// /// And then it could also vary by segment. - public static bool VariesByCulture(this PropertyType propertyType) => propertyType.Variations.VariesByCulture(); + public static bool VariesByCulture(this IPropertyType propertyType) => propertyType.Variations.VariesByCulture(); /// /// Determines whether the property type varies by segment. /// /// And then it could also vary by culture. - public static bool VariesBySegment(this PropertyType propertyType) => propertyType.Variations.VariesBySegment(); + public static bool VariesBySegment(this IPropertyType propertyType) => propertyType.Variations.VariesBySegment(); /// /// Determines whether the property type varies by culture and segment. /// - public static bool VariesByCultureAndSegment(this PropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); + public static bool VariesByCultureAndSegment(this IPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); /// /// Determines whether the content type is invariant. @@ -161,13 +161,13 @@ namespace Umbraco.Core if (variation.VariesByCulture()) { // varies by culture - // in exact mode, the culture cannot be null + // in exact mode, the culture cannot be null if (exact && culture == null) { if (throwIfInvalid) throw new NotSupportedException($"Culture may not be null because culture variation is enabled."); return false; - } + } } else { @@ -180,7 +180,7 @@ namespace Umbraco.Core throw new NotSupportedException($"Culture \"{culture}\" is invalid because culture variation is disabled."); return false; } - } + } // if it does not vary by segment // the segment cannot have a value diff --git a/src/Umbraco.Core/IO/IMediaFileSystem.cs b/src/Umbraco.Core/IO/IMediaFileSystem.cs index ed88516135..8ed0ba60ca 100644 --- a/src/Umbraco.Core/IO/IMediaFileSystem.cs +++ b/src/Umbraco.Core/IO/IMediaFileSystem.cs @@ -52,7 +52,7 @@ namespace Umbraco.Core.IO /// 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); + string StoreFile(IContentBase content, IPropertyType propertyType, string filename, Stream filestream, string oldpath); /// /// Copies a media file as a new media file, associated to a property of a content item. @@ -61,6 +61,6 @@ namespace Umbraco.Core.IO /// 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); + string CopyFile(IContentBase content, IPropertyType propertyType, string sourcepath); } } diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index 2de7bc2f81..edcbfadf0d 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -88,7 +88,7 @@ namespace Umbraco.Core.IO #region Associated Media Files /// - public string StoreFile(IContentBase content, PropertyType propertyType, string filename, Stream filestream, string oldpath) + public string StoreFile(IContentBase content, IPropertyType propertyType, string filename, Stream filestream, string oldpath) { if (content == null) throw new ArgumentNullException(nameof(content)); if (propertyType == null) throw new ArgumentNullException(nameof(propertyType)); @@ -107,7 +107,7 @@ namespace Umbraco.Core.IO } /// - public string CopyFile(IContentBase content, PropertyType propertyType, string sourcepath) + public string CopyFile(IContentBase content, IPropertyType propertyType, string sourcepath) { if (content == null) throw new ArgumentNullException(nameof(content)); if (propertyType == null) throw new ArgumentNullException(nameof(propertyType)); diff --git a/src/Umbraco.Core/Manifest/DataEditorConverter.cs b/src/Umbraco.Core/Manifest/DataEditorConverter.cs index 86982e17f2..86347d3894 100644 --- a/src/Umbraco.Core/Manifest/DataEditorConverter.cs +++ b/src/Umbraco.Core/Manifest/DataEditorConverter.cs @@ -1,6 +1,8 @@ using System; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Serialization; @@ -13,13 +15,15 @@ namespace Umbraco.Core.Manifest internal class DataEditorConverter : JsonReadConverter { private readonly ILogger _logger; + private readonly IIOHelper _ioHelper; /// /// Initializes a new instance of the class. /// - public DataEditorConverter(ILogger logger) + public DataEditorConverter(ILogger logger, IIOHelper ioHelper) { _logger = logger; + _ioHelper = ioHelper; } /// @@ -62,11 +66,11 @@ namespace Umbraco.Core.Manifest PrepareForPropertyEditor(jobject, dataEditor); else PrepareForParameterEditor(jobject, dataEditor); - + base.Deserialize(jobject, target, serializer); } - private static void PrepareForPropertyEditor(JObject jobject, DataEditor target) + private void PrepareForPropertyEditor(JObject jobject, DataEditor target) { if (jobject["editor"] == null) throw new InvalidOperationException("Missing 'editor' value."); @@ -86,6 +90,9 @@ namespace Umbraco.Core.Manifest if (jobject["editor"]["validation"] is JObject validation) jobject["editor"]["validation"] = RewriteValidators(validation); + if(jobject["editor"]["view"] is JValue view) + jobject["editor"]["view"] = RewriteVirtualUrl(view); + if (jobject["prevalues"] is JObject config) { // explicitly assign a configuration editor of type ConfigurationEditor @@ -100,6 +107,9 @@ namespace Umbraco.Core.Manifest { if (field["validation"] is JObject fvalidation) field["validation"] = RewriteValidators(fvalidation); + + if(field["view"] is JValue fview) + field["view"] = RewriteVirtualUrl(fview); } } @@ -118,7 +128,12 @@ namespace Umbraco.Core.Manifest } } - private static void PrepareForParameterEditor(JObject jobject, DataEditor target) + private string RewriteVirtualUrl(JValue view) + { + return _ioHelper.ResolveVirtualUrl(view.Value as string); + } + + private void PrepareForParameterEditor(JObject jobject, DataEditor target) { // in a manifest, a parameter editor looks like: // @@ -148,6 +163,9 @@ namespace Umbraco.Core.Manifest jobject["defaultConfig"] = config; jobject.Remove("config"); } + + if(jobject["editor"]?["view"] is JValue view) // We need to null check, if view do not exists, then editor do not exists + jobject["editor"]["view"] = RewriteVirtualUrl(view); } private static JArray RewriteValidators(JObject validation) diff --git a/src/Umbraco.Core/Manifest/ManifestDashboard.cs b/src/Umbraco.Core/Manifest/ManifestDashboard.cs index 33af12e3cd..2642ca3646 100644 --- a/src/Umbraco.Core/Manifest/ManifestDashboard.cs +++ b/src/Umbraco.Core/Manifest/ManifestDashboard.cs @@ -9,8 +9,6 @@ namespace Umbraco.Core.Manifest { public class ManifestDashboard : IDashboard { - private string _view; - [JsonProperty("alias", Required = Required.Always)] public string Alias { get; set; } @@ -19,11 +17,7 @@ namespace Umbraco.Core.Manifest public int Weight { get; set; } [JsonProperty("view", Required = Required.Always)] - public string View - { - get => _view; - set => _view = Current.IOHelper.ResolveVirtualUrl(value); - } + public string View { get; set; } [JsonProperty("sections")] public string[] Sections { get; set; } = Array.Empty(); diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index 7f2a1825b4..bf70def9dc 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -163,7 +163,7 @@ namespace Umbraco.Core.Manifest throw new ArgumentNullOrEmptyException(nameof(text)); var manifest = JsonConvert.DeserializeObject(text, - new DataEditorConverter(_logger), + new DataEditorConverter(_logger, _ioHelper), new ValueValidatorConverter(_validators), new DashboardAccessRuleConverter()); @@ -172,6 +172,19 @@ namespace Umbraco.Core.Manifest manifest.Scripts[i] = _ioHelper.ResolveVirtualUrl(manifest.Scripts[i]); for (var i = 0; i < manifest.Stylesheets.Length; i++) manifest.Stylesheets[i] = _ioHelper.ResolveVirtualUrl(manifest.Stylesheets[i]); + foreach (var contentApp in manifest.ContentApps) + { + contentApp.View = _ioHelper.ResolveVirtualUrl(contentApp.View); + } + foreach (var dashboard in manifest.Dashboards) + { + dashboard.View = _ioHelper.ResolveVirtualUrl(dashboard.View); + } + foreach (var gridEditor in manifest.GridEditors) + { + gridEditor.View = _ioHelper.ResolveVirtualUrl(gridEditor.View); + gridEditor.Render = _ioHelper.ResolveVirtualUrl(gridEditor.Render); + } // add property editors that are also parameter editors, to the parameter editors list // (the manifest format is kinda legacy) diff --git a/src/Umbraco.Core/Models/IDataValueEditor.cs b/src/Umbraco.Core/Models/IDataValueEditor.cs index cb68531cc7..5a1c6fd29d 100644 --- a/src/Umbraco.Core/Models/IDataValueEditor.cs +++ b/src/Umbraco.Core/Models/IDataValueEditor.cs @@ -59,12 +59,12 @@ namespace Umbraco.Core.PropertyEditors /// /// Converts a property value to a value for the editor. /// - object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null); + object ToEditor(IProperty property, IDataTypeService dataTypeService, string culture = null, string segment = null); // TODO: / deal with this when unplugging the xml cache // why property vs propertyType? services should be injected! etc... - IEnumerable ConvertDbToXml(Property property, IDataTypeService dataTypeService, ILocalizationService localizationService, bool published); - XNode ConvertDbToXml(PropertyType propertyType, object value, IDataTypeService dataTypeService); - string ConvertDbToString(PropertyType propertyType, object value, IDataTypeService dataTypeService); + IEnumerable ConvertDbToXml(IProperty property, IDataTypeService dataTypeService, ILocalizationService localizationService, bool published); + XNode ConvertDbToXml(IPropertyType propertyType, object value, IDataTypeService dataTypeService); + string ConvertDbToString(IPropertyType propertyType, object value, IDataTypeService dataTypeService); } } diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 76349823ac..9b2cda67c1 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -13,16 +13,16 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class Property : EntityBase + public class Property : EntityBase, IProperty { // _values contains all property values, including the invariant-neutral value - private List _values = new List(); + private List _values = new List(); // _pvalue contains the invariant-neutral property value - private PropertyValue _pvalue; + private IPropertyValue _pvalue; // _vvalues contains the (indexed) variant property values - private Dictionary _vvalues; + private Dictionary _vvalues; /// /// Initializes a new instance of the class. @@ -50,7 +50,7 @@ namespace Umbraco.Core.Models /// /// Represents a property value. /// - public class PropertyValue + public class PropertyValue : IPropertyValue { // TODO: Either we allow change tracking at this class level, or we add some special change tracking collections to the Property // class to deal with change tracking which variants have changed @@ -66,7 +66,7 @@ namespace Umbraco.Core.Models public string Culture { get => _culture; - internal set => _culture = value.IsNullOrWhiteSpace() ? null : value.ToLowerInvariant(); + set => _culture = value.IsNullOrWhiteSpace() ? null : value.ToLowerInvariant(); } /// @@ -77,23 +77,23 @@ namespace Umbraco.Core.Models public string Segment { get => _segment; - internal set => _segment = value?.ToLowerInvariant(); + set => _segment = value?.ToLowerInvariant(); } /// /// Gets or sets the edited value of the property. /// - public object EditedValue { get; internal set; } + public object EditedValue { get; set; } /// /// Gets or sets the published value of the property. /// - public object PublishedValue { get; internal set; } + public object PublishedValue { get; set; } /// /// Clones the property value. /// - public PropertyValue Clone() + public IPropertyValue Clone() => new PropertyValue { _culture = _culture, _segment = _segment, PublishedValue = PublishedValue, EditedValue = EditedValue }; } @@ -121,13 +121,13 @@ namespace Umbraco.Core.Models /// Returns the PropertyType, which this Property is based on /// [IgnoreDataMember] - public PropertyType PropertyType { get; private set; } + public IPropertyType PropertyType { get; private set; } /// /// Gets the list of values. /// [DataMember] - public IReadOnlyCollection Values + public IReadOnlyCollection Values { get => _values; set @@ -180,7 +180,7 @@ namespace Umbraco.Core.Models : null; } - private object GetPropertyValue(PropertyValue pvalue, bool published) + private object GetPropertyValue(IPropertyValue pvalue, bool published) { if (pvalue == null) return null; @@ -240,7 +240,7 @@ namespace Umbraco.Core.Models UnpublishValue(pvalue); } - private void PublishValue(PropertyValue pvalue) + private void PublishValue(IPropertyValue pvalue) { if (pvalue == null) return; @@ -251,7 +251,7 @@ namespace Umbraco.Core.Models DetectChanges(pvalue.EditedValue, origValue, nameof(Values), PropertyValueComparer, false); } - private void UnpublishValue(PropertyValue pvalue) + private void UnpublishValue(IPropertyValue pvalue) { if (pvalue == null) return; @@ -294,7 +294,7 @@ namespace Umbraco.Core.Models pvalue.EditedValue = value; } - private (PropertyValue, bool) GetPValue(bool create) + private (IPropertyValue, bool) GetPValue(bool create) { var change = false; if (_pvalue == null) @@ -307,7 +307,7 @@ namespace Umbraco.Core.Models return (_pvalue, change); } - private (PropertyValue, bool) GetPValue(string culture, string segment, bool create) + private (IPropertyValue, bool) GetPValue(string culture, string segment, bool create) { if (culture == null && segment == null) return GetPValue(create); @@ -316,7 +316,7 @@ namespace Umbraco.Core.Models if (_vvalues == null) { if (!create) return (null, false); - _vvalues = new Dictionary(); + _vvalues = new Dictionary(); change = true; } var k = new CompositeNStringNStringKey(culture, segment); diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 40af478ab8..1448e099be 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Models [Serializable] [DataContract(IsReference = true)] [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] - public class PropertyType : EntityBase, IEquatable + public class PropertyType : EntityBase, IPropertyType, IEquatable { private readonly bool _forceValueStorageType; private string _name; @@ -160,7 +160,7 @@ namespace Umbraco.Core.Models /// Gets or sets the database type for storing value for this property type. /// [DataMember] - internal ValueStorageType ValueStorageType + public ValueStorageType ValueStorageType { get => _valueStorageType; set @@ -175,7 +175,7 @@ namespace Umbraco.Core.Models /// /// For generic properties, the value is null. [DataMember] - internal Lazy PropertyGroupId + public Lazy PropertyGroupId { get => _propertyGroupId; set => SetPropertyValueAndDetectChanges(value, ref _propertyGroupId, nameof(PropertyGroupId)); diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationFieldsExtensions.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationFieldsExtensions.cs deleted file mode 100644 index 25fba622d5..0000000000 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationFieldsExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.PropertyEditors -{ - public static partial class ConfigurationFieldsExtensions - { - /// - /// Adds a configuration field. - /// - /// The list of configuration fields. - /// The key (alias) of the field. - /// The name (label) of the field. - /// The description for the field. - /// The path to the editor view to be used for the field. - /// Optional configuration used for field's editor. - public static void Add( - this List fields, - string key, - string name, - string description, - string view, - IDictionary config = null) - { - fields.Add(new ConfigurationField - { - Key = key, - Name = name, - Description = description, - View = view, - Config = config, - }); - } - } -} diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs index a9ce27d964..396d32c833 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs @@ -7,7 +7,6 @@ using System.Xml.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core.Composing; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; @@ -72,11 +71,7 @@ namespace Umbraco.Core.PropertyEditors /// folder, or (3) a view name which maps to views/propertyeditors/{view}/{view}.html. /// [JsonProperty("view", Required = Required.Always)] - public string View - { - get => _view; - set => _view = Current.IOHelper.ResolveVirtualUrl(value); - } + public string View { get; set; } /// /// The value type which reflects how it is validated and stored in the database @@ -237,7 +232,7 @@ namespace Umbraco.Core.PropertyEditors /// The object returned will automatically be serialized into json notation. For most property editors /// the value returned is probably just a string but in some cases a json structure will be returned. /// - public virtual object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) + public virtual object ToEditor(IProperty property, IDataTypeService dataTypeService, string culture = null, string segment = null) { var val = property.GetValue(culture, segment); if (val == null) return string.Empty; @@ -288,7 +283,7 @@ namespace Umbraco.Core.PropertyEditors /// /// Converts a property to Xml fragments. /// - public IEnumerable ConvertDbToXml(Property property, IDataTypeService dataTypeService, ILocalizationService localizationService, bool published) + public IEnumerable ConvertDbToXml(IProperty property, IDataTypeService dataTypeService, ILocalizationService localizationService, bool published) { published &= property.PropertyType.SupportsPublishing; @@ -322,7 +317,7 @@ namespace Umbraco.Core.PropertyEditors /// Returns an XText or XCData instance which must be wrapped in a element. /// If the value is empty we will not return as CDATA since that will just take up more space in the file. /// - public XNode ConvertDbToXml(PropertyType propertyType, object value, IDataTypeService dataTypeService) + public XNode ConvertDbToXml(IPropertyType propertyType, object value, IDataTypeService dataTypeService) { //check for null or empty value, we don't want to return CDATA if that is the case if (value == null || value.ToString().IsNullOrWhiteSpace()) @@ -348,7 +343,7 @@ namespace Umbraco.Core.PropertyEditors /// /// Converts a property value to a string. /// - public virtual string ConvertDbToString(PropertyType propertyType, object value, IDataTypeService dataTypeService) + public virtual string ConvertDbToString(IPropertyType propertyType, object value, IDataTypeService dataTypeService) { if (value == null) return string.Empty; diff --git a/src/Umbraco.Core/Services/PropertyValidationService.cs b/src/Umbraco.Core/Services/PropertyValidationService.cs index a037a83920..f619e5f47e 100644 --- a/src/Umbraco.Core/Services/PropertyValidationService.cs +++ b/src/Umbraco.Core/Services/PropertyValidationService.cs @@ -74,7 +74,7 @@ namespace Umbraco.Core.Services culture = culture.NullOrWhiteSpaceAsNull(); segment = segment.NullOrWhiteSpaceAsNull(); - Property.PropertyValue pvalue = null; + IPropertyValue pvalue = null; // if validating invariant/neutral, and it is supported, validate // (including ensuring that the value exists, if mandatory) @@ -120,7 +120,7 @@ namespace Umbraco.Core.Services /// /// /// True is property value is valid, otherwise false - private bool IsValidPropertyValue(Property property, object value) + private bool IsValidPropertyValue(IProperty property, object value) { return IsPropertyValueValid(property.PropertyType, value); } @@ -128,7 +128,7 @@ namespace Umbraco.Core.Services /// /// Determines whether a value is valid for this property type. /// - private bool IsPropertyValueValid(PropertyType propertyType, object value) + private bool IsPropertyValueValid(IPropertyType propertyType, object value) { var editor = _propertyEditors[propertyType.PropertyEditorAlias]; if (editor == null) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index f22a29075f..45e25d9a8f 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -36,7 +36,6 @@ - @@ -65,11 +64,14 @@ all - 1.3.0 + 1.4.0 1.0.0 + + 4.6.0 + 1.0.5 runtime; build; native; contentfiles; analyzers @@ -91,19 +93,19 @@ - 2.8.0 + 2.9.0 2.0.1 - 3.0.0 + 3.1.0 2.0.0 - 1.0.0 + 1.1.0 1.0.3 @@ -112,7 +114,7 @@ 2.2.2 - 4.0.0 + 4.1.0 @@ -270,7 +272,6 @@ - @@ -282,8 +283,6 @@ - - @@ -483,7 +482,6 @@ - @@ -1000,7 +998,6 @@ - diff --git a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj index 67919834ac..eb61e2a2c8 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj +++ b/src/Umbraco.ModelsBuilder.Embedded/Umbraco.ModelsBuilder.Embedded.csproj @@ -34,7 +34,6 @@ - @@ -97,6 +96,9 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + + 4.6.0 + diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 91047ba6a2..9eeb93384d 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -94,6 +94,10 @@ + + + + diff --git a/src/Umbraco.Tests/Models/PropertyTypeTests.cs b/src/Umbraco.Tests/Models/PropertyTypeTests.cs index 1bc99162af..b4d14e7bb5 100644 --- a/src/Umbraco.Tests/Models/PropertyTypeTests.cs +++ b/src/Umbraco.Tests/Models/PropertyTypeTests.cs @@ -54,7 +54,15 @@ namespace Umbraco.Tests.Models var allProps = clone.GetType().GetProperties(); foreach (var propertyInfo in allProps) { - Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(pt, null)); + var expected = propertyInfo.GetValue(pt, null); + var actual = propertyInfo.GetValue(clone, null); + if (propertyInfo.PropertyType == typeof(Lazy)) + { + expected = ((Lazy) expected).Value; + actual = ((Lazy) actual).Value; + } + + Assert.AreEqual(expected, actual, $"Value of propery: '{propertyInfo.Name}': {expected} != {actual}"); } } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index b9fd0f6640..149d48032b 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -219,6 +219,7 @@ namespace Umbraco.Tests.Testing Composition.RegisterUnique(); Composition.RegisterUnique(); Composition.RegisterUnique(); + Composition.RegisterUnique(); // register back office sections in the order we want them rendered Composition.WithCollectionBuilder().Append() diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index de76b94ff1..1292d2da2e 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -56,7 +56,6 @@ - @@ -107,6 +106,7 @@ + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index f2976716bb..a2ce0f95f7 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -70,7 +70,6 @@ - @@ -110,6 +109,7 @@ runtime; build; native; contentfiles; analyzers all + @@ -432,4 +432,4 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs b/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs index a03ad4aa00..657e2b2b49 100644 --- a/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PropertyEditors Validators.Add(new DateTimeValidator()); } - public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture= null, string segment = null) + public override object ToEditor(IProperty property, IDataTypeService dataTypeService, string culture= null, string segment = null) { var date = property.GetValue(culture, segment).TryConvertTo(); if (date.Success == false || date.Result == null) diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index 24e2fc29a5..5964964ab7 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -115,7 +115,7 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) + public override object ToEditor(IProperty property, IDataTypeService dataTypeService, string culture = null, string segment = null) { var val = property.GetValue(culture, segment); if (val == null) return string.Empty; diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs index 4aac8f54aa..fdeb726902 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web.PropertyEditors /// This is called to merge in the prevalue crops with the value that is saved - similar to the property value converter for the front-end /// - public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) + public override object ToEditor(IProperty property, IDataTypeService dataTypeService, string culture = null, string segment = null) { var val = property.GetValue(culture, segment); if (val == null) return null; @@ -161,7 +161,7 @@ namespace Umbraco.Web.PropertyEditors } - public override string ConvertDbToString(PropertyType propertyType, object value, IDataTypeService dataTypeService) + public override string ConvertDbToString(IPropertyType propertyType, object value, IDataTypeService dataTypeService) { if (value == null || string.IsNullOrEmpty(value.ToString())) return null; diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs index aa8fa73c7a..c2ad58a101 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web.PropertyEditors _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) + public override object ToEditor(IProperty property, IDataTypeService dataTypeService, string culture = null, string segment = null) { var value = property.GetValue(culture, segment)?.ToString(); diff --git a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs index fa82bc555c..23ceb0f22a 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs @@ -95,7 +95,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The legacy property editor saved this data as new line delimited! strange but we have to maintain that. /// - public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) + public override object ToEditor(IProperty property, IDataTypeService dataTypeService, string culture = null, string segment = null) { var val = property.GetValue(culture, segment); return val?.ToString().Split(new[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries) diff --git a/src/Umbraco.Web/PropertyEditors/MultipleValueEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleValueEditor.cs index bbeaff184e..4c59a8e3c5 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleValueEditor.cs @@ -33,7 +33,7 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) + public override object ToEditor(IProperty property, IDataTypeService dataTypeService, string culture = null, string segment = null) { var json = base.ToEditor(property, dataTypeService, culture, segment).ToString(); return JsonConvert.DeserializeObject(json) ?? Array.Empty(); diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index f511a97cac..778b69d9db 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -92,7 +92,7 @@ namespace Umbraco.Web.PropertyEditors #region DB to String - public override string ConvertDbToString(PropertyType propertyType, object propertyValue, IDataTypeService dataTypeService) + public override string ConvertDbToString(IPropertyType propertyType, object propertyValue, IDataTypeService dataTypeService) { if (propertyValue == null || string.IsNullOrWhiteSpace(propertyValue.ToString())) return string.Empty; @@ -152,7 +152,7 @@ namespace Umbraco.Web.PropertyEditors // note: there is NO variant support here - public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) + public override object ToEditor(IProperty property, IDataTypeService dataTypeService, string culture = null, string segment = null) { var val = property.GetValue(culture, segment); if (val == null || string.IsNullOrWhiteSpace(val.ToString())) diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 5743e9c1d5..03345bee3c 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -92,7 +92,7 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) + public override object ToEditor(IProperty property, IDataTypeService dataTypeService, string culture = null, string segment = null) { var val = property.GetValue(culture, segment); if (val == null) diff --git a/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs index 754cef1f31..f925daba30 100644 --- a/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The object returned will always be a string and if the database type is not a valid string type an exception is thrown /// - public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) + public override object ToEditor(IProperty property, IDataTypeService dataTypeService, string culture = null, string segment = null) { var val = property.GetValue(culture, segment); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 317786b970..bede1a1a6d 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -41,7 +41,6 @@ - @@ -1282,4 +1281,4 @@ - \ No newline at end of file + From c285a163932b8a2958757106b3a37ebc9f93fe27 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 8 Nov 2019 14:30:46 +0100 Subject: [PATCH 08/36] AB3649 - Moved more stuff --- .../PropertyEditors/ConfigurationField.cs | 28 ++++++++----------- .../IPropertyIndexValueFactory.cs | 2 +- .../ConfigurationEditorOfTConfiguration.cs | 5 ++-- .../DefaultPropertyIndexValueFactory.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 - .../GridPropertyIndexValueFactory.cs | 2 +- .../PropertyEditors/RichTextPropertyEditor.cs | 2 +- 7 files changed, 18 insertions(+), 24 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/PropertyEditors/ConfigurationField.cs (83%) rename src/{Umbraco.Core => Umbraco.Abstractions}/PropertyEditors/IPropertyIndexValueFactory.cs (92%) diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationField.cs b/src/Umbraco.Abstractions/PropertyEditors/ConfigurationField.cs similarity index 83% rename from src/Umbraco.Core/PropertyEditors/ConfigurationField.cs rename to src/Umbraco.Abstractions/PropertyEditors/ConfigurationField.cs index ee07f8dcef..52df839712 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationField.cs +++ b/src/Umbraco.Abstractions/PropertyEditors/ConfigurationField.cs @@ -1,15 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json; -using Umbraco.Core.Composing; -using Umbraco.Core.IO; +using System.Reflection; +using System.Runtime.Serialization; namespace Umbraco.Core.PropertyEditors { /// /// Represents a datatype configuration field for editing. /// + [DataContract] public class ConfigurationField { private string _view; @@ -50,37 +50,35 @@ namespace Umbraco.Core.PropertyEditors /// /// Gets or sets the key of the field. /// - [JsonProperty("key", Required = Required.Always)] + [DataMember(Name = "key", IsRequired = true)] public string Key { get; set; } /// /// Gets or sets the name of the field. /// - [JsonProperty("label", Required = Required.Always)] + [DataMember(Name = "label", IsRequired = true)] public string Name { get; set; } /// /// Gets or sets the property name of the field. /// - [JsonIgnore] public string PropertyName { get; set; } /// /// Gets or sets the property CLR type of the field. /// - [JsonIgnore] public Type PropertyType { get; set; } /// /// Gets or sets the description of the field. /// - [JsonProperty("description")] + [DataMember(Name = "description")] public string Description { get; set; } /// /// Gets or sets a value indicating whether to hide the label of the field. /// - [JsonProperty("hideLabel")] + [DataMember(Name = "hideLabel")] public bool HideLabel { get; set; } /// @@ -90,23 +88,19 @@ namespace Umbraco.Core.PropertyEditors /// Can be the full virtual path, or the relative path to the Umbraco folder, /// or a simple view name which will map to ~/Views/PreValueEditors/{view}.html. /// - [JsonProperty("view", Required = Required.Always)] - public string View - { - get => _view; - set => _view = Current.IOHelper.ResolveVirtualUrl(value); - } + [DataMember(Name = "view", IsRequired = true)] + public string View { get; set; } /// /// Gets the validators of the field. /// - [JsonProperty("validation")] + [DataMember(Name = "validation")] public List Validators { get; } /// /// Gets or sets extra configuration properties for the editor. /// - [JsonProperty("config")] + [DataMember(Name = "config")] public IDictionary Config { get; set; } } } diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyIndexValueFactory.cs b/src/Umbraco.Abstractions/PropertyEditors/IPropertyIndexValueFactory.cs similarity index 92% rename from src/Umbraco.Core/PropertyEditors/IPropertyIndexValueFactory.cs rename to src/Umbraco.Abstractions/PropertyEditors/IPropertyIndexValueFactory.cs index fd4e272f08..26552afc6f 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyIndexValueFactory.cs +++ b/src/Umbraco.Abstractions/PropertyEditors/IPropertyIndexValueFactory.cs @@ -19,6 +19,6 @@ namespace Umbraco.Core.PropertyEditors /// values. By default, there would be only one object: the property value. But some implementations may return /// more than one value for a given field. /// - IEnumerable>> GetIndexValues(Property property, string culture, string segment, bool published); + IEnumerable>> GetIndexValues(IProperty property, string culture, string segment, bool published); } } diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs index 7008c73433..bee923b50e 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs @@ -35,6 +35,7 @@ namespace Umbraco.Core.PropertyEditors ConfigurationField field; + var attributeView = Current.IOHelper.ResolveVirtualUrl(attribute.View); // if the field does not have its own type, use the base type if (attribute.Type == null) { @@ -47,7 +48,7 @@ namespace Umbraco.Core.PropertyEditors PropertyType = property.PropertyType, Description = attribute.Description, HideLabel = attribute.HideLabel, - View = attribute.View + View = attributeView }; fields.Add(field); @@ -81,7 +82,7 @@ namespace Umbraco.Core.PropertyEditors field.Name = attribute.Name; if (!string.IsNullOrWhiteSpace(attribute.View)) - field.View = attribute.View; + field.View = attributeView; if (!string.IsNullOrWhiteSpace(attribute.Description)) field.Description = attribute.Description; diff --git a/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs b/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs index 413f31d79e..0d06b59a9c 100644 --- a/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs +++ b/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.PropertyEditors public class DefaultPropertyIndexValueFactory : IPropertyIndexValueFactory { /// - public IEnumerable>> GetIndexValues(Property property, string culture, string segment, bool published) + public IEnumerable>> GetIndexValues(IProperty property, string culture, string segment, bool published) { yield return new KeyValuePair>( property.Alias, diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 45e25d9a8f..75e79b8fe0 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -287,7 +287,6 @@ - diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyIndexValueFactory.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyIndexValueFactory.cs index 0cf36122d7..af72f0b819 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyIndexValueFactory.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyIndexValueFactory.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PropertyEditors /// public class GridPropertyIndexValueFactory : IPropertyIndexValueFactory { - public IEnumerable>> GetIndexValues(Property property, string culture, string segment, bool published) + public IEnumerable>> GetIndexValues(IProperty property, string culture, string segment, bool published) { var result = new List>>(); diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 03345bee3c..1fa2c9323b 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -130,7 +130,7 @@ namespace Umbraco.Web.PropertyEditors internal class RichTextPropertyIndexValueFactory : IPropertyIndexValueFactory { - public IEnumerable>> GetIndexValues(Property property, string culture, string segment, bool published) + public IEnumerable>> GetIndexValues(IProperty property, string culture, string segment, bool published) { var val = property.GetValue(culture, segment, published); From 4c963d5fb2af8156f8627512c8ff380e784d3ede Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 8 Nov 2019 14:38:57 +0100 Subject: [PATCH 09/36] AB3649 - ILocalizationService --- src/Umbraco.Core/Umbraco.Core.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 75e79b8fe0..3241a3852c 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -299,7 +299,6 @@ - From e5c3b1f8aa02049ae0916ac1991cd171f0c32753 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 8 Nov 2019 15:10:05 +0100 Subject: [PATCH 10/36] AB3649 - More refactor of Property to IProperty... --- src/Umbraco.Abstractions/Models/IProperty.cs | 6 ++ .../Services/ILocalizationService.cs | 2 +- src/Umbraco.Core/ContentExtensions.cs | 6 +- src/Umbraco.Core/Models/ContentBase.cs | 6 +- .../Models/ContentTagsExtensions.cs | 2 +- src/Umbraco.Core/Models/IContentBase.cs | 2 +- .../Models/IPropertyCollection.cs | 23 ++++++ src/Umbraco.Core/Models/Property.cs | 12 +-- src/Umbraco.Core/Models/PropertyCollection.cs | 20 ++--- .../Models/PropertyTagsExtensions.cs | 18 ++--- .../Persistence/Factories/PropertyFactory.cs | 4 +- .../Services/Implement/ContentService.cs | 4 +- .../Services/Implement/EntityXmlSerializer.cs | 4 +- .../Services/PropertyValidationService.cs | 4 +- src/Umbraco.Core/Services/PublishResult.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Examine/BaseValueSetBuilder.cs | 4 +- src/Umbraco.Tests/Mapping/MappingTests.cs | 4 +- .../Mapping/ContentWebModelMappingTests.cs | 6 +- src/Umbraco.Tests/Views/web.config | 74 +++++++++++++++++++ src/Umbraco.Web/Editors/ContentController.cs | 2 +- .../Editors/ContentControllerBase.cs | 4 +- .../Editors/Filters/ContentModelValidator.cs | 6 +- .../PublishedContentHashtableConverter.cs | 2 +- .../Models/Mapping/ContentMapDefinition.cs | 6 +- .../Mapping/ContentPropertyBasicMapper.cs | 2 +- .../Mapping/ContentPropertyDisplayMapper.cs | 2 +- .../Mapping/ContentPropertyDtoMapper.cs | 2 +- .../Mapping/ContentPropertyMapDefinition.cs | 12 +-- .../Models/Mapping/MediaMapDefinition.cs | 4 +- .../Models/Mapping/MemberMapDefinition.cs | 4 +- .../Mapping/MemberTabsAndPropertiesMapper.cs | 2 +- .../Models/Mapping/TabsAndPropertiesMapper.cs | 6 +- .../FileUploadPropertyEditor.cs | 4 +- .../ImageCropperPropertyEditor.cs | 4 +- .../NuCache/PublishedContent.cs | 1 + 36 files changed, 186 insertions(+), 81 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Services/ILocalizationService.cs (99%) create mode 100644 src/Umbraco.Core/Models/IPropertyCollection.cs create mode 100644 src/Umbraco.Tests/Views/web.config diff --git a/src/Umbraco.Abstractions/Models/IProperty.cs b/src/Umbraco.Abstractions/Models/IProperty.cs index 308f4ae851..cd8a07393e 100644 --- a/src/Umbraco.Abstractions/Models/IProperty.cs +++ b/src/Umbraco.Abstractions/Models/IProperty.cs @@ -6,6 +6,8 @@ namespace Umbraco.Core.Models { public interface IProperty { + + ValueStorageType ValueStorageType { get; } /// /// Returns the PropertyType, which this Property is based on /// @@ -96,5 +98,9 @@ namespace Umbraco.Core.Models /// Enables change tracking. /// void EnableChangeTracking(); + + int PropertyTypeId { get; } + void PublishValues(string culture = "*", string segment = "*"); + void UnpublishValues(string culture = "*", string segment = "*"); } } diff --git a/src/Umbraco.Core/Services/ILocalizationService.cs b/src/Umbraco.Abstractions/Services/ILocalizationService.cs similarity index 99% rename from src/Umbraco.Core/Services/ILocalizationService.cs rename to src/Umbraco.Abstractions/Services/ILocalizationService.cs index 019e07493a..6566f983df 100644 --- a/src/Umbraco.Core/Services/ILocalizationService.cs +++ b/src/Umbraco.Abstractions/Services/ILocalizationService.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Services /// Adds or updates a translation for a dictionary item and language /// /// - /// + /// /// void AddOrUpdateDictionaryValue(IDictionaryItem item, ILanguage language, string value); diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index 8e404402d0..6732fd9394 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -108,7 +108,7 @@ namespace Umbraco.Core /// /// /// - public static IEnumerable GetNonGroupedProperties(this IContentBase content) + public static IEnumerable GetNonGroupedProperties(this IContentBase content) { return content.Properties .Where(x => x.PropertyType.PropertyGroupId == null) @@ -121,7 +121,7 @@ namespace Umbraco.Core /// /// /// - public static IEnumerable GetPropertiesForGroup(this IContentBase content, PropertyGroup propertyGroup) + public static IEnumerable GetPropertiesForGroup(this IContentBase content, PropertyGroup propertyGroup) { //get the properties for the current tab return content.Properties @@ -178,7 +178,7 @@ namespace Umbraco.Core } // gets or creates a property for a content item. - private static Property GetProperty(IContentBase content, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias) + private static IProperty GetProperty(IContentBase content, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias) { var property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); if (property != null) return property; diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index fbb68194b7..c87cb0a370 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Models { private int _contentTypeId; private int _writerId; - private PropertyCollection _properties; + private IPropertyCollection _properties; private ContentCultureInfosCollection _cultureInfos; internal IReadOnlyList AllPropertyTypes { get; } @@ -135,7 +135,7 @@ namespace Umbraco.Core.Models /// [DataMember] [DoNotClone] - public PropertyCollection Properties + public IPropertyCollection Properties { get => _properties; set @@ -490,7 +490,7 @@ namespace Umbraco.Core.Models if (clonedContent._properties != null) { clonedContent._properties.CollectionChanged -= PropertiesChanged; //clear this event handler if any - clonedContent._properties = (PropertyCollection)_properties.DeepClone(); //manually deep clone + clonedContent._properties = (IPropertyCollection)_properties.DeepClone(); //manually deep clone clonedContent._properties.CollectionChanged += clonedContent.PropertiesChanged; //re-assign correct event handler } diff --git a/src/Umbraco.Core/Models/ContentTagsExtensions.cs b/src/Umbraco.Core/Models/ContentTagsExtensions.cs index dd7a716520..7f9c012722 100644 --- a/src/Umbraco.Core/Models/ContentTagsExtensions.cs +++ b/src/Umbraco.Core/Models/ContentTagsExtensions.cs @@ -34,7 +34,7 @@ namespace Umbraco.Core.Models } // gets and validates the property - private static Property GetTagProperty(this IContentBase content, string propertyTypeAlias) + private static IProperty GetTagProperty(this IContentBase content, string propertyTypeAlias) { if (content == null) throw new ArgumentNullException(nameof(content)); diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs index 0f660181fb..1864996379 100644 --- a/src/Umbraco.Core/Models/IContentBase.cs +++ b/src/Umbraco.Core/Models/IContentBase.cs @@ -98,7 +98,7 @@ namespace Umbraco.Core.Models /// List of properties, which make up all the data available for this Content object /// /// Properties are loaded as part of the Content object graph - PropertyCollection Properties { get; set; } + IPropertyCollection Properties { get; set; } /// /// Gets a value indicating whether the content entity has a property with the supplied alias. diff --git a/src/Umbraco.Core/Models/IPropertyCollection.cs b/src/Umbraco.Core/Models/IPropertyCollection.cs new file mode 100644 index 0000000000..e5bd4f60fd --- /dev/null +++ b/src/Umbraco.Core/Models/IPropertyCollection.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Collections.Specialized; + +namespace Umbraco.Core.Models +{ + public interface IPropertyCollection : IEnumerable + { + bool TryGetValue(string propertyTypeAlias, out IProperty property); + bool Contains(string key); + + event NotifyCollectionChangedEventHandler CollectionChanged; + + void EnsurePropertyTypes(IEnumerable propertyTypes); + void EnsureCleanPropertyTypes(IEnumerable propertyTypes); + object DeepClone(); + + IProperty this[string name] { get; } + IProperty this[int index] { get; } + void Add(IProperty property); + + int Count { get; } + } +} diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 9b2cda67c1..9aa9ab4d74 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Models /// /// Initializes a new instance of the class. /// - public Property(PropertyType propertyType) + public Property(IPropertyType propertyType) { PropertyType = propertyType; } @@ -41,7 +41,7 @@ namespace Umbraco.Core.Models /// /// Initializes a new instance of the class. /// - public Property(int id, PropertyType propertyType) + public Property(int id, IPropertyType propertyType) { Id = id; PropertyType = propertyType; @@ -152,7 +152,7 @@ namespace Umbraco.Core.Models /// Returns the Id of the PropertyType, which this Property is based on /// [IgnoreDataMember] - internal int PropertyTypeId => PropertyType.Id; + public int PropertyTypeId => PropertyType.Id; /// /// Returns the DatabaseType that the underlaying DataType is using to store its values @@ -161,7 +161,7 @@ namespace Umbraco.Core.Models /// Only used internally when saving the property value. /// [IgnoreDataMember] - internal ValueStorageType ValueStorageType => PropertyType.ValueStorageType; + public ValueStorageType ValueStorageType => PropertyType.ValueStorageType; /// /// Gets the value. @@ -191,7 +191,7 @@ namespace Umbraco.Core.Models // internal - must be invoked by the content item // does *not* validate the value - content item must validate first - internal void PublishValues(string culture = "*", string segment = "*") + public void PublishValues(string culture = "*", string segment = "*") { culture = culture.NullOrWhiteSpaceAsNull(); segment = segment.NullOrWhiteSpaceAsNull(); @@ -216,7 +216,7 @@ namespace Umbraco.Core.Models } // internal - must be invoked by the content item - internal void UnpublishValues(string culture = "*", string segment = "*") + public void UnpublishValues(string culture = "*", string segment = "*") { culture = culture.NullOrWhiteSpaceAsNull(); segment = segment.NullOrWhiteSpaceAsNull(); diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs index c587a45424..2415be4dce 100644 --- a/src/Umbraco.Core/Models/PropertyCollection.cs +++ b/src/Umbraco.Core/Models/PropertyCollection.cs @@ -12,10 +12,10 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class PropertyCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable + public class PropertyCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable, IPropertyCollection { private readonly object _addLocker = new object(); - + internal Func AdditionValidator { get; set; } /// @@ -60,7 +60,7 @@ namespace Umbraco.Core.Models /// /// Replaces the property at the specified index with the specified property. /// - protected override void SetItem(int index, Property property) + protected override void SetItem(int index, IProperty property) { var oldItem = index >= 0 ? this[index] : property; base.SetItem(index, property); @@ -80,7 +80,7 @@ namespace Umbraco.Core.Models /// /// Inserts the specified property at the specified index. /// - protected override void InsertItem(int index, Property property) + protected override void InsertItem(int index, IProperty property) { base.InsertItem(index, property); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property)); @@ -98,7 +98,7 @@ namespace Umbraco.Core.Models /// /// Adds or updates a property. /// - internal new void Add(Property property) + public new void Add(IProperty property) { lock (_addLocker) // TODO: why are we locking here and not everywhere else?! { @@ -141,7 +141,7 @@ namespace Umbraco.Core.Models return -1; } - protected override string GetKeyForItem(Property item) + protected override string GetKeyForItem(IProperty item) { return item.Alias; } @@ -149,7 +149,7 @@ namespace Umbraco.Core.Models /// /// Gets the property with the specified PropertyType. /// - internal Property this[PropertyType propertyType] + internal IProperty this[IPropertyType propertyType] { get { @@ -157,7 +157,7 @@ namespace Umbraco.Core.Models } } - public bool TryGetValue(string propertyTypeAlias, out Property property) + public bool TryGetValue(string propertyTypeAlias, out IProperty property) { property = this.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); return property != null; @@ -176,7 +176,7 @@ namespace Umbraco.Core.Models /// /// Ensures that the collection contains properties for the specified property types. /// - protected internal void EnsurePropertyTypes(IEnumerable propertyTypes) + public void EnsurePropertyTypes(IEnumerable propertyTypes) { if (propertyTypes == null) return; @@ -188,7 +188,7 @@ namespace Umbraco.Core.Models /// /// Ensures that the collection does not contain properties not in the specified property types. /// - protected internal void EnsureCleanPropertyTypes(IEnumerable propertyTypes) + public void EnsureCleanPropertyTypes(IEnumerable propertyTypes) { if (propertyTypes == null) return; diff --git a/src/Umbraco.Core/Models/PropertyTagsExtensions.cs b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs index 63cf870221..1bac5c98f1 100644 --- a/src/Umbraco.Core/Models/PropertyTagsExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Models // gets the tag configuration for a property // from the datatype configuration, and the editor tag configuration attribute - internal static TagConfiguration GetTagConfiguration(this Property property) + internal static TagConfiguration GetTagConfiguration(this IProperty property) { if (property == null) throw new ArgumentNullException(nameof(property)); @@ -44,7 +44,7 @@ namespace Umbraco.Core.Models /// The tags. /// A value indicating whether to merge the tags with existing tags instead of replacing them. /// A culture, for multi-lingual properties. - public static void AssignTags(this Property property, IEnumerable tags, bool merge = false, string culture = null) + public static void AssignTags(this IProperty property, IEnumerable tags, bool merge = false, string culture = null) { if (property == null) throw new ArgumentNullException(nameof(property)); @@ -56,7 +56,7 @@ namespace Umbraco.Core.Models } // assumes that parameters are consistent with the datatype configuration - private static void AssignTags(this Property property, IEnumerable tags, bool merge, TagsStorageType storageType, char delimiter, string culture) + private static void AssignTags(this IProperty property, IEnumerable tags, bool merge, TagsStorageType storageType, char delimiter, string culture) { // set the property value var trimmedTags = tags.Select(x => x.Trim()).ToArray(); @@ -97,7 +97,7 @@ namespace Umbraco.Core.Models /// The property. /// The tags. /// A culture, for multi-lingual properties. - public static void RemoveTags(this Property property, IEnumerable tags, string culture = null) + public static void RemoveTags(this IProperty property, IEnumerable tags, string culture = null) { if (property == null) throw new ArgumentNullException(nameof(property)); @@ -109,7 +109,7 @@ namespace Umbraco.Core.Models } // assumes that parameters are consistent with the datatype configuration - private static void RemoveTags(this Property property, IEnumerable tags, TagsStorageType storageType, char delimiter, string culture) + private static void RemoveTags(this IProperty property, IEnumerable tags, TagsStorageType storageType, char delimiter, string culture) { // already empty = nothing to do var value = property.GetValue(culture)?.ToString(); @@ -131,7 +131,7 @@ namespace Umbraco.Core.Models } // used by ContentRepositoryBase - internal static IEnumerable GetTagsValue(this Property property, string culture = null) + internal static IEnumerable GetTagsValue(this IProperty property, string culture = null) { if (property == null) throw new ArgumentNullException(nameof(property)); @@ -142,7 +142,7 @@ namespace Umbraco.Core.Models return property.GetTagsValue(configuration.StorageType, configuration.Delimiter, culture); } - private static IEnumerable GetTagsValue(this Property property, TagsStorageType storageType, char delimiter, string culture = null) + private static IEnumerable GetTagsValue(this IProperty property, TagsStorageType storageType, char delimiter, string culture = null) { if (property == null) throw new ArgumentNullException(nameof(property)); @@ -182,7 +182,7 @@ namespace Umbraco.Core.Models /// This is used both by the content repositories to initialize a property with some tag values, and by the /// content controllers to update a property with values received from the property editor. /// - internal static void SetTagsValue(this Property property, object value, TagConfiguration tagConfiguration, string culture) + internal static void SetTagsValue(this IProperty property, object value, TagConfiguration tagConfiguration, string culture) { if (property == null) throw new ArgumentNullException(nameof(property)); if (tagConfiguration == null) throw new ArgumentNullException(nameof(tagConfiguration)); @@ -195,7 +195,7 @@ namespace Umbraco.Core.Models // assumes that parameters are consistent with the datatype configuration // value can be an enumeration of string, or a serialized value using storageType format - private static void SetTagsValue(Property property, object value, TagsStorageType storageType, char delimiter, string culture) + private static void SetTagsValue(IProperty property, object value, TagsStorageType storageType, char delimiter, string culture) { if (value == null) value = Enumerable.Empty(); diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index 9d9482fedb..fc31f61763 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -46,7 +46,7 @@ namespace Umbraco.Core.Persistence.Factories return properties; } - private static PropertyDataDto BuildDto(int versionId, Property property, int? languageId, string segment, object value) + private static PropertyDataDto BuildDto(int versionId, IProperty property, int? languageId, string segment, object value) { var dto = new PropertyDataDto { VersionId = versionId, PropertyTypeId = property.PropertyTypeId }; @@ -109,7 +109,7 @@ namespace Umbraco.Core.Persistence.Factories /// The value of this will be used to populate the edited cultures in the umbracoDocumentCultureVariation table. /// /// - public static IEnumerable BuildDtos(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable properties, + public static IEnumerable BuildDtos(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable properties, ILanguageRepository languageRepository, out bool edited, out HashSet editedCultures) { diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 5a43738230..2fa75e2f3f 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -1402,7 +1402,7 @@ namespace Umbraco.Core.Services.Implement if (d.Trashed) continue; // won't publish //publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed - Property[] invalidProperties = null; + IProperty[] invalidProperties = null; var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs, culture)); var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact); if (invalidProperties != null && invalidProperties.Length > 0) @@ -2602,7 +2602,7 @@ namespace Umbraco.Core.Services.Implement return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content); //validate the property values - Property[] invalidProperties = null; + IProperty[] invalidProperties = null; if (!impactsToPublish.All(x => _propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties, x))) return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content) { diff --git a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs index bc21da15a7..f115dacc6c 100644 --- a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs @@ -77,7 +77,7 @@ namespace Umbraco.Core.Services.Implement var children = _contentService.GetPagedChildren(content.Id, page++, pageSize, out total); SerializeChildren(children, xml, published); } - + } return xml; @@ -552,7 +552,7 @@ namespace Umbraco.Core.Services.Implement } // exports a property as XElements. - private IEnumerable SerializeProperty(Property property, bool published) + private IEnumerable SerializeProperty(IProperty property, bool published) { var propertyType = property.PropertyType; diff --git a/src/Umbraco.Core/Services/PropertyValidationService.cs b/src/Umbraco.Core/Services/PropertyValidationService.cs index f619e5f47e..1704d52206 100644 --- a/src/Umbraco.Core/Services/PropertyValidationService.cs +++ b/src/Umbraco.Core/Services/PropertyValidationService.cs @@ -31,7 +31,7 @@ namespace Umbraco.Core.Services /// /// Validates the content item's properties pass validation rules /// - public bool IsPropertyDataValid(IContent content, out Property[] invalidProperties, CultureImpact impact) + public bool IsPropertyDataValid(IContent content, out IProperty[] invalidProperties, CultureImpact impact) { // select invalid properties invalidProperties = content.Properties.Where(x => @@ -66,7 +66,7 @@ namespace Umbraco.Core.Services /// /// Gets a value indicating whether the property has valid values. /// - public bool IsPropertyValid(Property property, string culture = "*", string segment = "*") + public bool IsPropertyValid(IProperty property, string culture = "*", string segment = "*") { //NOTE - the pvalue and vvalues logic in here is borrowed directly from the Property.Values setter so if you are wondering what that's all about, look there. // The underlying Property._pvalue and Property._vvalues are not exposed but we can re-create these values ourselves which is what it's doing. diff --git a/src/Umbraco.Core/Services/PublishResult.cs b/src/Umbraco.Core/Services/PublishResult.cs index 4f1ff776a2..fe11d77cf3 100644 --- a/src/Umbraco.Core/Services/PublishResult.cs +++ b/src/Umbraco.Core/Services/PublishResult.cs @@ -32,6 +32,6 @@ namespace Umbraco.Core.Services /// /// Gets or sets the invalid properties, if the status failed due to validation. /// - public IEnumerable InvalidProperties { get; set; } + public IEnumerable InvalidProperties { get; set; } } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 3241a3852c..8df0c336db 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -241,6 +241,7 @@ + diff --git a/src/Umbraco.Examine/BaseValueSetBuilder.cs b/src/Umbraco.Examine/BaseValueSetBuilder.cs index 93cee88231..4a306aa5ff 100644 --- a/src/Umbraco.Examine/BaseValueSetBuilder.cs +++ b/src/Umbraco.Examine/BaseValueSetBuilder.cs @@ -24,7 +24,7 @@ namespace Umbraco.Examine /// public abstract IEnumerable GetValueSets(params TContent[] content); - protected void AddPropertyValue(Property property, string culture, string segment, IDictionary> values) + protected void AddPropertyValue(IProperty property, string culture, string segment, IDictionary> values) { var editor = _propertyEditors[property.PropertyType.PropertyEditorAlias]; if (editor == null) return; @@ -61,7 +61,7 @@ namespace Umbraco.Examine else values.Add($"{keyVal.Key}{cultureSuffix}", val.Yield()); } - + break; } } diff --git a/src/Umbraco.Tests/Mapping/MappingTests.cs b/src/Umbraco.Tests/Mapping/MappingTests.cs index e6a382692c..ad336168c6 100644 --- a/src/Umbraco.Tests/Mapping/MappingTests.cs +++ b/src/Umbraco.Tests/Mapping/MappingTests.cs @@ -285,10 +285,10 @@ namespace Umbraco.Tests.Mapping { public void DefineMaps(UmbracoMapper mapper) { - mapper.Define((source, context) => new ContentPropertyDto(), Map); + mapper.Define((source, context) => new ContentPropertyDto(), Map); } - private static void Map(Property source, ContentPropertyDto target, MapperContext context) + private static void Map(IProperty source, ContentPropertyDto target, MapperContext context) { } } diff --git a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs index 6a4054d5ae..996f02e16a 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs @@ -261,7 +261,7 @@ namespace Umbraco.Tests.Models.Mapping #region Assertions - private void AssertDisplayProperty(IContentProperties result, Property p) + private void AssertDisplayProperty(IContentProperties result, IProperty p) where T : ContentPropertyBasic { var pDto = result.Properties.SingleOrDefault(x => x.Alias == p.Alias); @@ -325,7 +325,7 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(content.Properties.Count(), result.Properties.Count(x => x.Alias.StartsWith("_umb_") == false)); } - private void AssertBasicProperty(IContentProperties result, Property p) + private void AssertBasicProperty(IContentProperties result, IProperty p) where T : ContentPropertyBasic { var pDto = result.Properties.SingleOrDefault(x => x.Alias == p.Alias); @@ -341,7 +341,7 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(pDto.Value, p.GetValue().ToString()); } - private void AssertProperty(IContentProperties result, Property p) + private void AssertProperty(IContentProperties result, IProperty p) { AssertBasicProperty(result, p); diff --git a/src/Umbraco.Tests/Views/web.config b/src/Umbraco.Tests/Views/web.config new file mode 100644 index 0000000000..efd80424e5 --- /dev/null +++ b/src/Umbraco.Tests/Views/web.config @@ -0,0 +1,74 @@ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 5c8e6fc2b5..11cc59a464 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1837,7 +1837,7 @@ namespace Umbraco.Web.Editors private void MapValuesForPersistence(ContentItemSave contentSave) { // inline method to determine if a property type varies - bool Varies(Property property) => property.PropertyType.VariesByCulture(); + bool Varies(IProperty property) => property.PropertyType.VariesByCulture(); var variantIndex = 0; diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index 300c777b3a..83167eb9ae 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -50,8 +50,8 @@ namespace Umbraco.Web.Editors internal void MapPropertyValuesForPersistence( TSaved contentItem, ContentPropertyCollectionDto dto, - Func getPropertyValue, - Action savePropertyValue, + Func getPropertyValue, + Action savePropertyValue, string culture) where TPersisted : IContentBase where TSaved : IContentSave diff --git a/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs b/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs index 4acf0c948e..14531227e1 100644 --- a/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs +++ b/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs @@ -46,7 +46,7 @@ namespace Umbraco.Web.Editors.Filters protected ContentModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, umbracoContextAccessor) { } - + /// /// Ensure the content exists /// @@ -85,7 +85,7 @@ namespace Umbraco.Web.Editors.Filters /// /// /// - protected bool ValidateProperties(List postedProperties, List persistedProperties, HttpActionContext actionContext) + protected bool ValidateProperties(List postedProperties, List persistedProperties, HttpActionContext actionContext) { foreach (var p in postedProperties) { @@ -142,7 +142,7 @@ namespace Umbraco.Web.Editors.Filters var postedValue = postedProp.Value; ValidatePropertyValue(model, modelWithProperties, editor, p, postedValue, modelState); - + } return modelState.IsValid; diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index 41f0e2fb65..cab20926ff 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -147,7 +147,7 @@ namespace Umbraco.Web.Macros _content = content; } - public PagePublishedProperty(IPublishedPropertyType propertyType, IPublishedContent content, Umbraco.Core.Models.Property property) + public PagePublishedProperty(IPublishedPropertyType propertyType, IPublishedContent content, IProperty property) : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored { _sourceValue = property.GetValue(); diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs index dc0df4ca96..d3b4353f2f 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs @@ -64,7 +64,7 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll private static void Map(IContent source, ContentPropertyCollectionDto target, MapperContext context) { - target.Properties = context.MapEnumerable(source.Properties); + target.Properties = context.MapEnumerable(source.Properties); } // Umbraco.Code.MapAll -AllowPreview -Errors -PersistedContent @@ -99,7 +99,7 @@ namespace Umbraco.Web.Models.Mapping target.Variants = _contentVariantMapper.Map(source, context); target.ContentDto = new ContentPropertyCollectionDto(); - target.ContentDto.Properties = context.MapEnumerable(source.Properties); + target.ContentDto.Properties = context.MapEnumerable(source.Properties); } // Umbraco.Code.MapAll -Segment -Language @@ -129,7 +129,7 @@ namespace Umbraco.Web.Models.Mapping target.Owner = _commonMapper.GetOwner(source, context); target.ParentId = source.ParentId; target.Path = source.Path; - target.Properties = context.MapEnumerable(source.Properties); + target.Properties = context.MapEnumerable(source.Properties); target.SortOrder = source.SortOrder; target.State = _basicStateMapper.Map(source, context); target.Trashed = source.Trashed; diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs index 36c1b360b2..4e49f2ea2a 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs @@ -33,7 +33,7 @@ namespace Umbraco.Web.Models.Mapping /// Assigns the PropertyEditor, Id, Alias and Value to the property ///
/// - public virtual void Map(Property property, TDestination dest, MapperContext context) + public virtual void Map(IProperty property, TDestination dest, MapperContext context) { var editor = _propertyEditors[property.PropertyType.PropertyEditorAlias]; if (editor == null) diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs index f68c5d8b44..12278e97ea 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.Models.Mapping { _textService = textService; } - public override void Map(Property originalProp, ContentPropertyDisplay dest, MapperContext context) + public override void Map(IProperty originalProp, ContentPropertyDisplay dest, MapperContext context) { base.Map(originalProp, dest, context); diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs index 72107c6201..f481ad445c 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs @@ -16,7 +16,7 @@ namespace Umbraco.Web.Models.Mapping : base(dataTypeService, entityService, logger, propertyEditors) { } - public override void Map(Property property, ContentPropertyDto dest, MapperContext context) + public override void Map(IProperty property, ContentPropertyDto dest, MapperContext context) { base.Map(property, dest, context); diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs index e6290cc19e..5d659fbf9e 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs @@ -27,9 +27,9 @@ namespace Umbraco.Web.Models.Mapping public void DefineMaps(UmbracoMapper mapper) { mapper.Define>((source, context) => new Tab(), Map); - mapper.Define((source, context) => new ContentPropertyBasic(), Map); - mapper.Define((source, context) => new ContentPropertyDto(), Map); - mapper.Define((source, context) => new ContentPropertyDisplay(), Map); + mapper.Define((source, context) => new ContentPropertyBasic(), Map); + mapper.Define((source, context) => new ContentPropertyDto(), Map); + mapper.Define((source, context) => new ContentPropertyDisplay(), Map); } // Umbraco.Code.MapAll -Properties -Alias -Expanded @@ -40,19 +40,19 @@ namespace Umbraco.Web.Models.Mapping target.Label = source.Name; } - private void Map(Property source, ContentPropertyBasic target, MapperContext context) + private void Map(IProperty source, ContentPropertyBasic target, MapperContext context) { // assume this is mapping everything and no MapAll is required _contentPropertyBasicConverter.Map(source, target, context); } - private void Map(Property source, ContentPropertyDto target, MapperContext context) + private void Map(IProperty source, ContentPropertyDto target, MapperContext context) { // assume this is mapping everything and no MapAll is required _contentPropertyDtoConverter.Map(source, target, context); } - private void Map(Property source, ContentPropertyDisplay target, MapperContext context) + private void Map(IProperty source, ContentPropertyDisplay target, MapperContext context) { // assume this is mapping everything and no MapAll is required _contentPropertyDisplayMapper.Map(source, target, context); diff --git a/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs index 05c006ec41..80bdc7ade4 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs @@ -42,7 +42,7 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll private static void Map(IMedia source, ContentPropertyCollectionDto target, MapperContext context) { - target.Properties = context.MapEnumerable(source.Properties); + target.Properties = context.MapEnumerable(source.Properties); } // Umbraco.Code.MapAll -Properties -Errors -Edited -Updater -Alias -IsContainer @@ -86,7 +86,7 @@ namespace Umbraco.Web.Models.Mapping target.Owner = _commonMapper.GetOwner(source, context); target.ParentId = source.ParentId; target.Path = source.Path; - target.Properties = context.MapEnumerable(source.Properties); + target.Properties = context.MapEnumerable(source.Properties); target.SortOrder = source.SortOrder; target.State = null; target.Trashed = source.Trashed; diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs index 8671bfe538..fd295803c1 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs @@ -113,7 +113,7 @@ namespace Umbraco.Web.Models.Mapping target.Owner = _commonMapper.GetOwner(source, context); target.ParentId = source.ParentId; target.Path = source.Path; - target.Properties = context.MapEnumerable(source.Properties); + target.Properties = context.MapEnumerable(source.Properties); target.SortOrder = source.SortOrder; target.State = null; target.Udi = Udi.Create(Constants.UdiEntityType.Member, source.Key); @@ -151,7 +151,7 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll private static void Map(IMember source, ContentPropertyCollectionDto target, MapperContext context) { - target.Properties = context.MapEnumerable(source.Properties); + target.Properties = context.MapEnumerable(source.Properties); } private MembershipScenario GetMembershipScenario() diff --git a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs index 8744b068a7..81d75de498 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs @@ -176,7 +176,7 @@ namespace Umbraco.Web.Models.Mapping /// /// /// - protected override List MapProperties(IContentBase content, List properties, MapperContext context) + protected override List MapProperties(IContentBase content, List properties, MapperContext context) { var result = base.MapProperties(content, properties, context); var member = (IMember)content; diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs index b8d76572fb..70a001735a 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs @@ -103,9 +103,9 @@ namespace Umbraco.Web.Models.Mapping /// /// /// - protected virtual List MapProperties(IContentBase content, List properties, MapperContext context) + protected virtual List MapProperties(IContentBase content, List properties, MapperContext context) { - return context.MapEnumerable(properties.OrderBy(x => x.PropertyType.SortOrder)); + return context.MapEnumerable(properties.OrderBy(x => x.PropertyType.SortOrder)); } } @@ -132,7 +132,7 @@ namespace Umbraco.Web.Models.Mapping var groupsGroupsByName = contentType.CompositionPropertyGroups.OrderBy(x => x.SortOrder).GroupBy(x => x.Name); foreach (var groupsByName in groupsGroupsByName) { - var properties = new List(); + var properties = new List(); // merge properties for groups with the same name foreach (var group in groupsByName) diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index a3396a5fb3..849ad443c8 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -48,7 +48,7 @@ namespace Umbraco.Web.PropertyEditors ///
/// The property. /// A value indicating whether a property is an upload field, and (optionally) has a non-empty value. - private static bool IsUploadField(Property property) + private static bool IsUploadField(IProperty property) { return property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField; } @@ -70,7 +70,7 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - private IEnumerable GetFilePathsFromPropertyValues(Property prop) + private IEnumerable GetFilePathsFromPropertyValues(IProperty prop) { var propVals = prop.Values; foreach (var propertyValue in propVals) diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs index 1d7c1eef65..d120210411 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs @@ -65,7 +65,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The property. /// A value indicating whether a property is an image cropper field, and (optionally) has a non-empty value. - private static bool IsCropperField(Property property) + private static bool IsCropperField(IProperty property) { return property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper; } @@ -111,7 +111,7 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - private IEnumerable GetFilePathsFromPropertyValues(Property prop) + private IEnumerable GetFilePathsFromPropertyValues(IProperty prop) { //parses out the src from a json string diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index bf4975714d..6d34a2af42 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -4,6 +4,7 @@ using System.Linq; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Exceptions; +using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Composing; using Umbraco.Web.Models; From d84963dac516fab3a9b21767b5570da012beb4b1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Nov 2019 16:07:47 +1100 Subject: [PATCH 11/36] Uses ITypeFinder abstraction where required --- .../Cache/FastDictionaryAppCache.cs | 9 +++- .../Cache/FastDictionaryAppCacheBase.cs | 9 +++- .../Cache/HttpRequestAppCache.cs | 3 +- .../Cache/ObjectCacheAppCache.cs | 6 ++- .../Composing/ITypeFinder.cs | 2 + .../Composing/TypeFinderExtensions.cs | 0 .../Composing/TypeHelper.cs | 22 +--------- src/Umbraco.Core/Composing/TypeFinder.cs | 42 +++++++++++++++++++ src/Umbraco.Core/Composing/TypeLoader.cs | 17 +++----- .../CompositionExtensions_Essentials.cs | 4 +- src/Umbraco.Core/Runtime/CoreRuntime.cs | 25 ++++++++--- src/Umbraco.Core/Scoping/Scope.cs | 14 ++++--- src/Umbraco.Core/Scoping/ScopeProvider.cs | 10 +++-- src/Umbraco.Core/Umbraco.Core.csproj | 2 - src/Umbraco.Examine/LuceneIndexCreator.cs | 9 +++- .../Cache/DeepCloneAppCacheTests.cs | 6 ++- .../Cache/HttpRequestAppCacheTests.cs | 8 +++- .../Cache/ObjectAppCacheTests.cs | 6 ++- .../Cache/WebCachingAppCacheTests.cs | 6 ++- .../Components/ComponentTests.cs | 3 +- src/Umbraco.Tests/Macros/MacroTests.cs | 9 ++-- src/Umbraco.Tests/Models/ContentTests.cs | 3 +- .../Repositories/DocumentRepositoryTest.cs | 4 +- .../Repositories/MediaRepositoryTest.cs | 4 +- .../Published/PropertyCacheLevelTests.cs | 6 ++- .../PublishedContent/NuCacheChildrenTests.cs | 5 ++- .../PublishedContent/NuCacheTests.cs | 5 ++- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 4 +- .../Scoping/ScopedNuCacheTests.cs | 5 ++- .../Scoping/ScopedRepositoryTests.cs | 4 +- .../ContentTypeServiceVariantsTests.cs | 6 ++- src/Umbraco.Tests/TestHelpers/TestObjects.cs | 5 ++- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 5 ++- src/Umbraco.Web/Cache/WebCachingAppCache.cs | 3 +- .../NuCache/PublishedSnapshotService.cs | 8 +++- src/Umbraco.Web/Runtime/WebRuntime.cs | 6 +-- .../Search/UmbracoIndexesCreator.cs | 7 +++- 37 files changed, 200 insertions(+), 92 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Composing/ITypeFinder.cs (97%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Composing/TypeFinderExtensions.cs (100%) diff --git a/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs index 35ba4d08ca..159f9cd7cb 100644 --- a/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs @@ -12,6 +12,13 @@ namespace Umbraco.Core.Cache /// public class FastDictionaryAppCache : IAppCache { + private readonly ITypeFinder _typeFinder; + + public FastDictionaryAppCache(ITypeFinder typeFinder) + { + _typeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); + } + /// /// Gets the internal items dictionary, for tests only! /// @@ -78,7 +85,7 @@ namespace Umbraco.Core.Cache /// public void ClearOfType(string typeName) { - var type = TypeHelper.GetTypeByName(typeName); + var type = _typeFinder.GetTypeByName(typeName); if (type == null) return; var isInterface = type.IsInterface; diff --git a/src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs index b49ecea671..57f4e7b9a2 100644 --- a/src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs +++ b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs @@ -12,6 +12,13 @@ namespace Umbraco.Core.Cache /// public abstract class FastDictionaryAppCacheBase : IAppCache { + private readonly ITypeFinder _typeFinder; + + protected FastDictionaryAppCacheBase(ITypeFinder typeFinder) + { + _typeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); + } + // prefix cache keys so we know which one are ours protected const string CacheItemPrefix = "umbrtmche"; @@ -116,7 +123,7 @@ namespace Umbraco.Core.Cache /// public virtual void ClearOfType(string typeName) { - var type = TypeHelper.GetTypeByName(typeName); + var type = _typeFinder.GetTypeByName(typeName); if (type == null) return; var isInterface = type.IsInterface; try diff --git a/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs b/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs index 4fad382cc8..41c930ae86 100644 --- a/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Web; +using Umbraco.Core.Composing; namespace Umbraco.Core.Cache { @@ -20,7 +21,7 @@ namespace Umbraco.Core.Cache /// /// Initializes a new instance of the class with a context, for unit tests! /// - public HttpRequestAppCache(Func requestItems) + public HttpRequestAppCache(Func requestItems, ITypeFinder typeFinder) : base(typeFinder) { ContextItems = requestItems; } diff --git a/src/Umbraco.Abstractions/Cache/ObjectCacheAppCache.cs b/src/Umbraco.Abstractions/Cache/ObjectCacheAppCache.cs index 7d5081baa5..208390276a 100644 --- a/src/Umbraco.Abstractions/Cache/ObjectCacheAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/ObjectCacheAppCache.cs @@ -13,13 +13,15 @@ namespace Umbraco.Core.Cache /// public class ObjectCacheAppCache : IAppPolicyCache { + private readonly ITypeFinder _typeFinder; private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); /// /// Initializes a new instance of the . /// - public ObjectCacheAppCache() + public ObjectCacheAppCache(ITypeFinder typeFinder) { + _typeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); // the MemoryCache is created with name "in-memory". That name is // used to retrieve configuration options. It does not identify the memory cache, i.e. // each instance of this class has its own, independent, memory cache. @@ -178,7 +180,7 @@ namespace Umbraco.Core.Cache /// public virtual void ClearOfType(string typeName) { - var type = TypeHelper.GetTypeByName(typeName); + var type = _typeFinder.GetTypeByName(typeName); if (type == null) return; var isInterface = type.IsInterface; try diff --git a/src/Umbraco.Core/Composing/ITypeFinder.cs b/src/Umbraco.Abstractions/Composing/ITypeFinder.cs similarity index 97% rename from src/Umbraco.Core/Composing/ITypeFinder.cs rename to src/Umbraco.Abstractions/Composing/ITypeFinder.cs index 844719b782..d5908f9c54 100644 --- a/src/Umbraco.Core/Composing/ITypeFinder.cs +++ b/src/Umbraco.Abstractions/Composing/ITypeFinder.cs @@ -6,6 +6,8 @@ namespace Umbraco.Core.Composing { public interface ITypeFinder { + Type GetTypeByName(string name); + /// /// Return a list of found local Assemblies that Umbraco should scan for type finding /// diff --git a/src/Umbraco.Core/Composing/TypeFinderExtensions.cs b/src/Umbraco.Abstractions/Composing/TypeFinderExtensions.cs similarity index 100% rename from src/Umbraco.Core/Composing/TypeFinderExtensions.cs rename to src/Umbraco.Abstractions/Composing/TypeFinderExtensions.cs diff --git a/src/Umbraco.Abstractions/Composing/TypeHelper.cs b/src/Umbraco.Abstractions/Composing/TypeHelper.cs index 1752521b5d..28eab6a5ec 100644 --- a/src/Umbraco.Abstractions/Composing/TypeHelper.cs +++ b/src/Umbraco.Abstractions/Composing/TypeHelper.cs @@ -19,28 +19,10 @@ namespace Umbraco.Core.Composing = new ConcurrentDictionary, PropertyInfo[]>(); private static readonly ConcurrentDictionary GetFieldsCache = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary TypeNamesCache - = new ConcurrentDictionary(); - + private static readonly Assembly[] EmptyAssemblies = new Assembly[0]; - /// - /// Returns a Type for the string type name - /// - /// - /// - public static Type GetTypeByName(string name) - { - // First try using the basic functionality - var type = Type.GetType(name); - if (type != null) return type; - - // It didn't parse, so try loading from each already loaded assembly and cache it - return TypeNamesCache.GetOrAdd(name, s => - AppDomain.CurrentDomain.GetAssemblies() - .Select(x => x.GetType(s)) - .FirstOrDefault(x => x != null)); - } + /// /// Based on a type we'll check if it is IEnumerable{T} (or similar) and if so we'll return a List{T}, this will also deal with array types and return List{T} for those too. diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index a1b1bf2fef..03fe115bbc 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Configuration; using System.IO; @@ -115,6 +116,7 @@ namespace Umbraco.Core.Composing private readonly object _localFilteredAssemblyCacheLocker = new object(); private readonly List _notifiedLoadExceptionAssemblies = new List(); private string[] _assembliesAcceptingLoadExceptions; + private static readonly ConcurrentDictionary TypeNamesCache= new ConcurrentDictionary(); private string[] AssembliesAcceptingLoadExceptions { @@ -310,8 +312,47 @@ namespace Umbraco.Core.Composing return GetClassesWithAttribute(attributeType, assemblyList, onlyConcreteClasses); } + /// + /// Returns a Type for the string type name + /// + /// + /// + public virtual Type GetTypeByName(string name) + { + // This is exactly what the BuildManager does, if the type is an assembly qualified type + // name it will find it. + if (TypeNameContainsAssembly(name)) + { + return Type.GetType(name); + } + + // It didn't parse, so try loading from each already loaded assembly and cache it + return TypeNamesCache.GetOrAdd(name, s => + AppDomain.CurrentDomain.GetAssemblies() + .Select(x => x.GetType(s)) + .FirstOrDefault(x => x != null)); + } + #region Private methods + // borrowed from aspnet System.Web.UI.Util + private static bool TypeNameContainsAssembly(string typeName) + { + return CommaIndexInTypeName(typeName) > 0; + } + + // borrowed from aspnet System.Web.UI.Util + private static int CommaIndexInTypeName(string typeName) + { + var num1 = typeName.LastIndexOf(','); + if (num1 < 0) + return -1; + var num2 = typeName.LastIndexOf(']'); + if (num2 > num1) + return -1; + return typeName.IndexOf(',', num2 + 1); + } + private IEnumerable GetClassesWithAttribute( Type attributeType, IEnumerable assemblies, @@ -510,5 +551,6 @@ namespace Umbraco.Core.Composing #endregion + } } diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index daa95ede49..1ee302aa6c 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -5,11 +5,9 @@ using System.Linq; using System.Reflection; using System.Text; using System.Threading; -using System.Web; using System.Web.Compilation; using Umbraco.Core.Cache; using Umbraco.Core.Collections; -using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; using File = System.IO.File; @@ -729,18 +727,13 @@ namespace Umbraco.Core.Composing // successfully retrieved types from the file cache: load foreach (var type in cacheResult.Result) { - try - { - // we use the build manager to ensure we get all types loaded, this is slightly slower than - // Type.GetType but if the types in the assembly aren't loaded yet it would fail whereas - // BuildManager will load them - this is how eg MVC loads types, etc - no need to make it - // more complicated - typeList.Add(BuildManager.GetType(type, true)); - } - catch (Exception ex) + var resolvedType = TypeFinder.GetTypeByName(type); + if (resolvedType != null) + typeList.Add(resolvedType); + else { // in case of any exception, we have to exit, and revert to scanning - _logger.Error(ex, "Getting {TypeName}: failed to load cache file type {CacheType}, reverting to scanning assemblies.", GetName(baseType, attributeType), type); + _logger.Warn("Getting {TypeName}: failed to load cache file type {CacheType}, reverting to scanning assemblies.", GetName(baseType, attributeType), type); scan = true; break; } diff --git a/src/Umbraco.Core/CompositionExtensions_Essentials.cs b/src/Umbraco.Core/CompositionExtensions_Essentials.cs index b85479716c..e4be927f71 100644 --- a/src/Umbraco.Core/CompositionExtensions_Essentials.cs +++ b/src/Umbraco.Core/CompositionExtensions_Essentials.cs @@ -19,7 +19,8 @@ namespace Umbraco.Core AppCaches appCaches, IUmbracoDatabaseFactory databaseFactory, TypeLoader typeLoader, - IRuntimeState state) + IRuntimeState state, + ITypeFinder typeFinder) { composition.RegisterUnique(logger); composition.RegisterUnique(profiler); @@ -30,6 +31,7 @@ namespace Umbraco.Core composition.RegisterUnique(factory => factory.GetInstance().SqlContext); composition.RegisterUnique(typeLoader); composition.RegisterUnique(state); + composition.RegisterUnique(typeFinder); } } } diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 8ccb740cc2..192d449d12 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -42,6 +42,11 @@ namespace Umbraco.Core.Runtime /// protected IProfilingLogger ProfilingLogger { get; private set; } + /// + /// Gets the + /// + protected ITypeFinder TypeFinder { get; private set; } + /// public IRuntimeState State => _state; @@ -56,6 +61,9 @@ namespace Umbraco.Core.Runtime var profiler = Profiler = GetProfiler(); var profilingLogger = ProfilingLogger = new ProfilingLogger(logger, profiler); + // type finder + TypeFinder = GetTypeFinder(); + // 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. // note that this REQUIRES that perWebRequestScope has NOT been enabled yet, else @@ -112,8 +120,7 @@ namespace Umbraco.Core.Runtime var configs = GetConfigs(); // type finder/loader - var typeFinder = new TypeFinder(Logger); - var typeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, configs.Global().LocalTempPath, ProfilingLogger); + var typeLoader = new TypeLoader(TypeFinder, appCaches.RuntimeCache, configs.Global().LocalTempPath, ProfilingLogger); // runtime state // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance' @@ -131,7 +138,7 @@ namespace Umbraco.Core.Runtime // create the composition composition = new Composition(register, typeLoader, ProfilingLogger, _state, configs); - composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, mainDom, appCaches, databaseFactory, typeLoader, _state); + composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, mainDom, appCaches, databaseFactory, typeLoader, _state, TypeFinder); // run handlers RuntimeOptions.DoRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory); @@ -289,7 +296,6 @@ namespace Umbraco.Core.Runtime /// public virtual void Compose(Composition composition) { - // nothing } #region Getters @@ -314,6 +320,13 @@ namespace Umbraco.Core.Runtime protected virtual IProfiler GetProfiler() => new LogProfiler(Logger); + /// + /// Gets a + /// + /// + protected virtual ITypeFinder GetTypeFinder() + => new TypeFinder(Logger); + /// /// Gets the application caches. /// @@ -324,9 +337,9 @@ namespace Umbraco.Core.Runtime // is overridden by the web runtime return new AppCaches( - new DeepCloneAppCache(new ObjectCacheAppCache()), + new DeepCloneAppCache(new ObjectCacheAppCache(TypeFinder)), NoAppCache.Instance, - new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); + new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache(TypeFinder)))); } // by default, returns null, meaning that Umbraco should auto-detect the application root path. diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index 84273e23da..3eabfbca9b 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -17,6 +17,7 @@ namespace Umbraco.Core.Scoping { private readonly ScopeProvider _scopeProvider; private readonly ILogger _logger; + private readonly ITypeFinder _typeFinder; private readonly IsolationLevel _isolationLevel; private readonly RepositoryCacheMode _repositoryCacheMode; @@ -35,7 +36,7 @@ namespace Umbraco.Core.Scoping // initializes a new scope private Scope(ScopeProvider scopeProvider, - ILogger logger, FileSystems fileSystems, Scope parent, ScopeContext scopeContext, bool detachable, + ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, Scope parent, ScopeContext scopeContext, bool detachable, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, @@ -45,6 +46,7 @@ namespace Umbraco.Core.Scoping { _scopeProvider = scopeProvider; _logger = logger; + _typeFinder = typeFinder; Context = scopeContext; @@ -109,26 +111,26 @@ namespace Umbraco.Core.Scoping // initializes a new scope public Scope(ScopeProvider scopeProvider, - ILogger logger, FileSystems fileSystems, bool detachable, ScopeContext scopeContext, + ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, bool detachable, ScopeContext scopeContext, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, logger, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) + : this(scopeProvider, logger, typeFinder, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) { } // initializes a new scope in a nested scopes chain, with its parent public Scope(ScopeProvider scopeProvider, - ILogger logger, FileSystems fileSystems, Scope parent, + ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, Scope parent, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, logger, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) + : this(scopeProvider, logger, typeFinder, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) { } public Guid InstanceId { get; } = Guid.NewGuid(); @@ -175,7 +177,7 @@ namespace Umbraco.Core.Scoping if (ParentScope != null) return ParentScope.IsolatedCaches; return _isolatedCaches ?? (_isolatedCaches - = new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); + = new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache(_typeFinder)))); } } diff --git a/src/Umbraco.Core/Scoping/ScopeProvider.cs b/src/Umbraco.Core/Scoping/ScopeProvider.cs index 3c0fa94327..60cb83fcd5 100644 --- a/src/Umbraco.Core/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/ScopeProvider.cs @@ -22,13 +22,15 @@ namespace Umbraco.Core.Scoping internal class ScopeProvider : IScopeProvider, IScopeAccessor { private readonly ILogger _logger; + private readonly ITypeFinder _typeFinder; private readonly FileSystems _fileSystems; - public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, ILogger logger) + public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, ILogger logger, ITypeFinder typeFinder) { DatabaseFactory = databaseFactory; _fileSystems = fileSystems; _logger = logger; + _typeFinder = typeFinder; // take control of the FileSystems _fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems; @@ -323,7 +325,7 @@ namespace Umbraco.Core.Scoping IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null) { - return new Scope(this, _logger, _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); + return new Scope(this, _logger, _typeFinder, _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); } /// @@ -379,13 +381,13 @@ namespace Umbraco.Core.Scoping { var ambientContext = AmbientContext; var newContext = ambientContext == null ? new ScopeContext() : null; - var scope = new Scope(this, _logger, _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); + var scope = new Scope(this, _logger, _typeFinder, _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); // assign only if scope creation did not throw! SetAmbient(scope, newContext ?? ambientContext); return scope; } - var nested = new Scope(this, _logger, _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); + var nested = new Scope(this, _logger, _typeFinder, _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); SetAmbient(nested, AmbientContext); return nested; } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index bfffc037ce..6643164020 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -136,9 +136,7 @@ - - diff --git a/src/Umbraco.Examine/LuceneIndexCreator.cs b/src/Umbraco.Examine/LuceneIndexCreator.cs index 97a929c9c8..47c4cce143 100644 --- a/src/Umbraco.Examine/LuceneIndexCreator.cs +++ b/src/Umbraco.Examine/LuceneIndexCreator.cs @@ -17,6 +17,13 @@ namespace Umbraco.Examine /// public abstract class LuceneIndexCreator : IIndexCreator { + private readonly ITypeFinder _typeFinder; + + protected LuceneIndexCreator(ITypeFinder typeFinder) + { + _typeFinder = typeFinder; + } + public abstract IEnumerable Create(); /// @@ -38,7 +45,7 @@ namespace Umbraco.Examine if (!configuredDirectoryFactory.IsNullOrWhiteSpace()) { //this should be a fully qualified type - var factoryType = TypeHelper.GetTypeByName(configuredDirectoryFactory); + var factoryType = _typeFinder.GetTypeByName(configuredDirectoryFactory); if (factoryType == null) throw new NullReferenceException("No directory type found for value: " + configuredDirectoryFactory); var directoryFactory = (IDirectoryFactory)Activator.CreateInstance(factoryType); return directoryFactory.CreateDirectory(dirInfo); diff --git a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs index c17d29a88f..77200be86e 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs @@ -2,10 +2,13 @@ using System.Diagnostics; using System.Reflection; using System.Web; +using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Collections; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Tests.Collections; @@ -23,7 +26,8 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - _provider = new DeepCloneAppCache(new WebCachingAppCache(HttpRuntime.Cache)); + var typeFinder = new TypeFinder(Mock.Of()); + _provider = new DeepCloneAppCache(new WebCachingAppCache(HttpRuntime.Cache, typeFinder)); } internal override IAppCache AppCache => _provider; diff --git a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs index 81a784fc01..b9c948c1de 100644 --- a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs @@ -1,5 +1,8 @@ -using NUnit.Framework; +using Moq; +using NUnit.Framework; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Cache @@ -13,8 +16,9 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); + var typeFinder = new TypeFinder(Mock.Of()); _ctx = new FakeHttpContextFactory("http://localhost/test"); - _appCache = new HttpRequestAppCache(() => _ctx.HttpContext.Items); + _appCache = new HttpRequestAppCache(() => _ctx.HttpContext.Items, typeFinder); } internal override IAppCache AppCache diff --git a/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs b/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs index b9c729f891..0fb8e574a8 100644 --- a/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs @@ -1,9 +1,12 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; namespace Umbraco.Tests.Cache { @@ -20,7 +23,8 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - _provider = new ObjectCacheAppCache(); + var typeFinder = new TypeFinder(Mock.Of()); + _provider = new ObjectCacheAppCache(typeFinder); } internal override IAppCache AppCache diff --git a/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs b/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs index 84a946036a..02986e2f78 100644 --- a/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs @@ -1,8 +1,11 @@ using System; using System.Diagnostics; using System.Web; +using Moq; using NUnit.Framework; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Web.Cache; namespace Umbraco.Tests.Cache @@ -17,7 +20,8 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - _appCache = new WebCachingAppCache(HttpRuntime.Cache); + var typeFinder = new TypeFinder(Mock.Of()); + _appCache = new WebCachingAppCache(HttpRuntime.Cache, typeFinder); } internal override IAppCache AppCache => _appCache; diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 2bd0d5d471..4fa25c2121 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -31,9 +31,10 @@ namespace Umbraco.Tests.Components var mock = new Mock(); var logger = Mock.Of(); + var typeFinder = new TypeFinder(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 p = new ScopeProvider(f, fs, logger, typeFinder); mock.Setup(x => x.GetInstance(typeof (ILogger))).Returns(logger); mock.Setup(x => x.GetInstance(typeof (IProfilingLogger))).Returns(new ProfilingLogger(Mock.Of(), Mock.Of())); diff --git a/src/Umbraco.Tests/Macros/MacroTests.cs b/src/Umbraco.Tests/Macros/MacroTests.cs index 9cc1d14954..0c57c70742 100644 --- a/src/Umbraco.Tests/Macros/MacroTests.cs +++ b/src/Umbraco.Tests/Macros/MacroTests.cs @@ -1,7 +1,9 @@ -using NUnit.Framework; +using Moq; +using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Macros; @@ -15,11 +17,12 @@ namespace Umbraco.Tests.Macros [SetUp] public void Setup() { + var typeFinder = new TypeFinder(Mock.Of()); //we DO want cache enabled for these tests var cacheHelper = new AppCaches( - new ObjectCacheAppCache(), + new ObjectCacheAppCache(typeFinder), NoAppCache.Instance, - new IsolatedCaches(type => new ObjectCacheAppCache())); + new IsolatedCaches(type => new ObjectCacheAppCache(typeFinder))); //Current.ApplicationContext = new ApplicationContext(cacheHelper, new ProfilingLogger(Mock.Of(), Mock.Of())); Current.Reset(); diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 524d6fe5b1..ffbf462b8e 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -9,6 +9,7 @@ using Newtonsoft.Json; using Umbraco.Core; using NUnit.Framework; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Composing.CompositionExtensions; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; @@ -269,7 +270,7 @@ namespace Umbraco.Tests.Models content.UpdateDate = DateTime.Now; content.WriterId = 23; - var runtimeCache = new ObjectCacheAppCache(); + var runtimeCache = new ObjectCacheAppCache(new TypeFinder(Mock.Of())); runtimeCache.Insert(content.Id.ToString(CultureInfo.InvariantCulture), () => content); var proflog = GetTestProfilingLogger(); diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index 4d62ec8301..af27d47f97 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -77,9 +77,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void CacheActiveForIntsAndGuids() { var realCache = new AppCaches( - new ObjectCacheAppCache(), + new ObjectCacheAppCache(TypeFinder), new DictionaryAppCache(), - new IsolatedCaches(t => new ObjectCacheAppCache())); + new IsolatedCaches(t => new ObjectCacheAppCache(TypeFinder))); var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index d04382e32e..fa916b554d 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -49,9 +49,9 @@ namespace Umbraco.Tests.Persistence.Repositories MediaTypeRepository mediaTypeRepository; var realCache = new AppCaches( - new ObjectCacheAppCache(), + new ObjectCacheAppCache(TypeFinder), new DictionaryAppCache(), - new IsolatedCaches(t => new ObjectCacheAppCache())); + new IsolatedCaches(t => new ObjectCacheAppCache(TypeFinder))); var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) diff --git a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs index 2f009d147e..fe2b080310 100644 --- a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs +++ b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs @@ -4,6 +4,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; @@ -124,8 +125,9 @@ namespace Umbraco.Tests.Published var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); - var elementsCache = new FastDictionaryAppCache(); - var snapshotCache = new FastDictionaryAppCache(); + var typeFinder = new TypeFinder(Mock.Of()); + var elementsCache = new FastDictionaryAppCache(typeFinder); + var snapshotCache = new FastDictionaryAppCache(typeFinder); var publishedSnapshot = new Mock(); publishedSnapshot.Setup(x => x.SnapshotCache).Returns(snapshotCache); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index 5d32606ee7..564e129650 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -138,6 +138,8 @@ namespace Umbraco.Tests.PublishedContent // create a data source for NuCache _source = new TestDataSource(kits); + var typeFinder = new TypeFinder(Mock.Of()); + // at last, create the complete NuCache snapshot service! var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; _snapshotService = new PublishedSnapshotService(options, @@ -158,7 +160,8 @@ namespace Umbraco.Tests.PublishedContent globalSettings, Mock.Of(), Mock.Of(), - new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), + typeFinder); // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index 0e05e6baad..d3a4a0d082 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -181,6 +181,8 @@ namespace Umbraco.Tests.PublishedContent // create a variation accessor _variationAccesor = new TestVariationContextAccessor(); + var typeFinder = new TypeFinder(Mock.Of()); + // at last, create the complete NuCache snapshot service! var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; _snapshotService = new PublishedSnapshotService(options, @@ -201,7 +203,8 @@ namespace Umbraco.Tests.PublishedContent globalSettings, Mock.Of(), Mock.Of(), - new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), + typeFinder); // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index a205478246..d1f2f48ed7 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -69,7 +69,7 @@ namespace Umbraco.Tests.Runtimes // 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); + composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState, typeFinder); // create the core runtime and have it compose itself var coreRuntime = new CoreRuntime(); @@ -260,7 +260,7 @@ namespace Umbraco.Tests.Runtimes // 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); + composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState, typeFinder); // create the core runtime and have it compose itself var coreRuntime = new CoreRuntime(); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index c7c403b260..0d2243c6dc 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -82,6 +82,8 @@ namespace Umbraco.Tests.Scoping var mediaRepository = Mock.Of(); var memberRepository = Mock.Of(); + var typeFinder = new TypeFinder(Mock.Of()); + return new PublishedSnapshotService( options, null, @@ -99,7 +101,8 @@ namespace Umbraco.Tests.Scoping Factory.GetInstance(), Factory.GetInstance(), Mock.Of(), - new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), + typeFinder); } 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 c7e4ddcb19..ae4544bf87 100644 --- a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs @@ -43,9 +43,9 @@ namespace Umbraco.Tests.Scoping { // this is what's created core web runtime return new AppCaches( - new DeepCloneAppCache(new ObjectCacheAppCache()), + new DeepCloneAppCache(new ObjectCacheAppCache(TypeFinder)), NoAppCache.Instance, - new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); + new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache(TypeFinder)))); } [TearDown] diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 0fe05f385f..bc515cfea3 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -8,6 +8,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence; @@ -53,6 +54,8 @@ namespace Umbraco.Tests.Services var mediaRepository = Mock.Of(); var memberRepository = Mock.Of(); + var typeFinder = new TypeFinder(Mock.Of()); + return new PublishedSnapshotService( options, null, @@ -70,7 +73,8 @@ namespace Umbraco.Tests.Services Factory.GetInstance(), Factory.GetInstance(), Mock.Of(), - new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), + typeFinder); } public class LocalServerMessenger : ServerMessengerBase diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 56ad22d414..4464a98511 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -230,7 +230,7 @@ namespace Umbraco.Tests.TestHelpers return container?.TryGetInstance() ?? Mock.Of(); } - public IScopeProvider GetScopeProvider(ILogger logger, FileSystems fileSystems = null, IUmbracoDatabaseFactory databaseFactory = null) + public IScopeProvider GetScopeProvider(ILogger logger, ITypeFinder typeFinder = null, FileSystems fileSystems = null, IUmbracoDatabaseFactory databaseFactory = null) { if (databaseFactory == null) { @@ -241,8 +241,9 @@ namespace Umbraco.Tests.TestHelpers databaseFactory = new UmbracoDatabaseFactory(Constants.System.UmbracoConnectionName, logger, new Lazy(() => mappers)); } + typeFinder = typeFinder ?? new TypeFinder(logger); fileSystems = fileSystems ?? new FileSystems(Current.Factory, logger); - var scopeProvider = new ScopeProvider(databaseFactory, fileSystems, logger); + var scopeProvider = new ScopeProvider(databaseFactory, fileSystems, logger, typeFinder); return scopeProvider; } } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 4a911680ab..38147fe45d 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -99,6 +99,8 @@ namespace Umbraco.Tests.Testing protected ILogger Logger => Factory.GetInstance(); + protected ITypeFinder TypeFinder => Factory.GetInstance(); + protected IProfiler Profiler => Factory.GetInstance(); protected virtual IProfilingLogger ProfilingLogger => Factory.GetInstance(); @@ -137,6 +139,7 @@ namespace Umbraco.Tests.Testing Composition = new Composition(register, typeLoader, proflogger, ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + Composition.RegisterUnique(typeLoader.TypeFinder); Composition.RegisterUnique(typeLoader); Composition.RegisterUnique(logger); Composition.RegisterUnique(profiler); @@ -358,7 +361,7 @@ namespace Umbraco.Tests.Testing Composition.WithCollectionBuilder(); // empty Composition.RegisterUnique(factory - => TestObjects.GetScopeProvider(factory.TryGetInstance(), factory.TryGetInstance(), factory.TryGetInstance())); + => TestObjects.GetScopeProvider(factory.TryGetInstance(), factory.TryGetInstance(), factory.TryGetInstance(), factory.TryGetInstance())); Composition.RegisterUnique(factory => (IScopeAccessor) factory.GetInstance()); Composition.ComposeServices(); diff --git a/src/Umbraco.Web/Cache/WebCachingAppCache.cs b/src/Umbraco.Web/Cache/WebCachingAppCache.cs index 3d169699f0..1879e8b69b 100644 --- a/src/Umbraco.Web/Cache/WebCachingAppCache.cs +++ b/src/Umbraco.Web/Cache/WebCachingAppCache.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Web.Caching; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; namespace Umbraco.Web.Cache { @@ -25,7 +26,7 @@ namespace Umbraco.Web.Cache /// /// Initializes a new instance of the class. /// - public WebCachingAppCache(System.Web.Caching.Cache cache) + public WebCachingAppCache(System.Web.Caching.Cache cache, ITypeFinder typeFinder) : base(typeFinder) { _cache = cache; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index eae2f33979..554e76c665 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -8,6 +8,7 @@ using CSharpTest.Net.Collections; using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -46,6 +47,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IPublishedModelFactory _publishedModelFactory; private readonly IDefaultCultureAccessor _defaultCultureAccessor; private readonly UrlSegmentProviderCollection _urlSegmentProviders; + private readonly ITypeFinder _typeFinder; // volatile because we read it with no lock private volatile bool _isReady; @@ -77,7 +79,8 @@ namespace Umbraco.Web.PublishedCache.NuCache IDataSource dataSource, IGlobalSettings globalSettings, IEntityXmlSerializer entitySerializer, IPublishedModelFactory publishedModelFactory, - UrlSegmentProviderCollection urlSegmentProviders) + UrlSegmentProviderCollection urlSegmentProviders, + ITypeFinder typeFinder) : base(publishedSnapshotAccessor, variationContextAccessor) { //if (Interlocked.Increment(ref _singletonCheck) > 1) @@ -94,6 +97,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _defaultCultureAccessor = defaultCultureAccessor; _globalSettings = globalSettings; _urlSegmentProviders = urlSegmentProviders; + _typeFinder = typeFinder; // we need an Xml serializer here so that the member cache can support XPath, // for members this is done by navigating the serialized-to-xml member @@ -1162,7 +1166,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _contentGen = contentSnap.Gen; _mediaGen = mediaSnap.Gen; _domainGen = domainSnap.Gen; - elementsCache = _elementsCache = new FastDictionaryAppCache(); + elementsCache = _elementsCache = new FastDictionaryAppCache(_typeFinder); } } diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index 09446e1dab..49db6646de 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -62,13 +62,13 @@ namespace Umbraco.Web.Runtime protected override AppCaches GetAppCaches() => new AppCaches( // we need to have the dep clone runtime cache provider to ensure // all entities are cached properly (cloned in and cloned out) - new DeepCloneAppCache(new WebCachingAppCache(HttpRuntime.Cache)), + new DeepCloneAppCache(new WebCachingAppCache(HttpRuntime.Cache, TypeFinder)), // we need request based cache when running in web-based context - new HttpRequestAppCache(() => HttpContext.Current?.Items), + new HttpRequestAppCache(() => HttpContext.Current?.Items, TypeFinder), new IsolatedCaches(type => // we need to have the dep clone runtime cache provider to ensure // all entities are cached properly (cloned in and cloned out) - new DeepCloneAppCache(new ObjectCacheAppCache()))); + new DeepCloneAppCache(new ObjectCacheAppCache(TypeFinder)))); #endregion } diff --git a/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs b/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs index ec536b9d75..b9dbc7d996 100644 --- a/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs +++ b/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs @@ -7,6 +7,7 @@ using Lucene.Net.Analysis.Standard; using Examine.LuceneEngine; using Examine; using Umbraco.Core; +using Umbraco.Core.Composing; namespace Umbraco.Web.Search { @@ -17,10 +18,12 @@ namespace Umbraco.Web.Search { // TODO: we should inject the different IValueSetValidator so devs can just register them instead of overriding this class? - public UmbracoIndexesCreator(IProfilingLogger profilingLogger, + public UmbracoIndexesCreator( + ITypeFinder typeFinder, + IProfilingLogger profilingLogger, ILocalizationService languageService, IPublicAccessService publicAccessService, - IMemberService memberService, IUmbracoIndexConfig umbracoIndexConfig) + IMemberService memberService, IUmbracoIndexConfig umbracoIndexConfig) : base(typeFinder) { ProfilingLogger = profilingLogger ?? throw new System.ArgumentNullException(nameof(profilingLogger)); LanguageService = languageService ?? throw new System.ArgumentNullException(nameof(languageService)); From a8face20239ada6c69891c0c22f33cf0d03bbf47 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Nov 2019 17:30:50 +1100 Subject: [PATCH 12/36] Moving TypeHelper ... but now need IOHelper... --- .../Composing/ITypeFinder.cs | 3 + .../Composing/TypeFinder.cs | 117 +++++++----------- .../Composing/TypeLoader.cs | 19 ++- .../UmbracoSettings/ITypeFinderConfig.cs | 11 ++ src/Umbraco.Core/Runtime/CoreRuntime.cs | 7 ++ src/Umbraco.Core/Umbraco.Core.csproj | 2 - .../Components/ComponentTests.cs | 2 +- .../Composing/ComposingTestBase.cs | 5 +- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 13 +- .../Composing/BuildManagerTypeFinder.cs | 105 ++++++++++++++++ src/Umbraco.Web/Runtime/WebRuntime.cs | 4 + src/Umbraco.Web/Umbraco.Web.csproj | 1 + 12 files changed, 189 insertions(+), 100 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Composing/TypeFinder.cs (82%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Composing/TypeLoader.cs (98%) create mode 100644 src/Umbraco.Abstractions/Configuration/UmbracoSettings/ITypeFinderConfig.cs create mode 100644 src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs diff --git a/src/Umbraco.Abstractions/Composing/ITypeFinder.cs b/src/Umbraco.Abstractions/Composing/ITypeFinder.cs index d5908f9c54..f302976dd6 100644 --- a/src/Umbraco.Abstractions/Composing/ITypeFinder.cs +++ b/src/Umbraco.Abstractions/Composing/ITypeFinder.cs @@ -4,6 +4,9 @@ using System.Reflection; namespace Umbraco.Core.Composing { + /// + /// Used to find objects by implemented types, names and/or attributes + /// public interface ITypeFinder { Type GetTypeByName(string name); diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Abstractions/Composing/TypeFinder.cs similarity index 82% rename from src/Umbraco.Core/Composing/TypeFinder.cs rename to src/Umbraco.Abstractions/Composing/TypeFinder.cs index 03fe115bbc..9d88153b0a 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Abstractions/Composing/TypeFinder.cs @@ -7,70 +7,51 @@ using System.Linq; using System.Reflection; using System.Security; using System.Text; -using System.Web.Compilation; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; namespace Umbraco.Core.Composing { - /// - /// A utility class to find all classes of a certain type by reflection in the current bin folder - /// of the web application. - /// + /// public class TypeFinder : ITypeFinder { private readonly ILogger _logger; - public TypeFinder(ILogger logger) + public TypeFinder(ILogger logger, ITypeFinderConfig typeFinderConfig = null) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _assembliesAcceptingLoadExceptions = typeFinderConfig?.AssembliesAcceptingLoadExceptions.Where(x => !x.IsNullOrWhiteSpace()).ToArray() ?? Array.Empty(); _allAssemblies = new Lazy>(() => { HashSet assemblies = null; try { - var isHosted = IOHelper.IsHosted; - - try + //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have + // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. + var binFolder = GetRootDirectorySafe(); + var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + //var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; + //var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + assemblies = new HashSet(); + foreach (var a in binAssemblyFiles) { - if (isHosted) + try { - assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); + var assName = AssemblyName.GetAssemblyName(a); + var ass = Assembly.Load(assName); + assemblies.Add(ass); } - } - catch (InvalidOperationException e) - { - if (e.InnerException is SecurityException == false) - throw; - } - - if (assemblies == null) - { - //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have - // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. - var binFolder = IOHelper.GetRootDirectoryBinFolder(); - var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - //var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; - //var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - assemblies = new HashSet(); - foreach (var a in binAssemblyFiles) + catch (Exception e) { - try + if (e is SecurityException || e is BadImageFormatException) { - var assName = AssemblyName.GetAssemblyName(a); - var ass = Assembly.Load(assName); - assemblies.Add(ass); + //swallow these exceptions } - catch (Exception e) + else { - if (e is SecurityException || e is BadImageFormatException) - { - //swallow these exceptions - } - else - { - throw; - } + throw; } } } @@ -80,25 +61,6 @@ namespace Umbraco.Core.Composing { assemblies.Add(a); } - - //here we are trying to get the App_Code assembly - var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported - var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); - //check if the folder exists and if there are any files in it with the supported file extensions - if (appCodeFolder.Exists && fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any())) - { - try - { - var appCodeAssembly = Assembly.Load("App_Code"); - if (assemblies.Contains(appCodeAssembly) == false) // BuildManager will find App_Code already - assemblies.Add(appCodeAssembly); - } - catch (FileNotFoundException ex) - { - //this will occur if it cannot load the assembly - _logger.Error(typeof(TypeFinder), ex, "Could not load assembly App_Code"); - } - } } catch (InvalidOperationException e) { @@ -115,31 +77,40 @@ namespace Umbraco.Core.Composing private volatile HashSet _localFilteredAssemblyCache; private readonly object _localFilteredAssemblyCacheLocker = new object(); private readonly List _notifiedLoadExceptionAssemblies = new List(); - private string[] _assembliesAcceptingLoadExceptions; private static readonly ConcurrentDictionary TypeNamesCache= new ConcurrentDictionary(); + private string _rootDir = ""; + private readonly string[] _assembliesAcceptingLoadExceptions; - private string[] AssembliesAcceptingLoadExceptions + // FIXME - this is only an interim change, once the IIOHelper stuff is merged we should use IIOHelper here + private string GetRootDirectorySafe() { - get + if (string.IsNullOrEmpty(_rootDir) == false) { - if (_assembliesAcceptingLoadExceptions != null) - return _assembliesAcceptingLoadExceptions; - - var s = ConfigurationManager.AppSettings[Constants.AppSettings.AssembliesAcceptingLoadExceptions]; - return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s) - ? Array.Empty() - : s.Split(',').Select(x => x.Trim()).ToArray(); + return _rootDir; } + + var codeBase = Assembly.GetExecutingAssembly().CodeBase; + var uri = new Uri(codeBase); + var path = uri.LocalPath; + var baseDirectory = Path.GetDirectoryName(path); + if (string.IsNullOrEmpty(baseDirectory)) + throw new PanicException("No root directory could be resolved."); + + _rootDir = baseDirectory.Contains("bin") + ? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1) + : baseDirectory; + + return _rootDir; } private bool AcceptsLoadExceptions(Assembly a) { - if (AssembliesAcceptingLoadExceptions.Length == 0) + if (_assembliesAcceptingLoadExceptions.Length == 0) return false; - if (AssembliesAcceptingLoadExceptions.Length == 1 && AssembliesAcceptingLoadExceptions[0] == "*") + if (_assembliesAcceptingLoadExceptions.Length == 1 && _assembliesAcceptingLoadExceptions[0] == "*") return true; var name = a.GetName().Name; // simple name of the assembly - return AssembliesAcceptingLoadExceptions.Any(pattern => + return _assembliesAcceptingLoadExceptions.Any(pattern => { if (pattern.Length > name.Length) return false; // pattern longer than name if (pattern.Length == name.Length) return pattern.InvariantEquals(name); // same length, must be identical diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Abstractions/Composing/TypeLoader.cs similarity index 98% rename from src/Umbraco.Core/Composing/TypeLoader.cs rename to src/Umbraco.Abstractions/Composing/TypeLoader.cs index 1ee302aa6c..e2ef86a45c 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Abstractions/Composing/TypeLoader.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Reflection; using System.Text; using System.Threading; -using System.Web.Compilation; using Umbraco.Core.Cache; using Umbraco.Core.Collections; using Umbraco.Core.IO; @@ -50,8 +49,9 @@ namespace Umbraco.Core.Composing /// The application runtime cache. /// Files storage location. /// A profiling logger. - public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger) - : this(typeFinder, runtimeCache, localTempPath, logger, true) + /// + public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, IEnumerable assembliesToScan = null) + : this(typeFinder, runtimeCache, localTempPath, logger, true, assembliesToScan) { } /// @@ -62,12 +62,14 @@ namespace Umbraco.Core.Composing /// Files storage location. /// A profiling logger. /// Whether to detect changes using hashes. - internal TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, bool detectChanges) + /// + public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, bool detectChanges, IEnumerable assembliesToScan = null) { TypeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); _runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache)); _localTempPath = localTempPath; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _assemblies = assembliesToScan; if (detectChanges) { @@ -99,13 +101,6 @@ namespace Umbraco.Core.Composing } } - /// - /// Initializes a new, test/blank, instance of the class. - /// - /// The initialized instance cannot get types. - internal TypeLoader() - { } - /// /// Returns the underlying /// @@ -122,7 +117,7 @@ namespace Umbraco.Core.Composing /// This is for unit tests. /// // internal for tests - internal IEnumerable AssembliesToScan + protected IEnumerable AssembliesToScan { get => _assemblies ?? (_assemblies = TypeFinder.AssembliesToScan); set => _assemblies = value; diff --git a/src/Umbraco.Abstractions/Configuration/UmbracoSettings/ITypeFinderConfig.cs b/src/Umbraco.Abstractions/Configuration/UmbracoSettings/ITypeFinderConfig.cs new file mode 100644 index 0000000000..fd5b18ed39 --- /dev/null +++ b/src/Umbraco.Abstractions/Configuration/UmbracoSettings/ITypeFinderConfig.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface ITypeFinderConfig + { + IEnumerable AssembliesAcceptingLoadExceptions { get; } + } +} diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 192d449d12..431627cfeb 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -58,11 +58,18 @@ namespace Umbraco.Core.Runtime // loggers var logger = Logger = GetLogger(); + if (logger == null) + throw new InvalidOperationException($"The object returned from {nameof(GetLogger)} cannot be null"); var profiler = Profiler = GetProfiler(); + if (profiler == null) + throw new InvalidOperationException($"The object returned from {nameof(GetProfiler)} cannot be null"); + var profilingLogger = ProfilingLogger = new ProfilingLogger(logger, profiler); // type finder TypeFinder = GetTypeFinder(); + if (TypeFinder == null) + throw new InvalidOperationException($"The object returned from {nameof(GetTypeFinder)} cannot be null"); // 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. diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 6643164020..f3ab82d731 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -177,8 +177,6 @@ - - diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 4fa25c2121..ca481352a7 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -52,7 +52,7 @@ namespace Umbraco.Tests.Components private static TypeLoader MockTypeLoader() { - return new TypeLoader(); + return new TypeLoader(Mock.Of(), Mock.Of(), IOHelper.MapPath("~/App_Data/TEMP"), Mock.Of()); } public static IRuntimeState MockRuntimeState(RuntimeLevel level) diff --git a/src/Umbraco.Tests/Composing/ComposingTestBase.cs b/src/Umbraco.Tests/Composing/ComposingTestBase.cs index 4b5be30cd9..743c71b2f3 100644 --- a/src/Umbraco.Tests/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests/Composing/ComposingTestBase.cs @@ -23,10 +23,7 @@ namespace Umbraco.Tests.Composing ProfilingLogger = new ProfilingLogger(Mock.Of(), Mock.Of()); var typeFinder = new TypeFinder(Mock.Of()); - TypeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), ProfilingLogger, detectChanges: false) - { - AssembliesToScan = AssembliesToScan - }; + TypeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), ProfilingLogger, false, AssembliesToScan); } [TearDown] diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 38147fe45d..0e1cd808a7 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -292,15 +292,12 @@ namespace Umbraco.Tests.Testing private static TypeLoader CreateCommonTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { var typeFinder = new TypeFinder(Mock.Of()); - return new TypeLoader(typeFinder, runtimeCache, globalSettings.LocalTempPath, logger, false) + return new TypeLoader(typeFinder, runtimeCache, globalSettings.LocalTempPath, logger, false, new[] { - AssembliesToScan = new[] - { - Assembly.Load("Umbraco.Core"), - Assembly.Load("Umbraco.Web"), - Assembly.Load("Umbraco.Tests") - } - }; + Assembly.Load("Umbraco.Core"), + Assembly.Load("Umbraco.Web"), + Assembly.Load("Umbraco.Tests") + }); } protected virtual void ComposeDatabase(UmbracoTestOptions.Database option) diff --git a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs new file mode 100644 index 0000000000..68c8308a9e --- /dev/null +++ b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Text; +using System.Threading.Tasks; +using System.Web.Compilation; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; + +namespace Umbraco.Web.Composing +{ + /// + /// An implementation of TypeFinder that uses the BuildManager to resolve references for aspnet framework hosted websites + /// + /// + /// This finder will also try to resolve dynamic assemblies created from App_Code + /// + internal class BuildManagerTypeFinder : TypeFinder, ITypeFinder + { + + public BuildManagerTypeFinder(ILogger logger, ITypeFinderConfig typeFinderConfig = null) : base(logger, typeFinderConfig) + { + _allAssemblies = new Lazy>(() => + { + var isHosted = IOHelper.IsHosted; + try + { + if (isHosted) + { + var assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); + + //here we are trying to get the App_Code assembly + var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported + var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); + //check if the folder exists and if there are any files in it with the supported file extensions + if (appCodeFolder.Exists && fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any())) + { + try + { + var appCodeAssembly = Assembly.Load("App_Code"); + if (assemblies.Contains(appCodeAssembly) == false) // BuildManager will find App_Code already + assemblies.Add(appCodeAssembly); + } + catch (FileNotFoundException ex) + { + //this will occur if it cannot load the assembly + logger.Error(typeof(TypeFinder), ex, "Could not load assembly App_Code"); + } + } + } + } + catch (InvalidOperationException e) + { + if (e.InnerException is SecurityException == false) + throw; + } + + // Not hosted, just use the default implementation + return new HashSet(base.AssembliesToScan); + }); + } + + private readonly Lazy> _allAssemblies; + + /// + /// Explicitly implement and return result from BuildManager + /// + /// + /// + Type ITypeFinder.GetTypeByName (string name) => BuildManager.GetType(name, false); + + /// + /// Explicitly implement and return result from BuildManager + /// + IEnumerable ITypeFinder.AssembliesToScan => _allAssemblies.Value; + + /// + /// TypeFinder config via appSettings + /// + internal class TypeFinderConfig : ITypeFinderConfig + { + private IEnumerable _assembliesAcceptingLoadExceptions; + public IEnumerable AssembliesAcceptingLoadExceptions + { + get + { + if (_assembliesAcceptingLoadExceptions != null) + return _assembliesAcceptingLoadExceptions; + + var s = ConfigurationManager.AppSettings[Constants.AppSettings.AssembliesAcceptingLoadExceptions]; + return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s) + ? Array.Empty() + : s.Split(',').Select(x => x.Trim()).ToArray(); + } + } + } + } +} diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index 49db6646de..b64968dba4 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Runtime; using Umbraco.Web.Cache; +using Umbraco.Web.Composing; using Umbraco.Web.Logging; namespace Umbraco.Web.Runtime @@ -17,6 +18,7 @@ namespace Umbraco.Web.Runtime { private readonly UmbracoApplicationBase _umbracoApplication; private IProfiler _webProfiler; + private BuildManagerTypeFinder _typeFinder; /// /// Initializes a new instance of the class. @@ -57,6 +59,8 @@ namespace Umbraco.Web.Runtime #region Getters + protected override ITypeFinder GetTypeFinder() => _typeFinder ??= new BuildManagerTypeFinder(Logger, new BuildManagerTypeFinder.TypeFinderConfig()); + protected override IProfiler GetProfiler() => _webProfiler; protected override AppCaches GetAppCaches() => new AppCaches( diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7097b0e6f5..79b0fe8744 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -135,6 +135,7 @@ + From 16ff38a45661271fc9b495bd55dac6e63198c32b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 11 Nov 2019 08:36:27 +0100 Subject: [PATCH 13/36] AB3649 - Moved IPropertyCollection, IMedia, and IContentBase --- .../Models/IContentBase.cs | 0 .../Models/IMedia.cs | 0 .../Models/IPropertyCollection.cs | 2 +- src/Umbraco.Core/Models/PropertyCollection.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 3 - src/Umbraco.Tests/Views/web.config | 74 ------------------- 6 files changed, 2 insertions(+), 79 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Models/IContentBase.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Models/IMedia.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Models/IPropertyCollection.cs (88%) delete mode 100644 src/Umbraco.Tests/Views/web.config diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Abstractions/Models/IContentBase.cs similarity index 100% rename from src/Umbraco.Core/Models/IContentBase.cs rename to src/Umbraco.Abstractions/Models/IContentBase.cs diff --git a/src/Umbraco.Core/Models/IMedia.cs b/src/Umbraco.Abstractions/Models/IMedia.cs similarity index 100% rename from src/Umbraco.Core/Models/IMedia.cs rename to src/Umbraco.Abstractions/Models/IMedia.cs diff --git a/src/Umbraco.Core/Models/IPropertyCollection.cs b/src/Umbraco.Abstractions/Models/IPropertyCollection.cs similarity index 88% rename from src/Umbraco.Core/Models/IPropertyCollection.cs rename to src/Umbraco.Abstractions/Models/IPropertyCollection.cs index e5bd4f60fd..ec2c4d90ca 100644 --- a/src/Umbraco.Core/Models/IPropertyCollection.cs +++ b/src/Umbraco.Abstractions/Models/IPropertyCollection.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Models event NotifyCollectionChangedEventHandler CollectionChanged; void EnsurePropertyTypes(IEnumerable propertyTypes); - void EnsureCleanPropertyTypes(IEnumerable propertyTypes); + void EnsureCleanPropertyTypes(IEnumerable propertyTypes); object DeepClone(); IProperty this[string name] { get; } diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs index 2415be4dce..4c4d083692 100644 --- a/src/Umbraco.Core/Models/PropertyCollection.cs +++ b/src/Umbraco.Core/Models/PropertyCollection.cs @@ -188,7 +188,7 @@ namespace Umbraco.Core.Models /// /// Ensures that the collection does not contain properties not in the specified property types. /// - public void EnsureCleanPropertyTypes(IEnumerable propertyTypes) + public void EnsureCleanPropertyTypes(IEnumerable propertyTypes) { if (propertyTypes == null) return; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 8df0c336db..7f51a855b3 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -230,18 +230,15 @@ - - - diff --git a/src/Umbraco.Tests/Views/web.config b/src/Umbraco.Tests/Views/web.config deleted file mode 100644 index efd80424e5..0000000000 --- a/src/Umbraco.Tests/Views/web.config +++ /dev/null @@ -1,74 +0,0 @@ - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From f154d9c333b4127b67d37979902526636993f5c6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Nov 2019 18:56:14 +1100 Subject: [PATCH 14/36] Moves TypeLoader to abstractions --- .../Composing/TypeLoader.cs | 52 ++++++++++-------- src/Umbraco.Core/IO/IOHelper.cs | 2 + src/Umbraco.Core/Runtime/CoreRuntime.cs | 20 ++++++- .../DistributedCache/DistributedCacheTests.cs | 3 +- .../Components/ComponentTests.cs | 7 ++- .../Composing/CollectionBuildersTests.cs | 3 +- .../Composing/ComposingTestBase.cs | 4 +- .../Composing/CompositionTests.cs | 4 +- .../Composing/LazyCollectionBuilderTests.cs | 11 ++-- .../Composing/PackageActionCollectionTests.cs | 3 +- .../Composing/TypeLoaderTests.cs | 53 ++++++++++--------- src/Umbraco.Tests/CoreThings/UdiTests.cs | 5 +- src/Umbraco.Tests/IO/FileSystemsTests.cs | 2 +- .../PropertyEditors/ImageCropperTest.cs | 2 +- .../PropertyEditorValueEditorTests.cs | 2 +- .../Published/ConvertersTests.cs | 2 +- .../PublishedContentSnapshotTestBase.cs | 18 ++++--- .../PublishedContent/PublishedContentTests.cs | 18 ++++--- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 6 ++- .../Scoping/ScopeEventDispatcherTests.cs | 2 +- .../TestHelpers/BaseUsingSqlCeSyntax.cs | 6 ++- src/Umbraco.Tests/TestHelpers/TestHelper.cs | 10 +++- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 25 +++++---- .../Web/TemplateUtilitiesTests.cs | 4 +- src/Umbraco.Tests/Web/UmbracoHelperTests.cs | 6 ++- .../Composing/BuildManagerTypeFinder.cs | 6 +-- src/Umbraco.Web/Runtime/WebRuntime.cs | 2 +- 27 files changed, 171 insertions(+), 107 deletions(-) diff --git a/src/Umbraco.Abstractions/Composing/TypeLoader.cs b/src/Umbraco.Abstractions/Composing/TypeLoader.cs index 2573e5c467..d2f203e112 100644 --- a/src/Umbraco.Abstractions/Composing/TypeLoader.cs +++ b/src/Umbraco.Abstractions/Composing/TypeLoader.cs @@ -13,19 +13,22 @@ using File = System.IO.File; namespace Umbraco.Core.Composing { + + /// /// Provides methods to find and instantiate types. /// /// - /// This class should be used to get all types, the class should never be used directly. + /// This class should be used to get all types, the class should never be used directly. /// In most cases this class is not used directly but through extension methods that retrieve specific types. /// This class caches the types it knows to avoid excessive assembly scanning and shorten startup times, relying /// on a hash of the DLLs in the ~/bin folder to check for cache expiration. /// - public class TypeLoader + public sealed class TypeLoader { private const string CacheKey = "umbraco-types.list"; + private readonly IIOHelper _ioHelper; private readonly IAppPolicyCache _runtimeCache; private readonly IProfilingLogger _logger; @@ -39,33 +42,36 @@ namespace Umbraco.Core.Composing private string _currentAssembliesHash; private IEnumerable _assemblies; private bool _reportedChange; - private readonly string _localTempPath; + private readonly DirectoryInfo _localTempPath; private string _fileBasePath; /// /// Initializes a new instance of the class. /// + /// /// /// The application runtime cache. /// Files storage location. /// A profiling logger. /// - public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, IEnumerable assembliesToScan = null) - : this(typeFinder, runtimeCache, localTempPath, logger, true, assembliesToScan) + public TypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, IProfilingLogger logger, IEnumerable assembliesToScan = null) + : this(ioHelper, typeFinder, runtimeCache, localTempPath, logger, true, assembliesToScan) { } /// /// Initializes a new instance of the class. /// + /// /// /// The application runtime cache. /// Files storage location. /// A profiling logger. /// Whether to detect changes using hashes. /// - public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, bool detectChanges, IEnumerable assembliesToScan = null) + public TypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, IProfilingLogger logger, bool detectChanges, IEnumerable assembliesToScan = null) { TypeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); + _ioHelper = ioHelper; _runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache)); _localTempPath = localTempPath; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -117,25 +123,21 @@ namespace Umbraco.Core.Composing /// This is for unit tests. /// // internal for tests - protected IEnumerable AssembliesToScan - { - get => _assemblies ?? (_assemblies = TypeFinder.AssembliesToScan); - set => _assemblies = value; - } + public IEnumerable AssembliesToScan => _assemblies ?? (_assemblies = TypeFinder.AssembliesToScan); /// /// Gets the type lists. /// /// For unit tests. // internal for tests - internal IEnumerable TypeLists => _types.Values; + public IEnumerable TypeLists => _types.Values; /// /// Sets a type list. /// /// For unit tests. // internal for tests - internal void AddTypeList(TypeList typeList) + public void AddTypeList(TypeList typeList) { var tobject = typeof(object); // CompositeTypeTypeKey does not support null values _types[new CompositeTypeTypeKey(typeList.BaseType ?? tobject, typeList.AttributeType ?? tobject)] = typeList; @@ -182,12 +184,16 @@ namespace Umbraco.Core.Composing _currentAssembliesHash = GetFileHash(new List> { + // TODO: Would be nicer to abstract this logic out into IAssemblyHash + + // TODO: Use constants from SystemDirectories when we can (once it's ported to netstandard lib) + // the bin folder and everything in it - new Tuple(new DirectoryInfo(Current.IOHelper.MapPath(SystemDirectories.Bin)), false), + new Tuple(new DirectoryInfo(_ioHelper.MapPath("~/bin")), false), // the app code folder and everything in it - new Tuple(new DirectoryInfo(Current.IOHelper.MapPath("~/App_Code")), false), + new Tuple(new DirectoryInfo(_ioHelper.MapPath("~/App_Code")), false), // global.asax (the app domain also monitors this, if it changes will do a full restart) - new Tuple(new FileInfo(Current.IOHelper.MapPath("~/global.asax")), false) + new Tuple(new FileInfo(_ioHelper.MapPath("~/global.asax")), false) }, _logger); return _currentAssembliesHash; @@ -269,7 +275,7 @@ namespace Umbraco.Core.Composing /// A profiling logger. /// The hash. // internal for tests - internal static string GetFileHash(IEnumerable filesAndFolders, IProfilingLogger logger) + public static string GetFileHash(IEnumerable filesAndFolders, IProfilingLogger logger) { using (logger.DebugDuration("Determining hash of code files on disk", "Hash determined")) { @@ -300,7 +306,7 @@ namespace Umbraco.Core.Composing private const int FileDeleteTimeout = 4000; // milliseconds // internal for tests - internal Attempt> TryGetCached(Type baseType, Type attributeType) + public Attempt> TryGetCached(Type baseType, Type attributeType) { var cache = _runtimeCache.GetCacheItem, IEnumerable>>(CacheKey, ReadCacheSafe, TimeSpan.FromSeconds(ListFileCacheDuration)); @@ -333,7 +339,7 @@ namespace Umbraco.Core.Composing } // internal for tests - internal Dictionary, IEnumerable> ReadCache() + public Dictionary, IEnumerable> ReadCache() { var cache = new Dictionary, IEnumerable>(); @@ -379,7 +385,7 @@ namespace Umbraco.Core.Composing } // internal for tests - internal string GetTypesListFilePath() => GetFileBasePath() + ".list"; + public string GetTypesListFilePath() => GetFileBasePath() + ".list"; private string GetTypesHashFilePath() => GetFileBasePath() + ".hash"; @@ -390,7 +396,7 @@ namespace Umbraco.Core.Composing if (_fileBasePath != null) return _fileBasePath; - _fileBasePath = Path.Combine(_localTempPath, "TypesCache", "umbraco-types." + NetworkHelper.FileSafeMachineName); + _fileBasePath = Path.Combine(_localTempPath.FullName, "TypesCache", "umbraco-types." + NetworkHelper.FileSafeMachineName); // ensure that the folder exists var directory = Path.GetDirectoryName(_fileBasePath); @@ -404,7 +410,7 @@ namespace Umbraco.Core.Composing } // internal for tests - internal void WriteCache() + public void WriteCache() { _logger.Debug("Writing cache file."); var typesListFilePath = GetTypesListFilePath(); @@ -780,7 +786,7 @@ namespace Umbraco.Core.Composing /// Represents a list of types obtained by looking for types inheriting/implementing a /// specified type, and/or marked with a specified attribute type. ///
- internal class TypeList + public sealed class TypeList { private readonly HashSet _types = new HashSet(); diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index a81c0ab559..36215b267c 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -13,6 +13,8 @@ namespace Umbraco.Core.IO { public class IOHelper : IIOHelper { + internal static IIOHelper Default { get; } = new IOHelper(); + /// /// Gets or sets a value forcing Umbraco to consider it is non-hosted. /// diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index ad644cd56b..42c39bfabf 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Web; using System.Web.Hosting; using Umbraco.Core.Cache; @@ -47,6 +48,11 @@ namespace Umbraco.Core.Runtime ///
protected ITypeFinder TypeFinder { get; private set; } + /// + /// Gets the + /// + protected IIOHelper IOHelper { get; private set; } + /// public IRuntimeState State => _state; @@ -66,11 +72,14 @@ namespace Umbraco.Core.Runtime var profilingLogger = ProfilingLogger = new ProfilingLogger(logger, profiler); - // type finder TypeFinder = GetTypeFinder(); if (TypeFinder == null) throw new InvalidOperationException($"The object returned from {nameof(GetTypeFinder)} cannot be null"); + IOHelper = GetIOHelper(); + if (IOHelper == null) + throw new InvalidOperationException($"The object returned from {nameof(GetIOHelper)} cannot be null"); + // 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. // note that this REQUIRES that perWebRequestScope has NOT been enabled yet, else @@ -127,7 +136,7 @@ namespace Umbraco.Core.Runtime var configs = GetConfigs(); // type finder/loader - var typeLoader = new TypeLoader(TypeFinder, appCaches.RuntimeCache, configs.Global().LocalTempPath, ProfilingLogger); + var typeLoader = new TypeLoader(IOHelper, TypeFinder, appCaches.RuntimeCache, new DirectoryInfo(configs.Global().LocalTempPath), ProfilingLogger); // runtime state // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance' @@ -334,6 +343,13 @@ namespace Umbraco.Core.Runtime protected virtual ITypeFinder GetTypeFinder() => new TypeFinder(Logger); + /// + /// Gets a + /// + /// + protected virtual IIOHelper GetIOHelper() + => Umbraco.Core.IO.IOHelper.Default; + /// /// Gets the application caches. /// diff --git a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs index 650cc64720..4ef08a15f5 100644 --- a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Sync; using Umbraco.Tests.Components; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Cache.DistributedCache { @@ -25,7 +26,7 @@ namespace Umbraco.Tests.Cache.DistributedCache { var register = RegisterFactory.Create(); - var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(register, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.RegisterUnique(_ => new TestServerRegistrar()); composition.RegisterUnique(_ => new TestServerMessenger()); diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index ca481352a7..5a437e402f 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Moq; using NUnit.Framework; @@ -52,7 +53,8 @@ namespace Umbraco.Tests.Components private static TypeLoader MockTypeLoader() { - return new TypeLoader(Mock.Of(), Mock.Of(), IOHelper.MapPath("~/App_Data/TEMP"), Mock.Of()); + var ioHelper = IOHelper.Default; + return new TypeLoader(ioHelper, Mock.Of(), Mock.Of(), new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), Mock.Of()); } public static IRuntimeState MockRuntimeState(RuntimeLevel level) @@ -362,8 +364,9 @@ namespace Umbraco.Tests.Components [Test] public void AllComposers() { + var ioHelper = IOHelper.Default; var typeFinder = new TypeFinder(Mock.Of()); - var typeLoader = new TypeLoader(typeFinder, AppCaches.Disabled.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), Mock.Of()); + var typeLoader = new TypeLoader(ioHelper, typeFinder, AppCaches.Disabled.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), Mock.Of()); var register = MockRegister(); var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); diff --git a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs index ec757e09f0..339bc7a3aa 100644 --- a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs +++ b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Composing; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Tests.Components; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Composing { @@ -21,7 +22,7 @@ namespace Umbraco.Tests.Composing Current.Reset(); var register = RegisterFactory.Create(); - _composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + _composition = new Composition(register, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); } [TearDown] diff --git a/src/Umbraco.Tests/Composing/ComposingTestBase.cs b/src/Umbraco.Tests/Composing/ComposingTestBase.cs index 743c71b2f3..8a0dc46e7f 100644 --- a/src/Umbraco.Tests/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests/Composing/ComposingTestBase.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; using System.Reflection; using Moq; using NUnit.Framework; @@ -23,7 +24,8 @@ namespace Umbraco.Tests.Composing ProfilingLogger = new ProfilingLogger(Mock.Of(), Mock.Of()); var typeFinder = new TypeFinder(Mock.Of()); - TypeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), ProfilingLogger, false, AssembliesToScan); + var ioHelper = IOHelper.Default; + TypeLoader = new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), ProfilingLogger, false, AssembliesToScan); } [TearDown] diff --git a/src/Umbraco.Tests/Composing/CompositionTests.cs b/src/Umbraco.Tests/Composing/CompositionTests.cs index 9668d7619a..5516d6cab8 100644 --- a/src/Umbraco.Tests/Composing/CompositionTests.cs +++ b/src/Umbraco.Tests/Composing/CompositionTests.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -37,7 +38,8 @@ namespace Umbraco.Tests.Composing var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); var typeFinder = new TypeFinder(Mock.Of()); - var typeLoader = new TypeLoader(typeFinder, Mock.Of(), IOHelper.MapPath("~/App_Data/TEMP"), logger); + var ioHelper = IOHelper.Default; + var typeLoader = new TypeLoader(ioHelper, typeFinder, Mock.Of(), new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), logger); var composition = new Composition(mockedRegister, typeLoader, logger, Mock.Of()); // create the factory, ensure it is the mocked factory diff --git a/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs b/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs index 3996eba89f..ed5a6e3ee2 100644 --- a/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs +++ b/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Tests.Components; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Composing { @@ -40,7 +41,7 @@ namespace Umbraco.Tests.Composing public void LazyCollectionBuilderHandlesTypes() { var container = CreateRegister(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add() @@ -66,7 +67,7 @@ namespace Umbraco.Tests.Composing public void LazyCollectionBuilderHandlesProducers() { var container = CreateRegister(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add(() => new[] { typeof(TransientObject3), typeof(TransientObject2) }) @@ -91,7 +92,7 @@ namespace Umbraco.Tests.Composing public void LazyCollectionBuilderHandlesTypesAndProducers() { var container = CreateRegister(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add() @@ -117,7 +118,7 @@ namespace Umbraco.Tests.Composing public void LazyCollectionBuilderThrowsOnIllegalTypes() { var container = CreateRegister(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add() @@ -139,7 +140,7 @@ namespace Umbraco.Tests.Composing public void LazyCollectionBuilderCanExcludeTypes() { var container = CreateRegister(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add() diff --git a/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs b/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs index 07db4be3d3..5586f6baa0 100644 --- a/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs +++ b/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.PackageActions; using Umbraco.Tests.Components; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Composing { @@ -20,7 +21,7 @@ namespace Umbraco.Tests.Composing { var container = RegisterFactory.Create(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add(() => TypeLoader.GetPackageActions()); diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index b135405615..3d726e3906 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -28,24 +28,29 @@ namespace Umbraco.Tests.Composing { // this ensures it's reset var typeFinder = new TypeFinder(Mock.Of()); - _typeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()), false); + _typeLoader = new TypeLoader(IOHelper.Default, typeFinder, NoAppCache.Instance, + new DirectoryInfo(IOHelper.Default.MapPath("~/App_Data/TEMP")), + new ProfilingLogger(Mock.Of(), Mock.Of()), false, + + // for testing, we'll specify which assemblies are scanned for the PluginTypeResolver + // TODO: Should probably update this so it only searches this assembly and add custom types to be found + new[] + { + this.GetType().Assembly, + typeof(System.Guid).Assembly, + typeof(NUnit.Framework.Assert).Assembly, + typeof(Microsoft.CSharp.CSharpCodeProvider).Assembly, + typeof(System.Xml.NameTable).Assembly, + typeof(System.Configuration.GenericEnumConverter).Assembly, + typeof(System.Web.SiteMap).Assembly, + //typeof(TabPage).Assembly, + typeof(System.Web.Mvc.ActionResult).Assembly, + typeof(TypeFinder).Assembly, + typeof(UmbracoContext).Assembly + }); + + - // for testing, we'll specify which assemblies are scanned for the PluginTypeResolver - // TODO: Should probably update this so it only searches this assembly and add custom types to be found - _typeLoader.AssembliesToScan = new[] - { - this.GetType().Assembly, - typeof(System.Guid).Assembly, - typeof(NUnit.Framework.Assert).Assembly, - typeof(Microsoft.CSharp.CSharpCodeProvider).Assembly, - typeof(System.Xml.NameTable).Assembly, - typeof(System.Configuration.GenericEnumConverter).Assembly, - typeof(System.Web.SiteMap).Assembly, - //typeof(TabPage).Assembly, - typeof(System.Web.Mvc.ActionResult).Assembly, - typeof(TypeFinder).Assembly, - typeof(UmbracoContext).Assembly - }; } [TearDown] @@ -190,15 +195,15 @@ AnotherContentFinder [Test] public void Create_Cached_Plugin_File() { - var types = new[] { typeof (TypeLoader), typeof (TypeLoaderTests), typeof (UmbracoContext) }; + var types = new[] { typeof(TypeLoader), typeof(TypeLoaderTests), typeof(UmbracoContext) }; - var typeList1 = new TypeLoader.TypeList(typeof (object), null); + var typeList1 = new TypeLoader.TypeList(typeof(object), null); foreach (var type in types) typeList1.Add(type); _typeLoader.AddTypeList(typeList1); _typeLoader.WriteCache(); - var plugins = _typeLoader.TryGetCached(typeof (object), null); - var diffType = _typeLoader.TryGetCached(typeof (object), typeof (ObsoleteAttribute)); + var plugins = _typeLoader.TryGetCached(typeof(object), null); + var diffType = _typeLoader.TryGetCached(typeof(object), typeof(ObsoleteAttribute)); Assert.IsTrue(plugins.Success); //this will be false since there is no cache of that type resolution kind @@ -281,16 +286,16 @@ AnotherContentFinder { var types = new HashSet(); - var propEditors = new TypeLoader.TypeList(typeof (DataEditor), null); + var propEditors = new TypeLoader.TypeList(typeof(DataEditor), null); propEditors.Add(typeof(LabelPropertyEditor)); types.Add(propEditors); - var found = types.SingleOrDefault(x => x.BaseType == typeof (DataEditor) && x.AttributeType == null); + var found = types.SingleOrDefault(x => x.BaseType == typeof(DataEditor) && x.AttributeType == null); Assert.IsNotNull(found); //This should not find a type list of this type - var shouldNotFind = types.SingleOrDefault(x => x.BaseType == typeof (IDataEditor) && x.AttributeType == null); + var shouldNotFind = types.SingleOrDefault(x => x.BaseType == typeof(IDataEditor) && x.AttributeType == null); Assert.IsNull(shouldNotFind); } diff --git a/src/Umbraco.Tests/CoreThings/UdiTests.cs b/src/Umbraco.Tests/CoreThings/UdiTests.cs index a6b87ad57b..dc2265eac2 100644 --- a/src/Umbraco.Tests/CoreThings/UdiTests.cs +++ b/src/Umbraco.Tests/CoreThings/UdiTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using Moq; @@ -25,10 +26,10 @@ namespace Umbraco.Tests.CoreThings { // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var container = new Mock(); - var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); + var ioHelper = IOHelper.Default; var typeFinder = new TypeFinder(Mock.Of()); container.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( - new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()))); + new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), new ProfilingLogger(Mock.Of(), Mock.Of()))); Current.Factory = container.Object; Udi.ResetUdiTypes(); diff --git a/src/Umbraco.Tests/IO/FileSystemsTests.cs b/src/Umbraco.Tests/IO/FileSystemsTests.cs index 872d7c2526..254371ae32 100644 --- a/src/Umbraco.Tests/IO/FileSystemsTests.cs +++ b/src/Umbraco.Tests/IO/FileSystemsTests.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.IO { _register = RegisterFactory.Create(); - var composition = new Composition(_register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(_register, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.Register(_ => Mock.Of()); composition.Register(_ => Mock.Of()); diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index 8d2ab84d35..5bea491973 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -70,7 +70,7 @@ namespace Umbraco.Tests.PropertyEditors try { var container = RegisterFactory.Create(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder(); diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs index 764f6ac4a4..eda6d4f5bb 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs @@ -24,7 +24,7 @@ namespace Umbraco.Tests.PropertyEditors Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture; var register = RegisterFactory.Create(); - var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(register, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); register.Register(_ => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefaultUmbracoSettings()))); diff --git a/src/Umbraco.Tests/Published/ConvertersTests.cs b/src/Umbraco.Tests/Published/ConvertersTests.cs index 671129848c..360df5daeb 100644 --- a/src/Umbraco.Tests/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests/Published/ConvertersTests.cs @@ -178,7 +178,7 @@ namespace Umbraco.Tests.Published Current.Reset(); var register = RegisterFactory.Create(); - var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(register, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Append() diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs index 7e6ae75356..d0e780a92b 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs @@ -1,5 +1,7 @@ using System; +using System.IO; using System.Linq; +using System.Reflection; using System.Web.Routing; using Moq; using Umbraco.Core; @@ -12,6 +14,7 @@ using Umbraco.Core.Composing; using Current = Umbraco.Core.Composing.Current; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -41,16 +44,15 @@ namespace Umbraco.Tests.PublishedContent Composition.RegisterUnique(f => new PublishedModelFactory(f.GetInstance().GetTypes())); } - protected override TypeLoader CreateTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) + protected override TypeLoader CreateTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - var pluginManager = base.CreateTypeLoader(runtimeCache, globalSettings, logger); + var baseLoader = base.CreateTypeLoader(ioHelper, typeFinder, runtimeCache, globalSettings, logger); - // this is so the model factory looks into the test assembly - pluginManager.AssembliesToScan = pluginManager.AssembliesToScan - .Union(new[] { typeof (PublishedContentMoreTests).Assembly }) - .ToList(); - - return pluginManager; + return new TypeLoader(ioHelper, typeFinder, runtimeCache, new DirectoryInfo(globalSettings.LocalTempPath), logger, false, + // this is so the model factory looks into the test assembly + baseLoader.AssembliesToScan + .Union(new[] {typeof(PublishedContentMoreTests).Assembly}) + .ToList()); } private UmbracoContext GetUmbracoContext() diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 6ef632bf90..b42dc32b24 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Web; using NUnit.Framework; @@ -14,6 +15,7 @@ using Moq; using Newtonsoft.Json; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -83,16 +85,16 @@ namespace Umbraco.Tests.PublishedContent ContentTypesCache.GetPublishedContentTypeByAlias = alias => alias.InvariantEquals("home") ? homeType : anythingType; } - protected override TypeLoader CreateTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) + + protected override TypeLoader CreateTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - var pluginManager = base.CreateTypeLoader(runtimeCache, globalSettings, logger); + var baseLoader = base.CreateTypeLoader(ioHelper, typeFinder, runtimeCache, globalSettings, logger); - // this is so the model factory looks into the test assembly - pluginManager.AssembliesToScan = pluginManager.AssembliesToScan - .Union(new[] { typeof(PublishedContentTests).Assembly }) - .ToList(); - - return pluginManager; + return new TypeLoader(ioHelper, typeFinder, runtimeCache, new DirectoryInfo(globalSettings.LocalTempPath), logger, false, + // this is so the model factory looks into the test assembly + baseLoader.AssembliesToScan + .Union(new[] { typeof(PublishedContentTests).Assembly }) + .ToList()); } private readonly Guid _node1173Guid = Guid.NewGuid(); diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index c9b34bf3de..bf1c83be9f 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -62,7 +62,8 @@ namespace Umbraco.Tests.Runtimes var appCaches = AppCaches.Disabled; var databaseFactory = new UmbracoDatabaseFactory(logger, new Lazy(() => factory.GetInstance())); var typeFinder = new TypeFinder(logger); - var typeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), profilingLogger); + var ioHelper = IOHelper.Default; + var typeLoader = new TypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger); var mainDom = new SimpleMainDom(); var runtimeState = new RuntimeState(logger, null, null, new Lazy(() => mainDom), new Lazy(() => factory.GetInstance())); @@ -251,7 +252,8 @@ namespace Umbraco.Tests.Runtimes var appCaches = AppCaches.Disabled; var databaseFactory = Mock.Of(); var typeFinder = new TypeFinder(Mock.Of()); - var typeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), profilingLogger); + var ioHelper = IOHelper.Default; + var typeLoader = new TypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger); var runtimeState = Mock.Of(); Mock.Get(runtimeState).Setup(x => x.Level).Returns(RuntimeLevel.Run); var mainDom = Mock.Of(); diff --git a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs index 001553a8ae..74e79c4ce3 100644 --- a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs @@ -32,7 +32,7 @@ namespace Umbraco.Tests.Scoping var register = RegisterFactory.Create(); - var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(register, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); _testObjects = new TestObjects(register); diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index a2f08604b5..e1e987c4bf 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using Moq; using NPoco; using NUnit.Framework; @@ -36,10 +37,11 @@ namespace Umbraco.Tests.TestHelpers var container = RegisterFactory.Create(); + var ioHelper = IOHelper.Default; var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); var typeFinder = new TypeFinder(Mock.Of()); - var typeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, - IOHelper.MapPath("~/App_Data/TEMP"), + var typeLoader = new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, + new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), logger, false); diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 647797a500..134eb8272b 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -7,10 +7,13 @@ using System.IO; using System.Linq; using System.Reflection; using System.Threading; +using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.IO; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.PropertyEditors; @@ -24,13 +27,16 @@ namespace Umbraco.Tests.TestHelpers public static class TestHelper { - + public static TypeLoader GetMockedTypeLoader() + { + return new TypeLoader(Mock.Of(), Mock.Of(), Mock.Of(), new DirectoryInfo(IOHelper.Default.MapPath("~/App_Data/TEMP")), Mock.Of()); + } /// /// Gets the current assembly directory. /// /// The assembly directory. - static public string CurrentAssemblyDirectory + public static string CurrentAssemblyDirectory { get { diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 0e1cd808a7..273202ae7d 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -99,6 +99,8 @@ namespace Umbraco.Tests.Testing protected ILogger Logger => Factory.GetInstance(); + protected IIOHelper IOHelper => Factory.GetInstance(); + protected ITypeFinder TypeFinder => Factory.GetInstance(); protected IProfiler Profiler => Factory.GetInstance(); @@ -132,13 +134,17 @@ namespace Umbraco.Tests.Testing var (logger, profiler) = GetLoggers(Options.Logger); var proflogger = new ProfilingLogger(logger, profiler); var appCaches = GetAppCaches(); + var ioHelper = Umbraco.Core.IO.IOHelper.Default; + var typeFinder = new TypeFinder(logger); var globalSettings = SettingsForTests.GetDefaultGlobalSettings(); - var typeLoader = GetTypeLoader(appCaches.RuntimeCache, globalSettings, proflogger, Options.TypeLoader); + var typeLoader = GetTypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, globalSettings, proflogger, Options.TypeLoader); var register = RegisterFactory.Create(); Composition = new Composition(register, typeLoader, proflogger, ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + + Composition.RegisterUnique(ioHelper); Composition.RegisterUnique(typeLoader.TypeFinder); Composition.RegisterUnique(typeLoader); Composition.RegisterUnique(logger); @@ -268,31 +274,30 @@ namespace Umbraco.Tests.Testing .ComposeWebMappingProfiles(); } - protected virtual TypeLoader GetTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger, UmbracoTestOptions.TypeLoader option) + protected virtual TypeLoader GetTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger, UmbracoTestOptions.TypeLoader option) { switch (option) { case UmbracoTestOptions.TypeLoader.Default: - return _commonTypeLoader ?? (_commonTypeLoader = CreateCommonTypeLoader(runtimeCache, globalSettings, logger)); + return _commonTypeLoader ?? (_commonTypeLoader = CreateCommonTypeLoader(ioHelper, typeFinder, runtimeCache, globalSettings, logger)); case UmbracoTestOptions.TypeLoader.PerFixture: - return _featureTypeLoader ?? (_featureTypeLoader = CreateTypeLoader(runtimeCache, globalSettings, logger)); + return _featureTypeLoader ?? (_featureTypeLoader = CreateTypeLoader(ioHelper, typeFinder, runtimeCache, globalSettings, logger)); case UmbracoTestOptions.TypeLoader.PerTest: - return CreateTypeLoader(runtimeCache, globalSettings, logger); + return CreateTypeLoader(ioHelper, typeFinder, runtimeCache, globalSettings, logger); default: throw new ArgumentOutOfRangeException(nameof(option)); } } - protected virtual TypeLoader CreateTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) + protected virtual TypeLoader CreateTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - return CreateCommonTypeLoader(runtimeCache, globalSettings, logger); + return CreateCommonTypeLoader(ioHelper, typeFinder, runtimeCache, globalSettings, logger); } // common to all tests = cannot be overriden - private static TypeLoader CreateCommonTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) + private static TypeLoader CreateCommonTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - var typeFinder = new TypeFinder(Mock.Of()); - return new TypeLoader(typeFinder, runtimeCache, globalSettings.LocalTempPath, logger, false, new[] + return new TypeLoader(ioHelper, typeFinder, runtimeCache, new DirectoryInfo(globalSettings.LocalTempPath), logger, false, new[] { Assembly.Load("Umbraco.Core"), Assembly.Load("Umbraco.Web"), diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index 907c5efdd2..e3678a1db5 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Linq; using System.Web; using Moq; @@ -40,8 +41,9 @@ namespace Umbraco.Tests.Web // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var factory = new Mock(); var typeFinder = new TypeFinder(Mock.Of()); + var ioHelper = IOHelper.Default; factory.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( - new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()))); + new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), new ProfilingLogger(Mock.Of(), Mock.Of()))); factory.Setup(x => x.GetInstance(typeof (ServiceContext))).Returns(serviceContext); var settings = SettingsForTests.GetDefaultUmbracoSettings(); diff --git a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs index 0b3a8c2c7b..cdf17e12e9 100644 --- a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Text; using Examine.LuceneEngine; using Lucene.Net.Analysis; @@ -259,14 +260,15 @@ namespace Umbraco.Tests.Web { // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var container = new Mock(); - var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); var typeFinder = new TypeFinder(Mock.Of()); + var ioHelper = IOHelper.Default; container .Setup(x => x.GetInstance(typeof(TypeLoader))) .Returns(new TypeLoader( + ioHelper, typeFinder, NoAppCache.Instance, - Current.IOHelper.MapPath("~/App_Data/TEMP"), + new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), new ProfilingLogger(Mock.Of(), Mock.Of()) ) ); diff --git a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs index 68c8308a9e..d0cdf76702 100644 --- a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs +++ b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs @@ -25,11 +25,11 @@ namespace Umbraco.Web.Composing internal class BuildManagerTypeFinder : TypeFinder, ITypeFinder { - public BuildManagerTypeFinder(ILogger logger, ITypeFinderConfig typeFinderConfig = null) : base(logger, typeFinderConfig) + public BuildManagerTypeFinder(IIOHelper ioHelper, ILogger logger, ITypeFinderConfig typeFinderConfig = null) : base(logger, typeFinderConfig) { _allAssemblies = new Lazy>(() => { - var isHosted = IOHelper.IsHosted; + var isHosted = ioHelper.IsHosted; try { if (isHosted) @@ -38,7 +38,7 @@ namespace Umbraco.Web.Composing //here we are trying to get the App_Code assembly var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported - var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); + var appCodeFolder = new DirectoryInfo(ioHelper.MapPath(ioHelper.ResolveUrl("~/App_code"))); //check if the folder exists and if there are any files in it with the supported file extensions if (appCodeFolder.Exists && fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any())) { diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index b64968dba4..2ebec69da0 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -59,7 +59,7 @@ namespace Umbraco.Web.Runtime #region Getters - protected override ITypeFinder GetTypeFinder() => _typeFinder ??= new BuildManagerTypeFinder(Logger, new BuildManagerTypeFinder.TypeFinderConfig()); + protected override ITypeFinder GetTypeFinder() => _typeFinder ??= new BuildManagerTypeFinder(IOHelper, Logger, new BuildManagerTypeFinder.TypeFinderConfig()); protected override IProfiler GetProfiler() => _webProfiler; From dd24d512fd3941f41fcbe18c09c963641c26a473 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Nov 2019 19:01:10 +1100 Subject: [PATCH 15/36] fixes startup order --- src/Umbraco.Core/Runtime/CoreRuntime.cs | 8 ++++---- src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 42c39bfabf..b6a4f07dfd 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -72,14 +72,14 @@ namespace Umbraco.Core.Runtime var profilingLogger = ProfilingLogger = new ProfilingLogger(logger, profiler); - TypeFinder = GetTypeFinder(); - if (TypeFinder == null) - throw new InvalidOperationException($"The object returned from {nameof(GetTypeFinder)} cannot be null"); - IOHelper = GetIOHelper(); if (IOHelper == null) throw new InvalidOperationException($"The object returned from {nameof(GetIOHelper)} cannot be null"); + TypeFinder = GetTypeFinder(); + if (TypeFinder == null) + throw new InvalidOperationException($"The object returned from {nameof(GetTypeFinder)} cannot be null"); + // 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. // note that this REQUIRES that perWebRequestScope has NOT been enabled yet, else diff --git a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs index d0cdf76702..15c8d6d8d6 100644 --- a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs +++ b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs @@ -27,6 +27,9 @@ namespace Umbraco.Web.Composing public BuildManagerTypeFinder(IIOHelper ioHelper, ILogger logger, ITypeFinderConfig typeFinderConfig = null) : base(logger, typeFinderConfig) { + if (ioHelper == null) throw new ArgumentNullException(nameof(ioHelper)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + _allAssemblies = new Lazy>(() => { var isHosted = ioHelper.IsHosted; From 46b91b799121d18d96c8b3eccbc199e6d1f5b10b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Nov 2019 19:11:40 +1100 Subject: [PATCH 16/36] no c#8 --- src/Umbraco.Web/Runtime/WebRuntime.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index 2ebec69da0..2f1ef43e4c 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -59,7 +59,7 @@ namespace Umbraco.Web.Runtime #region Getters - protected override ITypeFinder GetTypeFinder() => _typeFinder ??= new BuildManagerTypeFinder(IOHelper, Logger, new BuildManagerTypeFinder.TypeFinderConfig()); + protected override ITypeFinder GetTypeFinder() => _typeFinder ?? (_typeFinder = new BuildManagerTypeFinder(IOHelper, Logger, new BuildManagerTypeFinder.TypeFinderConfig())); protected override IProfiler GetProfiler() => _webProfiler; From d3fb1ae8424d5307b90fe7e4bb253121504adb03 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Nov 2019 19:46:11 +1100 Subject: [PATCH 17/36] fixing tests --- src/Umbraco.Tests/TestHelpers/TestHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 134eb8272b..e07dbe1e5a 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -29,7 +29,7 @@ namespace Umbraco.Tests.TestHelpers public static TypeLoader GetMockedTypeLoader() { - return new TypeLoader(Mock.Of(), Mock.Of(), Mock.Of(), new DirectoryInfo(IOHelper.Default.MapPath("~/App_Data/TEMP")), Mock.Of()); + return new TypeLoader(IOHelper.Default, Mock.Of(), Mock.Of(), new DirectoryInfo(IOHelper.Default.MapPath("~/App_Data/TEMP")), Mock.Of()); } /// From 3242f78aa619191aa38e8dae6cd9df679cebf7fd Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Nov 2019 21:38:18 +1100 Subject: [PATCH 18/36] Fixes tests --- src/Umbraco.Tests/Composing/TypeLoaderTests.cs | 2 +- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 3d726e3906..5163e6a4e8 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -274,7 +274,7 @@ AnotherContentFinder public void GetDataEditors() { var types = _typeLoader.GetDataEditors(); - Assert.AreEqual(38, types.Count()); + Assert.AreEqual(37, types.Count()); } /// diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 273202ae7d..d1e2ab2f6d 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -99,9 +99,9 @@ namespace Umbraco.Tests.Testing protected ILogger Logger => Factory.GetInstance(); - protected IIOHelper IOHelper => Factory.GetInstance(); + protected IIOHelper IOHelper { get; private set; } - protected ITypeFinder TypeFinder => Factory.GetInstance(); + protected ITypeFinder TypeFinder { get; private set; } protected IProfiler Profiler => Factory.GetInstance(); @@ -133,19 +133,19 @@ namespace Umbraco.Tests.Testing var (logger, profiler) = GetLoggers(Options.Logger); var proflogger = new ProfilingLogger(logger, profiler); + IOHelper = Umbraco.Core.IO.IOHelper.Default; + TypeFinder = new TypeFinder(logger); var appCaches = GetAppCaches(); - var ioHelper = Umbraco.Core.IO.IOHelper.Default; - var typeFinder = new TypeFinder(logger); var globalSettings = SettingsForTests.GetDefaultGlobalSettings(); - var typeLoader = GetTypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, globalSettings, proflogger, Options.TypeLoader); + var typeLoader = GetTypeLoader(IOHelper, TypeFinder, appCaches.RuntimeCache, globalSettings, proflogger, Options.TypeLoader); var register = RegisterFactory.Create(); Composition = new Composition(register, typeLoader, proflogger, ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - Composition.RegisterUnique(ioHelper); - Composition.RegisterUnique(typeLoader.TypeFinder); + Composition.RegisterUnique(IOHelper); + Composition.RegisterUnique(TypeFinder); Composition.RegisterUnique(typeLoader); Composition.RegisterUnique(logger); Composition.RegisterUnique(profiler); From f90fc48ca8eaa3ee17640e53a394fbf39ba1d16a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 11 Nov 2019 15:42:14 +0100 Subject: [PATCH 19/36] AB3649 - Moved IContent and ContentSchedule --- .../Models/ContentSchedule.cs | 2 +- .../Models/ContentScheduleAction.cs | 0 .../Models/ContentScheduleCollection.cs | 0 .../Models/IContent.cs | 0 src/Umbraco.Core/Manifest/ManifestParser.cs | 10 +++++++--- src/Umbraco.Core/Umbraco.Core.csproj | 4 ---- src/Umbraco.Tests/Manifest/ManifestParserTests.cs | 3 ++- 7 files changed, 10 insertions(+), 9 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Models/ContentSchedule.cs (98%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Models/ContentScheduleAction.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Models/ContentScheduleCollection.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Models/IContent.cs (100%) diff --git a/src/Umbraco.Core/Models/ContentSchedule.cs b/src/Umbraco.Abstractions/Models/ContentSchedule.cs similarity index 98% rename from src/Umbraco.Core/Models/ContentSchedule.cs rename to src/Umbraco.Abstractions/Models/ContentSchedule.cs index cac4a0fd1c..4dba0456b0 100644 --- a/src/Umbraco.Core/Models/ContentSchedule.cs +++ b/src/Umbraco.Abstractions/Models/ContentSchedule.cs @@ -36,7 +36,7 @@ namespace Umbraco.Core.Models /// Gets the unique identifier of the document targeted by the scheduled action. /// [DataMember] - public Guid Id { get; internal set; } + public Guid Id { get; set; } /// /// Gets the culture of the scheduled action. diff --git a/src/Umbraco.Core/Models/ContentScheduleAction.cs b/src/Umbraco.Abstractions/Models/ContentScheduleAction.cs similarity index 100% rename from src/Umbraco.Core/Models/ContentScheduleAction.cs rename to src/Umbraco.Abstractions/Models/ContentScheduleAction.cs diff --git a/src/Umbraco.Core/Models/ContentScheduleCollection.cs b/src/Umbraco.Abstractions/Models/ContentScheduleCollection.cs similarity index 100% rename from src/Umbraco.Core/Models/ContentScheduleCollection.cs rename to src/Umbraco.Abstractions/Models/ContentScheduleCollection.cs diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Abstractions/Models/IContent.cs similarity index 100% rename from src/Umbraco.Core/Models/IContent.cs rename to src/Umbraco.Abstractions/Models/IContent.cs diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index bf70def9dc..35206b3e62 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Serialization; namespace Umbraco.Core.Manifest { @@ -17,6 +18,7 @@ namespace Umbraco.Core.Manifest /// public class ManifestParser { + private readonly IJsonSerializer _jsonSerializer; private static readonly string Utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); private readonly IAppPolicyCache _cache; @@ -30,9 +32,11 @@ namespace Umbraco.Core.Manifest /// /// Initializes a new instance of the class. /// - public ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ManifestFilterCollection filters, ILogger logger, IIOHelper ioHelper) + public ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ManifestFilterCollection filters, ILogger logger, IIOHelper ioHelper, IJsonSerializer jsonSerializer) : this(appCaches, validators, filters, "~/App_Plugins", logger, ioHelper) - { } + { + _jsonSerializer = jsonSerializer; + } /// /// Initializes a new instance of the class. @@ -198,7 +202,7 @@ namespace Umbraco.Core.Manifest // purely for tests internal IEnumerable ParseGridEditors(string text) { - return JsonConvert.DeserializeObject>(text); + return _jsonSerializer.Deserialize>(text); } } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7f51a855b3..ec6c053d7f 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -229,7 +229,6 @@ - @@ -425,9 +424,6 @@ - - - diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs index 0fcb7ac9e0..fcc7ccd79b 100644 --- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs @@ -13,6 +13,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.Validators; using Umbraco.Core.Services; using Umbraco.Core.Dashboards; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Manifest { @@ -44,7 +45,7 @@ namespace Umbraco.Tests.Manifest new RequiredValidator(Mock.Of()), new RegexValidator(Mock.Of(), null) }; - _parser = new ManifestParser(AppCaches.Disabled, new ManifestValueValidatorCollection(validators), new ManifestFilterCollection(Array.Empty()), Mock.Of(), Current.IOHelper); + _parser = new ManifestParser(AppCaches.Disabled, new ManifestValueValidatorCollection(validators), new ManifestFilterCollection(Array.Empty()), Mock.Of(), Current.IOHelper, new JsonNetSerializer()); } [Test] From fce8ac87a2649917d7101c2fa622bf5296160f3b Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2019 12:37:21 +1100 Subject: [PATCH 20/36] Moves a bunch of the xml/xpath classes/interfaces and remvoes unused code of XmlHelper --- .../Xml/XPath/INavigableContent.cs | 2 +- .../Xml/XPath/INavigableContentType.cs | 2 +- .../Xml/XPath/INavigableFieldType.cs | 2 +- .../Xml/XPath/INavigableSource.cs | 2 +- .../Xml/XPath/MacroNavigator.cs | 4 +- .../Xml/XPath/NavigableNavigator.cs | 8 +- .../Xml/XPath/RenamedRootNavigator.cs | 0 .../Xml/XPathVariable.cs | 0 src/Umbraco.Abstractions/Xml/XmlHelper.cs | 221 +++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 10 +- src/Umbraco.Core/Xml/XmlHelper.cs | 430 ------------------ 11 files changed, 232 insertions(+), 449 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Xml/XPath/INavigableContent.cs (98%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Xml/XPath/INavigableContentType.cs (93%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Xml/XPath/INavigableFieldType.cs (95%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Xml/XPath/INavigableSource.cs (96%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Xml/XPath/MacroNavigator.cs (99%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Xml/XPath/NavigableNavigator.cs (99%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Xml/XPath/RenamedRootNavigator.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Xml/XPathVariable.cs (100%) create mode 100644 src/Umbraco.Abstractions/Xml/XmlHelper.cs delete mode 100644 src/Umbraco.Core/Xml/XmlHelper.cs diff --git a/src/Umbraco.Core/Xml/XPath/INavigableContent.cs b/src/Umbraco.Abstractions/Xml/XPath/INavigableContent.cs similarity index 98% rename from src/Umbraco.Core/Xml/XPath/INavigableContent.cs rename to src/Umbraco.Abstractions/Xml/XPath/INavigableContent.cs index eeb7891726..c0b3192830 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableContent.cs +++ b/src/Umbraco.Abstractions/Xml/XPath/INavigableContent.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.Xml.XPath /// /// Represents a content that can be navigated via XPath. /// - interface INavigableContent + public interface INavigableContent { /// /// Gets the unique identifier of the navigable content. diff --git a/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs b/src/Umbraco.Abstractions/Xml/XPath/INavigableContentType.cs similarity index 93% rename from src/Umbraco.Core/Xml/XPath/INavigableContentType.cs rename to src/Umbraco.Abstractions/Xml/XPath/INavigableContentType.cs index 94b225467c..1fff0b60bb 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs +++ b/src/Umbraco.Abstractions/Xml/XPath/INavigableContentType.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Xml.XPath /// /// Represents the type of a content that can be navigated via XPath. /// - interface INavigableContentType + public interface INavigableContentType { /// /// Gets the name of the content type. diff --git a/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs b/src/Umbraco.Abstractions/Xml/XPath/INavigableFieldType.cs similarity index 95% rename from src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs rename to src/Umbraco.Abstractions/Xml/XPath/INavigableFieldType.cs index 32a6f64751..d741060865 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs +++ b/src/Umbraco.Abstractions/Xml/XPath/INavigableFieldType.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Xml.XPath /// Represents the type of a field of a content that can be navigated via XPath. /// /// A field can be an attribute or a property. - interface INavigableFieldType + public interface INavigableFieldType { /// /// Gets the name of the field type. diff --git a/src/Umbraco.Core/Xml/XPath/INavigableSource.cs b/src/Umbraco.Abstractions/Xml/XPath/INavigableSource.cs similarity index 96% rename from src/Umbraco.Core/Xml/XPath/INavigableSource.cs rename to src/Umbraco.Abstractions/Xml/XPath/INavigableSource.cs index 68dc9e906a..7a605574d7 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableSource.cs +++ b/src/Umbraco.Abstractions/Xml/XPath/INavigableSource.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Xml.XPath /// /// Represents a source of content that can be navigated via XPath. /// - interface INavigableSource + public interface INavigableSource { /// /// Gets a content identified by its unique identifier. diff --git a/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs b/src/Umbraco.Abstractions/Xml/XPath/MacroNavigator.cs similarity index 99% rename from src/Umbraco.Core/Xml/XPath/MacroNavigator.cs rename to src/Umbraco.Abstractions/Xml/XPath/MacroNavigator.cs index 8b4755107a..dc2246834e 100644 --- a/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs +++ b/src/Umbraco.Abstractions/Xml/XPath/MacroNavigator.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Xml.XPath /// /// Provides a cursor model for navigating {macro /} as if it were XML. /// - class MacroNavigator : XPathNavigator + public class MacroNavigator : XPathNavigator { private readonly XmlNameTable _nameTable; private readonly MacroRoot _macro; @@ -55,7 +55,7 @@ namespace Umbraco.Core.Xml.XPath private int _tabs; private readonly int _uid = GetUid(); private static int _uidg; - private readonly static object Uidl = new object(); + private static readonly object Uidl = new object(); private static int GetUid() { lock (Uidl) diff --git a/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs b/src/Umbraco.Abstractions/Xml/XPath/NavigableNavigator.cs similarity index 99% rename from src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs rename to src/Umbraco.Abstractions/Xml/XPath/NavigableNavigator.cs index dc89de8372..e178ab918e 100644 --- a/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs +++ b/src/Umbraco.Abstractions/Xml/XPath/NavigableNavigator.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Xml.XPath /// /// Provides a cursor model for navigating Umbraco data as if it were XML. /// - class NavigableNavigator : XPathNavigator + public class NavigableNavigator : XPathNavigator { // "The XmlNameTable stores atomized strings of any local name, namespace URI, // and prefix used by the XPathNavigator. This means that when the same Name is @@ -1089,7 +1089,7 @@ namespace Umbraco.Core.Xml.XPath #region State management // the possible state positions - internal enum StatePosition + public enum StatePosition { Root, Element, @@ -1101,10 +1101,10 @@ namespace Umbraco.Core.Xml.XPath // gets the state // for unit tests only - internal State InternalState => _state; + public State InternalState => _state; // represents the XPathNavigator state - internal class State + public class State { public StatePosition Position { get; set; } diff --git a/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs b/src/Umbraco.Abstractions/Xml/XPath/RenamedRootNavigator.cs similarity index 100% rename from src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs rename to src/Umbraco.Abstractions/Xml/XPath/RenamedRootNavigator.cs diff --git a/src/Umbraco.Core/Xml/XPathVariable.cs b/src/Umbraco.Abstractions/Xml/XPathVariable.cs similarity index 100% rename from src/Umbraco.Core/Xml/XPathVariable.cs rename to src/Umbraco.Abstractions/Xml/XPathVariable.cs diff --git a/src/Umbraco.Abstractions/Xml/XmlHelper.cs b/src/Umbraco.Abstractions/Xml/XmlHelper.cs new file mode 100644 index 0000000000..0a435b23f7 --- /dev/null +++ b/src/Umbraco.Abstractions/Xml/XmlHelper.cs @@ -0,0 +1,221 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml; +using System.Xml.XPath; +using Umbraco.Core.Exceptions; + +namespace Umbraco.Core.Xml +{ + /// + /// The XmlHelper class contains general helper methods for working with xml in umbraco. + /// + public class XmlHelper + { + + /// + /// Gets a value indicating whether a specified string contains only xml whitespace characters. + /// + /// The string. + /// true if the string contains only xml whitespace characters. + /// As per XML 1.1 specs, space, \t, \r and \n. + public static bool IsXmlWhitespace(string s) + { + // as per xml 1.1 specs - anything else is significant whitespace + s = s.Trim(' ', '\t', '\r', '\n'); + return s.Length == 0; + } + + /// + /// Creates a new XPathDocument from an xml string. + /// + /// The xml string. + /// An XPathDocument created from the xml string. + public static XPathDocument CreateXPathDocument(string xml) + { + return new XPathDocument(new XmlTextReader(new StringReader(xml))); + } + + /// + /// Tries to create a new XPathDocument from an xml string. + /// + /// The xml string. + /// The XPath document. + /// A value indicating whether it has been possible to create the document. + public static bool TryCreateXPathDocument(string xml, out XPathDocument doc) + { + try + { + doc = CreateXPathDocument(xml); + return true; + } + catch (Exception) + { + doc = null; + return false; + } + } + + /// + /// Tries to create a new XPathDocument from a property value. + /// + /// The value of the property. + /// The XPath document. + /// A value indicating whether it has been possible to create the document. + /// The value can be anything... Performance-wise, this is bad. + public static bool TryCreateXPathDocumentFromPropertyValue(object value, out XPathDocument doc) + { + // DynamicNode.ConvertPropertyValueByDataType first cleans the value by calling + // XmlHelper.StripDashesInElementOrAttributeName - this is because the XML is + // to be returned as a DynamicXml and element names such as "value-item" are + // invalid and must be converted to "valueitem". But we don't have that sort of + // problem here - and we don't need to bother with dashes nor dots, etc. + + doc = null; + var xml = value as string; + if (xml == null) return false; // no a string + if (CouldItBeXml(xml) == false) return false; // string does not look like it's xml + if (IsXmlWhitespace(xml)) return false; // string is whitespace, xml-wise + if (TryCreateXPathDocument(xml, out doc) == false) return false; // string can't be parsed into xml + + var nav = doc.CreateNavigator(); + if (nav.MoveToFirstChild()) + { + //SD: This used to do this but the razor macros and the entire razor macros section is gone, it was all legacy, it seems this method isn't even + // used apart from for tests so don't think this matters. In any case, we no longer check for this! + + //var name = nav.LocalName; // must not match an excluded tag + //if (UmbracoConfig.For.UmbracoSettings().Scripting.NotDynamicXmlDocumentElements.All(x => x.Element.InvariantEquals(name) == false)) return true; + + return true; + } + + doc = null; + return false; + } + + + /// + /// Sorts the children of a parentNode. + /// + /// The parent node. + /// An XPath expression to select children of to sort. + /// A function returning the value to order the nodes by. + public static void SortNodes( + XmlNode parentNode, + string childNodesXPath, + Func orderBy) + { + var sortedChildNodes = parentNode.SelectNodes(childNodesXPath).Cast() + .OrderBy(orderBy) + .ToArray(); + + // append child nodes to last position, in sort-order + // so all child nodes will go after the property nodes + foreach (var node in sortedChildNodes) + parentNode.AppendChild(node); // moves the node to the last position + } + + + /// + /// Sorts a single child node of a parentNode. + /// + /// The parent node. + /// An XPath expression to select children of to sort. + /// The child node to sort. + /// A function returning the value to order the nodes by. + /// A value indicating whether sorting was needed. + /// Assuming all nodes but are sorted, this will move the node to + /// the right position without moving all the nodes (as SortNodes would do) - should improve perfs. + public static bool SortNode( + XmlNode parentNode, + string childNodesXPath, + XmlNode node, + Func orderBy) + { + var nodeSortOrder = orderBy(node); + var childNodesAndOrder = parentNode.SelectNodes(childNodesXPath).Cast() + .Select(x => Tuple.Create(x, orderBy(x))).ToArray(); + + // only one node = node is in the right place already, obviously + if (childNodesAndOrder.Length == 1) return false; + + // find the first node with a sortOrder > node.sortOrder + var i = 0; + while (i < childNodesAndOrder.Length && childNodesAndOrder[i].Item2 <= nodeSortOrder) + i++; + + // if one was found + if (i < childNodesAndOrder.Length) + { + // and node is just before, we're done already + // else we need to move it right before the node that was found + if (i == 0 || childNodesAndOrder[i - 1].Item1 != node) + { + parentNode.InsertBefore(node, childNodesAndOrder[i].Item1); + return true; + } + } + else // i == childNodesAndOrder.Length && childNodesAndOrder.Length > 1 + { + // and node is the last one, we're done already + // else we need to append it as the last one + // (and i > 1, see above) + if (childNodesAndOrder[i - 1].Item1 != node) + { + parentNode.AppendChild(node); + return true; + } + } + return false; + } + + + + /// + /// Gets the value of a XmlNode + /// + /// The XmlNode. + /// the value as a string + public static string GetNodeValue(XmlNode n) + { + var value = string.Empty; + if (n == null || n.FirstChild == null) + return value; + value = n.FirstChild.Value ?? n.InnerXml; + return value.Replace("", "", "]]>"); + } + + /// + /// Determines whether the specified string appears to be XML. + /// + /// The XML string. + /// + /// true if the specified string appears to be XML; otherwise, false. + /// + public static bool CouldItBeXml(string xml) + { + if (string.IsNullOrEmpty(xml)) return false; + + xml = xml.Trim(); + return xml.StartsWith("<") && xml.EndsWith(">") && xml.Contains('/'); + } + + /// + /// Return a dictionary of attributes found for a string based tag + /// + /// + /// + public static Dictionary GetAttributesFromElement(string tag) + { + var m = + Regex.Matches(tag, "(?\\S*)=\"(?[^\"]*)\"", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + // fix for issue 14862: return lowercase attributes for case insensitive matching + var d = m.Cast().ToDictionary(attributeSet => attributeSet.Groups["attributeName"].Value.ToString().ToLower(), attributeSet => attributeSet.Groups["attributeValue"].Value.ToString()); + return d; + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5f18cb3d95..34c2295a9f 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1169,18 +1169,9 @@ - - - - - - - - - @@ -1194,5 +1185,6 @@ Umbraco.Abstractions + \ No newline at end of file diff --git a/src/Umbraco.Core/Xml/XmlHelper.cs b/src/Umbraco.Core/Xml/XmlHelper.cs deleted file mode 100644 index 53d56c1aac..0000000000 --- a/src/Umbraco.Core/Xml/XmlHelper.cs +++ /dev/null @@ -1,430 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Xml; -using System.Xml.Linq; -using System.Xml.XPath; -using Umbraco.Core.Composing; -using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; - -namespace Umbraco.Core.Xml -{ - /// - /// The XmlHelper class contains general helper methods for working with xml in umbraco. - /// - public class XmlHelper - { - /// - /// Creates or sets an attribute on the XmlNode if an Attributes collection is available - /// - /// - /// - /// - /// - public static void SetAttribute(XmlDocument xml, XmlNode n, string name, string value) - { - if (xml == null) throw new ArgumentNullException("xml"); - if (n == null) throw new ArgumentNullException("n"); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); - - if (n.Attributes == null) - { - return; - } - if (n.Attributes[name] == null) - { - var a = xml.CreateAttribute(name); - a.Value = value; - n.Attributes.Append(a); - } - else - { - n.Attributes[name].Value = value; - } - } - - /// - /// Gets a value indicating whether a specified string contains only xml whitespace characters. - /// - /// The string. - /// true if the string contains only xml whitespace characters. - /// As per XML 1.1 specs, space, \t, \r and \n. - public static bool IsXmlWhitespace(string s) - { - // as per xml 1.1 specs - anything else is significant whitespace - s = s.Trim(' ', '\t', '\r', '\n'); - return s.Length == 0; - } - - /// - /// Creates a new XPathDocument from an xml string. - /// - /// The xml string. - /// An XPathDocument created from the xml string. - public static XPathDocument CreateXPathDocument(string xml) - { - return new XPathDocument(new XmlTextReader(new StringReader(xml))); - } - - /// - /// Tries to create a new XPathDocument from an xml string. - /// - /// The xml string. - /// The XPath document. - /// A value indicating whether it has been possible to create the document. - public static bool TryCreateXPathDocument(string xml, out XPathDocument doc) - { - try - { - doc = CreateXPathDocument(xml); - return true; - } - catch (Exception) - { - doc = null; - return false; - } - } - - /// - /// Tries to create a new XPathDocument from a property value. - /// - /// The value of the property. - /// The XPath document. - /// A value indicating whether it has been possible to create the document. - /// The value can be anything... Performance-wise, this is bad. - public static bool TryCreateXPathDocumentFromPropertyValue(object value, out XPathDocument doc) - { - // DynamicNode.ConvertPropertyValueByDataType first cleans the value by calling - // XmlHelper.StripDashesInElementOrAttributeName - this is because the XML is - // to be returned as a DynamicXml and element names such as "value-item" are - // invalid and must be converted to "valueitem". But we don't have that sort of - // problem here - and we don't need to bother with dashes nor dots, etc. - - doc = null; - var xml = value as string; - if (xml == null) return false; // no a string - if (CouldItBeXml(xml) == false) return false; // string does not look like it's xml - if (IsXmlWhitespace(xml)) return false; // string is whitespace, xml-wise - if (TryCreateXPathDocument(xml, out doc) == false) return false; // string can't be parsed into xml - - var nav = doc.CreateNavigator(); - if (nav.MoveToFirstChild()) - { - //SD: This used to do this but the razor macros and the entire razor macros section is gone, it was all legacy, it seems this method isn't even - // used apart from for tests so don't think this matters. In any case, we no longer check for this! - - //var name = nav.LocalName; // must not match an excluded tag - //if (UmbracoConfig.For.UmbracoSettings().Scripting.NotDynamicXmlDocumentElements.All(x => x.Element.InvariantEquals(name) == false)) return true; - - return true; - } - - doc = null; - return false; - } - - - /// - /// Sorts the children of a parentNode. - /// - /// The parent node. - /// An XPath expression to select children of to sort. - /// A function returning the value to order the nodes by. - internal static void SortNodes( - XmlNode parentNode, - string childNodesXPath, - Func orderBy) - { - var sortedChildNodes = parentNode.SelectNodes(childNodesXPath).Cast() - .OrderBy(orderBy) - .ToArray(); - - // append child nodes to last position, in sort-order - // so all child nodes will go after the property nodes - foreach (var node in sortedChildNodes) - parentNode.AppendChild(node); // moves the node to the last position - } - - - /// - /// Sorts a single child node of a parentNode. - /// - /// The parent node. - /// An XPath expression to select children of to sort. - /// The child node to sort. - /// A function returning the value to order the nodes by. - /// A value indicating whether sorting was needed. - /// Assuming all nodes but are sorted, this will move the node to - /// the right position without moving all the nodes (as SortNodes would do) - should improve perfs. - internal static bool SortNode( - XmlNode parentNode, - string childNodesXPath, - XmlNode node, - Func orderBy) - { - var nodeSortOrder = orderBy(node); - var childNodesAndOrder = parentNode.SelectNodes(childNodesXPath).Cast() - .Select(x => Tuple.Create(x, orderBy(x))).ToArray(); - - // only one node = node is in the right place already, obviously - if (childNodesAndOrder.Length == 1) return false; - - // find the first node with a sortOrder > node.sortOrder - var i = 0; - while (i < childNodesAndOrder.Length && childNodesAndOrder[i].Item2 <= nodeSortOrder) - i++; - - // if one was found - if (i < childNodesAndOrder.Length) - { - // and node is just before, we're done already - // else we need to move it right before the node that was found - if (i == 0 || childNodesAndOrder[i - 1].Item1 != node) - { - parentNode.InsertBefore(node, childNodesAndOrder[i].Item1); - return true; - } - } - else // i == childNodesAndOrder.Length && childNodesAndOrder.Length > 1 - { - // and node is the last one, we're done already - // else we need to append it as the last one - // (and i > 1, see above) - if (childNodesAndOrder[i - 1].Item1 != node) - { - parentNode.AppendChild(node); - return true; - } - } - return false; - } - - - /// - /// Opens a file as a XmlDocument. - /// - /// The relative file path. ie. /config/umbraco.config - /// Returns a XmlDocument class - public static XmlDocument OpenAsXmlDocument(string filePath) - { - using (var reader = new XmlTextReader(Current.IOHelper.MapPath(filePath)) {WhitespaceHandling = WhitespaceHandling.All}) - { - var xmlDoc = new XmlDocument(); - //Load the file into the XmlDocument - xmlDoc.Load(reader); - - return xmlDoc; - } - } - - /// - /// creates a XmlAttribute with the specified name and value - /// - /// The xmldocument. - /// The name of the attribute. - /// The value of the attribute. - /// a XmlAttribute - public static XmlAttribute AddAttribute(XmlDocument xd, string name, string value) - { - if (xd == null) throw new ArgumentNullException("xd"); - if (string.IsNullOrEmpty(name)) throw new ArgumentNullOrEmptyException(nameof(name)); - - var temp = xd.CreateAttribute(name); - temp.Value = value; - return temp; - } - - /// - /// Creates a text XmlNode with the specified name and value - /// - /// The xmldocument. - /// The node name. - /// The node value. - /// a XmlNode - public static XmlNode AddTextNode(XmlDocument xd, string name, string value) - { - if (xd == null) throw new ArgumentNullException("xd"); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); - - var temp = xd.CreateNode(XmlNodeType.Element, name, ""); - temp.AppendChild(xd.CreateTextNode(value)); - return temp; - } - - /// - /// Sets or Creates a text XmlNode with the specified name and value - /// - /// The xmldocument. - /// The node to set or create the child text node on - /// The node name. - /// The node value. - /// a XmlNode - public static XmlNode SetTextNode(XmlDocument xd, XmlNode parent, string name, string value) - { - if (xd == null) throw new ArgumentNullException("xd"); - if (parent == null) throw new ArgumentNullException("parent"); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); - - var child = parent.SelectSingleNode(name); - if (child != null) - { - child.InnerText = value; - return child; - } - return AddTextNode(xd, name, value); - } - - /// - /// Sets or creates an Xml node from its inner Xml. - /// - /// The xmldocument. - /// The node to set or create the child text node on - /// The node name. - /// The node inner Xml. - /// a XmlNode - public static XmlNode SetInnerXmlNode(XmlDocument xd, XmlNode parent, string name, string value) - { - if (xd == null) throw new ArgumentNullException(nameof(xd)); - if (parent == null) throw new ArgumentNullException(nameof(parent)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); - - var child = parent.SelectSingleNode(name) ?? xd.CreateNode(XmlNodeType.Element, name, ""); - child.InnerXml = value; - return child; - } - - /// - /// Creates a cdata XmlNode with the specified name and value - /// - /// The xmldocument. - /// The node name. - /// The node value. - /// A XmlNode - public static XmlNode AddCDataNode(XmlDocument xd, string name, string value) - { - if (xd == null) throw new ArgumentNullException("xd"); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); - - var temp = xd.CreateNode(XmlNodeType.Element, name, ""); - temp.AppendChild(xd.CreateCDataSection(value)); - return temp; - } - - /// - /// Sets or Creates a cdata XmlNode with the specified name and value - /// - /// The xmldocument. - /// The node to set or create the child text node on - /// The node name. - /// The node value. - /// a XmlNode - public static XmlNode SetCDataNode(XmlDocument xd, XmlNode parent, string name, string value) - { - if (xd == null) throw new ArgumentNullException("xd"); - if (parent == null) throw new ArgumentNullException("parent"); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); - - var child = parent.SelectSingleNode(name); - if (child != null) - { - child.InnerXml = ""; ; - return child; - } - return AddCDataNode(xd, name, value); - } - - /// - /// Gets the value of a XmlNode - /// - /// The XmlNode. - /// the value as a string - internal static string GetNodeValue(XmlNode n) - { - var value = string.Empty; - if (n == null || n.FirstChild == null) - return value; - value = n.FirstChild.Value ?? n.InnerXml; - return value.Replace("", "", "]]>"); - } - - /// - /// Determines whether the specified string appears to be XML. - /// - /// The XML string. - /// - /// true if the specified string appears to be XML; otherwise, false. - /// - public static bool CouldItBeXml(string xml) - { - if (string.IsNullOrEmpty(xml)) return false; - - xml = xml.Trim(); - return xml.StartsWith("<") && xml.EndsWith(">") && xml.Contains('/'); - } - - /// - /// Splits the specified delimited string into an XML document. - /// - /// The data. - /// The separator. - /// Name of the root. - /// Name of the element. - /// Returns an System.Xml.XmlDocument representation of the delimited string data. - internal static XmlDocument Split(string data, string[] separator, string rootName, string elementName) - { - return Split(new XmlDocument(), data, separator, rootName, elementName); - } - - /// - /// Splits the specified delimited string into an XML document. - /// - /// The XML document. - /// The delimited string data. - /// The separator. - /// Name of the root node. - /// Name of the element node. - /// Returns an System.Xml.XmlDocument representation of the delimited string data. - internal static XmlDocument Split(XmlDocument xml, string data, string[] separator, string rootName, string elementName) - { - // load new XML document. - xml.LoadXml(string.Concat("<", rootName, "/>")); - - // get the data-value, check it isn't empty. - if (!string.IsNullOrEmpty(data)) - { - // explode the values into an array - var values = data.Split(separator, StringSplitOptions.None); - - // loop through the array items. - foreach (string value in values) - { - // add each value to the XML document. - var xn = XmlHelper.AddTextNode(xml, elementName, value); - xml.DocumentElement.AppendChild(xn); - } - } - - // return the XML node. - return xml; - } - - /// - /// Return a dictionary of attributes found for a string based tag - /// - /// - /// - internal static Dictionary GetAttributesFromElement(string tag) - { - var m = - Regex.Matches(tag, "(?\\S*)=\"(?[^\"]*)\"", - RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - // fix for issue 14862: return lowercase attributes for case insensitive matching - var d = m.Cast().ToDictionary(attributeSet => attributeSet.Groups["attributeName"].Value.ToString().ToLower(), attributeSet => attributeSet.Groups["attributeValue"].Value.ToString()); - return d; - } - } -} From e58edd7d5d18968c09ccd1e79ee683b236f4cd38 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2019 12:56:17 +1100 Subject: [PATCH 21/36] Moves remaining Umbraco.Core.Xml items to abstractions project --- .../Xml/DynamicContext.cs | 0 .../Xml/UmbracoXPathPathSyntaxParser.cs | 4 +--- .../Xml/XPathNavigatorExtensions.cs | 2 +- .../Xml/XmlNamespaces.cs | 0 .../Xml/XmlNodeListFactory.cs | 2 +- src/{Umbraco.Core => Umbraco.Abstractions}/XmlExtensions.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 6 ------ 7 files changed, 4 insertions(+), 12 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Xml/DynamicContext.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Xml/UmbracoXPathPathSyntaxParser.cs (98%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Xml/XPathNavigatorExtensions.cs (98%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Xml/XmlNamespaces.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Xml/XmlNodeListFactory.cs (99%) rename src/{Umbraco.Core => Umbraco.Abstractions}/XmlExtensions.cs (99%) diff --git a/src/Umbraco.Core/Xml/DynamicContext.cs b/src/Umbraco.Abstractions/Xml/DynamicContext.cs similarity index 100% rename from src/Umbraco.Core/Xml/DynamicContext.cs rename to src/Umbraco.Abstractions/Xml/DynamicContext.cs diff --git a/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs b/src/Umbraco.Abstractions/Xml/UmbracoXPathPathSyntaxParser.cs similarity index 98% rename from src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs rename to src/Umbraco.Abstractions/Xml/UmbracoXPathPathSyntaxParser.cs index b31fa6a8df..0c8136421d 100644 --- a/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs +++ b/src/Umbraco.Abstractions/Xml/UmbracoXPathPathSyntaxParser.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Umbraco.Core.Exceptions; namespace Umbraco.Core.Xml @@ -11,7 +9,7 @@ namespace Umbraco.Core.Xml /// This is used to parse our customize Umbraco XPath expressions (i.e. that include special tokens like $site) into /// a real XPath statement /// - internal class UmbracoXPathPathSyntaxParser + public class UmbracoXPathPathSyntaxParser { /// /// Parses custom umbraco xpath expression diff --git a/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs b/src/Umbraco.Abstractions/Xml/XPathNavigatorExtensions.cs similarity index 98% rename from src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs rename to src/Umbraco.Abstractions/Xml/XPathNavigatorExtensions.cs index b80836dde8..bb3b41511b 100644 --- a/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs +++ b/src/Umbraco.Abstractions/Xml/XPathNavigatorExtensions.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.Xml /// /// Provides extensions to XPathNavigator. /// - internal static class XPathNavigatorExtensions + public static class XPathNavigatorExtensions { /// /// Selects a node set, using the specified XPath expression. diff --git a/src/Umbraco.Core/Xml/XmlNamespaces.cs b/src/Umbraco.Abstractions/Xml/XmlNamespaces.cs similarity index 100% rename from src/Umbraco.Core/Xml/XmlNamespaces.cs rename to src/Umbraco.Abstractions/Xml/XmlNamespaces.cs diff --git a/src/Umbraco.Core/Xml/XmlNodeListFactory.cs b/src/Umbraco.Abstractions/Xml/XmlNodeListFactory.cs similarity index 99% rename from src/Umbraco.Core/Xml/XmlNodeListFactory.cs rename to src/Umbraco.Abstractions/Xml/XmlNodeListFactory.cs index bc8deaab2a..0a5f2c859d 100644 --- a/src/Umbraco.Core/Xml/XmlNodeListFactory.cs +++ b/src/Umbraco.Abstractions/Xml/XmlNodeListFactory.cs @@ -7,7 +7,7 @@ using System.Xml.XPath; namespace Umbraco.Core.Xml { - class XmlNodeListFactory + public class XmlNodeListFactory { private XmlNodeListFactory() { } diff --git a/src/Umbraco.Core/XmlExtensions.cs b/src/Umbraco.Abstractions/XmlExtensions.cs similarity index 99% rename from src/Umbraco.Core/XmlExtensions.cs rename to src/Umbraco.Abstractions/XmlExtensions.cs index 199e16195b..ef0132dd69 100644 --- a/src/Umbraco.Core/XmlExtensions.cs +++ b/src/Umbraco.Abstractions/XmlExtensions.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core /// /// Extension methods for xml objects /// - internal static class XmlExtensions + public static class XmlExtensions { public static bool HasAttribute(this XmlAttributeCollection attributes, string attributeName) { diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 34c2295a9f..51187a49b7 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1166,12 +1166,6 @@ Properties\SolutionInfo.cs - - - - - - From 38986f3bfd66f3936170c67508e7252adeae5aaf Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2019 13:33:02 +1100 Subject: [PATCH 22/36] Abstracts out IStylesheet, IStylesheetProperty --- .../Models/IStylesheet.cs | 30 +++++++++++++++++ .../Models/IStylesheetProperty.cs | 11 +++++++ src/Umbraco.Abstractions/Models/Stylesheet.cs | 6 ++-- .../Models/StylesheetProperty.cs | 2 +- .../Repositories/IStylesheetRepository.cs | 4 +-- .../Implement/StylesheetRepository.cs | 33 +++++++++++-------- src/Umbraco.Core/Services/IFileService.cs | 8 ++--- .../Services/Implement/FileService.cs | 24 +++++++------- .../Repositories/StylesheetRepositoryTest.cs | 32 ++++++++++-------- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 4 +++ .../Cache/DistributedCacheBinder_Handlers.cs | 4 +-- src/Umbraco.Web/Editors/CodeFileController.cs | 6 ++-- 12 files changed, 111 insertions(+), 53 deletions(-) create mode 100644 src/Umbraco.Abstractions/Models/IStylesheet.cs create mode 100644 src/Umbraco.Abstractions/Models/IStylesheetProperty.cs diff --git a/src/Umbraco.Abstractions/Models/IStylesheet.cs b/src/Umbraco.Abstractions/Models/IStylesheet.cs new file mode 100644 index 0000000000..737118d646 --- /dev/null +++ b/src/Umbraco.Abstractions/Models/IStylesheet.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + public interface IStylesheet : IFile, IRememberBeingDirty + { + /// + /// Returns a list of umbraco back office enabled stylesheet properties + /// + /// + /// An umbraco back office enabled stylesheet property has a special prefix, for example: + /// + /// /** umb_name: MyPropertyName */ p { font-size: 1em; } + /// + IEnumerable Properties { get; } + + /// + /// Adds an Umbraco stylesheet property for use in the back office + /// + /// + void AddProperty(IStylesheetProperty property); + + /// + /// Removes an Umbraco stylesheet property + /// + /// + void RemoveProperty(string name); + } +} diff --git a/src/Umbraco.Abstractions/Models/IStylesheetProperty.cs b/src/Umbraco.Abstractions/Models/IStylesheetProperty.cs new file mode 100644 index 0000000000..c44a147d54 --- /dev/null +++ b/src/Umbraco.Abstractions/Models/IStylesheetProperty.cs @@ -0,0 +1,11 @@ +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + public interface IStylesheetProperty : IRememberBeingDirty + { + string Alias { get; set; } + string Name { get; } + string Value { get; set; } + } +} diff --git a/src/Umbraco.Abstractions/Models/Stylesheet.cs b/src/Umbraco.Abstractions/Models/Stylesheet.cs index df5786a340..48f00a1650 100644 --- a/src/Umbraco.Abstractions/Models/Stylesheet.cs +++ b/src/Umbraco.Abstractions/Models/Stylesheet.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class Stylesheet : File + public class Stylesheet : File, IStylesheet { public Stylesheet(string path) : this(path, null) @@ -120,7 +120,7 @@ namespace Umbraco.Core.Models /// /** umb_name: MyPropertyName */ p { font-size: 1em; } /// [IgnoreDataMember] - public IEnumerable Properties + public IEnumerable Properties { get { return _properties.Value; } } @@ -129,7 +129,7 @@ namespace Umbraco.Core.Models /// Adds an Umbraco stylesheet property for use in the back office /// /// - public void AddProperty(StylesheetProperty property) + public void AddProperty(IStylesheetProperty property) { if (Properties.Any(x => x.Name.InvariantEquals(property.Name))) { diff --git a/src/Umbraco.Abstractions/Models/StylesheetProperty.cs b/src/Umbraco.Abstractions/Models/StylesheetProperty.cs index 089f89deb6..bc895113bc 100644 --- a/src/Umbraco.Abstractions/Models/StylesheetProperty.cs +++ b/src/Umbraco.Abstractions/Models/StylesheetProperty.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class StylesheetProperty : BeingDirtyBase, IValueObject + public class StylesheetProperty : BeingDirtyBase, IValueObject, IStylesheetProperty { private string _alias; private string _value; diff --git a/src/Umbraco.Core/Persistence/Repositories/IStylesheetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IStylesheetRepository.cs index 1643e6e7a7..10771eb6d0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IStylesheetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IStylesheetRepository.cs @@ -3,9 +3,9 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { - public interface IStylesheetRepository : IReadRepository, IWriteRepository + public interface IStylesheetRepository : IReadRepository, IWriteRepository { - bool ValidateStylesheet(Stylesheet stylesheet); + bool ValidateStylesheet(IStylesheet stylesheet); Stream GetFileContentStream(string filepath); void SetFileContent(string filepath, Stream content); long GetFileSize(string filepath); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs index 52ff14b0dc..d5db3e2cf4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs @@ -10,15 +10,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Represents the Stylesheet Repository /// - internal class StylesheetRepository : FileRepository, IStylesheetRepository + internal class StylesheetRepository : FileRepository, IStylesheetRepository { - public StylesheetRepository(IFileSystems fileSystems) + private readonly IIOHelper _ioHelper; + + public StylesheetRepository(IFileSystems fileSystems, IIOHelper ioHelper) : base(fileSystems.StylesheetsFileSystem) - { } + { + _ioHelper = ioHelper; + } #region Overrides of FileRepository - public override Stylesheet Get(string id) + public override IStylesheet Get(string id) { // get the relative path within the filesystem // (though... id should be relative already) @@ -51,16 +55,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } - public override void Save(Stylesheet entity) + public override void Save(IStylesheet entity) { - base.Save(entity); + // TODO: Casting :/ Do we need GetFileContent below? Need to look into it later + var stylesheet = (Stylesheet)entity; + + base.Save(stylesheet); // ensure that from now on, content is lazy-loaded - if (entity.GetFileContent == null) - entity.GetFileContent = file => GetFileContent(file.OriginalPath); + if (stylesheet.GetFileContent == null) + stylesheet.GetFileContent = file => GetFileContent(file.OriginalPath); } - public override IEnumerable GetMany(params string[] ids) + public override IEnumerable GetMany(params string[] ids) { //ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries ids = ids @@ -92,14 +99,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// If null or not specified, will return the stylesheets at the root path relative to the IFileSystem /// /// - public IEnumerable GetStylesheetsAtPath(string rootPath = null) + public IEnumerable GetStylesheetsAtPath(string rootPath = null) { return FileSystem.GetFiles(rootPath ?? string.Empty, "*.css").Select(Get); } private static readonly List ValidExtensions = new List { "css" }; - public bool ValidateStylesheet(Stylesheet stylesheet) + public bool ValidateStylesheet(IStylesheet stylesheet) { // get full path string fullPath; @@ -115,8 +122,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // validate path and extension var validDir = SystemDirectories.Css; - var isValidPath = Current.IOHelper.VerifyEditPath(fullPath, validDir); - var isValidExtension = Current.IOHelper.VerifyFileExtension(stylesheet.Path, ValidExtensions); + var isValidPath = _ioHelper.VerifyEditPath(fullPath, validDir); + var isValidExtension = _ioHelper.VerifyFileExtension(stylesheet.Path, ValidExtensions); return isValidPath && isValidExtension; } diff --git a/src/Umbraco.Core/Services/IFileService.cs b/src/Umbraco.Core/Services/IFileService.cs index 5fe52559ee..eba7b98ef2 100644 --- a/src/Umbraco.Core/Services/IFileService.cs +++ b/src/Umbraco.Core/Services/IFileService.cs @@ -31,21 +31,21 @@ namespace Umbraco.Core.Services /// Gets a list of all objects /// /// An enumerable list of objects - IEnumerable GetStylesheets(params string[] names); + IEnumerable GetStylesheets(params string[] names); /// /// Gets a object by its name /// /// Name of the stylesheet incl. extension /// A object - Stylesheet GetStylesheetByName(string name); + IStylesheet GetStylesheetByName(string name); /// /// Saves a /// /// to save /// Optional id of the user saving the stylesheet - void SaveStylesheet(Stylesheet stylesheet, int userId = Constants.Security.SuperUserId); + void SaveStylesheet(IStylesheet stylesheet, int userId = Constants.Security.SuperUserId); /// /// Deletes a stylesheet by its name @@ -59,7 +59,7 @@ namespace Umbraco.Core.Services /// /// to validate /// True if Stylesheet is valid, otherwise false - bool ValidateStylesheet(Stylesheet stylesheet); + bool ValidateStylesheet(IStylesheet stylesheet); /// /// Gets a list of all objects diff --git a/src/Umbraco.Core/Services/Implement/FileService.cs b/src/Umbraco.Core/Services/Implement/FileService.cs index 2cdff9a8e4..a6615ca574 100644 --- a/src/Umbraco.Core/Services/Implement/FileService.cs +++ b/src/Umbraco.Core/Services/Implement/FileService.cs @@ -49,7 +49,7 @@ namespace Umbraco.Core.Services.Implement /// Gets a list of all objects /// /// An enumerable list of objects - public IEnumerable GetStylesheets(params string[] names) + public IEnumerable GetStylesheets(params string[] names) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { @@ -62,7 +62,7 @@ namespace Umbraco.Core.Services.Implement /// /// Name of the stylesheet incl. extension /// A object - public Stylesheet GetStylesheetByName(string name) + public IStylesheet GetStylesheetByName(string name) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { @@ -75,11 +75,11 @@ namespace Umbraco.Core.Services.Implement /// /// to save /// - public void SaveStylesheet(Stylesheet stylesheet, int userId = Constants.Security.SuperUserId) + public void SaveStylesheet(IStylesheet stylesheet, int userId = Constants.Security.SuperUserId) { using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(stylesheet); + var saveEventArgs = new SaveEventArgs(stylesheet); if (scope.Events.DispatchCancelable(SavingStylesheet, this, saveEventArgs)) { scope.Complete(); @@ -91,7 +91,7 @@ namespace Umbraco.Core.Services.Implement saveEventArgs.CanCancel = false; scope.Events.Dispatch(SavedStylesheet, this, saveEventArgs); - Audit(AuditType.Save, userId, -1, ObjectTypes.GetName(UmbracoObjectTypes.Stylesheet)); + Audit(AuditType.Save, userId, -1, UmbracoObjectTypes.Stylesheet.GetName()); scope.Complete(); } } @@ -112,7 +112,7 @@ namespace Umbraco.Core.Services.Implement return; } - var deleteEventArgs = new DeleteEventArgs(stylesheet); + var deleteEventArgs = new DeleteEventArgs(stylesheet); if (scope.Events.DispatchCancelable(DeletingStylesheet, this, deleteEventArgs)) { scope.Complete(); @@ -123,7 +123,7 @@ namespace Umbraco.Core.Services.Implement deleteEventArgs.CanCancel = false; scope.Events.Dispatch(DeletedStylesheet, this, deleteEventArgs); - Audit(AuditType.Delete, userId, -1, ObjectTypes.GetName(UmbracoObjectTypes.Stylesheet)); + Audit(AuditType.Delete, userId, -1, UmbracoObjectTypes.Stylesheet.GetName()); scope.Complete(); } } @@ -133,7 +133,7 @@ namespace Umbraco.Core.Services.Implement /// /// to validate /// True if Stylesheet is valid, otherwise false - public bool ValidateStylesheet(Stylesheet stylesheet) + public bool ValidateStylesheet(IStylesheet stylesheet) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { @@ -1116,12 +1116,12 @@ namespace Umbraco.Core.Services.Implement /// /// Occurs before Delete /// - public static event TypedEventHandler> DeletingStylesheet; + public static event TypedEventHandler> DeletingStylesheet; /// /// Occurs after Delete /// - public static event TypedEventHandler> DeletedStylesheet; + public static event TypedEventHandler> DeletedStylesheet; /// /// Occurs before Save @@ -1146,12 +1146,12 @@ namespace Umbraco.Core.Services.Implement /// /// Occurs before Save /// - public static event TypedEventHandler> SavingStylesheet; + public static event TypedEventHandler> SavingStylesheet; /// /// Occurs after Save /// - public static event TypedEventHandler> SavedStylesheet; + public static event TypedEventHandler> SavedStylesheet; /// /// Occurs before Save diff --git a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs index 6fae1d4749..2358fb257d 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs @@ -6,6 +6,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core.IO; using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -30,6 +31,11 @@ namespace Umbraco.Tests.Persistence.Repositories _fileSystem.AddFile("styles.css", stream); } + private IStylesheetRepository CreateRepository() + { + return new StylesheetRepository(_fileSystems, new IOHelper()); + } + [Test] public void Can_Instantiate_Repository() { @@ -37,7 +43,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (ScopeProvider.CreateScope()) { // Act - var repository = new StylesheetRepository(_fileSystems); + var repository = CreateRepository(); // Assert @@ -51,7 +57,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystems); + var repository = CreateRepository(); // Act var stylesheet = new Stylesheet("test-add.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; @@ -69,7 +75,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystems); + var repository = CreateRepository(); // Act var stylesheet = new Stylesheet("test-update.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; @@ -96,10 +102,10 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystems); + var repository = CreateRepository(); // Act - var stylesheet = new Stylesheet("test-update.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; + IStylesheet stylesheet = new Stylesheet("test-update.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); @@ -123,7 +129,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystems); + var repository = CreateRepository(); // Act var stylesheet = new Stylesheet("test-update.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; @@ -142,7 +148,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystems); + var repository = CreateRepository(); // Act var stylesheet = new Stylesheet("test-delete.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; @@ -163,7 +169,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystems); + var repository = CreateRepository(); // Act var stylesheet = repository.Get("styles.css"); @@ -182,7 +188,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystems); + var repository = CreateRepository(); var stylesheet = new Stylesheet("styles-v2.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); @@ -205,7 +211,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystems); + var repository = CreateRepository(); var stylesheet = new Stylesheet("styles-v2.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; repository.Save(stylesheet); @@ -228,7 +234,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystems); + var repository = CreateRepository(); // Act var exists = repository.Exists("styles.css"); @@ -245,9 +251,9 @@ namespace Umbraco.Tests.Persistence.Repositories using (ScopeProvider.CreateScope()) { - var repository = new StylesheetRepository(_fileSystems); + var repository = CreateRepository(); - var stylesheet = new Stylesheet("test-path-1.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" }; + IStylesheet 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")); diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index b9fd0f6640..5b0e486a52 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -99,6 +99,8 @@ namespace Umbraco.Tests.Testing protected ILogger Logger => Factory.GetInstance(); + protected IIOHelper IOHelper { get; private set; } + protected IProfiler Profiler => Factory.GetInstance(); protected virtual IProfilingLogger ProfilingLogger => Factory.GetInstance(); @@ -129,6 +131,7 @@ namespace Umbraco.Tests.Testing var (logger, profiler) = GetLoggers(Options.Logger); var proflogger = new ProfilingLogger(logger, profiler); + IOHelper = new IOHelper(); var appCaches = GetAppCaches(); var globalSettings = SettingsForTests.GetDefaultGlobalSettings(); var typeLoader = GetTypeLoader(appCaches.RuntimeCache, globalSettings, proflogger, Options.TypeLoader); @@ -137,6 +140,7 @@ namespace Umbraco.Tests.Testing Composition = new Composition(register, typeLoader, proflogger, ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + Composition.RegisterUnique(IOHelper); Composition.RegisterUnique(typeLoader); Composition.RegisterUnique(logger); Composition.RegisterUnique(profiler); diff --git a/src/Umbraco.Web/Cache/DistributedCacheBinder_Handlers.cs b/src/Umbraco.Web/Cache/DistributedCacheBinder_Handlers.cs index 3b2cf3e23d..c56b7a094f 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheBinder_Handlers.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheBinder_Handlers.cs @@ -380,8 +380,8 @@ namespace Umbraco.Web.Cache } // TODO: our weird events handling wants this for now - private void FileService_DeletedStylesheet(IFileService sender, DeleteEventArgs e) { } - private void FileService_SavedStylesheet(IFileService sender, SaveEventArgs e) { } + private void FileService_DeletedStylesheet(IFileService sender, DeleteEventArgs e) { } + private void FileService_SavedStylesheet(IFileService sender, SaveEventArgs e) { } #endregion diff --git a/src/Umbraco.Web/Editors/CodeFileController.cs b/src/Umbraco.Web/Editors/CodeFileController.cs index ae870f695e..c5ded5cada 100644 --- a/src/Umbraco.Web/Editors/CodeFileController.cs +++ b/src/Umbraco.Web/Editors/CodeFileController.cs @@ -190,7 +190,7 @@ namespace Umbraco.Web.Editors var stylesheet = Services.FileService.GetStylesheetByName(virtualPath); if (stylesheet != null) { - var display = Mapper.Map(stylesheet); + var display = Mapper.Map(stylesheet); display.FileType = Core.Constants.Trees.Stylesheets; display.Path = Url.GetTreePathFromFilePath(stylesheet.Path); display.Id = System.Web.HttpUtility.UrlEncode(stylesheet.Path); @@ -511,7 +511,7 @@ namespace Umbraco.Web.Editors name => new Script(name)); } - private Stylesheet CreateOrUpdateStylesheet(CodeFileDisplay display) + private IStylesheet CreateOrUpdateStylesheet(CodeFileDisplay display) { return CreateOrUpdateFile(display, ".css", Current.FileSystems.StylesheetsFileSystem, name => Services.FileService.GetStylesheetByName(name), @@ -521,7 +521,7 @@ namespace Umbraco.Web.Editors } private T CreateOrUpdateFile(CodeFileDisplay display, string extension, IFileSystem fileSystem, - Func getFileByName, Action saveFile, Func createFile) where T : Core.Models.File + Func getFileByName, Action saveFile, Func createFile) where T : Core.Models.IFile { //must always end with the correct extension display.Name = EnsureCorrectFileExtension(display.Name, extension); From 545a427d1165da1b49b22b7134a14140719ffa13 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Nov 2019 13:49:56 +1100 Subject: [PATCH 23/36] abstracts out IScript, stops casting stuff --- src/Umbraco.Abstractions/Models/IScript.cs | 7 ++ src/Umbraco.Abstractions/Models/Script.cs | 7 +- .../Repositories/IScriptRepository.cs | 7 +- .../Repositories/IStylesheetRepository.cs | 3 + .../Implement/ScriptRepository.cs | 29 +++--- .../Implement/StylesheetRepository.cs | 2 +- src/Umbraco.Core/Services/IFileService.cs | 44 ++++----- .../Services/Implement/FileService.cs | 90 ++++++------------- .../Repositories/ScriptRepositoryTest.cs | 30 ++++--- src/Umbraco.Web/Editors/CodeFileController.cs | 4 +- .../Models/Mapping/CodeFileMapDefinition.cs | 16 ++-- 11 files changed, 110 insertions(+), 129 deletions(-) create mode 100644 src/Umbraco.Abstractions/Models/IScript.cs diff --git a/src/Umbraco.Abstractions/Models/IScript.cs b/src/Umbraco.Abstractions/Models/IScript.cs new file mode 100644 index 0000000000..9fdc321107 --- /dev/null +++ b/src/Umbraco.Abstractions/Models/IScript.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Models +{ + public interface IScript : IFile + { + + } +} diff --git a/src/Umbraco.Abstractions/Models/Script.cs b/src/Umbraco.Abstractions/Models/Script.cs index b6e49f72ee..be96c04ddd 100644 --- a/src/Umbraco.Abstractions/Models/Script.cs +++ b/src/Umbraco.Abstractions/Models/Script.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class Script : File + public class Script : File, IScript { public Script(string path) : this(path, (Func) null) @@ -25,9 +25,6 @@ namespace Umbraco.Core.Models /// /// Overrides the default Entity identity check. /// - public override bool HasIdentity - { - get { return string.IsNullOrEmpty(Path) == false; } - } + public override bool HasIdentity => string.IsNullOrEmpty(Path) == false; } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IScriptRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IScriptRepository.cs index ea88cb7618..70226777b5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IScriptRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IScriptRepository.cs @@ -3,11 +3,14 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { - public interface IScriptRepository : IReadRepository, IWriteRepository