From 41c2d01078602852f97337a5af360b5404fda0a2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 9 Mar 2020 11:03:42 +1100 Subject: [PATCH] initial POC code --- src/Umbraco.Core/Composing/TypeFinder.cs | 627 +++++++++++++++--- src/Umbraco.Core/Composing/TypeLoader.cs | 44 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Runtime/CoreRuntime.cs | 3 +- .../Cache/DeepCloneAppCacheTests.cs | 2 +- .../Cache/HttpRequestAppCacheTests.cs | 2 +- .../Cache/ObjectAppCacheTests.cs | 2 +- .../Components/ComponentTests.cs | 4 +- .../Composing/ComposingTestBase.cs | 2 +- .../Composing/CompositionTests.cs | 2 +- .../Composing/TypeFinderTests.cs | 7 +- .../Composing/TypeLoaderTests.cs | 2 +- src/Umbraco.Tests/Macros/MacroTests.cs | 2 +- src/Umbraco.Tests/Models/ContentTests.cs | 2 +- .../Published/PropertyCacheLevelTests.cs | 2 +- .../PublishedContent/NuCacheChildrenTests.cs | 2 +- .../PublishedContent/NuCacheTests.cs | 2 +- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 4 +- .../Scoping/ScopedNuCacheTests.cs | 2 +- .../ContentTypeServiceVariantsTests.cs | 2 +- .../TestHelpers/BaseUsingSqlCeSyntax.cs | 2 +- .../Stubs/TestControllerFactory.cs | 2 +- src/Umbraco.Tests/TestHelpers/TestObjects.cs | 2 +- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 2 +- src/Umbraco.Tests/Web/UmbracoHelperTests.cs | 2 +- .../Composing/BuildManagerTypeFinder.cs | 5 +- src/Umbraco.Web/Runtime/WebRuntime.cs | 2 +- 27 files changed, 591 insertions(+), 142 deletions(-) diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 9d88153b0a..2f2d81a72c 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -1,15 +1,16 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Configuration; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; using System.Security; using System.Text; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; using Umbraco.Core.Logging; namespace Umbraco.Core.Composing @@ -18,91 +19,26 @@ namespace Umbraco.Core.Composing public class TypeFinder : ITypeFinder { private readonly ILogger _logger; + private readonly IAssemblyProvider _assemblyProvider; - public TypeFinder(ILogger logger, ITypeFinderConfig typeFinderConfig = null) + //public TypeFinder(ILogger logger, ITypeFinderConfig typeFinderConfig = null) + // : this(logger, new DefaultUmbracoAssemblyProvider(Assembly.GetEntryAssembly()?.GetName()?.Name), typeFinderConfig) + //{ + //} + + public TypeFinder(ILogger logger, IAssemblyProvider assemblyProvider, 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 - { - //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) - { - 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); - } - } - catch (InvalidOperationException e) - { - if (e.InnerException is SecurityException == false) - throw; - } - - return assemblies; - }); + _assemblyProvider = assemblyProvider; + _assembliesAcceptingLoadExceptions = typeFinderConfig?.AssembliesAcceptingLoadExceptions.Where(x => !x.IsNullOrWhiteSpace()).ToArray() ?? Array.Empty(); } - //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 static readonly ConcurrentDictionary TypeNamesCache= new ConcurrentDictionary(); - private string _rootDir = ""; + private static readonly ConcurrentDictionary TypeNamesCache= new ConcurrentDictionary(); private readonly string[] _assembliesAcceptingLoadExceptions; - // FIXME - this is only an interim change, once the IIOHelper stuff is merged we should use IIOHelper here - private string GetRootDirectorySafe() - { - if (string.IsNullOrEmpty(_rootDir) == false) - { - 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) @@ -119,22 +55,8 @@ namespace Umbraco.Core.Composing }); } - /// - /// 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 - /// - private IEnumerable GetAllAssemblies() - { - return _allAssemblies.Value; - } + + private IEnumerable GetAllAssemblies() => _assemblyProvider.Assemblies; /// public IEnumerable AssembliesToScan @@ -522,6 +444,525 @@ namespace Umbraco.Core.Composing #endregion + } + /// + /// lazily load a reference to all local assemblies and gac 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 + /// + public class BruteForceAssemblyProvider : IAssemblyProvider + { + public BruteForceAssemblyProvider() + { + _allAssemblies = new Lazy>(() => + { + HashSet assemblies = null; + 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(); + 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); + } + } + catch (InvalidOperationException e) + { + if (e.InnerException is SecurityException == false) + throw; + } + + return assemblies; + }); + } + + private readonly Lazy> _allAssemblies; + private string _rootDir = string.Empty; + + public IEnumerable Assemblies => _allAssemblies.Value; + + // FIXME - this is only an interim change, once the IIOHelper stuff is merged we should use IIOHelper here + private string GetRootDirectorySafe() + { + if (string.IsNullOrEmpty(_rootDir) == false) + { + 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; + } + } + + /// + /// Provides a list of loaded assemblies that can be scanned + /// + public interface IAssemblyProvider + { + IEnumerable Assemblies { get; } + } + + /// + /// Discovers assemblies that are part of the Umbraco application using the DependencyContext. + /// + /// + /// Happily "borrowed" from aspnet: https://github.com/aspnet/Mvc/blob/230a13d0e13e4c7e192bc6623762bfd4cde726ef/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultAssemblyPartDiscoveryProvider.cs + /// + /// TODO: Happily borrow their unit tests too + /// + public class DefaultUmbracoAssemblyProvider : IAssemblyProvider + { + private readonly string _entryPointAssemblyName; + + public DefaultUmbracoAssemblyProvider(string entryPointAssemblyName) + { + if (string.IsNullOrWhiteSpace(entryPointAssemblyName)) + throw new ArgumentException($"{entryPointAssemblyName} cannot be null or empty", nameof(entryPointAssemblyName)); + + _entryPointAssemblyName = entryPointAssemblyName; + } + + public IEnumerable Assemblies + { + get + { + var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssemblyName }, new[] { "Umbraco.Core" }); + foreach(var found in finder.Find()) + { + yield return Assembly.Load(found); + } + } + } + + //public IEnumerable Assemblies => DiscoverAssemblyParts(_entryPointAssemblyName); + + //internal static HashSet ReferenceAssemblies { get; } = new HashSet(StringComparer.OrdinalIgnoreCase) + //{ + // "Umbraco.Core", + // "Umbraco.Web" + //}; + + //internal static IEnumerable DiscoverAssemblyParts(string entryPointAssemblyName) + //{ + // var entryAssembly = Assembly.Load(new AssemblyName(entryPointAssemblyName)); + // var context = DependencyContext.Load(entryAssembly); + + // var candidates = GetCandidateAssemblies(entryAssembly, context); + + // return candidates; + //} + + //internal static IEnumerable GetCandidateAssemblies(Assembly entryAssembly, DependencyContext dependencyContext) + //{ + // if (dependencyContext == null) + // { + // // Use the entry assembly as the sole candidate. + // return new[] { entryAssembly }; + // } + + // //includeRefLibs == true - so that Umbraco.Core is also returned! + // return GetCandidateLibraries(dependencyContext, includeRefLibs: true) + // .SelectMany(library => library.GetDefaultAssemblyNames(dependencyContext)) + // .Select(Assembly.Load); + //} + + ///// + ///// Returns a list of libraries that references the assemblies in . + ///// + ///// + ///// + ///// True to also include libs in the ReferenceAssemblies list + ///// + ///// + //internal static IEnumerable GetCandidateLibraries(DependencyContext dependencyContext, bool includeRefLibs) + //{ + // if (ReferenceAssemblies == null) + // { + // return Enumerable.Empty(); + // } + + // var candidatesResolver = new CandidateResolver(dependencyContext.RuntimeLibraries, ReferenceAssemblies, includeRefLibs); + // return candidatesResolver.GetCandidates(); + //} + + //private class CandidateResolver + //{ + // private readonly bool _includeRefLibs; + // private readonly IDictionary _dependencies; + + // /// + // /// Constructor + // /// + // /// + // /// + // /// + // /// True to also include libs in the ReferenceAssemblies list + // /// + // public CandidateResolver(IEnumerable dependencies, ISet referenceAssemblies, bool includeRefLibs) + // { + // _includeRefLibs = includeRefLibs; + + // _dependencies = dependencies + // .ToDictionary(d => d.Name, d => CreateDependency(d, referenceAssemblies), StringComparer.OrdinalIgnoreCase); + // } + + // /// + // /// Create a Dependency + // /// + // /// + // /// + // /// + // private static Dependency CreateDependency(RuntimeLibrary library, ISet referenceAssemblies) + // { + // var classification = DependencyClassification.Unknown; + // if (referenceAssemblies.Contains(library.Name)) + // { + // classification = DependencyClassification.UmbracoReference; + // } + + // return new Dependency(library, classification); + // } + + // private DependencyClassification ComputeClassification(string dependency) + // { + // Debug.Assert(_dependencies.ContainsKey(dependency)); + + // var candidateEntry = _dependencies[dependency]; + // if (candidateEntry.Classification != DependencyClassification.Unknown) + // { + // return candidateEntry.Classification; + // } + // else + // { + // var classification = DependencyClassification.NotCandidate; + // foreach (var candidateDependency in candidateEntry.Library.Dependencies) + // { + // var dependencyClassification = ComputeClassification(candidateDependency.Name); + // if (dependencyClassification == DependencyClassification.Candidate || + // dependencyClassification == DependencyClassification.UmbracoReference) + // { + // classification = DependencyClassification.Candidate; + // break; + // } + // } + + // candidateEntry.Classification = classification; + + // return classification; + // } + // } + + // public IEnumerable GetCandidates() + // { + // foreach (var dependency in _dependencies) + // { + // var classification = ComputeClassification(dependency.Key); + // if (classification == DependencyClassification.Candidate || + // //if the flag is set, also ensure to include any UmbracoReference classifications + // (_includeRefLibs && classification == DependencyClassification.UmbracoReference)) + // { + // yield return dependency.Value.Library; + // } + // } + // } + + // private class Dependency + // { + // public Dependency(RuntimeLibrary library, DependencyClassification classification) + // { + // Library = library; + // Classification = classification; + // } + + // public RuntimeLibrary Library { get; } + + // public DependencyClassification Classification { get; set; } + + // public override string ToString() + // { + // return $"Library: {Library.Name}, Classification: {Classification}"; + // } + // } + + // private enum DependencyClassification + // { + // Unknown = 0, + // Candidate = 1, + // NotCandidate = 2, + // UmbracoReference = 3 + // } + //} + } + + /// + /// Resolves assemblies that reference one of the specified "targetAssemblies" either directly or transitively. + /// + public class ReferenceResolver + { + private readonly HashSet _mvcAssemblies; + private readonly IReadOnlyList _assemblyItems; + private readonly Dictionary _classifications; + + public ReferenceResolver(IReadOnlyList targetAssemblies, IReadOnlyList assemblyItems) + { + _mvcAssemblies = new HashSet(targetAssemblies, StringComparer.Ordinal); + _assemblyItems = assemblyItems; + _classifications = new Dictionary(); + + Lookup = new Dictionary(StringComparer.Ordinal); + foreach (var item in assemblyItems) + { + Lookup[item.AssemblyName] = item; + } + } + + protected Dictionary Lookup { get; } + + public IReadOnlyList ResolveAssemblies() + { + var applicationParts = new List(); + + foreach (var item in _assemblyItems) + { + var classification = Resolve(item); + if (classification == Classification.ReferencesMvc) + { + applicationParts.Add(item.AssemblyName); + } + } + + return applicationParts; + } + + private Classification Resolve(AssemblyItem assemblyItem) + { + if (_classifications.TryGetValue(assemblyItem, out var classification)) + { + return classification; + } + + // Initialize the dictionary with a value to short-circuit recursive references. + classification = Classification.Unknown; + _classifications[assemblyItem] = classification; + + if (assemblyItem.Path == null) + { + // We encountered a dependency that isn't part of this assembly's dependency set. We'll see if it happens to be an MVC assembly + // since that's the only useful determination we can make given the assembly name. + classification = _mvcAssemblies.Contains(assemblyItem.AssemblyName) ? + Classification.IsMvc : + Classification.DoesNotReferenceMvc; + } + else if (assemblyItem.IsFrameworkReference) + { + // We do not allow transitive references to MVC via a framework reference to count. + // e.g. depending on Microsoft.AspNetCore.SomeThingNewThatDependsOnMvc would not result in an assembly being treated as + // referencing MVC. + classification = _mvcAssemblies.Contains(assemblyItem.AssemblyName) ? + Classification.IsMvc : + Classification.DoesNotReferenceMvc; + } + else if (_mvcAssemblies.Contains(assemblyItem.AssemblyName)) + { + classification = Classification.IsMvc; + } + else + { + classification = Classification.DoesNotReferenceMvc; + foreach (var reference in GetReferences(assemblyItem.Path)) + { + var referenceClassification = Resolve(reference); + + if (referenceClassification == Classification.IsMvc || referenceClassification == Classification.ReferencesMvc) + { + classification = Classification.ReferencesMvc; + break; + } + } + } + + Debug.Assert(classification != Classification.Unknown); + _classifications[assemblyItem] = classification; + return classification; + } + + protected virtual IReadOnlyList GetReferences(string file) + { + try + { + if (!File.Exists(file)) + { + throw new ReferenceAssemblyNotFoundException(file); + } + + using var peReader = new PEReader(File.OpenRead(file)); + if (!peReader.HasMetadata) + { + return Array.Empty(); // not a managed assembly + } + + var metadataReader = peReader.GetMetadataReader(); + + var references = new List(); + foreach (var handle in metadataReader.AssemblyReferences) + { + var reference = metadataReader.GetAssemblyReference(handle); + var referenceName = metadataReader.GetString(reference.Name); + + if (!Lookup.TryGetValue(referenceName, out var assemblyItem)) + { + // A dependency references an item that isn't referenced by this project. + // We'll construct an item for so that we can calculate the classification based on it's name. + assemblyItem = new AssemblyItem + { + AssemblyName = referenceName, + }; + + Lookup[referenceName] = assemblyItem; + } + + references.Add(assemblyItem); + } + + return references; + } + catch (BadImageFormatException) + { + // not a PE file, or invalid metadata + } + + return Array.Empty(); // not a managed assembly + } + + protected enum Classification + { + Unknown, + DoesNotReferenceMvc, + ReferencesMvc, + IsMvc, + } + } + + public class AssemblyItem + { + public string Path { get; set; } + + public bool IsFrameworkReference { get; set; } + + public string AssemblyName { get; set; } + } + + internal class ReferenceAssemblyNotFoundException : Exception + { + public ReferenceAssemblyNotFoundException(string fileName) + { + FileName = fileName; + } + + public string FileName { get; } + } + + // borrowed from here https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/FindAssembliesWithReferencesTo.cs + public class FindAssembliesWithReferencesTo + { + private readonly string[] _referenceAssemblies; + private readonly string[] _targetAssemblyNames; + + public FindAssembliesWithReferencesTo(string[] referenceAssemblies, string[] targetAssemblyNames) + { + _referenceAssemblies = referenceAssemblies; + _targetAssemblyNames = targetAssemblyNames; + } + + public IEnumerable Find() + { + var referenceItems = new List(); + foreach (var item in _referenceAssemblies) + { + //var assemblyName = new AssemblyName(item).Name; + var assembly = Assembly.Load(item); + referenceItems.Add(new AssemblyItem + { + AssemblyName = assembly.GetName().Name, //assemblyName, + IsFrameworkReference = false, + Path = GetAssemblyLocation(assembly) + }); + } + + var provider = new ReferenceResolver(_targetAssemblyNames, referenceItems); + try + { + var assemblyNames = provider.ResolveAssemblies(); + return assemblyNames.ToArray(); + } + catch (ReferenceAssemblyNotFoundException ex) + { + throw; + //// Print a warning and return. We cannot produce a correct document at this point. + //var warning = "Reference assembly {0} could not be found. This is typically caused by build errors in referenced projects."; + //Log.LogWarning(null, "RAZORSDK1007", null, null, 0, 0, 0, 0, warning, ex.FileName); + //return true; + } + catch (Exception ex) + { + throw; + //Log.LogErrorFromException(ex); + } + } + + internal static string GetAssemblyLocation(Assembly assembly) + { + if (Uri.TryCreate(assembly.CodeBase, UriKind.Absolute, out var result) && + result.IsFile && string.IsNullOrWhiteSpace(result.Fragment)) + { + return result.LocalPath; + } + + return assembly.Location; + } } } diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index 76d00c472d..4d8b5c984c 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -516,29 +516,29 @@ namespace Umbraco.Core.Composing #region Get Assembly Attributes - /// - /// Gets the assembly attributes of the specified type . - /// - /// The attribute type. - /// - /// The assembly attributes of the specified type . - /// - public IEnumerable GetAssemblyAttributes() - where T : Attribute - { - return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); - } + ///// + ///// Gets the assembly attributes of the specified type . + ///// + ///// The attribute type. + ///// + ///// The assembly attributes of the specified type . + ///// + //public IEnumerable GetAssemblyAttributes() + // where T : Attribute + //{ + // return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); + //} - /// - /// Gets all the assembly attributes. - /// - /// - /// All assembly attributes. - /// - public IEnumerable GetAssemblyAttributes() - { - return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); - } + ///// + ///// Gets all the assembly attributes. + ///// + ///// + ///// All assembly attributes. + ///// + //public IEnumerable GetAssemblyAttributes() + //{ + // return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); + //} /// /// Gets the assembly attributes of the specified . diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7a15e7fbed..49d5090070 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 8e4401495d..2422e6018f 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Reflection; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; @@ -369,7 +370,7 @@ namespace Umbraco.Core.Runtime /// /// protected virtual ITypeFinder GetTypeFinder() - => new TypeFinder(Logger); + => new TypeFinder(Logger, new DefaultUmbracoAssemblyProvider(Assembly.GetEntryAssembly()?.GetName()?.Name)); /// diff --git a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs index 3a504ad4e2..bba7ea0061 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); _memberCache = new ObjectCacheAppCache(typeFinder); _provider = new DeepCloneAppCache(_memberCache); diff --git a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs index b9c948c1de..d2d3b795d6 100644 --- a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); _ctx = new FakeHttpContextFactory("http://localhost/test"); _appCache = new HttpRequestAppCache(() => _ctx.HttpContext.Items, typeFinder); } diff --git a/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs b/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs index 772e92cabe..5d6808f678 100644 --- a/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs @@ -21,7 +21,7 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); _provider = new ObjectCacheAppCache(typeFinder); } diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index bef72e5fb7..9d44aa562e 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -32,7 +32,7 @@ namespace Umbraco.Tests.Components var mock = new Mock(); var logger = Mock.Of(); - var typeFinder = new TypeFinder(logger); + var typeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(typeof(ComponentTests).Assembly.GetName().Name)); var f = new UmbracoDatabaseFactory(logger, new Lazy(() => new MapperCollection(Enumerable.Empty())), TestHelper.GetConfigs(), TestHelper.DbProviderFactoryCreator); var fs = new FileSystems(mock.Object, logger, TestHelper.IOHelper, SettingsForTests.GenerateMockGlobalSettings()); var coreDebug = Mock.Of(); @@ -371,7 +371,7 @@ namespace Umbraco.Tests.Components public void AllComposers() { var ioHelper = TestHelper.IOHelper; - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var typeLoader = new TypeLoader(ioHelper, typeFinder, AppCaches.Disabled.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), Mock.Of()); var register = MockRegister(); diff --git a/src/Umbraco.Tests/Composing/ComposingTestBase.cs b/src/Umbraco.Tests/Composing/ComposingTestBase.cs index ac7dd8be2a..c4d048cd7b 100644 --- a/src/Umbraco.Tests/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests/Composing/ComposingTestBase.cs @@ -22,7 +22,7 @@ namespace Umbraco.Tests.Composing { ProfilingLogger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var ioHelper = TestHelper.IOHelper; TypeLoader = new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), ProfilingLogger, false, AssembliesToScan); } diff --git a/src/Umbraco.Tests/Composing/CompositionTests.cs b/src/Umbraco.Tests/Composing/CompositionTests.cs index 4dfaf6871d..a720fe045f 100644 --- a/src/Umbraco.Tests/Composing/CompositionTests.cs +++ b/src/Umbraco.Tests/Composing/CompositionTests.cs @@ -38,7 +38,7 @@ namespace Umbraco.Tests.Composing .Returns(() => factoryFactory?.Invoke(mockedFactory)); var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var ioHelper = TestHelper.IOHelper; 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(), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache); diff --git a/src/Umbraco.Tests/Composing/TypeFinderTests.cs b/src/Umbraco.Tests/Composing/TypeFinderTests.cs index 5fe4c241d6..969be8c0fd 100644 --- a/src/Umbraco.Tests/Composing/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeFinderTests.cs @@ -57,7 +57,7 @@ namespace Umbraco.Tests.Composing [Test] public void Find_Class_Of_Type_With_Attribute() { - var typeFinder = new TypeFinder(GetTestProfilingLogger()); + var typeFinder = new TypeFinder(GetTestProfilingLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var typesFound = typeFinder.FindClassesOfTypeWithAttribute(_assemblies); Assert.AreEqual(2, typesFound.Count()); } @@ -65,12 +65,15 @@ namespace Umbraco.Tests.Composing [Test] public void Find_Classes_With_Attribute() { - var typeFinder = new TypeFinder(GetTestProfilingLogger()); + var typeFinder = new TypeFinder(GetTestProfilingLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); 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 }); Assert.AreEqual(22, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] + + typesFound = typeFinder.FindClassesWithAttribute(); + Assert.AreEqual(22, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] } private static IProfilingLogger GetTestProfilingLogger() diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 6658c689e1..2d6f1b85e8 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.Composing public void Initialize() { // this ensures it's reset - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); _typeLoader = new TypeLoader(TestHelper.IOHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(TestHelper.IOHelper.MapPath("~/App_Data/TEMP")), new ProfilingLogger(Mock.Of(), Mock.Of()), false, diff --git a/src/Umbraco.Tests/Macros/MacroTests.cs b/src/Umbraco.Tests/Macros/MacroTests.cs index 66adfc4a97..3cc77b6732 100644 --- a/src/Umbraco.Tests/Macros/MacroTests.cs +++ b/src/Umbraco.Tests/Macros/MacroTests.cs @@ -17,7 +17,7 @@ namespace Umbraco.Tests.Macros [SetUp] public void Setup() { - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); //we DO want cache enabled for these tests var cacheHelper = new AppCaches( new ObjectCacheAppCache(typeFinder), diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 7dec69e3be..9a7e25944f 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -269,7 +269,7 @@ namespace Umbraco.Tests.Models content.UpdateDate = DateTime.Now; content.WriterId = 23; - var runtimeCache = new ObjectCacheAppCache(new TypeFinder(Mock.Of())); + var runtimeCache = new ObjectCacheAppCache(new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name))); runtimeCache.Insert(content.Id.ToString(CultureInfo.InvariantCulture), () => content); var proflog = GetTestProfilingLogger(); diff --git a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs index d3e6dae26b..329626a984 100644 --- a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs +++ b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs @@ -127,7 +127,7 @@ namespace Umbraco.Tests.Published var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var elementsCache = new FastDictionaryAppCache(typeFinder); var snapshotCache = new FastDictionaryAppCache(typeFinder); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index de12fcf7aa..d868e8bc2e 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -143,7 +143,7 @@ namespace Umbraco.Tests.PublishedContent // create a data source for NuCache _source = new TestDataSource(kits); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var settings = Mock.Of(); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index 08da25ba9a..cf2be84f24 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -184,7 +184,7 @@ namespace Umbraco.Tests.PublishedContent // create a variation accessor _variationAccesor = new TestVariationContextAccessor(); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var settings = Mock.Of(); // at last, create the complete NuCache snapshot service! diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 6811f9f8de..b97aae2e7e 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -63,7 +63,7 @@ namespace Umbraco.Tests.Runtimes var profilingLogger = new ProfilingLogger(logger, profiler); var appCaches = AppCaches.Disabled; var databaseFactory = new UmbracoDatabaseFactory(logger, new Lazy(() => factory.GetInstance()), TestHelper.GetConfigs(), TestHelper.DbProviderFactoryCreator); - var typeFinder = new TypeFinder(logger); + var typeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var ioHelper = TestHelper.IOHelper; var hostingEnvironment = Mock.Of(); var typeLoader = new TypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger); @@ -256,7 +256,7 @@ namespace Umbraco.Tests.Runtimes var profilingLogger = new ProfilingLogger(logger, profiler); var appCaches = AppCaches.Disabled; var databaseFactory = Mock.Of(); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var ioHelper = TestHelper.IOHelper; var typeLoader = new TypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger); var runtimeState = Mock.Of(); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 783beafb2e..5cc10d735d 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -85,7 +85,7 @@ namespace Umbraco.Tests.Scoping var memberRepository = Mock.Of(); var hostingEnvironment = TestHelper.GetHostingEnvironment(); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var settings = Mock.Of(); return new PublishedSnapshotService( diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 1b6b632a10..a2855aa1bd 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -58,7 +58,7 @@ namespace Umbraco.Tests.Services var memberRepository = Mock.Of(); var hostingEnvironment = Mock.Of(); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var settings = Mock.Of(); return new PublishedSnapshotService( diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index 2bd36947c6..0f54725246 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -38,7 +38,7 @@ namespace Umbraco.Tests.TestHelpers var ioHelper = TestHelper.IOHelper; var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var typeLoader = new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), logger, diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs index c79a0f5c47..773ef85b1b 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs @@ -40,7 +40,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs { if (_factory != null) return _factory(requestContext); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var types = typeFinder.FindClassesOfType(new[] { Assembly.GetExecutingAssembly() }); var controllerTypes = types.Where(x => x.Name.Equals(controllerName + "Controller", StringComparison.InvariantCultureIgnoreCase)); diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index f237fb0fc7..32b891f2eb 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -247,7 +247,7 @@ namespace Umbraco.Tests.TestHelpers databaseFactory = new UmbracoDatabaseFactory(Constants.System.UmbracoConnectionName, logger, new Lazy(() => mappers), TestHelper.GetConfigs(), TestHelper.DbProviderFactoryCreator); } - typeFinder = typeFinder ?? new TypeFinder(logger); + typeFinder = typeFinder ?? new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); fileSystems = fileSystems ?? new FileSystems(Current.Factory, logger, TestHelper.IOHelper, SettingsForTests.GenerateMockGlobalSettings()); var coreDebug = Current.Configs.CoreDebug(); var mediaFileSystem = Mock.Of(); diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index de0db554f3..f064e7ffa9 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -171,7 +171,7 @@ namespace Umbraco.Tests.Testing var proflogger = new ProfilingLogger(logger, profiler); IOHelper = TestHelper.IOHelper; - TypeFinder = new TypeFinder(logger); + TypeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var appCaches = GetAppCaches(); var globalSettings = SettingsForTests.GetDefaultGlobalSettings(); var settings = SettingsForTests.GetDefaultUmbracoSettings(); diff --git a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs index b479961896..e4ae4c9a58 100644 --- a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.Web { // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var container = new Mock(); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var ioHelper = TestHelper.IOHelper; container .Setup(x => x.GetInstance(typeof(TypeLoader))) diff --git a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs index 994e8c26c4..bd583bf29d 100644 --- a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs +++ b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs @@ -27,7 +27,8 @@ namespace Umbraco.Web.Composing IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, ILogger logger, - ITypeFinderConfig typeFinderConfig = null) : base(logger, typeFinderConfig) + IAssemblyProvider assemblyProvider, + ITypeFinderConfig typeFinderConfig = null) : base(logger, assemblyProvider, typeFinderConfig) { if (ioHelper == null) throw new ArgumentNullException(nameof(ioHelper)); if (hostingEnvironment == null) throw new ArgumentNullException(nameof(hostingEnvironment)); @@ -60,6 +61,8 @@ namespace Umbraco.Web.Composing logger.Error(typeof(TypeFinder), ex, "Could not load assembly App_Code"); } } + + return assemblies; } } catch (InvalidOperationException e) diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index 582f35db70..5fab9ff9d4 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -92,7 +92,7 @@ namespace Umbraco.Web.Runtime #region Getters - protected override ITypeFinder GetTypeFinder() => _typeFinder ?? (_typeFinder = new BuildManagerTypeFinder(IOHelper, HostingEnvironment, Logger, new BuildManagerTypeFinder.TypeFinderConfig(new TypeFinderSettings()))); + //protected override ITypeFinder GetTypeFinder() => _typeFinder ?? (_typeFinder = new BuildManagerTypeFinder(IOHelper, HostingEnvironment, Logger, new BuildManagerTypeFinder.TypeFinderConfig(new TypeFinderSettings()))); protected override AppCaches GetAppCaches() => new AppCaches( // we need to have the dep clone runtime cache provider to ensure