From 9dcc6b285fa0e474998666228f78c7cf02beb476 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 25 Aug 2016 15:09:51 +0200 Subject: [PATCH] Resvolution - Booting --- src/Umbraco.Core/ApplicationContext.cs | 6 - src/Umbraco.Core/Components/BootLoader.cs | 229 ++++ .../Components/DisableComponentAttribute.cs | 18 + .../Components/EnableComponentAttribute.cs | 18 + .../Components/IUmbracoComponent.cs | 11 + .../Components/IUmbracoCoreComponent.cs | 5 + .../Components/IUmbracoUserComponent.cs | 5 + .../Components/RequireComponentAttribute.cs | 15 + .../Components/UmbracoComponentBase.cs | 13 + .../Components/UmbracoCoreComponent.cs | 5 + .../{CoreBootManager.cs => CoreRuntime.cs} | 957 +++++++------- .../DependencyInjection/Current.cs | 17 +- .../LightInjectExtensions.cs | 17 +- .../OrderedCollectionBuilderBase.cs | 21 + src/Umbraco.Core/IBootManager.cs | 31 - src/Umbraco.Core/IRuntime.cs | 62 + .../ApplicationEventsResolver.cs | 172 --- .../ContainerSingleObjectResolver.cs | 131 -- .../ObjectResolution/ObjectLifetimeScope.cs | 23 - .../ObjectResolution/Resolution.cs | 6 +- .../ObjectResolution/ResolverBase.cs | 131 -- .../ObjectResolution/ResolverCollection.cs | 69 - .../SingleObjectResolverBase.cs | 130 -- src/Umbraco.Core/Plugins/PluginManager.cs | 4 +- src/Umbraco.Core/TopologicalSorter.cs | 64 +- src/Umbraco.Core/Umbraco.Core.csproj | 19 +- src/Umbraco.Core/UmbracoApplicationBase.cs | 320 ++--- .../Components/ComponentTests.cs | 153 +++ .../ActionCollectionTests.cs | 9 +- .../CollectionBuildersTests.cs | 2 +- .../LazyCollectionBuilderTests.cs | 2 +- .../PackageActionCollectionTests.cs} | 176 ++- .../ResolverBaseTest.cs | 20 +- .../DependencyInjection/TempTests.cs | 7 +- .../XsltExtensionsResolverTests.cs | 105 +- .../Resolvers/ResolutionTests.cs | 264 ---- .../Resolvers/SingleResolverTests.cs | 131 -- .../Routing/RenderRouteHandlerTests.cs | 2 +- .../CoreRuntimeTests.cs} | 390 +++--- .../WebRuntimeTests.cs} | 12 +- .../TestHelpers/BaseUmbracoApplicationTest.cs | 3 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 19 +- src/Umbraco.Web/Current.cs | 11 +- src/Umbraco.Web/ManifestWatcherComponent.cs | 44 + .../Strategies/NotificationsHandler.cs | 1 - src/Umbraco.Web/Umbraco.Web.csproj | 3 +- src/Umbraco.Web/UmbracoApplication.cs | 35 +- .../{WebBootManager.cs => WebRuntime.cs} | 1147 ++++++++--------- 48 files changed, 2282 insertions(+), 2753 deletions(-) create mode 100644 src/Umbraco.Core/Components/BootLoader.cs create mode 100644 src/Umbraco.Core/Components/DisableComponentAttribute.cs create mode 100644 src/Umbraco.Core/Components/EnableComponentAttribute.cs create mode 100644 src/Umbraco.Core/Components/IUmbracoComponent.cs create mode 100644 src/Umbraco.Core/Components/IUmbracoCoreComponent.cs create mode 100644 src/Umbraco.Core/Components/IUmbracoUserComponent.cs create mode 100644 src/Umbraco.Core/Components/RequireComponentAttribute.cs create mode 100644 src/Umbraco.Core/Components/UmbracoComponentBase.cs create mode 100644 src/Umbraco.Core/Components/UmbracoCoreComponent.cs rename src/Umbraco.Core/{CoreBootManager.cs => CoreRuntime.cs} (52%) delete mode 100644 src/Umbraco.Core/IBootManager.cs create mode 100644 src/Umbraco.Core/IRuntime.cs delete mode 100644 src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs delete mode 100644 src/Umbraco.Core/ObjectResolution/ContainerSingleObjectResolver.cs delete mode 100644 src/Umbraco.Core/ObjectResolution/ObjectLifetimeScope.cs delete mode 100644 src/Umbraco.Core/ObjectResolution/ResolverBase.cs delete mode 100644 src/Umbraco.Core/ObjectResolution/ResolverCollection.cs delete mode 100644 src/Umbraco.Core/ObjectResolution/SingleObjectResolverBase.cs create mode 100644 src/Umbraco.Tests/Components/ComponentTests.cs rename src/Umbraco.Tests/{Resolvers => DependencyInjection}/ActionCollectionTests.cs (95%) rename src/Umbraco.Tests/{Resolvers => DependencyInjection}/CollectionBuildersTests.cs (99%) rename src/Umbraco.Tests/{Resolvers => DependencyInjection}/LazyCollectionBuilderTests.cs (99%) rename src/Umbraco.Tests/{Resolvers/PackageActionsResolverTests.cs => DependencyInjection/PackageActionCollectionTests.cs} (89%) rename src/Umbraco.Tests/{Resolvers => DependencyInjection}/ResolverBaseTest.cs (73%) rename src/Umbraco.Tests/{Resolvers => DependencyInjection}/XsltExtensionsResolverTests.cs (83%) delete mode 100644 src/Umbraco.Tests/Resolvers/ResolutionTests.cs delete mode 100644 src/Umbraco.Tests/Resolvers/SingleResolverTests.cs rename src/Umbraco.Tests/{BootManagers/CoreBootManagerTests.cs => Runtimes/CoreRuntimeTests.cs} (57%) rename src/Umbraco.Tests/{BootManagers/WebBootManagerTests.cs => Runtimes/WebRuntimeTests.cs} (84%) create mode 100644 src/Umbraco.Web/ManifestWatcherComponent.cs rename src/Umbraco.Web/{WebBootManager.cs => WebRuntime.cs} (77%) diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index 3dbc68638a..d29bd99438 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -348,12 +348,6 @@ namespace Umbraco.Core ApplicationCache.IsolatedRuntimeCache.ClearAllCaches(); } - // reset all resolvers - ResolverCollection.ResetAll(); - - // reset resolution itself (though this should be taken care of by resetting any of the resolvers above) - Resolution.Reset(); - // reset the instance objects ApplicationCache = null; if (_databaseContext != null) //need to check the internal field here diff --git a/src/Umbraco.Core/Components/BootLoader.cs b/src/Umbraco.Core/Components/BootLoader.cs new file mode 100644 index 0000000000..6adf5f74d9 --- /dev/null +++ b/src/Umbraco.Core/Components/BootLoader.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using LightInject; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Components +{ + // note: this class is NOT thread-safe in any ways + + internal class BootLoader + { + private readonly ServiceContainer _container; + private readonly ProfilingLogger _proflog; + private IUmbracoComponent[] _components; + private bool _booted; + + private const int LogThresholdMilliseconds = 200; + + /// + /// Initializes a new instance of the class. + /// + /// The application container. + public BootLoader(ServiceContainer container) + { + if (container == null) throw new ArgumentNullException(nameof(container)); + _container = container; + _proflog = container.GetInstance(); + } + + // fixme - sort out it all + // fixme - what about executing when no database? when not configured? see all event handler! + // rules + // + // UmbracoCoreComponent is special and requires every IUmbracoCoreComponent + // IUmbracoUserComponent is special and requires UmbracoCoreComponent + // + // process Enable/Disable for *all* regardless of whether they'll end up being enabled or disabled + // process Require *only* for those that end up being enabled + // requiring something that's disabled is going to cause an exception + // + // works: + // gets the list of all discovered components + // handles dependencies and order via topological graph + // handle enable/disable (precedence?) + // OR vice-versa as, if it's disabled, it has no dependency! + // BUT then the order is pretty much random - bah + // for each component, run Compose + // for each component, discover & run Initialize methods + // + // should we register components on a clone? (benchmark!) + // should we get then in a scope => disposed? + // do we want to keep them around? + // + + // what's with ServiceProvider and PluginManager? + // + // do we need events? + // initialize, starting, started + // become + // ?, compose, initialize + + private class EnableInfo + { + public bool Enabled; + public int Weight = -1; + } + + public void Boot(IEnumerable componentTypes) + { + if (_booted) throw new InvalidOperationException("Can not boot, has already booted."); + + using (_proflog.TraceDuration($"Booting Umbraco {UmbracoVersion.GetSemanticVersion().ToSemanticString()} on {NetworkHelper.MachineName}.", "Booted.")) + { + var orderedComponentTypes = PrepareComponentTypes(componentTypes); + InstanciateComponents(orderedComponentTypes); + ComposeComponents(); + InitializeComponents(); + } + + // rejoice! + _booted = true; + } + + private IEnumerable PrepareComponentTypes(IEnumerable componentTypes) + { + using (_proflog.DebugDuration("Preparing component types.", "Prepared component types.")) + { + return PrepareComponentTypes2(componentTypes); + } + } + + private static IEnumerable PrepareComponentTypes2(IEnumerable componentTypes) + { + var componentTypeList = componentTypes.ToList(); + + if (componentTypeList.Contains(typeof(UmbracoCoreComponent)) == false) + componentTypeList.Add(typeof(UmbracoCoreComponent)); + + var enabled = new Dictionary(); + + // process the enable/disable attributes + // remote declarations (when a component enables/disables *another* component) + // have priority over local declarations (when a component disables itself) so that + // ppl can enable components that, by default, are disabled + // what happens in case of conflicting remote declarations is unspecified. more + // precisely, the last declaration to be processed wins, but the order of the + // declarations depends on the type finder and is unspecified + // we *could* fix this by adding a weight property to both attributes... + foreach (var componentType in componentTypeList) + { + foreach (var attr in componentType.GetCustomAttributes()) + { + var type = attr.EnabledType ?? componentType; + EnableInfo enableInfo; + if (enabled.TryGetValue(type, out enableInfo) == false) enableInfo = enabled[type] = new EnableInfo(); + var weight = type == componentType ? 1 : 2; + if (enableInfo.Weight > weight) continue; + + enableInfo.Enabled = true; + enableInfo.Weight = weight; + } + foreach (var attr in componentType.GetCustomAttributes()) + { + var type = attr.DisabledType ?? componentType; + if (type == typeof(UmbracoCoreComponent)) throw new InvalidOperationException("Cannot disable UmbracoCoreComponent."); + EnableInfo enableInfo; + if (enabled.TryGetValue(type, out enableInfo) == false) enableInfo = enabled[type] = new EnableInfo(); + var weight = type == componentType ? 1 : 2; + if (enableInfo.Weight > weight) continue; + + enableInfo.Enabled = false; + enableInfo.Weight = weight; + } + } + + // remove components that end up being disabled + foreach (var kvp in enabled.Where(x => x.Value.Enabled == false)) + componentTypeList.Remove(kvp.Key); + + // sort the components according to their dependencies + var coreComponentTypes = componentTypeList.Where(x => x.Implements()).ToArray(); + var items = new List>(); + var temp = new List(); // reduce allocs + foreach (var type in componentTypeList) + { + temp.Clear(); + if (type == typeof(UmbracoCoreComponent)) temp.AddRange(coreComponentTypes); + if (type.Implements()) temp.Add(typeof(UmbracoCoreComponent)); + temp.AddRange(type.GetCustomAttributes().Select(x => x.RequiredType)); + var dependsOn = temp.Distinct().ToArray(); + + // check for broken dependencies + foreach (var broken in temp.Where(x => componentTypeList.Contains(x) == false)) + throw new Exception($"Broken component dependency: {type.FullName} -> {broken.FullName}."); + + items.Add(new TopologicalSorter.DependencyField(type.FullName, dependsOn.Select(x => x.FullName).ToArray(), new Lazy(() => type))); + } + return TopologicalSorter.GetSortedItems(items); + } + + private void InstanciateComponents(IEnumerable types) + { + using (_proflog.DebugDuration("Instanciating components.", "Instanciated components.")) + { + _components = types.Select(x => (IUmbracoComponent) Activator.CreateInstance(x)).ToArray(); + } + } + + private void ComposeComponents() + { + using (_proflog.DebugDuration($"Composing components. (log when >{LogThresholdMilliseconds}ms)", "Composed components.")) + { + foreach (var component in _components) + { + var componentType = component.GetType(); + using (_proflog.DebugDuration($"Composing {componentType.FullName}.", $"Composed {componentType.FullName}.", LogThresholdMilliseconds)) + { + component.Compose(_container); + } + } + } + } + + private void InitializeComponents() + { + // use a container scope to ensure that PerScope instances are disposed + // components that require instances that should not survive should register them with PerScope lifetime + using (_proflog.DebugDuration($"Initializing components. (log when >{LogThresholdMilliseconds}ms)", "Initialized components.")) + using (_container.BeginScope()) + { + foreach (var component in _components) + { + var componentType = component.GetType(); + var initializers = componentType.GetMethods(BindingFlags.Instance | BindingFlags.Public) + .Where(x => x.Name == "Initialize" && x.IsGenericMethod == false); + using (_proflog.DebugDuration($"Initializing {componentType.FullName}.", $"Initialised {componentType.FullName}.", LogThresholdMilliseconds)) + { + foreach (var initializer in initializers) + { + var parameters = initializer.GetParameters() + .Select(x => _container.GetInstance(x.ParameterType)) + .ToArray(); + initializer.Invoke(component, parameters); + } + } + } + } + } + + public void Terminate() + { + if (_booted == false) throw new InvalidOperationException("Cannot terminate, has not booted."); + + using (_proflog.DebugDuration($"Terminating Umbraco. (log components when >{LogThresholdMilliseconds}ms)", "Terminated Umbraco.")) + { + foreach (var component in _components) + { + var componentType = component.GetType(); + using (_proflog.DebugDuration($"Terminating {componentType.FullName}.", $"Terminated {componentType.FullName}.", LogThresholdMilliseconds)) + { + component.Terminate(); + } + } + } + } + } +} diff --git a/src/Umbraco.Core/Components/DisableComponentAttribute.cs b/src/Umbraco.Core/Components/DisableComponentAttribute.cs new file mode 100644 index 0000000000..d80681aeb7 --- /dev/null +++ b/src/Umbraco.Core/Components/DisableComponentAttribute.cs @@ -0,0 +1,18 @@ +using System; + +namespace Umbraco.Core.Components +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class DisableComponentAttribute : Attribute + { + public DisableComponentAttribute() + { } + + public DisableComponentAttribute(Type disabledType) + { + DisabledType = disabledType; + } + + public Type DisabledType { get; } + } +} diff --git a/src/Umbraco.Core/Components/EnableComponentAttribute.cs b/src/Umbraco.Core/Components/EnableComponentAttribute.cs new file mode 100644 index 0000000000..81cb6666b5 --- /dev/null +++ b/src/Umbraco.Core/Components/EnableComponentAttribute.cs @@ -0,0 +1,18 @@ +using System; + +namespace Umbraco.Core.Components +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class EnableComponentAttribute : Attribute + { + public EnableComponentAttribute() + { } + + public EnableComponentAttribute(Type enabledType) + { + EnabledType = enabledType; + } + + public Type EnabledType { get; } + } +} diff --git a/src/Umbraco.Core/Components/IUmbracoComponent.cs b/src/Umbraco.Core/Components/IUmbracoComponent.cs new file mode 100644 index 0000000000..787ce3c792 --- /dev/null +++ b/src/Umbraco.Core/Components/IUmbracoComponent.cs @@ -0,0 +1,11 @@ +using LightInject; + +namespace Umbraco.Core.Components +{ + public interface IUmbracoComponent + { + void Compose(ServiceContainer container); + + void Terminate(); + } +} diff --git a/src/Umbraco.Core/Components/IUmbracoCoreComponent.cs b/src/Umbraco.Core/Components/IUmbracoCoreComponent.cs new file mode 100644 index 0000000000..459da50b5e --- /dev/null +++ b/src/Umbraco.Core/Components/IUmbracoCoreComponent.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Core.Components +{ + public interface IUmbracoCoreComponent : IUmbracoComponent + { } +} diff --git a/src/Umbraco.Core/Components/IUmbracoUserComponent.cs b/src/Umbraco.Core/Components/IUmbracoUserComponent.cs new file mode 100644 index 0000000000..260b479fa3 --- /dev/null +++ b/src/Umbraco.Core/Components/IUmbracoUserComponent.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Core.Components +{ + public interface IUmbracoUserComponent : IUmbracoComponent + { } +} diff --git a/src/Umbraco.Core/Components/RequireComponentAttribute.cs b/src/Umbraco.Core/Components/RequireComponentAttribute.cs new file mode 100644 index 0000000000..f0f406795f --- /dev/null +++ b/src/Umbraco.Core/Components/RequireComponentAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Umbraco.Core.Components +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] + public class RequireComponentAttribute : Attribute + { + public RequireComponentAttribute(Type requiredType) + { + RequiredType = requiredType; + } + + public Type RequiredType { get; } + } +} diff --git a/src/Umbraco.Core/Components/UmbracoComponentBase.cs b/src/Umbraco.Core/Components/UmbracoComponentBase.cs new file mode 100644 index 0000000000..7b89b25a31 --- /dev/null +++ b/src/Umbraco.Core/Components/UmbracoComponentBase.cs @@ -0,0 +1,13 @@ +using LightInject; + +namespace Umbraco.Core.Components +{ + public abstract class UmbracoComponentBase : IUmbracoComponent + { + public virtual void Compose(ServiceContainer container) + { } + + public virtual void Terminate() + { } + } +} diff --git a/src/Umbraco.Core/Components/UmbracoCoreComponent.cs b/src/Umbraco.Core/Components/UmbracoCoreComponent.cs new file mode 100644 index 0000000000..cbc29e137e --- /dev/null +++ b/src/Umbraco.Core/Components/UmbracoCoreComponent.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Core.Components +{ + public class UmbracoCoreComponent : UmbracoComponentBase + { } +} diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreRuntime.cs similarity index 52% rename from src/Umbraco.Core/CoreBootManager.cs rename to src/Umbraco.Core/CoreRuntime.cs index 6e09f7eb2b..187e11fb20 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreRuntime.cs @@ -1,466 +1,491 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using System.Threading; -using AutoMapper; -using LightInject; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Manifest; -using Umbraco.Core.Models.Mapping; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.ObjectResolution; -using Umbraco.Core.Persistence.Migrations; -using Umbraco.Core.Plugins; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Core.Sync; -using Umbraco.Core.Strings; -using Umbraco.Core._Legacy.PackageActions; - - -namespace Umbraco.Core -{ - - /// - /// A bootstrapper for the Umbraco application which initializes all objects for the Core of the application - /// - /// - /// This does not provide any startup functionality relating to web objects - /// - public class CoreBootManager : IBootManager - { - protected ProfilingLogger ProfilingLogger { get; private set; } - private DisposableTimer _timer; - protected PluginManager PluginManager { get; private set; } - - private IServiceContainer _appStartupEvtContainer; - private bool _isInitialized; - private bool _isStarted; - private bool _isComplete; - private readonly UmbracoApplicationBase _umbracoApplication; - protected ApplicationContext ApplicationContext { get; private set; } - protected CacheHelper ApplicationCache { get; private set; } - - protected UmbracoApplicationBase UmbracoApplication => _umbracoApplication; - - protected ServiceContainer Container => Current.Container; // fixme kill - - protected IServiceProvider ServiceProvider { get; private set; } - - public CoreBootManager(UmbracoApplicationBase umbracoApplication) - { - if (umbracoApplication == null) throw new ArgumentNullException("umbracoApplication"); - _umbracoApplication = umbracoApplication; - } - - internal CoreBootManager(UmbracoApplicationBase umbracoApplication, ProfilingLogger logger) - { - if (umbracoApplication == null) throw new ArgumentNullException("umbracoApplication"); - if (logger == null) throw new ArgumentNullException("logger"); - _umbracoApplication = umbracoApplication; - ProfilingLogger = logger; - } - - public virtual IBootManager Initialize() - { - if (_isInitialized) - throw new InvalidOperationException("The boot manager has already been initialized"); - - // the logger has been created by UmbracoApplicationBase - // fixme why not the profiling logger etc?! OR have them all created by the boot manager? - //Create logger/profiler, and their resolvers, these are special resolvers that can be resolved before frozen so we can start logging - var logger = Current.Logger; - - var profiler = CreateProfiler(); - Container.RegisterInstance(profiler); // fixme - re-registered?! - //ProfilerResolver.Current = new ProfilerResolver(profiler) { CanResolveBeforeFrozen = true }; - - ProfilingLogger = new ProfilingLogger(logger, profiler); - - ApplicationCache = CreateApplicationCache(); - - _timer = ProfilingLogger.TraceDuration( - string.Format("Umbraco {0} application starting on {1}", UmbracoVersion.GetSemanticVersion().ToSemanticString(), NetworkHelper.MachineName), - "Umbraco application startup complete"); - - ServiceProvider = new ActivatorServiceProvider(); - - //create the plugin manager - //TODO: this is currently a singleton but it would be better if it weren't. Unfortunately the only way to get - // rid of this singleton would be to put it into IoC and then use the ServiceLocator pattern. - PluginManager.Current = PluginManager = new PluginManager(ApplicationCache.RuntimeCache, ProfilingLogger, true); - - //build up core IoC servoces - ConfigureCoreServices(Container); - - //set the singleton resolved from the core container - ApplicationContext.Current = ApplicationContext = Container.GetInstance(); - - //TODO: Remove these for v8! - LegacyPropertyEditorIdToAliasConverter.CreateMappingsForCoreEditors(); - LegacyParameterEditorAliasConverter.CreateMappingsForCoreEditors(); - - //Create a 'child'container which is a copy of all of the current registrations and begin a sub scope for it - // this child container will be used to manage the application event handler instances and the scope will be - // completed at the end of the boot process to allow garbage collection - _appStartupEvtContainer = Container.Clone(); - _appStartupEvtContainer.BeginScope(); - _appStartupEvtContainer.RegisterCollection(PluginManager.ResolveApplicationStartupHandlers()); - - //build up standard IoC services - ConfigureApplicationServices(Container); - - InitializeResolvers(); - InitializeModelMappers(); - - //now we need to call the initialize methods - Parallel.ForEach(_appStartupEvtContainer.GetAllInstances(), x => - { - try - { - using (ProfilingLogger.DebugDuration( - $"Executing {x.GetType()} in ApplicationInitialized", - $"Executed {x.GetType()} in ApplicationInitialized", - //only log if more than 150ms - 150)) - { - x.OnApplicationInitialized(UmbracoApplication, ApplicationContext); - } - } - catch (Exception ex) - { - ProfilingLogger.Logger.Error("An error occurred running OnApplicationInitialized for handler " + x.GetType(), ex); - throw; - } - }); - - _isInitialized = true; - - return this; - } - - /// - /// Build the core container which contains all core things requird to build an app context - /// - internal virtual void ConfigureCoreServices(ServiceContainer container) - { - //Logging - container.RegisterInstance(ProfilingLogger.Profiler); - container.RegisterInstance(ProfilingLogger); - - //Config - container.RegisterFrom(); - - //Cache - container.RegisterInstance(ApplicationCache); - container.RegisterInstance(ApplicationCache.RuntimeCache); - - //Datalayer/Repositories/SQL/Database/etc... - container.RegisterFrom(); - - //Data Services/ServiceContext/etc... - container.RegisterFrom(); - - //ModelMappers - container.RegisterFrom(); - - //TODO: Don't think we'll need this when the resolvers are all container resolvers - container.RegisterSingleton(); - container.RegisterInstance(PluginManager); - - container.RegisterSingleton(); - container.Register(factory => FileSystemProviderManager.Current.GetFileSystemProvider()); - } - - /// - /// Called to customize the IoC container - /// - /// - internal virtual void ConfigureApplicationServices(ServiceContainer container) - { - - } - - /// - /// Creates the ApplicationCache based on a new instance of System.Web.Caching.Cache - /// - protected virtual CacheHelper CreateApplicationCache() - { - var cacheHelper = new CacheHelper( - //we need to have the dep clone runtime cache provider to ensure - //all entities are cached properly (cloned in and cloned out) - new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()), - new StaticCacheProvider(), - //we have no request based cache when not running in web-based context - new NullCacheProvider(), - new IsolatedRuntimeCache(type => - //we need to have the dep clone runtime cache provider to ensure - //all entities are cached properly (cloned in and cloned out) - new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); - - return cacheHelper; - } - - /// - /// This method initializes all of the model mappers registered in the container - /// - protected void InitializeModelMappers() - { - Mapper.Initialize(configuration => - { - //foreach (var m in ApplicationEventsResolver.Current.ApplicationEventHandlers.OfType()) - foreach (var m in Container.GetAllInstances()) - { - m.ConfigureMappings(configuration, ApplicationContext); - } - }); - } - - /// - /// Creates the application's IProfiler - /// - protected virtual IProfiler CreateProfiler() - { - return new LogProfiler(ProfilingLogger.Logger); - } - - /// - /// Special method to extend the use of Umbraco by enabling the consumer to overwrite - /// the absolute path to the root of an Umbraco site/solution, which is used for stuff - /// like Umbraco.Core.IO.IOHelper.MapPath etc. - /// - /// Absolute - protected virtual void InitializeApplicationRootPath(string rootPath) - { - IO.IOHelper.SetRootDirectory(rootPath); - } - - /// - /// Fires after initialization and calls the callback to allow for customizations to occur & - /// Ensure that the OnApplicationStarting methods of the IApplicationEvents are called - /// - /// - /// - public virtual IBootManager Startup(Action afterStartup) - { - if (_isStarted) - throw new InvalidOperationException("The boot manager has already been initialized"); - - //call OnApplicationStarting of each application events handler - Parallel.ForEach(_appStartupEvtContainer.GetAllInstances(), x => - { - try - { - using (ProfilingLogger.DebugDuration( - $"Executing {x.GetType()} in ApplicationStarting", - $"Executed {x.GetType()} in ApplicationStarting", - //only log if more than 150ms - 150)) - { - x.OnApplicationStarting(UmbracoApplication, ApplicationContext); - } - } - catch (Exception ex) - { - ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarting for handler " + x.GetType(), ex); - throw; - } - }); - - if (afterStartup != null) - { - afterStartup(ApplicationContext.Current); - } - - _isStarted = true; - - return this; - } - - /// - /// Fires after startup and calls the callback once customizations are locked - /// - /// - /// - public virtual IBootManager Complete(Action afterComplete) - { - if (_isComplete) - throw new InvalidOperationException("The boot manager has already been completed"); - - FreezeResolution(); - - //Here we need to make sure the db can be connected to - EnsureDatabaseConnection(); - - - //This is a special case for the user service, we need to tell it if it's an upgrade, if so we need to ensure that - // exceptions are bubbled up if a user is attempted to be persisted during an upgrade (i.e. when they auth to login) - ((UserService) ApplicationContext.Services.UserService).IsUpgrading = true; - - - - //call OnApplicationStarting of each application events handler - Parallel.ForEach(_appStartupEvtContainer.GetAllInstances(), x => - { - try - { - using (ProfilingLogger.DebugDuration( - $"Executing {x.GetType()} in ApplicationStarted", - $"Executed {x.GetType()} in ApplicationStarted", - //only log if more than 150ms - 150)) - { - x.OnApplicationStarted(UmbracoApplication, ApplicationContext); - } - } - catch (Exception ex) - { - ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarted for handler " + x.GetType(), ex); - throw; - } - }); - - //end the current scope which was created to intantiate all of the startup handlers, - //this will dispose them if they're IDisposable - _appStartupEvtContainer.EndCurrentScope(); - //NOTE: DO NOT Dispose this cloned container since it will also dispose of any instances - // resolved from the parent container - _appStartupEvtContainer = null; - - if (afterComplete != null) - { - afterComplete(ApplicationContext.Current); - } - - _isComplete = true; - - // we're ready to serve content! - ApplicationContext.IsReady = true; - - //stop the timer and log the output - _timer.Dispose(); - return this; - } - - /// - /// We cannot continue if the db cannot be connected to - /// - private void EnsureDatabaseConnection() - { - if (ApplicationContext.IsConfigured == false) return; - if (ApplicationContext.DatabaseContext.IsDatabaseConfigured == false) return; - - //try now - if (ApplicationContext.DatabaseContext.CanConnect) - return; - - var currentTry = 0; - while (currentTry < 5) - { - //first wait, then retry - Thread.Sleep(1000); - - if (ApplicationContext.DatabaseContext.CanConnect) - break; - - currentTry++; - } - - if (currentTry == 5) - { - throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but the Umbraco cannot connect to the database."); - } - } - - /// - /// Freeze resolution to not allow Resolvers to be modified - /// - protected virtual void FreezeResolution() - { - Resolution.Freeze(); - } - - /// - /// Create the resolvers - /// - protected virtual void InitializeResolvers() - { - - var manifestParser = new ManifestParser(ProfilingLogger.Logger, new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), ApplicationCache.RuntimeCache); - var manifestBuilder = new ManifestBuilder(ApplicationCache.RuntimeCache, manifestParser); - - Container.Register(_ => manifestBuilder); // will be injected in eg PropertyEditorCollectionBuilder - - PropertyEditorCollectionBuilder.Register(Container) - .AddProducer(() => PluginManager.ResolvePropertyEditors()); - - ParameterEditorCollectionBuilder.Register(Container) - .AddProducer(() => PluginManager.ResolveParameterEditors()); - - // setup validators with our predefined validators - ValidatorCollectionBuilder.Register(Container) - .Add() - .Add() - .Add() - .Add() - .Add() - .Add(); - - //by default we'll use the db server registrar unless the developer has the legacy - // dist calls enabled, in which case we'll use the config server registrar - if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled) - { - Container.RegisterSingleton(_ => new ConfigServerRegistrar(UmbracoConfig.For.UmbracoSettings())); - } - else - { - Container.RegisterSingleton(_ => - new DatabaseServerRegistrar( - new Lazy(() => ApplicationContext.Services.ServerRegistrationService), - new DatabaseServerRegistrarOptions())); - } - - - //by default we'll use the database server messenger with default options (no callbacks), - // this will be overridden in the web startup - // fixme - painful, have to take care of lifetime! - we CANNOT ask users to remember! - // fixme - same issue with PublishedContentModelFactory and many more, I guess! - Container.Register( - _ => new DatabaseServerMessenger(ApplicationContext, true, new DatabaseServerMessengerOptions()), - new PerContainerLifetime()); - - //RepositoryResolver.Current = new RepositoryResolver( - // new RepositoryFactory(ApplicationCache)); - - CacheRefresherCollectionBuilder.Register(Container) - .AddProducer(() => PluginManager.ResolveCacheRefreshers()); - - PackageActionCollectionBuilder.Register(Container) - .AddProducer(f => f.GetInstance().ResolvePackageActions()); - - //the database migration objects - MigrationCollectionBuilder.Register(Container) - .AddProducer(() => PluginManager.ResolveTypes()); - - // need to filter out the ones we dont want!! - PropertyValueConverterCollectionBuilder.Register(Container) - .Append(PluginManager.ResolveTypes()); - - // use the new DefaultShortStringHelper - Container.RegisterSingleton(factory - => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetInstance()))); - - UrlSegmentProviderCollectionBuilder.Register(Container) - .Append(); - - // by default, no factory (ie, noop) is activated - Container.RegisterSingleton(); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using AutoMapper; +using LightInject; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.DependencyInjection; +using Umbraco.Core.Exceptions; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Manifest; +using Umbraco.Core.Models.Mapping; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Persistence.Migrations; +using Umbraco.Core.Plugins; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Core.Sync; +using Umbraco.Core.Strings; +using Umbraco.Core._Legacy.PackageActions; + +namespace Umbraco.Core +{ + + /// + /// Represents the Core Umbraco runtime. + /// + /// Does not handle any of the web-related aspects of Umbraco (startup, etc). It + /// should be possible to use this runtime in console apps. + public class CoreRuntime : IRuntime + { + private BootLoader _bootLoader; + + private DisposableTimer _timer; + + private IServiceContainer _appStartupEvtContainer; + + private bool _isInitialized; + private bool _isStarted; + private bool _isComplete; + + + // what the UmbracoApplication does is... + // create the container and configure for core + // create and register a logger + // then: + //GetBootManager() + // .Initialize() + // .Startup(appContext => OnApplicationStarting(sender, e)) + // .Complete(appContext => OnApplicationStarted(sender, e)); + // + // edit so it becomes: + //GetBootLoader() + // .Boot(); + // + // merge all RegisterX into one Register + // + // WebBootLoader should + // configure the container for web BUT that should be AFTER the components have initialized? OR? + // + // Startup runs all app event handler OnApplicationStarting methods + // then triggers the OnApplicationStarting event of the app + // Complete + // freezes resolution + // ensures database connection (else?) + // tells user service it is upgrading (?) + // runs all app event handler OnApplicationStarted methods + // then triggers the OnApplicationStarted event of the app + // and sets Ready + // + // note: what's deciding whether install, upgrade, run? + + private RuntimeState _state; // fixme what about web?! + + // fixme - temp + public virtual void Boot(ServiceContainer container) + { + // create and register essential stuff + + Logger = container.GetInstance(); + container.RegisterInstance(Profiler = GetProfiler()); + container.RegisterInstance(ProfilingLogger = new ProfilingLogger(Current.Logger, Profiler)); + + container.RegisterInstance(_state = new RuntimeState()); + + // then compose + Compose(container); + + // 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 + // the container will fail to create a scope since there is no http context when + // the application starts. + // the boot loader is kept in the runtime for as long as Umbraco runs, and components + // are NOT disposed - which is not a big deal as long as they remain lightweight + // objects. + + _bootLoader = new BootLoader(container); + _bootLoader.Boot(GetComponentTypes()); + } + + public virtual void Terminate() + { + _bootLoader?.Terminate(); + } + + public virtual void Compose(ServiceContainer container) + { + // create and register essential stuff + + var cache = GetApplicationCache(); + container.RegisterInstance(cache); + container.RegisterInstance(cache.RuntimeCache); + + container.RegisterInstance(new PluginManager(cache.RuntimeCache, ProfilingLogger)); + + // register from roots + container.RegisterFrom(); // fixme - used to be before caches? + container.RegisterFrom(); + container.RegisterFrom(); + container.RegisterFrom(); + } + + protected virtual void Compose1(ServiceContainer container) + { + // fixme need to cleanup below + + //TODO: Don't think we'll need this when the resolvers are all container resolvers + container.RegisterSingleton(); + container.RegisterSingleton(); + container.Register(factory => FileSystemProviderManager.Current.GetFileSystemProvider()); + } + + protected virtual void Compose2(ServiceContainer container) + { + // fixme - should we capture Logger, etc here or use factory? + + // register manifest builder, will be injected in eg PropertyEditorCollectionBuilder + container.RegisterSingleton(factory + => new ManifestParser(factory.GetInstance(), new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), factory.GetInstance())); + container.RegisterSingleton(); + + PropertyEditorCollectionBuilder.Register(container) + .AddProducer(factory => factory.GetInstance().ResolvePropertyEditors()); + + ParameterEditorCollectionBuilder.Register(container) + .AddProducer(factory => factory.GetInstance().ResolveParameterEditors()); + + // register our predefined validators + ValidatorCollectionBuilder.Register(container) + .Add() + .Add() + .Add() + .Add() + .Add() + .Add(); + + // register a server registrar, by default it's the db registrar unless the dev + // has the legacy dist calls enabled - fixme - should obsolete the legacy thing + container.RegisterSingleton(_ => UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled + ? (IServerRegistrar)new ConfigServerRegistrar(UmbracoConfig.For.UmbracoSettings()) + : (IServerRegistrar)new DatabaseServerRegistrar( + new Lazy(() => ApplicationContext.Services.ServerRegistrationService), + new DatabaseServerRegistrarOptions())); + + // by default we'll use the database server messenger with default options (no callbacks), + // this will be overridden in the web startup + // fixme - painful, have to take care of lifetime! - we CANNOT ask users to remember! + // fixme - same issue with PublishedContentModelFactory and many more, I guess! + container.RegisterSingleton(_ => new DatabaseServerMessenger(ApplicationContext, true, new DatabaseServerMessengerOptions())); + + CacheRefresherCollectionBuilder.Register(container) + .AddProducer(factory => factory.GetInstance().ResolveCacheRefreshers()); + + PackageActionCollectionBuilder.Register(container) + .AddProducer(f => f.GetInstance().ResolvePackageActions()); + + MigrationCollectionBuilder.Register(container) + .AddProducer(factory => factory.GetInstance().ResolveTypes()); + + // need to filter out the ones we dont want!! fixme - what does that mean? + PropertyValueConverterCollectionBuilder.Register(container) + .Append(factory => factory.GetInstance().ResolveTypes()); + + container.RegisterSingleton(factory + => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetInstance()))); + + UrlSegmentProviderCollectionBuilder.Register(container) + .Append(); + + // by default, register a noop factory + container.RegisterSingleton(); + } + + #region Locals + + protected ILogger Logger { get; private set; } + + protected IProfiler Profiler { get; private set; } + + protected ProfilingLogger ProfilingLogger { get; private set; } + + // fixme do we need that one? + protected PluginManager PluginManager { get; private set; } + + #endregion + + #region Getters + + protected virtual IEnumerable GetComponentTypes() => Current.PluginManager.ResolveTypes(); + + protected virtual IProfiler GetProfiler() => new LogProfiler(Logger); + + protected virtual CacheHelper GetApplicationCache() => new CacheHelper( + // we need to have the dep clone runtime cache provider to ensure + // all entities are cached properly (cloned in and cloned out) + new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()), + new StaticCacheProvider(), + // we have no request based cache when not running in web-based context + new NullCacheProvider(), + new IsolatedRuntimeCache(type => + // we need to have the dep clone runtime cache provider to ensure + // all entities are cached properly (cloned in and cloned out) + new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); + + #endregion + + #region Core + + // cannot run if the db is not there + // tries to connect to db (if configured) + private void EnsureDatabaseConnection() + { + if (ApplicationContext.IsConfigured == false) return; + if (ApplicationContext.DatabaseContext.IsDatabaseConfigured == false) return; + + for (var i = 0; i < 5; i++) + { + if (ApplicationContext.DatabaseContext.CanConnect) return; + Thread.Sleep(1000); + } + + throw new UmbracoStartupFailedException("Umbraco cannot start: a connection string is configured but Umbraco could not connect to the database."); + } + + protected void InitializeModelMappers() + { + Mapper.Initialize(configuration => + { + // fixme why ApplicationEventHandler?! + //foreach (var m in ApplicationEventsResolver.Current.ApplicationEventHandlers.OfType()) + foreach (var m in Container.GetAllInstances()) + { + m.ConfigureMappings(configuration, ApplicationContext); + } + }); + } + + /// + /// Special method to extend the use of Umbraco by enabling the consumer to overwrite + /// the absolute path to the root of an Umbraco site/solution, which is used for stuff + /// like Umbraco.Core.IO.IOHelper.MapPath etc. + /// + /// Absolute Umbraco root path. + protected virtual void InitializeApplicationRootPath(string rootPath) + { + IOHelper.SetRootDirectory(rootPath); + } + + #endregion + + // FIXME everything below needs to be sorted out! + + protected ApplicationContext ApplicationContext { get; private set; } + + protected CacheHelper ApplicationCache { get; private set; } + + protected UmbracoApplicationBase UmbracoApplication { get; } + + protected ServiceContainer Container => Current.Container; // fixme kill + + protected IServiceProvider ServiceProvider { get; private set; } + + public CoreRuntime(UmbracoApplicationBase umbracoApplication) + { + if (umbracoApplication == null) throw new ArgumentNullException("umbracoApplication"); + UmbracoApplication = umbracoApplication; + } + + internal CoreRuntime(UmbracoApplicationBase umbracoApplication, ProfilingLogger logger) + { + if (umbracoApplication == null) throw new ArgumentNullException("umbracoApplication"); + if (logger == null) throw new ArgumentNullException("logger"); + UmbracoApplication = umbracoApplication; + ProfilingLogger = logger; + } + + public virtual IRuntime Initialize() + { + if (_isInitialized) + throw new InvalidOperationException("The boot manager has already been initialized"); + + //ApplicationCache = Container.GetInstance(); //GetApplicationCache(); + + _timer = ProfilingLogger.TraceDuration( + string.Format("Umbraco {0} application starting on {1}", UmbracoVersion.GetSemanticVersion().ToSemanticString(), NetworkHelper.MachineName), + "Umbraco application startup complete"); + + ServiceProvider = new ActivatorServiceProvider(); + + //create the plugin manager + //TODO: this is currently a singleton but it would be better if it weren't. Unfortunately the only way to get + // rid of this singleton would be to put it into IoC and then use the ServiceLocator pattern. + PluginManager.Current = PluginManager = Current.PluginManager; //new PluginManager(ApplicationCache.RuntimeCache, ProfilingLogger, true); + + // register + Compose1(Container); + + //set the singleton resolved from the core container + // fixme - last thing to understand before we merge the two Compose() methods! + ApplicationContext.Current = ApplicationContext = Container.GetInstance(); + + // register - why 2? + Compose2(Container); + + //TODO: Remove these for v8! + LegacyPropertyEditorIdToAliasConverter.CreateMappingsForCoreEditors(); + LegacyParameterEditorAliasConverter.CreateMappingsForCoreEditors(); + + InitializeModelMappers(); + + //now we need to call the initialize methods + //Create a 'child'container which is a copy of all of the current registrations and begin a sub scope for it + // this child container will be used to manage the application event handler instances and the scope will be + // completed at the end of the boot process to allow garbage collection + // using (Container.BeginScope()) { } // fixme - throws + _appStartupEvtContainer = Container.Clone(); // fixme - but WHY clone? because *then* it has its own scope?! bah ;-( + //using (_appStartupEvtContainer.BeginScope()) // fixme - works wtf? + _appStartupEvtContainer.BeginScope(); // fixme - but then attend to end a scope before all child scopes are completed wtf?! + _appStartupEvtContainer.RegisterCollection(factory => factory.GetInstance().ResolveApplicationStartupHandlers()); + + // fixme - parallel? what about our dependencies? + Parallel.ForEach(_appStartupEvtContainer.GetAllInstances(), x => + { + try + { + using (ProfilingLogger.DebugDuration( + $"Executing {x.GetType()} in ApplicationInitialized", + $"Executed {x.GetType()} in ApplicationInitialized", + //only log if more than 150ms + 150)) + { + x.OnApplicationInitialized(UmbracoApplication, ApplicationContext); + } + } + catch (Exception ex) + { + ProfilingLogger.Logger.Error("An error occurred running OnApplicationInitialized for handler " + x.GetType(), ex); + throw; + } + }); + + _isInitialized = true; + + return this; + } + + + /// + /// Fires after initialization and calls the callback to allow for customizations to occur & + /// Ensure that the OnApplicationStarting methods of the IApplicationEvents are called + /// + /// + /// + public virtual IRuntime Startup(Action afterStartup) + { + if (_isStarted) + throw new InvalidOperationException("The boot manager has already been initialized"); + + //call OnApplicationStarting of each application events handler + Parallel.ForEach(_appStartupEvtContainer.GetAllInstances(), x => + { + try + { + using (ProfilingLogger.DebugDuration( + $"Executing {x.GetType()} in ApplicationStarting", + $"Executed {x.GetType()} in ApplicationStarting", + //only log if more than 150ms + 150)) + { + x.OnApplicationStarting(UmbracoApplication, ApplicationContext); + } + } + catch (Exception ex) + { + ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarting for handler " + x.GetType(), ex); + throw; + } + }); + + if (afterStartup != null) + { + afterStartup(ApplicationContext.Current); + } + + _isStarted = true; + + return this; + } + + /// + /// Fires after startup and calls the callback once customizations are locked + /// + /// + /// + public virtual IRuntime Complete(Action afterComplete) + { + if (_isComplete) + throw new InvalidOperationException("The boot manager has already been completed"); + + FreezeResolution(); + + //Here we need to make sure the db can be connected to + EnsureDatabaseConnection(); + + + //This is a special case for the user service, we need to tell it if it's an upgrade, if so we need to ensure that + // exceptions are bubbled up if a user is attempted to be persisted during an upgrade (i.e. when they auth to login) + ((UserService) ApplicationContext.Services.UserService).IsUpgrading = true; + + + + //call OnApplicationStarting of each application events handler + Parallel.ForEach(_appStartupEvtContainer.GetAllInstances(), x => + { + try + { + using (ProfilingLogger.DebugDuration( + $"Executing {x.GetType()} in ApplicationStarted", + $"Executed {x.GetType()} in ApplicationStarted", + //only log if more than 150ms + 150)) + { + x.OnApplicationStarted(UmbracoApplication, ApplicationContext); + } + } + catch (Exception ex) + { + ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarted for handler " + x.GetType(), ex); + throw; + } + }); + + //end the current scope which was created to intantiate all of the startup handlers, + //this will dispose them if they're IDisposable + _appStartupEvtContainer.EndCurrentScope(); + //NOTE: DO NOT Dispose this cloned container since it will also dispose of any instances + // resolved from the parent container + _appStartupEvtContainer = null; + + if (afterComplete != null) + { + afterComplete(ApplicationContext.Current); + } + + _isComplete = true; + + // we're ready to serve content! + ApplicationContext.IsReady = true; + + //stop the timer and log the output + _timer.Dispose(); + return this; + } + + + /// + /// Freeze resolution to not allow Resolvers to be modified + /// + protected virtual void FreezeResolution() + { + Resolution.Freeze(); + } + } +} diff --git a/src/Umbraco.Core/DependencyInjection/Current.cs b/src/Umbraco.Core/DependencyInjection/Current.cs index 5b0f179bf1..be20a65aad 100644 --- a/src/Umbraco.Core/DependencyInjection/Current.cs +++ b/src/Umbraco.Core/DependencyInjection/Current.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Dictionary; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Plugins; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Strings; using Umbraco.Core.Sync; @@ -15,19 +16,20 @@ namespace Umbraco.Core.DependencyInjection // this class is here to support the transition from singletons and resolvers to injection, // by providing a static access to singleton services - it is initialized once with a service // container, in CoreBootManager. - // ideally, it should not exist. practically, time will tell. + // obviously, this is some sort of service locator anti-pattern. ideally, it should not exist. + // practically... time will tell. public static class Current { private static ServiceContainer _container; - public static ServiceContainer Container + internal static ServiceContainer Container { get { if (_container == null) throw new Exception("No container has been set."); return _container; } - internal set // ok to set - don't be stupid + set // ok to set - don't be stupid { if (_container != null) throw new Exception("A container has already been set."); _container = value; @@ -44,6 +46,7 @@ namespace Umbraco.Core.DependencyInjection _shortStringHelper = null; _logger = null; _profiler = null; + _profilingLogger = null; Resetted?.Invoke(null, EventArgs.Empty); } @@ -52,6 +55,9 @@ namespace Umbraco.Core.DependencyInjection #region Getters + public static PluginManager PluginManager + => Container.GetInstance(); + public static UrlSegmentProviderCollection UrlSegmentProviders => Container.GetInstance(); @@ -99,6 +105,7 @@ namespace Umbraco.Core.DependencyInjection private static ILogger _logger; private static IProfiler _profiler; + private static ProfilingLogger _profilingLogger; public static ILogger Logger => _logger ?? (_logger = _container?.TryGetInstance() @@ -108,6 +115,10 @@ namespace Umbraco.Core.DependencyInjection => _profiler ?? (_profiler = _container?.TryGetInstance() ?? new LogProfiler(Logger)); + public static ProfilingLogger ProfilingLogger + => _profilingLogger ?? (_profilingLogger = _container?.TryGetInstance()) + ?? new ProfilingLogger(Logger, Profiler); + #endregion } } diff --git a/src/Umbraco.Core/DependencyInjection/LightInjectExtensions.cs b/src/Umbraco.Core/DependencyInjection/LightInjectExtensions.cs index ca73b48145..c5fb643e27 100644 --- a/src/Umbraco.Core/DependencyInjection/LightInjectExtensions.cs +++ b/src/Umbraco.Core/DependencyInjection/LightInjectExtensions.cs @@ -153,6 +153,8 @@ namespace Umbraco.Core.DependencyInjection return container.AvailableServices.SingleOrDefault(x => x.ServiceType == typeofTService && x.ServiceName == name); } + // FIXME or just use names?! + /// /// In order for LightInject to deal with enumerables of the same type, each one needs to be registered as their explicit types /// @@ -167,9 +169,14 @@ namespace Umbraco.Core.DependencyInjection where TLifetime : ILifetime, new() { foreach (var type in implementationTypes) - { container.Register(type, new TLifetime()); - } + } + + public static void RegisterCollection(this IServiceContainer container, Func> implementationTypes) + where TLifetime : ILifetime, new() + { + foreach (var type in implementationTypes(container)) + container.Register(type, new TLifetime()); } /// @@ -188,5 +195,11 @@ namespace Umbraco.Core.DependencyInjection container.Register(type); } } + + public static void RegisterCollection(this IServiceContainer container, Func> implementationTypes) + { + foreach (var type in implementationTypes(container)) + container.Register(type); + } } } diff --git a/src/Umbraco.Core/DependencyInjection/OrderedCollectionBuilderBase.cs b/src/Umbraco.Core/DependencyInjection/OrderedCollectionBuilderBase.cs index 55bf274af9..f6f185995f 100644 --- a/src/Umbraco.Core/DependencyInjection/OrderedCollectionBuilderBase.cs +++ b/src/Umbraco.Core/DependencyInjection/OrderedCollectionBuilderBase.cs @@ -58,6 +58,27 @@ namespace Umbraco.Core.DependencyInjection return This; } + /// + /// Appends types to the collections. + /// + /// The types to append. + /// The builder. + public TBuilder Append(Func> types) + { + Configure(list => + { + foreach (var type in types(Container)) + { + // would be detected by CollectionBuilderBase when registering, anyways, but let's fail fast + if (typeof(TItem).IsAssignableFrom(type) == false) + throw new InvalidOperationException($"Cannot register type {type.FullName} as it does not inherit from/implement {typeof(TItem).FullName}."); + if (list.Contains(type)) list.Remove(type); + list.Add(type); + } + }); + return This; + } + /// /// Appends a type after another type. /// diff --git a/src/Umbraco.Core/IBootManager.cs b/src/Umbraco.Core/IBootManager.cs deleted file mode 100644 index d1adfb7bf4..0000000000 --- a/src/Umbraco.Core/IBootManager.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; - -namespace Umbraco.Core -{ - /// - /// A bootstrapper interface for the Umbraco application - /// - public interface IBootManager - { - /// - /// Fires first in the application startup process before any customizations can occur - /// - /// - IBootManager Initialize(); - - /// - /// Fires after initialization and calls the callback to allow for customizations to occur - /// - /// - /// - IBootManager Startup(Action afterStartup); - - /// - /// Fires after startup and calls the callback once customizations are locked - /// - /// - /// - IBootManager Complete(Action afterComplete); - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/IRuntime.cs b/src/Umbraco.Core/IRuntime.cs new file mode 100644 index 0000000000..5321e91272 --- /dev/null +++ b/src/Umbraco.Core/IRuntime.cs @@ -0,0 +1,62 @@ +using System; +using LightInject; + +namespace Umbraco.Core +{ + // fixme - move! + public class RuntimeState + { + /// + /// Gets a value indicating whether the application is running in debug mode. + /// + public bool Debug { get; } + + public RuntimeSomething Something { get; } + } + + public enum RuntimeSomething + { + Boot, + Run + } + + /// + /// Defines the Umbraco runtime. + /// + public interface IRuntime + { + /// + /// Boots the runtime. + /// + /// The application service container. + void Boot(ServiceContainer container); + + /// + /// Terminates the runtime. + /// + void Terminate(); + + // fixme - everything below is obsolete! + + /// + /// Fires first in the application startup process before any customizations can occur + /// + /// + IRuntime Initialize(); + + /// + /// Fires after initialization and calls the callback to allow for customizations to occur + /// + /// + /// + IRuntime Startup(Action afterStartup); + + /// + /// Fires after startup and calls the callback once customizations are locked + /// + /// + /// + IRuntime Complete(Action afterComplete); + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs deleted file mode 100644 index 97c725609c..0000000000 --- a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs +++ /dev/null @@ -1,172 +0,0 @@ -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Threading; -//using Umbraco.Core.LightInject; -//using Umbraco.Core.Logging; -//using Umbraco.Core.Services; -//using umbraco.interfaces; - -//namespace Umbraco.Core.ObjectResolution -//{ -// /// -// /// A resolver to return all IApplicationEvents objects -// /// -// /// -// /// This is disposable because after the app has started it should be disposed to release any memory being occupied by instances. -// /// -// internal sealed class ApplicationEventsResolver : ManyObjectsResolverBase, IDisposable -// { -// private IServiceContainer _container; -// private readonly LegacyStartupHandlerResolver _legacyResolver; - -// public ApplicationEventsResolver(IServiceContainer container, IEnumerable applicationEventHandlers) -// : base(container.GetInstance(), container.GetInstance(), applicationEventHandlers) -// { -// //create the legacy resolver and only include the legacy types -// _legacyResolver = new LegacyStartupHandlerResolver( -// container, -// applicationEventHandlers.Where(x => TypeHelper.IsTypeAssignableFrom(x) == false)); -// } - -// ///// -// ///// Constructor -// ///// -// ///// -// ///// -// ///// -// ///// -// //internal ApplicationEventsResolver(IServiceContainer parentContainer, IServiceProvider serviceProvider, ILogger logger, IEnumerable applicationEventHandlers) -// // : base(serviceProvider, logger, applicationEventHandlers) -// //{ -// // //create the legacy resolver and only include the legacy types -// // _legacyResolver = new LegacyStartupHandlerResolver( -// // applicationEventHandlers.Where(x => !TypeHelper.IsTypeAssignableFrom(x))); -// //} - -// /// -// /// Override in order to only return types of IApplicationEventHandler and above, -// /// do not include the legacy types of IApplicationStartupHandler -// /// -// protected override IEnumerable InstanceTypes -// { -// get { return base.InstanceTypes.Where(TypeHelper.IsTypeAssignableFrom); } -// } - -// /// -// /// Gets the implementations. -// /// -// public IEnumerable ApplicationEventHandlers -// { -// get -// { - -// //return Values; -// } -// } - -// /// -// /// Create instances of all of the legacy startup handlers -// /// -// public void InstantiateLegacyStartupHandlers() -// { -// //this will instantiate them all -// var handlers = _legacyResolver.LegacyStartupHandlers; -// } - -// protected override bool SupportsClear -// { -// get { return false; } -// } - -// protected override bool SupportsInsert -// { -// get { return false; } -// } - -// private class LegacyStartupHandlerResolver : ManyObjectsResolverBase, IDisposable -// { -// internal LegacyStartupHandlerResolver(IServiceContainer container, IEnumerable applicationEventHandlers) -// : base(container.GetInstance(), container.GetInstance(), applicationEventHandlers) -// { -// } - -// //internal LegacyStartupHandlerResolver(IEnumerable legacyStartupHandlers) -// // : base(legacyStartupHandlers) -// //{ - -// //} - -// public IEnumerable LegacyStartupHandlers -// { -// get { return Values; } -// } - -// public void Dispose() -// { -// ResetCollections(); -// } -// } - -// private bool _disposed; -// private readonly ReaderWriterLockSlim _disposalLocker = new ReaderWriterLockSlim(); - -// /// -// /// Gets a value indicating whether this instance is disposed. -// /// -// /// -// /// true if this instance is disposed; otherwise, false. -// /// -// public bool IsDisposed -// { -// get { return _disposed; } -// } - -// /// -// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. -// /// -// /// 2 -// public void Dispose() -// { -// Dispose(true); - -// // Use SupressFinalize in case a subclass of this type implements a finalizer. -// GC.SuppressFinalize(this); -// } - -// ~ApplicationEventsResolver() -// { -// // Run dispose but let the class know it was due to the finalizer running. -// Dispose(false); -// } - -// private void Dispose(bool disposing) -// { -// // Only operate if we haven't already disposed -// if (IsDisposed || disposing == false) return; - -// using (new WriteLock(_disposalLocker)) -// { -// // Check again now we're inside the lock -// if (IsDisposed) return; - -// // Call to actually release resources. This method is only -// // kept separate so that the entire disposal logic can be used as a VS snippet -// DisposeResources(); - -// // Indicate that the instance has been disposed. -// _disposed = true; -// } -// } - -// /// -// /// Clear out all of the instances, we don't want them hanging around and cluttering up memory -// /// -// private void DisposeResources() -// { -// _legacyResolver.Dispose(); -// ResetCollections(); -// } - -// } -//} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/ContainerSingleObjectResolver.cs b/src/Umbraco.Core/ObjectResolution/ContainerSingleObjectResolver.cs deleted file mode 100644 index ddfaa5882c..0000000000 --- a/src/Umbraco.Core/ObjectResolution/ContainerSingleObjectResolver.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Linq; -using LightInject; -using Umbraco.Core.Persistence.Migrations.Syntax.Create; - -namespace Umbraco.Core.ObjectResolution -{ - - /// - /// A single object resolver that can be configured to use IoC to instantiate and wire up the object - /// - /// - /// - public abstract class ContainerSingleObjectResolver : SingleObjectResolverBase - where TResolved : class - where TResolver : ResolverBase - { - private readonly IServiceContainer _container; - - - #region Constructors used for test - ONLY so that a container is not required and will just revert to using the normal SingleObjectResolverBase - - [Obsolete("Used for tests only - should remove")] - internal ContainerSingleObjectResolver() - { - } - - [Obsolete("Used for tests only - should remove")] - internal ContainerSingleObjectResolver(TResolved value) - : base(value) - { - } - - [Obsolete("Used for tests only - should remove")] - internal ContainerSingleObjectResolver(bool canBeNull) - : base(canBeNull) - { - } - - [Obsolete("Used for tests only - should remove")] - internal ContainerSingleObjectResolver(TResolved value, bool canBeNull) - : base(value, canBeNull) - { - } - #endregion - - /// - /// Initialize the resolver to use IoC, when using this contructor the type must be set manually - /// - /// - internal ContainerSingleObjectResolver(IServiceContainer container) - { - if (container == null) throw new ArgumentNullException(nameof(container)); - _container = container; - } - - /// - /// Initializes the resolver to use IoC - /// - /// - /// - internal ContainerSingleObjectResolver(IServiceContainer container, Func implementationType) - { - _container = container; - _container.Register(implementationType, new PerContainerLifetime()); - } - - /// - /// Gets or sets the resolved object instance. - /// - /// - /// value is set to null, but cannot be null (CanBeNull is false). - /// value is read and is null, but cannot be null (CanBeNull is false), - /// or value is set (read) and resolution is (not) frozen. - protected override TResolved Value - { - get - { - return _container == null - ? base.Value - : _container.GetInstance(); - } - set - { - if (_container != null) - { - var avail = _container.AvailableServices.Any(x => x.ServiceType == typeof (TResolved)); - - if (avail) - { - // must override with the proper name! - _container.Override( - sr => sr.ServiceType == typeof (TResolved), - (factory, registration) => - { - registration.Value = value; - registration.Lifetime = new PerContainerLifetime(); - return registration; - }); - } - else - { - // cannot override something that hasn't been registered yet! - _container.Register(new ServiceRegistration - { - ServiceType = typeof (TResolved), - // no! use Value below! - //ImplementingType = value.GetType(), - ServiceName = "", - Lifetime = new PerContainerLifetime(), - Value = value - }); - } - } - base.Value = value; - } - } - - /// - /// Gets a value indicating whether the resolved object instance is null. - /// - public override bool HasValue - { - get - { - if (_container == null) return base.HasValue; - return (_container.TryGetInstance() == null) == false; - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/ObjectLifetimeScope.cs b/src/Umbraco.Core/ObjectResolution/ObjectLifetimeScope.cs deleted file mode 100644 index e7330ae124..0000000000 --- a/src/Umbraco.Core/ObjectResolution/ObjectLifetimeScope.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Umbraco.Core.ObjectResolution -{ - /// - /// Specifies the lifetime scope of resolved objects. - /// - public enum ObjectLifetimeScope - { - /// - /// A per-request object instance is created. - /// - HttpRequest, - - /// - /// A single application-wide object instance is created. - /// - Application, - - /// - /// A new object instance is created each time one is requested. - /// - Transient - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/Resolution.cs b/src/Umbraco.Core/ObjectResolution/Resolution.cs index 31661f6f7b..192d411683 100644 --- a/src/Umbraco.Core/ObjectResolution/Resolution.cs +++ b/src/Umbraco.Core/ObjectResolution/Resolution.cs @@ -1,10 +1,14 @@ using System; -using System.Linq; using System.Threading; using Umbraco.Core.Logging; namespace Umbraco.Core.ObjectResolution { + // fixme + // this is the last bit that needs to go + // however, if it goes, we're missing Resolution.Frozen even which is used here and there + // => how can we do it? + /// /// Represents the status of objects resolution. /// diff --git a/src/Umbraco.Core/ObjectResolution/ResolverBase.cs b/src/Umbraco.Core/ObjectResolution/ResolverBase.cs deleted file mode 100644 index b5e1e14df2..0000000000 --- a/src/Umbraco.Core/ObjectResolution/ResolverBase.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Threading; -using LightInject; - -namespace Umbraco.Core.ObjectResolution -{ - /// - /// Base non-generic class for resolvers - /// - public abstract class ResolverBase - { - protected ResolverBase(Action resetAction) - { - //add itself to the internal collection - ResolverCollection.Add(this, resetAction); - } - - } - - /// - /// The base class for all resolvers. - /// - /// The type of the concrete resolver class. - /// Provides singleton management to all resolvers. - public abstract class ResolverBase : ResolverBase - where TResolver : ResolverBase - { - - /// - /// The underlying singleton object instance - /// - static TResolver _resolver; - - /// - /// The lock for the singleton. - /// - /// - /// Though resharper says this is in error, it is actually correct. We want a different lock object for each generic type. - /// See this for details: http://confluence.jetbrains.net/display/ReSharper/Static+field+in+generic+type - /// - static readonly ReaderWriterLockSlim ResolversLock = new ReaderWriterLockSlim(); - - /// - /// Constructor set the reset action for the underlying object - /// - protected ResolverBase() - : base(() => Reset()) - { - - } - - /// - /// Gets or sets the resolver singleton instance. - /// - /// The value can be set only once, and cannot be read before it has been set. - /// value is read before it has been set, or value is set again once it has already been set. - /// value is null. - public static TResolver Current - { - get - { - using (new ReadLock(ResolversLock)) - { - if (_resolver == null) - throw new InvalidOperationException(string.Format( - "Current has not been initialized on {0}. You must initialize Current before trying to read it.", - typeof(TResolver).FullName)); - return _resolver; - } - } - - set - { - using (Resolution.Configuration) - using (new WriteLock(ResolversLock)) - { - if (value == null) - throw new ArgumentNullException("value"); - if (_resolver != null) - throw new InvalidOperationException(string.Format( - "Current has already been initialized on {0}. It is not possible to re-initialize Current once it has been initialized.", - typeof(TResolver).FullName)); - _resolver = value; - } - } - } - - /// - /// Gets a value indicating whether a the singleton instance has been set. - /// - public static bool HasCurrent - { - get - { - using (new ReadLock(ResolversLock)) - { - return _resolver != null; - } - } - } - - /// - /// Resets the resolver singleton instance to null. - /// - /// - /// To be used in unit tests. DO NOT USE THIS DURING PRODUCTION. - /// - /// - /// By default this is true because we always need to reset resolution before we reset a resolver, however in some insanely rare cases like unit testing you might not want to do this. - /// - protected internal static void Reset(bool resetResolution = true) - { - - //In order to reset a resolver, we always must reset the resolution - if (resetResolution) - { - Resolution.Reset(); - } - - //ensure its removed from the collection - ResolverCollection.Remove(_resolver); - - using (Resolution.Configuration) - using (new WriteLock(ResolversLock)) - { - _resolver = null; - } - - } - } -} diff --git a/src/Umbraco.Core/ObjectResolution/ResolverCollection.cs b/src/Umbraco.Core/ObjectResolution/ResolverCollection.cs deleted file mode 100644 index 34a7ff68bc..0000000000 --- a/src/Umbraco.Core/ObjectResolution/ResolverCollection.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Linq; - -namespace Umbraco.Core.ObjectResolution -{ - /// - /// Simply used to track all ManyObjectsResolverBase instances so that we can - /// reset them all at once really easily. - /// - /// - /// Normally we'd use TypeFinding for this but because many of the resolvers are internal this won't work. - /// We'd rather not keep a static list of them so we'll dynamically add to this list based on the base - /// class of the ManyObjectsResolverBase. - /// - internal static class ResolverCollection - { - private static readonly ConcurrentDictionary Resolvers = new ConcurrentDictionary(); - - /// - /// Returns the number of resolvers created - /// - internal static int Count - { - get { return Resolvers.Count; } - } - - /// - /// Resets all resolvers - /// - internal static void ResetAll() - { - //take out each item from the bag and reset it - var keys = Resolvers.Keys.ToArray(); - foreach (var k in keys) - { - Action resetAction; - while (Resolvers.TryRemove(k, out resetAction)) - { - //call the reset action for the resolver - resetAction(); - } - } - } - - /// - /// This is called when the static Reset method or a ResolverBase{T} is called. - /// - internal static void Remove(ResolverBase resolver) - { - if (resolver == null) return; - Action action; - Resolvers.TryRemove(resolver, out action); - } - - /// - /// Adds a resolver to the collection - /// - /// - /// - /// - /// This is called when the creation of a ResolverBase occurs - /// - internal static void Add(ResolverBase resolver, Action resetAction) - { - Resolvers.TryAdd(resolver, resetAction); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/SingleObjectResolverBase.cs b/src/Umbraco.Core/ObjectResolution/SingleObjectResolverBase.cs deleted file mode 100644 index 7bb502e9fd..0000000000 --- a/src/Umbraco.Core/ObjectResolution/SingleObjectResolverBase.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Threading; - -namespace Umbraco.Core.ObjectResolution -{ - /// - /// The base class for all single-object resolvers. - /// - /// The type of the concrete resolver class. - /// The type of the resolved object. - /// - /// Resolves "single" objects ie objects for which there is only one application-wide instance, such as the MVC Controller factory. - /// - public abstract class SingleObjectResolverBase : ResolverBase - where TResolved : class - where TResolver : ResolverBase - { - private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); - private readonly bool _canBeNull; - private TResolved _value; - - #region Constructors - - /// - /// Initialize a new instance of the class. - /// - /// By default CanBeNull is false, so Value has to be initialized before being read, - /// otherwise an exception will be thrown when reading it. - protected SingleObjectResolverBase() - : this(false) - { } - - /// - /// Initialize a new instance of the class with an instance of the resolved object. - /// - /// An instance of the resolved object. - /// By default CanBeNull is false, so value has to be non-null, or Value has to be - /// initialized before being accessed, otherwise an exception will be thrown when reading it. - protected SingleObjectResolverBase(TResolved value) - : this(false) - { - _value = value; - } - - /// - /// Initialize a new instance of the class with a value indicating whether the resolved object instance can be null. - /// - /// A value indicating whether the resolved object instance can be null. - /// If CanBeNull is false, Value has to be initialized before being read, - /// otherwise an exception will be thrown when reading it. - protected SingleObjectResolverBase(bool canBeNull) - { - _canBeNull = canBeNull; - } - - /// - /// Initialize a new instance of the class with an instance of the resolved object, - /// and a value indicating whether that instance can be null. - /// - /// An instance of the resolved object. - /// A value indicating whether the resolved object instance can be null. - /// If CanBeNull is false, value has to be non-null, or Value has to be initialized before being read, - /// otherwise an exception will be thrown when reading it. - protected SingleObjectResolverBase(TResolved value, bool canBeNull) - { - _value = value; - _canBeNull = canBeNull; - } - - #endregion - - /// - /// Gets or sets a value indicating whether the resolver can resolve objects before resolution is frozen. - /// - /// This is false by default and is used for some special internal resolvers. - internal bool CanResolveBeforeFrozen { get; set; } - - /// - /// Gets a value indicating whether the resolved object instance can be null. - /// - public bool CanBeNull - { - get { return _canBeNull; } - } - - /// - /// Gets a value indicating whether the resolved object instance is null. - /// - public virtual bool HasValue - { - get { return _value != null; } - } - - /// - /// Gets or sets the resolved object instance. - /// - /// - /// value is set to null, but cannot be null (CanBeNull is false). - /// value is read and is null, but cannot be null (CanBeNull is false), - /// or value is set (read) and resolution is (not) frozen. - protected virtual TResolved Value - { - get - { - using (Resolution.Reader(CanResolveBeforeFrozen)) - using (new ReadLock(_lock)) - { - if (!_canBeNull && _value == null) - throw new InvalidOperationException(string.Format( - "Resolver {0} requires a value, and none was supplied.", this.GetType().FullName)); - - return _value; - } - } - - set - { - using (Resolution.Configuration) - using (var l = new UpgradeableReadLock(_lock)) - { - if (!_canBeNull && value == null) - throw new ArgumentNullException("value"); - - l.UpgradeToWriteLock(); - _value = value; - } - } - } - } -} diff --git a/src/Umbraco.Core/Plugins/PluginManager.cs b/src/Umbraco.Core/Plugins/PluginManager.cs index 09c219a0fb..606589c6dc 100644 --- a/src/Umbraco.Core/Plugins/PluginManager.cs +++ b/src/Umbraco.Core/Plugins/PluginManager.cs @@ -96,6 +96,8 @@ namespace Umbraco.Core.Plugins } } + // fixme - somehow we NEED to get rid of this Current accessor + /// /// Gets the current plugin manager. /// @@ -111,7 +113,7 @@ namespace Umbraco.Core.Plugins { var appctx = ApplicationContext.Current; var cacheProvider = appctx == null // fixme - should Current have an ApplicationCache? - ? new NullCacheProvider() + ? new NullCacheProvider() : appctx.ApplicationCache.RuntimeCache; ProfilingLogger profilingLogger; if (appctx == null) diff --git a/src/Umbraco.Core/TopologicalSorter.cs b/src/Umbraco.Core/TopologicalSorter.cs index d25e1da406..699106d7bb 100644 --- a/src/Umbraco.Core/TopologicalSorter.cs +++ b/src/Umbraco.Core/TopologicalSorter.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; namespace Umbraco.Core { @@ -21,8 +20,8 @@ namespace Umbraco.Core _vertices = new int[size]; _matrix = new int[size, size]; _numVerts = 0; - for (int i = 0; i < size; i++) - for (int j = 0; j < size; j++) + for (var i = 0; i < size; i++) + for (var j = 0; j < size; j++) _matrix[i, j] = 0; _sortedArray = new int[size]; // sorted vert labels } @@ -45,7 +44,7 @@ namespace Umbraco.Core while (_numVerts > 0) // while vertices remain, { // get a vertex with no successors, or -1 - int currentVertex = NoSuccessors(); + var currentVertex = NoSuccessors(); if (currentVertex == -1) // must be a cycle throw new Exception("Graph has cycles"); @@ -66,18 +65,16 @@ namespace Umbraco.Core // returns vert with no successors (or -1 if no such verts) private int NoSuccessors() { - for (int row = 0; row < _numVerts; row++) + for (var row = 0; row < _numVerts; row++) { - bool isEdge = false; // edge from row to column in adjMat - for (int col = 0; col < _numVerts; col++) + var isEdge = false; // edge from row to column in adjMat + for (var col = 0; col < _numVerts; col++) { - if (_matrix[row, col] > 0) // if edge to another, - { - isEdge = true; - break; // this vertex has a successor try another - } + if (_matrix[row, col] <= 0) continue; + isEdge = true; + break; // this vertex has a successor try another } - if (!isEdge) // if no edges, has no successors + if (isEdge == false) // if no edges, has no successors return row; } return -1; // no @@ -88,13 +85,13 @@ namespace Umbraco.Core // if not last vertex, delete from vertexList if (delVert != _numVerts - 1) { - for (int j = delVert; j < _numVerts - 1; j++) + for (var j = delVert; j < _numVerts - 1; j++) _vertices[j] = _vertices[j + 1]; - for (int row = delVert; row < _numVerts - 1; row++) + for (var row = delVert; row < _numVerts - 1; row++) MoveRowUp(row, _numVerts); - for (int col = delVert; col < _numVerts - 1; col++) + for (var col = delVert; col < _numVerts - 1; col++) MoveColLeft(col, _numVerts - 1); } _numVerts--; // one less vertex @@ -102,13 +99,13 @@ namespace Umbraco.Core private void MoveRowUp(int row, int length) { - for (int col = 0; col < length; col++) + for (var col = 0; col < length; col++) _matrix[row, col] = _matrix[row + 1, col]; } private void MoveColLeft(int col, int length) { - for (int row = 0; row < length; row++) + for (var row = 0; row < length; row++) _matrix[row, col] = _matrix[row, col + 1]; } @@ -118,9 +115,9 @@ namespace Umbraco.Core public static IEnumerable GetSortedItems(List> fields) where T : class { - int[] sortOrder = GetTopologicalSortOrder(fields); + var sortOrder = GetTopologicalSortOrder(fields); var list = new List(); - for (int i = 0; i < sortOrder.Length; i++) + for (var i = 0; i < sortOrder.Length; i++) { var field = fields[sortOrder[i]]; list.Add(field.Item.Value); @@ -131,34 +128,31 @@ namespace Umbraco.Core internal static int[] GetTopologicalSortOrder(List> fields) where T : class { - var g = new TopologicalSorter(fields.Count()); + var g = new TopologicalSorter(fields.Count); var indexes = new Dictionary(); //add vertices - for (int i = 0; i < fields.Count(); i++) + for (var i = 0; i < fields.Count; i++) { indexes[fields[i].Alias.ToLowerInvariant()] = g.AddVertex(i); } //add edges - for (int i = 0; i < fields.Count; i++) + for (var i = 0; i < fields.Count; i++) { - if (fields[i].DependsOn != null) - { - for (int j = 0; j < fields[i].DependsOn.Length; j++) - { - if (indexes.ContainsKey(fields[i].DependsOn[j].ToLowerInvariant()) == false) - throw new IndexOutOfRangeException( - string.Format( - "The alias '{0}' has an invalid dependency. The dependency '{1}' does not exist in the list of aliases", - fields[i], fields[i].DependsOn[j])); + if (fields[i].DependsOn == null) continue; - g.AddEdge(i, indexes[fields[i].DependsOn[j].ToLowerInvariant()]); - } + for (var j = 0; j < fields[i].DependsOn.Length; j++) + { + if (indexes.ContainsKey(fields[i].DependsOn[j].ToLowerInvariant()) == false) + throw new IndexOutOfRangeException( + $"The alias '{fields[i]}' has an invalid dependency. The dependency '{fields[i].DependsOn[j]}' does not exist in the list of aliases"); + + g.AddEdge(i, indexes[fields[i].DependsOn[j].ToLowerInvariant()]); } } - int[] result = g.Sort(); + var result = g.Sort(); return result; } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index d2d3dc456d..c83be03233 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -109,6 +109,15 @@ + + + + + + + + + @@ -208,7 +217,7 @@ - + @@ -321,7 +330,6 @@ - @@ -718,8 +726,6 @@ - - @@ -1123,7 +1129,7 @@ - + @@ -1137,10 +1143,7 @@ - - - diff --git a/src/Umbraco.Core/UmbracoApplicationBase.cs b/src/Umbraco.Core/UmbracoApplicationBase.cs index fe0a13809c..734c3a2ffc 100644 --- a/src/Umbraco.Core/UmbracoApplicationBase.cs +++ b/src/Umbraco.Core/UmbracoApplicationBase.cs @@ -10,20 +10,21 @@ using Umbraco.Core.Logging; namespace Umbraco.Core { - /// - /// The abstract class for the Umbraco HttpApplication + /// Provides an abstract base class for the Umbraco HttpApplication. /// /// - /// This is exposed in the core so that we can have the IApplicationEventHandler in the core project so that - /// IApplicationEventHandler's can fire/execute outside of the web contenxt (i.e. in console applications) + /// This is exposed in Core so that we can have the IApplicationEventHandler in the core project so that + /// IApplicationEventHandler's can fire/execute outside of the web contenxt (i.e. in console applications). fixme wtf? /// public abstract class UmbracoApplicationBase : HttpApplication { + private IRuntime _runtime; + /// - /// Gets a boot manager. + /// Gets a runtime. /// - protected abstract IBootManager GetBootManager(); + protected abstract IRuntime GetRuntime(); /// /// Gets a logger. @@ -33,11 +34,17 @@ namespace Umbraco.Core return Logger.CreateWithDefaultLog4NetConfiguration(); } - /// - /// Boots up the Umbraco application. - /// - internal void StartApplication(object sender, EventArgs e) + #region Start + + // fixme? dont make much sense! + public event EventHandler ApplicationStarting; + public event EventHandler ApplicationStarted; + + // internal for tests + internal void HandleApplicationStart(object sender, EventArgs evargs) { + // NOTE: THIS IS WHERE EVERYTHING BEGINS! + // create the container for the application, and configure. // the boot manager is responsible for registrations var container = new ServiceContainer(); @@ -48,6 +55,7 @@ namespace Umbraco.Core // (profiler etc depend on boot manager) var logger = GetLogger(); container.RegisterInstance(logger); + // now it is ok to use Current.Logger // take care of unhandled exceptions - there is nothing we can do to // prevent the entire w3wp process to go down but at least we can try @@ -59,71 +67,170 @@ namespace Umbraco.Core var msg = "Unhandled exception in AppDomain"; if (isTerminating) msg += " (terminating)"; + msg += "."; logger.Error(msg, exception); }; - // boot - GetBootManager() + // get runtime & boot + _runtime = GetRuntime(); + _runtime.Boot(container); + + // this is extra that should get removed + _runtime .Initialize() - .Startup(appContext => OnApplicationStarting(sender, e)) - .Complete(appContext => OnApplicationStarted(sender, e)); + .Startup(appContext => OnApplicationStarting(sender, evargs)) + .Complete(appContext => OnApplicationStarted(sender, evargs)); } - #region Events - - public event EventHandler ApplicationStarting; - public event EventHandler ApplicationStarted; - - /// - /// Called when the HttpApplication.Init() is fired, allows developers to subscribe to the HttpApplication events - /// - /// - /// Needs to be static otherwise null refs occur - though I don't know why FIXME wtf? - /// - public static event EventHandler ApplicationInit; - public static event EventHandler ApplicationError; - public static event EventHandler ApplicationEnd; - - - /// - /// Initializes the Umbraco application - /// - /// - /// - protected void Application_Start(object sender, EventArgs e) + // called by ASP.NET (auto event wireup) once per app domain + // do NOT set instance data here - only static (see docs) + // sender is System.Web.HttpApplicationFactory, evargs is EventArgs.Empty + protected void Application_Start(object sender, EventArgs evargs) { Thread.CurrentThread.SanitizeThreadCulture(); - StartApplication(sender, e); + HandleApplicationStart(sender, evargs); } - /// - /// Override init and raise the event - /// - /// - /// DID YOU KNOW? The Global.asax Init call is the thing that initializes all of the httpmodules, ties up a bunch of stuff with IIS, etc... - /// Therefore, since OWIN is an HttpModule when running in IIS/ASP.Net the OWIN startup is not executed until this method fires and by that - /// time, Umbraco has performed it's bootup sequence. - /// + #endregion + + #region Init + + // this event can only be static since there will be several instances of this class + public static event EventHandler ApplicationInit; + + private void OnApplicationInit(object sender, EventArgs evargs) + { + try + { + ApplicationInit?.Invoke(sender, evargs); + } + catch (Exception ex) + { + Current.Logger.Error("Exception in an ApplicationInit event handler.", ex); + throw; + } + } + + // called by ASP.NET for every HttpApplication instance after all modules have been created + // which means that this will be called *many* times for different apps when Umbraco runs public override void Init() { + // note: base.Init() is what initializes all of the httpmodules, ties up a bunch of stuff with IIS, etc... + // therefore, since OWIN is an HttpModule when running in IIS/ASP.Net the OWIN startup is not executed + // until this method fires and by that time - Umbraco has booted already + base.Init(); OnApplicationInit(this, new EventArgs()); } + #endregion + + #region End + + // this event can only be static since there will be several instances of this class + public static event EventHandler ApplicationEnd; + + protected virtual void OnApplicationEnd(object sender, EventArgs evargs) + { + ApplicationEnd?.Invoke(this, EventArgs.Empty); + } + + // internal for tests + internal void HandleApplicationEnd() + { + if (_runtime != null) + { + _runtime.Terminate(); + _runtime.DisposeIfDisposable(); + _runtime = null; + } + + if (SystemUtilities.GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted) return; + + // try to log the detailed shutdown message (typical asp.net hack: http://weblogs.asp.net/scottgu/433194) + try + { + var runtime = (HttpRuntime) typeof(HttpRuntime).InvokeMember("_theRuntime", + BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.GetField, + null, null, null); + if (runtime == null) + return; + + var shutDownMessage = (string)runtime.GetType().InvokeMember("_shutDownMessage", + BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField, + null, runtime, null); + + var shutDownStack = (string)runtime.GetType().InvokeMember("_shutDownStack", + BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField, + null, runtime, null); + + var shutdownMsg = $"Application shutdown. Details: {HostingEnvironment.ShutdownReason}\r\n\r\n_shutDownMessage={shutDownMessage}\r\n\r\n_shutDownStack={shutDownStack}"; + + Current.Logger.Info(shutdownMsg); + } + catch (Exception) + { + //if for some reason that fails, then log the normal output + Current.Logger.Info("Application shutdown. Reason: " + HostingEnvironment.ShutdownReason); + } + } + + // called by ASP.NET (auto event wireup) once per app domain + // sender is System.Web.HttpApplicationFactory, evargs is EventArgs.Empty + protected void Application_End(object sender, EventArgs evargs) + { + HandleApplicationEnd(); + OnApplicationEnd(sender, evargs); + LogManager.Shutdown(); + } + + #endregion + + #region Error + + // this event can only be static since there will be several instances of this class + public static event EventHandler ApplicationError; + + protected virtual void OnApplicationError(object sender, EventArgs evargs) + { + ApplicationError?.Invoke(this, EventArgs.Empty); + } + + private void HandleApplicationError() + { + var exception = Server.GetLastError(); + + // ignore HTTP errors + if (exception.GetType() == typeof(HttpException)) return; + + Current.Logger.Error("An unhandled exception occurred.", exception); + } + + // called by ASP.NET (auto event wireup) at any phase in the application life cycle + protected void Application_Error(object sender, EventArgs e) + { + // when unhandled errors occur + HandleApplicationError(); + OnApplicationError(sender, e); + } + + #endregion + + /// /// Developers can override this method to modify objects on startup /// /// - /// - protected virtual void OnApplicationStarting(object sender, EventArgs e) + /// + protected virtual void OnApplicationStarting(object sender, EventArgs evargs) { try { - ApplicationStarting?.Invoke(sender, e); + ApplicationStarting?.Invoke(sender, evargs); } catch (Exception ex) { - LogHelper.Error("An error occurred in an ApplicationStarting event handler", ex); + Current.Logger.Error("An error occurred in an ApplicationStarting event handler", ex); throw; } } @@ -132,125 +239,18 @@ namespace Umbraco.Core /// Developers can override this method to do anything they need to do once the application startup routine is completed. /// /// - /// - protected virtual void OnApplicationStarted(object sender, EventArgs e) + /// + protected virtual void OnApplicationStarted(object sender, EventArgs evargs) { try { - ApplicationStarted?.Invoke(sender, e); + ApplicationStarted?.Invoke(sender, evargs); } catch (Exception ex) { - LogHelper.Error("An error occurred in an ApplicationStarted event handler", ex); + Current.Logger.Error("An error occurred in an ApplicationStarted event handler", ex); throw; } } - - /// - /// Called to raise the ApplicationInit event - /// - /// - /// - private void OnApplicationInit(object sender, EventArgs e) - { - try - { - ApplicationInit?.Invoke(sender, e); - } - catch (Exception ex) - { - LogHelper.Error("An error occurred in an ApplicationInit event handler", ex); - throw; - } - } - - /// - /// A method that can be overridden to invoke code when the application has an error. - /// - /// - /// - protected virtual void OnApplicationError(object sender, EventArgs e) - { - ApplicationError?.Invoke(this, EventArgs.Empty); - } - - protected void Application_Error(object sender, EventArgs e) - { - // Code that runs when an unhandled error occurs - - // Get the exception object. - var exc = Server.GetLastError(); - - // Ignore HTTP errors - if (exc.GetType() == typeof(HttpException)) - { - return; - } - - Current.Logger.Error("An unhandled exception occurred", exc); - - OnApplicationError(sender, e); - } - - /// - /// A method that can be overridden to invoke code when the application shuts down. - /// - /// - /// - protected virtual void OnApplicationEnd(object sender, EventArgs e) - { - ApplicationEnd?.Invoke(this, EventArgs.Empty); - } - - protected void Application_End(object sender, EventArgs e) - { - if (SystemUtilities.GetCurrentTrustLevel() == AspNetHostingPermissionLevel.Unrestricted) - { - //Try to log the detailed shutdown message (typical asp.net hack: http://weblogs.asp.net/scottgu/433194) - try - { - var runtime = (HttpRuntime)typeof(HttpRuntime).InvokeMember("_theRuntime", - BindingFlags.NonPublic - | BindingFlags.Static - | BindingFlags.GetField, - null, - null, - null); - if (runtime == null) - return; - - var shutDownMessage = (string)runtime.GetType().InvokeMember("_shutDownMessage", - BindingFlags.NonPublic - | BindingFlags.Instance - | BindingFlags.GetField, - null, - runtime, - null); - - var shutDownStack = (string)runtime.GetType().InvokeMember("_shutDownStack", - BindingFlags.NonPublic - | BindingFlags.Instance - | BindingFlags.GetField, - null, - runtime, - null); - - var shutdownMsg = $"{HostingEnvironment.ShutdownReason}\r\n\r\n_shutDownMessage={shutDownMessage}\r\n\r\n_shutDownStack={shutDownStack}"; - - Current.Logger.Info("Application shutdown. Details: " + shutdownMsg); - } - catch (Exception) - { - //if for some reason that fails, then log the normal output - Current.Logger.Info("Application shutdown. Reason: " + HostingEnvironment.ShutdownReason); - } - } - OnApplicationEnd(sender, e); - - // last thing to do is shutdown log4net - LogManager.Shutdown(); - } - - #endregion } } diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs new file mode 100644 index 0000000000..8415755ebe --- /dev/null +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using LightInject; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Components; +using Umbraco.Core.DependencyInjection; +using Umbraco.Core.Logging; + +namespace Umbraco.Tests.Components +{ + [TestFixture] + public class ComponentTests + { + private static readonly List Composed = new List(); + private static readonly List Initialized = new List(); + + [TearDown] + public void TearDown() + { + Current.Reset(); + } + + [Test] + public void Boot() + { + var container = new ServiceContainer(); + container.ConfigureUmbracoCore(); + + var logger = Mock.Of(); + var profiler = new LogProfiler(logger); + container.RegisterInstance(logger); + container.RegisterInstance(profiler); + container.RegisterInstance(new ProfilingLogger(logger, profiler)); + + var thing = new BootLoader(container); + Composed.Clear(); + thing.Boot(new [] { typeof (Component1), typeof (Component2), typeof (Component3), typeof (Component4) }); + Assert.AreEqual(4, Composed.Count); + Assert.AreEqual(typeof(Component1), Composed[0]); + Assert.AreEqual(typeof(Component4), Composed[1]); + Assert.AreEqual(typeof(Component2), Composed[2]); + Assert.AreEqual(typeof(Component3), Composed[3]); + } + + [Test] + public void BrokenDependency() + { + var container = new ServiceContainer(); + container.ConfigureUmbracoCore(); + + var logger = Mock.Of(); + var profiler = new LogProfiler(logger); + container.RegisterInstance(logger); + container.RegisterInstance(profiler); + container.RegisterInstance(new ProfilingLogger(logger, profiler)); + + var thing = new BootLoader(container); + Composed.Clear(); + try + { + thing.Boot(new[] { typeof(Component1), typeof(Component2), typeof(Component3) }); + Assert.Fail("Expected exception."); + } + catch (Exception e) + { + Assert.AreEqual("Broken component dependency: Umbraco.Tests.Components.ComponentTests+Component2 -> Umbraco.Tests.Components.ComponentTests+Component4.", e.Message); + } + } + + [Test] + public void Initialize() + { + var container = new ServiceContainer(); + container.ConfigureUmbracoCore(); + + container.Register(); + + var logger = Mock.Of(); + var profiler = new LogProfiler(logger); + container.RegisterInstance(logger); + container.RegisterInstance(profiler); + container.RegisterInstance(new ProfilingLogger(logger, profiler)); + + var thing = new BootLoader(container); + Composed.Clear(); + thing.Boot(new[] { typeof(Component1), typeof(Component5) }); + Assert.AreEqual(2, Composed.Count); + Assert.AreEqual(typeof(Component1), Composed[0]); + Assert.AreEqual(typeof(Component5), Composed[1]); + Assert.AreEqual(1, Initialized.Count); + Assert.AreEqual("Umbraco.Tests.Components.ComponentTests+SomeResource", Initialized[0]); + } + + public class Component1 : UmbracoComponentBase + { + public override void Compose(ServiceContainer container) + { + base.Compose(container); + Composed.Add(GetType()); + } + } + + [RequireComponent(typeof(Component4))] + public class Component2 : UmbracoComponentBase, IUmbracoCoreComponent + { + public override void Compose(ServiceContainer container) + { + base.Compose(container); + Composed.Add(GetType()); + } + } + + public class Component3 : UmbracoComponentBase, IUmbracoUserComponent + { + public override void Compose(ServiceContainer container) + { + base.Compose(container); + Composed.Add(GetType()); + } + } + + public class Component4 : UmbracoComponentBase + { + public override void Compose(ServiceContainer container) + { + base.Compose(container); + Composed.Add(GetType()); + } + } + + public class Component5 : UmbracoComponentBase + { + public override void Compose(ServiceContainer container) + { + base.Compose(container); + Composed.Add(GetType()); + } + + public void Initialize(ISomeResource resource) + { + Initialized.Add(resource.GetType().FullName); + } + } + + public interface ISomeResource { } + + public class SomeResource : ISomeResource { } + } +} diff --git a/src/Umbraco.Tests/Resolvers/ActionCollectionTests.cs b/src/Umbraco.Tests/DependencyInjection/ActionCollectionTests.cs similarity index 95% rename from src/Umbraco.Tests/Resolvers/ActionCollectionTests.cs rename to src/Umbraco.Tests/DependencyInjection/ActionCollectionTests.cs index dc75cd0431..755588b7b8 100644 --- a/src/Umbraco.Tests/Resolvers/ActionCollectionTests.cs +++ b/src/Umbraco.Tests/DependencyInjection/ActionCollectionTests.cs @@ -1,15 +1,10 @@ using System.Linq; -using LightInject; -using Moq; using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.ObjectResolution; -using Umbraco.Web.UI.Pages; using Umbraco.Web; +using Umbraco.Web.UI.Pages; using Umbraco.Web._Legacy.Actions; -namespace Umbraco.Tests.Resolvers +namespace Umbraco.Tests.DependencyInjection { [TestFixture] public class ActionCollectionTests : ResolverBaseTest diff --git a/src/Umbraco.Tests/Resolvers/CollectionBuildersTests.cs b/src/Umbraco.Tests/DependencyInjection/CollectionBuildersTests.cs similarity index 99% rename from src/Umbraco.Tests/Resolvers/CollectionBuildersTests.cs rename to src/Umbraco.Tests/DependencyInjection/CollectionBuildersTests.cs index 4fbf66aa96..b2e4efef63 100644 --- a/src/Umbraco.Tests/Resolvers/CollectionBuildersTests.cs +++ b/src/Umbraco.Tests/DependencyInjection/CollectionBuildersTests.cs @@ -5,7 +5,7 @@ using LightInject; using NUnit.Framework; using Umbraco.Core.DependencyInjection; -namespace Umbraco.Tests.Resolvers +namespace Umbraco.Tests.DependencyInjection { [TestFixture] public class CollectionBuildersTests diff --git a/src/Umbraco.Tests/Resolvers/LazyCollectionBuilderTests.cs b/src/Umbraco.Tests/DependencyInjection/LazyCollectionBuilderTests.cs similarity index 99% rename from src/Umbraco.Tests/Resolvers/LazyCollectionBuilderTests.cs rename to src/Umbraco.Tests/DependencyInjection/LazyCollectionBuilderTests.cs index fcaac5a80c..d7186d8b67 100644 --- a/src/Umbraco.Tests/Resolvers/LazyCollectionBuilderTests.cs +++ b/src/Umbraco.Tests/DependencyInjection/LazyCollectionBuilderTests.cs @@ -6,7 +6,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.DependencyInjection; -namespace Umbraco.Tests.Resolvers +namespace Umbraco.Tests.DependencyInjection { [TestFixture] public class LazyCollectionBuilderTests diff --git a/src/Umbraco.Tests/Resolvers/PackageActionsResolverTests.cs b/src/Umbraco.Tests/DependencyInjection/PackageActionCollectionTests.cs similarity index 89% rename from src/Umbraco.Tests/Resolvers/PackageActionsResolverTests.cs rename to src/Umbraco.Tests/DependencyInjection/PackageActionCollectionTests.cs index 3e3bebb507..a916f40a98 100644 --- a/src/Umbraco.Tests/Resolvers/PackageActionsResolverTests.cs +++ b/src/Umbraco.Tests/DependencyInjection/PackageActionCollectionTests.cs @@ -1,90 +1,88 @@ -using System; -using System.Linq; -using System.Xml; -using LightInject; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Plugins; -using Umbraco.Core.ObjectResolution; -using Umbraco.Core._Legacy.PackageActions; - -namespace Umbraco.Tests.Resolvers -{ - [TestFixture] - public class PackageActionsResolverTests : ResolverBaseTest - { - // NOTE - // ManyResolverTests ensure that we'll get our actions back and PackageActionResolver works, - // so all we're testing here is that plugin manager _does_ find our package actions - // which should be ensured by PlugingManagerTests anyway, so this is useless? - [Test] - public void FindAllPackageActions() - { - var container = new ServiceContainer(); - container.ConfigureUmbracoCore(); - - PackageActionCollectionBuilder.Register(container) - .AddProducer(() => PluginManager.ResolvePackageActions()); - - var actions = Current.PackageActions; - Assert.AreEqual(2, actions.Count()); - - // order is unspecified, but both must be there - bool hasAction1 = actions.ElementAt(0) is PackageAction1 || actions.ElementAt(1) is PackageAction1; - bool hasAction2 = actions.ElementAt(0) is PackageAction2 || actions.ElementAt(1) is PackageAction2; - Assert.IsTrue(hasAction1); - Assert.IsTrue(hasAction2); - } - - #region Classes for tests - - public class PackageAction1 : IPackageAction - { - public bool Execute(string packageName, XmlNode xmlData) - { - throw new NotImplementedException(); - } - - public string Alias() - { - return "pa1"; - } - - public bool Undo(string packageName, XmlNode xmlData) - { - throw new NotImplementedException(); - } - - public XmlNode SampleXml() - { - throw new NotImplementedException(); - } - } - - public class PackageAction2 : IPackageAction - { - public bool Execute(string packageName, XmlNode xmlData) - { - throw new NotImplementedException(); - } - - public string Alias() - { - return "pa2"; - } - - public bool Undo(string packageName, XmlNode xmlData) - { - throw new NotImplementedException(); - } - - public XmlNode SampleXml() - { - throw new NotImplementedException(); - } - } - - #endregion - } +using System; +using System.Linq; +using System.Xml; +using LightInject; +using NUnit.Framework; +using Umbraco.Core.DependencyInjection; +using Umbraco.Core.Plugins; +using Umbraco.Core._Legacy.PackageActions; + +namespace Umbraco.Tests.DependencyInjection +{ + [TestFixture] + public class PackageActionCollectionTests : ResolverBaseTest + { + // NOTE + // ManyResolverTests ensure that we'll get our actions back and PackageActionResolver works, + // so all we're testing here is that plugin manager _does_ find our package actions + // which should be ensured by PlugingManagerTests anyway, so this is useless? + [Test] + public void FindAllPackageActions() + { + var container = new ServiceContainer(); + container.ConfigureUmbracoCore(); + + PackageActionCollectionBuilder.Register(container) + .AddProducer(() => PluginManager.ResolvePackageActions()); + + var actions = Current.PackageActions; + Assert.AreEqual(2, actions.Count()); + + // order is unspecified, but both must be there + bool hasAction1 = actions.ElementAt(0) is PackageAction1 || actions.ElementAt(1) is PackageAction1; + bool hasAction2 = actions.ElementAt(0) is PackageAction2 || actions.ElementAt(1) is PackageAction2; + Assert.IsTrue(hasAction1); + Assert.IsTrue(hasAction2); + } + + #region Classes for tests + + public class PackageAction1 : IPackageAction + { + public bool Execute(string packageName, XmlNode xmlData) + { + throw new NotImplementedException(); + } + + public string Alias() + { + return "pa1"; + } + + public bool Undo(string packageName, XmlNode xmlData) + { + throw new NotImplementedException(); + } + + public XmlNode SampleXml() + { + throw new NotImplementedException(); + } + } + + public class PackageAction2 : IPackageAction + { + public bool Execute(string packageName, XmlNode xmlData) + { + throw new NotImplementedException(); + } + + public string Alias() + { + return "pa2"; + } + + public bool Undo(string packageName, XmlNode xmlData) + { + throw new NotImplementedException(); + } + + public XmlNode SampleXml() + { + throw new NotImplementedException(); + } + } + + #endregion + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Resolvers/ResolverBaseTest.cs b/src/Umbraco.Tests/DependencyInjection/ResolverBaseTest.cs similarity index 73% rename from src/Umbraco.Tests/Resolvers/ResolverBaseTest.cs rename to src/Umbraco.Tests/DependencyInjection/ResolverBaseTest.cs index bb30d46883..2748ab83a5 100644 --- a/src/Umbraco.Tests/Resolvers/ResolverBaseTest.cs +++ b/src/Umbraco.Tests/DependencyInjection/ResolverBaseTest.cs @@ -2,18 +2,15 @@ using System.Collections.Generic; using System.Reflection; using Moq; using NUnit.Framework; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Plugins; -using Umbraco.Core.Profiling; -using Umbraco.Core._Legacy.PackageActions; -namespace Umbraco.Tests.Resolvers +namespace Umbraco.Tests.DependencyInjection { - public abstract class ResolverBaseTest + public abstract class ResolverBaseTest // fixme rename, do something! { protected PluginManager PluginManager { get; private set; } protected ProfilingLogger ProfilingLogger { get; private set; } @@ -38,15 +35,10 @@ namespace Umbraco.Tests.Resolvers Current.Reset(); } - protected virtual IEnumerable AssembliesToScan - { - get + protected virtual IEnumerable AssembliesToScan + => new[] { - return new[] - { - this.GetType().Assembly // this assembly only - }; - } - } + GetType().Assembly // this assembly only + }; } } \ No newline at end of file diff --git a/src/Umbraco.Tests/DependencyInjection/TempTests.cs b/src/Umbraco.Tests/DependencyInjection/TempTests.cs index 88a62e4769..cf933ca3d2 100644 --- a/src/Umbraco.Tests/DependencyInjection/TempTests.cs +++ b/src/Umbraco.Tests/DependencyInjection/TempTests.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using LightInject; +using LightInject; using NUnit.Framework; using Umbraco.Core.DependencyInjection; using Umbraco.Web.Routing; diff --git a/src/Umbraco.Tests/Resolvers/XsltExtensionsResolverTests.cs b/src/Umbraco.Tests/DependencyInjection/XsltExtensionsResolverTests.cs similarity index 83% rename from src/Umbraco.Tests/Resolvers/XsltExtensionsResolverTests.cs rename to src/Umbraco.Tests/DependencyInjection/XsltExtensionsResolverTests.cs index 913e63acb0..a3d1c0b742 100644 --- a/src/Umbraco.Tests/Resolvers/XsltExtensionsResolverTests.cs +++ b/src/Umbraco.Tests/DependencyInjection/XsltExtensionsResolverTests.cs @@ -1,58 +1,49 @@ -using System.Linq; -using LightInject; -using Moq; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; -using Umbraco.Core.Macros; -using Umbraco.Core.ObjectResolution; -using Umbraco.Core.Profiling; -using Umbraco.Web; -using Umbraco.Web.Macros; -using umbraco; -using Umbraco.Web._Legacy.Actions; - -namespace Umbraco.Tests.Resolvers -{ - [TestFixture] - public class XsltExtensionsResolverTests : ResolverBaseTest - { - // NOTE - // ManyResolverTests ensure that we'll get our actions back and ActionsResolver works, - // so all we're testing here is that plugin manager _does_ find our actions - // which should be ensured by PlugingManagerTests anyway, so this is useless? - // maybe not as it seems to handle the "instance" thing... so we test that we respect the singleton? - [Test] - public void Find_All_Extensions() - { - var container = new ServiceContainer(); - var builder = new XsltExtensionCollectionBuilder(container); - builder.AddExtensionObjectProducer(() => PluginManager.ResolveXsltExtensions()); - var extensions = builder.CreateCollection(); - - Assert.AreEqual(3, extensions.Count()); - - Assert.IsTrue(extensions.Select(x => x.ExtensionObject.GetType()).Contains(typeof (XsltEx1))); - Assert.IsTrue(extensions.Select(x => x.ExtensionObject.GetType()).Contains(typeof(XsltEx2))); - Assert.AreEqual("test1", extensions.Single(x => x.ExtensionObject.GetType() == typeof(XsltEx1)).Namespace); - Assert.AreEqual("test2", extensions.Single(x => x.ExtensionObject.GetType() == typeof(XsltEx2)).Namespace); - } - - #region Classes for tests - - [Umbraco.Core.Macros.XsltExtension("test1")] - public class XsltEx1 - { - - } - - //test with legacy one - [umbraco.XsltExtension("test2")] - public class XsltEx2 - { - } - - #endregion - } +using System.Linq; +using LightInject; +using NUnit.Framework; +using Umbraco.Core.Macros; +using Umbraco.Web; + +namespace Umbraco.Tests.DependencyInjection +{ + [TestFixture] + public class XsltExtensionsResolverTests : ResolverBaseTest + { + // NOTE + // ManyResolverTests ensure that we'll get our actions back and ActionsResolver works, + // so all we're testing here is that plugin manager _does_ find our actions + // which should be ensured by PlugingManagerTests anyway, so this is useless? + // maybe not as it seems to handle the "instance" thing... so we test that we respect the singleton? + [Test] + public void Find_All_Extensions() + { + var container = new ServiceContainer(); + var builder = new XsltExtensionCollectionBuilder(container); + builder.AddExtensionObjectProducer(() => PluginManager.ResolveXsltExtensions()); + var extensions = builder.CreateCollection(); + + Assert.AreEqual(3, extensions.Count()); + + Assert.IsTrue(extensions.Select(x => x.ExtensionObject.GetType()).Contains(typeof (XsltEx1))); + Assert.IsTrue(extensions.Select(x => x.ExtensionObject.GetType()).Contains(typeof(XsltEx2))); + Assert.AreEqual("test1", extensions.Single(x => x.ExtensionObject.GetType() == typeof(XsltEx1)).Namespace); + Assert.AreEqual("test2", extensions.Single(x => x.ExtensionObject.GetType() == typeof(XsltEx2)).Namespace); + } + + #region Classes for tests + + [Umbraco.Core.Macros.XsltExtension("test1")] + public class XsltEx1 + { + + } + + //test with legacy one + [umbraco.XsltExtension("test2")] + public class XsltEx2 + { + } + + #endregion + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Resolvers/ResolutionTests.cs b/src/Umbraco.Tests/Resolvers/ResolutionTests.cs deleted file mode 100644 index 9dc3166616..0000000000 --- a/src/Umbraco.Tests/Resolvers/ResolutionTests.cs +++ /dev/null @@ -1,264 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -using Umbraco.Core; -using Umbraco.Core.ObjectResolution; -using NUnit.Framework; - -namespace Umbraco.Tests.Resolvers -{ - [TestFixture] - public class ResolutionTests - { - [SetUp] - public void Setup() - { - } - - [TearDown] - public void TearDown() - { - BaseResolver.Reset(); - BaseResolver2.Reset(); - BaseResolver3.Reset(); - } - - #region Resolvers and Resolved - - class BaseResolver : ResolverBase - { } - - class BaseResolver2 : ResolverBase - { } - - class BaseResolver3 : ResolverBase - { } - - #endregion - - #region Test Resolution - - [Test] - public void ResolutionCanFreeze() - { - Resolution.Freeze(); - Assert.IsTrue(Resolution.IsFrozen); - } - - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void ResolutionCannotFreezeAgain() - { - Resolution.Freeze(); - Resolution.Freeze(); // throws - } - - [Test] - public void ResolutionCanReset() - { - Resolution.Freeze(); - Resolution.Reset(); - Assert.IsFalse(Resolution.IsFrozen); - } - - [Test] - public void ResolutionCanConfigureBeforeFreeze() - { - using (Resolution.Configuration) - { } - } - - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void ResolutionCannotConfigureOnceFrozen() - { - Resolution.Freeze(); - - using (Resolution.Configuration) // throws - { } - } - - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void ResolutionCanDetectIfNotFrozen() - { - using (Resolution.Reader()) // throws - {} - } - - [Test] - public void ResolutionCanEnsureIsFrozen() - { - Resolution.Freeze(); - using (Resolution.Reader()) // ok - {} - } - - [Test] - public void ResolutionFrozenEventTriggers() - { - int count = 0; - object copySender = null; - EventArgs copyArgs = null; - Resolution.Frozen += (sender, args) => - { - copySender = sender; - copyArgs = args; - count++; - }; - Assert.AreEqual(0, count); - Resolution.Freeze(); - - Assert.AreEqual(1, count); - Assert.IsNull(copySender); - Assert.IsNull(copyArgs); - } - - [Test] - public void ResolutionResetClearsFrozenEvent() - { - int count = 0; - Resolution.Frozen += (sender, args) => - { - count++; - }; - Resolution.Freeze(); - Assert.AreEqual(1, count); - - Resolution.Reset(); - - Resolution.Freeze(); - Assert.AreEqual(1, count); - } - - #endregion - - #region Test ResolverBase - - // unused - - [Test] - public void BaseResolverCurrentCanBeNullOnFreeze() - { - // does not throw, even though BaseResolver.Current is not initialized - Resolution.Freeze(); - } - - // set - - [Test] - public void BaseResolverCurrentCanSetBeforeFreeze() - { - BaseResolver.Current = new BaseResolver(); - } - - [Test] - [ExpectedException(typeof(ArgumentNullException))] - public void BaseResolverCurrentCannotSetToNull() - { - BaseResolver.Current = null; // throws - } - - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void BaseResolverCurrentCannotSetAgain() - { - BaseResolver.Current = new BaseResolver(); - - // cannot set again - BaseResolver.Current = new BaseResolver(); // throws - } - - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void BaseResolverCurrentCannotSetOnceFrozen() - { - Resolution.Freeze(); - - // cannot set once frozen - BaseResolver.Current = new BaseResolver(); // throws - } - - // get - - // ignore: see BaseResolverCurrentCanGetBeforeFreeze - //[Test] - [ExpectedException(typeof(InvalidOperationException))] - public void BaseResolverCurrentCannotGetBeforeFreeze() - { - BaseResolver.Current = new BaseResolver(); - - // cannot get before freeze - var resolver = BaseResolver.Current; // throws - } - - [Test] - public void BaseResolverCurrentCanGetBeforeFreeze() - { - BaseResolver.Current = new BaseResolver(); - var resolver = BaseResolver.Current; - } - - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void BaseResolverCurrentCannotGetIfNotSet() - { - Resolution.Freeze(); - - // cannot get before freeze - var resolver = BaseResolver.Current; // throws - } - - [Test] - public void BaseResolverCurrentCanGetOnceFrozen() - { - BaseResolver.Current = new BaseResolver(); - Resolution.Freeze(); - var resolver = BaseResolver.Current; - } - - #endregion - - [Test] - public void Resolver_Collection_Is_Updated() - { - BaseResolver.Current = new BaseResolver(); - BaseResolver2.Current = new BaseResolver2(); - BaseResolver3.Current = new BaseResolver3(); - Assert.AreEqual(3, ResolverCollection.Count); - } - - [Test] - public void Resolver_Collection_Is_Reset() - { - BaseResolver.Current = new BaseResolver(); - BaseResolver2.Current = new BaseResolver2(); - BaseResolver3.Current = new BaseResolver3(); - - ResolverCollection.ResetAll(); - - Assert.AreEqual(0, ResolverCollection.Count); - Assert.Throws(() => - { - var c = BaseResolver.Current; - }); - Assert.Throws(() => - { - var c = BaseResolver2.Current; - }); - Assert.Throws(() => - { - var c = BaseResolver3.Current; - }); - - //this should not error! - BaseResolver.Current = new BaseResolver(); - BaseResolver2.Current = new BaseResolver2(); - BaseResolver3.Current = new BaseResolver3(); - - Assert.Pass(); - } - } -} diff --git a/src/Umbraco.Tests/Resolvers/SingleResolverTests.cs b/src/Umbraco.Tests/Resolvers/SingleResolverTests.cs deleted file mode 100644 index c97360307c..0000000000 --- a/src/Umbraco.Tests/Resolvers/SingleResolverTests.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -using Umbraco.Core; -using Umbraco.Core.ObjectResolution; -using NUnit.Framework; - -namespace Umbraco.Tests.Resolvers -{ - [TestFixture] - public class SingleResolverTests - { - [SetUp] - public void Setup() - { - SingleResolver.Reset(); - } - - [TearDown] - public void TearDown() - { - SingleResolver.Reset(); - } - - #region Resolvers and Resolved - - public class Resolved - { } - - public sealed class SingleResolver : SingleObjectResolverBase - { - public SingleResolver() - : base() - { } - - public SingleResolver(bool canBeNull) - : base(canBeNull) - { } - - public SingleResolver(Resolved value) - : base(value) - { } - - public SingleResolver(Resolved value, bool canBeNull) - : base(value, canBeNull) - { } - - public Resolved Resolved { get { return Value; } set { Value = value; } } - } - - #endregion - - #region Test SingleResolver - - [Test] - public void SingleResolverCanSetValueBeforeFreeze() - { - var resolver = new SingleResolver(); - resolver.Resolved = new Resolved(); - } - - [Test] // is this normal? - public void SingleResolverCanInitializeWithNullValueEvenIfCannotBeNull() - { - var resolver = new SingleResolver(null, false); - } - - [Test] - [ExpectedException(typeof(ArgumentNullException))] - public void SingleResolverCannotSetValueToNullIfCannotBeNull() - { - var resolver = new SingleResolver(); - resolver.Resolved = null; // throws - } - - [Test] - public void SingleResolverCanSetValueToNullIfCanBeNull() - { - var resolver = new SingleResolver(true); - resolver.Resolved = null; - } - - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void SingleResolverCannotSetValueOnceFrozen() - { - var resolver = new SingleResolver(); - Resolution.Freeze(); - resolver.Resolved = new Resolved(); // throws - } - - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void SingleResolverCannotGetValueBeforeFreeze() - { - var resolver = new SingleResolver(); - resolver.Resolved = new Resolved(); - var resolved = resolver.Resolved; // throws - } - - [Test] - public void SingleResolverCanGetValueOnceFrozen() - { - var resolver = new SingleResolver(); - var resolved = resolver.Resolved = new Resolved(); - Resolution.Freeze(); - Assert.AreSame(resolved, resolver.Resolved); - } - - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void SingleResolverCannotGetNullValueIfCannotBeNull() - { - var resolver = new SingleResolver(); - Resolution.Freeze(); - var resolved = resolver.Resolved; // throws - } - - [Test] - public void SingleResolverCanGetNullValueIfCanBeNull() - { - var resolver = new SingleResolver(true); - Resolution.Freeze(); - Assert.IsNull(resolver.Resolved); - } - - #endregion - } -} diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index b25d6bfa55..c4fbc775f3 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -29,7 +29,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.UmbracoPath = "~/umbraco"; - var webBoot = new WebBootManager(new UmbracoApplication(), new ProfilingLogger(Mock.Of(), Mock.Of()), true); + var webBoot = new WebRuntime(new UmbracoApplication(), new ProfilingLogger(Mock.Of(), Mock.Of()), true); //webBoot.Initialize(); //webBoot.Startup(null); -> don't call startup, we don't want any other application event handlers to bind for this test. //webBoot.Complete(null); diff --git a/src/Umbraco.Tests/BootManagers/CoreBootManagerTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs similarity index 57% rename from src/Umbraco.Tests/BootManagers/CoreBootManagerTests.cs rename to src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs index 49bf42d9e0..612ea9d54d 100644 --- a/src/Umbraco.Tests/BootManagers/CoreBootManagerTests.cs +++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs @@ -1,172 +1,218 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web; -using Examine; -using LightInject; -using Moq; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Tests.TestHelpers; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Persistence; -using Umbraco.Core.Profiling; -using Umbraco.Core.Services; -using UmbracoExamine; - -namespace Umbraco.Tests.BootManagers -{ - [TestFixture] - public class CoreBootManagerTests : BaseUmbracoConfigurationTest - { - [TearDown] - public override void TearDown() - { - base.TearDown(); - - ResolverCollection.ResetAll(); - TestApplicationEventHandler.Reset(); - Resolution.Reset(); - - Current.Reset(); - } - - - /// - /// test application using a CoreBootManager instance to boot - /// - public class TestApp : UmbracoApplicationBase - { - private readonly ILogger _logger = Mock.Of(); - - protected override IBootManager GetBootManager() - { - return new TestBootManager(this, new ProfilingLogger(_logger, Mock.Of())); - } - - protected override ILogger GetLogger() - { - return _logger; - } - } - - /// - /// Test boot manager to add a custom application event handler - /// - public class TestBootManager : CoreBootManager - { - public TestBootManager(UmbracoApplicationBase umbracoApplication, ProfilingLogger logger) - : base(umbracoApplication, logger) - { } - - internal override void ConfigureCoreServices(ServiceContainer container) - { - base.ConfigureCoreServices(container); - - container.Register(factory => SettingsForTests.GetDefault()); - container.Register(factory => new DatabaseContext( - factory.GetInstance(), - factory.GetInstance()), new PerContainerLifetime()); - container.RegisterSingleton(); - } - } - - /// - /// test event handler - /// - public class TestApplicationEventHandler : DisposableObject, IApplicationEventHandler - { - public static void Reset() - { - Initialized = false; - Starting = false; - Started = false; - HasBeenDisposed = false; - } - - public static bool Initialized; - public static bool Starting; - public static bool Started; - public static bool HasBeenDisposed; - - public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - Initialized = true; - } - - public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - Starting = true; - } - - public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - Started = true; - } - - protected override void DisposeResources() - { - HasBeenDisposed = true; - } - } - - [Test] - public void Disposes_App_Startup_Handlers_After_Startup() - { - using (var app = new TestApp()) - { - app.StartApplication(app, new EventArgs()); - - Assert.IsTrue(TestApplicationEventHandler.HasBeenDisposed); - } - } - - [Test] - public void Handle_IApplicationEventHandler_Objects_Outside_Web_Context() - { - using (var app = new TestApp()) - { - app.StartApplication(app, new EventArgs()); - - Assert.IsTrue(TestApplicationEventHandler.Initialized); - Assert.IsTrue(TestApplicationEventHandler.Starting); - Assert.IsTrue(TestApplicationEventHandler.Started); - } - } - - [Test] - public void Raises_Starting_Events() - { - using (var app = new TestApp()) - { - EventHandler starting = (sender, args) => - { - Assert.IsTrue(TestApplicationEventHandler.Initialized); - Assert.IsTrue(TestApplicationEventHandler.Starting); - Assert.IsFalse(TestApplicationEventHandler.Started); - }; - EventHandler started = (sender, args) => - { - Assert.IsTrue(TestApplicationEventHandler.Initialized); - Assert.IsTrue(TestApplicationEventHandler.Starting); - Assert.IsTrue(TestApplicationEventHandler.Started); - }; - - app.ApplicationStarting += starting; - app.ApplicationStarted += started; - - app.StartApplication(app, new EventArgs()); - - app.ApplicationStarting -= starting; - app.ApplicationStarting -= started; - } - } - - } -} +using System; +using System.Collections.Generic; +using LightInject; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Components; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.DependencyInjection; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Tests.TestHelpers; +using UmbracoExamine; + +namespace Umbraco.Tests.Runtimes +{ + [TestFixture] + public class CoreRuntimeTests : BaseUmbracoConfigurationTest + { + [TearDown] // fixme TearDown is INHERITED + public override void TearDown() + { + base.TearDown(); + + TestApplicationEventHandler.Reset(); + + Current.Reset(); + } + + // test application + public class TestUmbracoApplication : UmbracoApplicationBase + { + private readonly ILogger _logger = Mock.Of(); + + protected override IRuntime GetRuntime() + { + return new TestRuntime(this, new ProfilingLogger(_logger, Mock.Of())); + } + + protected override ILogger GetLogger() + { + return _logger; + } + } + + // test runtime - inheriting from core runtime + public class TestRuntime : CoreRuntime + { + public TestRuntime(UmbracoApplicationBase umbracoApplication, ProfilingLogger logger) + : base(umbracoApplication, logger) + { } + + protected override void Compose1(ServiceContainer container) + { + base.Compose1(container); + + container.Register(factory => SettingsForTests.GetDefault()); + container.Register(factory => new DatabaseContext( + factory.GetInstance(), + factory.GetInstance()), new PerContainerLifetime()); + container.RegisterSingleton(); + } + + protected override IEnumerable GetComponentTypes() + { + return new[] { typeof(TestComponent) }; + } + } + + public class TestComponent : UmbracoComponentBase + { + // test + public static bool Ctored; + public static bool Composed; + public static bool Initialized1; + public static bool Initialized2; + public static bool Terminated; + + public TestComponent() + : base() + { + Ctored = true; + } + + public override void Compose(ServiceContainer container) + { + base.Compose(container); + Composed = true; + } + + public void Initialize() + { + Initialized1 = true; + } + + public void Initialize(ILogger logger) + { + Initialized2 = true; + } + + public override void Terminate() + { + base.Terminate(); + Terminated = true; + } + } + + // test all event handler + // fixme should become test component + public class TestApplicationEventHandler : DisposableObject, IApplicationEventHandler + { + public static void Reset() + { + Initialized = false; + Starting = false; + Started = false; + HasBeenDisposed = false; + } + + public static bool Initialized; + public static bool Starting; + public static bool Started; + public static bool HasBeenDisposed; + + public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + Initialized = true; + } + + public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + Starting = true; + } + + public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + Started = true; + } + + protected override void DisposeResources() + { + HasBeenDisposed = true; + } + } + + [Test] + public void ComponentLifeCycle() + { + using (var app = new TestUmbracoApplication()) + { + app.HandleApplicationStart(app, new EventArgs()); + + Assert.IsTrue(TestComponent.Ctored); + Assert.IsTrue(TestComponent.Composed); + Assert.IsTrue(TestComponent.Initialized1); + Assert.IsTrue(TestComponent.Initialized2); + + Assert.IsFalse(TestComponent.Terminated); + + app.HandleApplicationEnd(); + Assert.IsTrue(TestComponent.Terminated); + } + } + + // note: components are NOT disposed after boot + [Test] + public void Disposes_App_Startup_Handlers_After_Startup() + { + using (var app = new TestUmbracoApplication()) + { + app.HandleApplicationStart(app, new EventArgs()); + + Assert.IsTrue(TestApplicationEventHandler.HasBeenDisposed); + } + } + + [Test] + public void Handle_IApplicationEventHandler_Objects_Outside_Web_Context() + { + using (var app = new TestUmbracoApplication()) + { + app.HandleApplicationStart(app, new EventArgs()); + + Assert.IsTrue(TestApplicationEventHandler.Initialized); + Assert.IsTrue(TestApplicationEventHandler.Starting); + Assert.IsTrue(TestApplicationEventHandler.Started); + } + } + + [Test] + public void Raises_Starting_Events() + { + using (var app = new TestUmbracoApplication()) + { + EventHandler starting = (sender, args) => + { + Assert.IsTrue(TestApplicationEventHandler.Initialized); + Assert.IsTrue(TestApplicationEventHandler.Starting); + Assert.IsFalse(TestApplicationEventHandler.Started); + }; + EventHandler started = (sender, args) => + { + Assert.IsTrue(TestApplicationEventHandler.Initialized); + Assert.IsTrue(TestApplicationEventHandler.Starting); + Assert.IsTrue(TestApplicationEventHandler.Started); + }; + + app.ApplicationStarting += starting; + app.ApplicationStarted += started; + + app.HandleApplicationStart(app, new EventArgs()); + + app.ApplicationStarting -= starting; + app.ApplicationStarting -= started; + } + } + } +} diff --git a/src/Umbraco.Tests/BootManagers/WebBootManagerTests.cs b/src/Umbraco.Tests/Runtimes/WebRuntimeTests.cs similarity index 84% rename from src/Umbraco.Tests/BootManagers/WebBootManagerTests.cs rename to src/Umbraco.Tests/Runtimes/WebRuntimeTests.cs index b6c2a6a553..5bae96f92b 100644 --- a/src/Umbraco.Tests/BootManagers/WebBootManagerTests.cs +++ b/src/Umbraco.Tests/Runtimes/WebRuntimeTests.cs @@ -5,10 +5,10 @@ using Umbraco.Core.Profiling; using Umbraco.Web; using Umbraco.Web.Mvc; -namespace Umbraco.Tests.BootManagers +namespace Umbraco.Tests.Runtimes { [TestFixture] - public class WebBootManagerTests + public class WebRuntimeTests { [Test] public void WrapViewEngines_HasEngines_WrapsAll() @@ -19,7 +19,7 @@ namespace Umbraco.Tests.BootManagers new PluginViewEngine() }; - WebBootManager.WrapViewEngines(engines); + WebRuntime.WrapViewEngines(engines); Assert.That(engines.Count, Is.EqualTo(2)); Assert.That(engines[0], Is.InstanceOf()); @@ -35,7 +35,7 @@ namespace Umbraco.Tests.BootManagers new PluginViewEngine() }; - WebBootManager.WrapViewEngines(engines); + WebRuntime.WrapViewEngines(engines); Assert.That(engines.Count, Is.EqualTo(2)); Assert.That(((ProfilingViewEngine)engines[0]).Inner, Is.InstanceOf()); @@ -52,7 +52,7 @@ namespace Umbraco.Tests.BootManagers profiledEngine }; - WebBootManager.WrapViewEngines(engines); + WebRuntime.WrapViewEngines(engines); Assert.That(engines[0], Is.SameAs(profiledEngine)); } @@ -61,7 +61,7 @@ namespace Umbraco.Tests.BootManagers public void WrapViewEngines_CollectionIsNull_DoesNotThrow() { IList engines = null; - Assert.DoesNotThrow(() => WebBootManager.WrapViewEngines(engines)); + Assert.DoesNotThrow(() => WebRuntime.WrapViewEngines(engines)); Assert.That(engines, Is.Null); } diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index 279f382923..5d6196d0c9 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -250,8 +250,7 @@ namespace Umbraco.Tests.TestHelpers { if (PluginManager.Current == null || PluginManagerResetRequired) { - PluginManager.Current = new PluginManager( - CacheHelper.RuntimeCache, ProfilingLogger, false) + PluginManager.Current = new PluginManager(CacheHelper.RuntimeCache, ProfilingLogger, false) { AssembliesToScan = new[] { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index edefbadf79..3b1b4a25cb 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -227,6 +227,7 @@ + @@ -270,7 +271,7 @@ - + @@ -316,8 +317,8 @@ - - + + @@ -389,7 +390,7 @@ - + @@ -478,7 +479,7 @@ - + @@ -496,10 +497,8 @@ - - - - + + @@ -561,7 +560,7 @@ - + diff --git a/src/Umbraco.Web/Current.cs b/src/Umbraco.Web/Current.cs index 063c762ada..0056c4da68 100644 --- a/src/Umbraco.Web/Current.cs +++ b/src/Umbraco.Web/Current.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Macros; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence.Migrations; +using Umbraco.Core.Plugins; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Strings; using Umbraco.Core.Sync; @@ -24,9 +25,7 @@ using CoreCurrent = Umbraco.Core.DependencyInjection.Current; namespace Umbraco.Web { - // must remain internal - this class is here to support the transition from singletons - // and resolvers to injection - by providing a static access to singleton services - it - // is initialized once with a service container, in WebBootManager. + // see notes in Umbraco.Core.DependencyInjection.Current. public static class Current { private static readonly object Locker = new object(); @@ -51,7 +50,7 @@ namespace Umbraco.Web CoreCurrent.Reset(); } - public static ServiceContainer Container + private static ServiceContainer Container => CoreCurrent.Container; // Facade @@ -200,6 +199,8 @@ namespace Umbraco.Web // proxy Core for convenience + public static PluginManager PluginManager => CoreCurrent.PluginManager; + public static UrlSegmentProviderCollection UrlSegmentProviders => CoreCurrent.UrlSegmentProviders; public static CacheRefresherCollection CacheRefreshers => CoreCurrent.CacheRefreshers; @@ -228,6 +229,8 @@ namespace Umbraco.Web public static IProfiler Profiler => CoreCurrent.Profiler; + public static ProfilingLogger ProfilingLogger => CoreCurrent.ProfilingLogger; + #endregion } } diff --git a/src/Umbraco.Web/ManifestWatcherComponent.cs b/src/Umbraco.Web/ManifestWatcherComponent.cs new file mode 100644 index 0000000000..a7e0e5a032 --- /dev/null +++ b/src/Umbraco.Web/ManifestWatcherComponent.cs @@ -0,0 +1,44 @@ +using System.IO; +using Umbraco.Core; +using Umbraco.Core.Components; +using Umbraco.Core.IO; +using Umbraco.Core.Manifest; + +namespace Umbraco.Web +{ + //[RequireComponent(typeof(object))] // fixme - the one that ensures that runtime.Something is ok + public class ManifestWatcherComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + // if configured and in debug mode, a ManifestWatcher watches App_Plugins folders for + // package.manifest chances and restarts the application on any change + private ManifestWatcher _mw; + + public void Initialize(RuntimeState runtime) + { + // fixme + // if this is a core component it cannot depend on UmbracoCoreComponent - indicates that everything is OK + // so what-if UmbracoCoreComponent ... aha ... need another one? UmbracoRuntimeComponent? + // or should we have it in IRuntime AND inject IRuntime? IRuntimeInternal vs IRuntime? + + // runtime should be INJECTED! aha! + // BUT how can we tell that runtime is "ready enough"? need to depend on some sort of UmbracoRuntimeComponent? + // and... will this kind of dependency issue be repro everywhere?! + if (runtime.Something < RuntimeSomething.Run || runtime.Debug == false) return; + + //if (ApplicationContext.Current.IsConfigured == false || GlobalSettings.DebugMode == false) + // return; + + var appPlugins = IOHelper.MapPath("~/App_Plugins/"); + if (Directory.Exists(appPlugins) == false) return; + + _mw = new ManifestWatcher(Current.Logger); + _mw.Start(Directory.GetDirectories(appPlugins)); + } + + public override void Terminate() + { + _mw?.Dispose(); + _mw = null; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/NotificationsHandler.cs b/src/Umbraco.Web/Strategies/NotificationsHandler.cs index 8f51104661..a13c9ceda3 100644 --- a/src/Umbraco.Web/Strategies/NotificationsHandler.cs +++ b/src/Umbraco.Web/Strategies/NotificationsHandler.cs @@ -74,6 +74,5 @@ namespace Umbraco.Web.Strategies content, ActionUnPublish.Instance, applicationContext)); } - } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 6e88a0e6e3..1bab99e1ef 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -210,6 +210,7 @@ + @@ -1087,7 +1088,7 @@ - + diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index 533ec6c440..dc0dc238eb 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -1,9 +1,4 @@ -using System; -using System.IO; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Manifest; +using Umbraco.Core; namespace Umbraco.Web { @@ -12,33 +7,9 @@ namespace Umbraco.Web /// public class UmbracoApplication : UmbracoApplicationBase { - // if configured and in debug mode, a ManifestWatcher watches App_Plugins folders for - // package.manifest chances and restarts the application on any change - private ManifestWatcher _mw; - - protected override void OnApplicationStarted(object sender, EventArgs e) + protected override IRuntime GetRuntime() { - base.OnApplicationStarted(sender, e); - - if (ApplicationContext.Current.IsConfigured == false || GlobalSettings.DebugMode == false) - return; - - var appPlugins = IOHelper.MapPath("~/App_Plugins/"); - if (Directory.Exists(appPlugins) == false) return; - - _mw = new ManifestWatcher(Current.Logger); - _mw.Start(Directory.GetDirectories(appPlugins)); - } - - protected override void OnApplicationEnd(object sender, EventArgs e) - { - base.OnApplicationEnd(sender, e); - _mw?.Dispose(); - } - - protected override IBootManager GetBootManager() - { - return new WebBootManager(this); + return new WebRuntime(this); } } } diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebRuntime.cs similarity index 77% rename from src/Umbraco.Web/WebBootManager.cs rename to src/Umbraco.Web/WebRuntime.cs index 60f9cf70ef..46b6faff91 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebRuntime.cs @@ -1,585 +1,562 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Configuration; -using System.Linq; -using System.Web; -using System.Web.Configuration; -using System.Web.Http; -using System.Web.Http.Dispatcher; -using System.Web.Mvc; -using System.Web.Routing; -using ClientDependency.Core.Config; -using Examine; -using LightInject; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Dictionary; -using Umbraco.Core.Logging; -using Umbraco.Core.Macros; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.ValueConverters; -using Umbraco.Core.Sync; -using Umbraco.Web.Dictionary; -using Umbraco.Web.Install; -using Umbraco.Web.Media; -using Umbraco.Web.Media.ThumbnailProviders; -using Umbraco.Web.Mvc; -using Umbraco.Web.PublishedCache; -using Umbraco.Web.Routing; -using Umbraco.Web.Security; -using Umbraco.Web.UI.JavaScript; -using Umbraco.Web.WebApi; -using Umbraco.Core.Events; -using Umbraco.Core.Cache; -using Umbraco.Core.Services; -using Umbraco.Web.Services; -using Umbraco.Web.Editors; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Services.Changes; -using Umbraco.Web.Cache; -using Umbraco.Web.DependencyInjection; -using Umbraco.Web.HealthCheck; -using Umbraco.Web.HealthCheck.Checks.DataIntegrity; -using Umbraco.Web._Legacy.Actions; -using UmbracoExamine; -using Action = System.Action; -using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; -using ProfilingViewEngine = Umbraco.Core.Profiling.ProfilingViewEngine; -using TypeHelper = Umbraco.Core.Plugins.TypeHelper; - - -namespace Umbraco.Web -{ - /// - /// A bootstrapper for the Umbraco application which initializes all objects including the Web portion of the application - /// - public class WebBootManager : CoreBootManager - { - //TODO: Fix this - we need to manually perform re-indexing on startup when necessary Examine lib no longer does this - //NOTE: see the Initialize method for what this is used for - //private static readonly List IndexesToRebuild = new List(); - - public WebBootManager(UmbracoApplicationBase umbracoApplication) - : base(umbracoApplication) - { } - - /// - /// Constructor for unit tests, ensures some resolvers are not initialized - /// - /// - /// - /// - internal WebBootManager(UmbracoApplicationBase umbracoApplication, ProfilingLogger logger, bool isForTesting) - : base(umbracoApplication, logger) - { } - - /// - /// Initialize objects before anything during the boot cycle happens - /// - /// - public override IBootManager Initialize() - { - //TODO: Fix this - we need to manually perform re-indexing on startup when necessary Examine lib no longer does this - ////This is basically a hack for this item: http://issues.umbraco.org/issue/U4-5976 - // // when Examine initializes it will try to rebuild if the indexes are empty, however in many cases not all of Examine's - // // event handlers will be assigned during bootup when the rebuilding starts which is a problem. So with the examine 0.1.58.2941 build - // // it has an event we can subscribe to in order to cancel this rebuilding process, but what we'll do is cancel it and postpone the rebuilding until the - // // boot process has completed. It's a hack but it works. - //ExamineManager.Instance.BuildingEmptyIndexOnStartup += OnInstanceOnBuildingEmptyIndexOnStartup; - - base.Initialize(); - - //setup mvc and webapi services - SetupMvcAndWebApi(); - - // Backwards compatibility - set the path and URL type for ClientDependency 1.5.1 [LK] - ClientDependency.Core.CompositeFiles.Providers.XmlFileMapper.FileMapVirtualFolder = "~/App_Data/TEMP/ClientDependency"; - ClientDependency.Core.CompositeFiles.Providers.BaseCompositeFileProcessingProvider.UrlTypeDefault = ClientDependency.Core.CompositeFiles.Providers.CompositeUrlType.Base64QueryStrings; - - var section = ConfigurationManager.GetSection("system.web/httpRuntime") as HttpRuntimeSection; - if (section != null) - { - //set the max url length for CDF to be the smallest of the max query length, max request length - ClientDependency.Core.CompositeFiles.CompositeDependencyHandler.MaxHandlerUrlLength = Math.Min(section.MaxQueryStringLength, section.MaxRequestLength); - } - - //Register a custom renderer - used to process property editor dependencies - var renderer = new DependencyPathRenderer(); - renderer.Initialize("Umbraco.DependencyPathRenderer", new NameValueCollection - { - { "compositeFileHandlerPath", ClientDependencySettings.Instance.CompositeFileHandlerPath } - }); - ClientDependencySettings.Instance.MvcRendererCollection.Add(renderer); - - // Disable the X-AspNetMvc-Version HTTP Header - MvcHandler.DisableMvcResponseHeader = true; - - InstallHelper.DeleteLegacyInstaller(); - - return this; - } - - /// - /// Override this method in order to ensure that the UmbracoContext is also created, this can only be - /// created after resolution is frozen! - /// - protected override void FreezeResolution() - { - base.FreezeResolution(); - - //before we do anything, we'll ensure the umbraco context - //see: http://issues.umbraco.org/issue/U4-1717 - var httpContext = new HttpContextWrapper(UmbracoApplication.Context); - UmbracoContext.EnsureContext( - httpContext, ApplicationContext, - Current.FacadeService, - new WebSecurity(httpContext, ApplicationContext), - UmbracoConfig.For.UmbracoSettings(), - Current.UrlProviders, - false); - } - - /// - /// Ensure the current profiler is the web profiler - /// - protected override IProfiler CreateProfiler() - { - return new WebProfiler(); - } - - /// - /// Ensure that the OnApplicationStarted methods of the IApplicationEvents are called - /// - /// - /// - public override IBootManager Complete(Action afterComplete) - { - //Wrap viewengines in the profiling engine - WrapViewEngines(ViewEngines.Engines); - - //add global filters - ConfigureGlobalFilters(); - - //set routes - CreateRoutes(); - - base.Complete(afterComplete); - - //rebuild any empty indexes - //TODO: Do we want to make this optional? otherwise the only way to disable this on startup - // would be to implement a custom WebBootManager and override this method - RebuildIndexes(true); - - //Now ensure webapi is initialized after everything - GlobalConfiguration.Configuration.EnsureInitialized(); - - return this; - } - - internal static void ConfigureGlobalFilters() - { - GlobalFilters.Filters.Add(new EnsurePartialViewMacroViewContextFilterAttribute()); - } - - internal static void WrapViewEngines(IList viewEngines) - { - if (viewEngines == null || viewEngines.Count == 0) return; - - var originalEngines = viewEngines.Select(e => e).ToArray(); - viewEngines.Clear(); - foreach (var engine in originalEngines) - { - var wrappedEngine = engine is ProfilingViewEngine ? engine : new ProfilingViewEngine(engine); - viewEngines.Add(wrappedEngine); - } - } - - /// - /// Creates the application cache based on the HttpRuntime cache - /// - protected override CacheHelper CreateApplicationCache() - { - //create a web-based cache helper - var cacheHelper = new CacheHelper( - //we need to have the dep clone runtime cache provider to ensure - //all entities are cached properly (cloned in and cloned out) - new DeepCloneRuntimeCacheProvider(new HttpRuntimeCacheProvider(HttpRuntime.Cache)), - new StaticCacheProvider(), - //we need request based cache when running in web-based context - new HttpRequestCacheProvider(), - new IsolatedRuntimeCache(type => - //we need to have the dep clone runtime cache provider to ensure - //all entities are cached properly (cloned in and cloned out) - new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); - - return cacheHelper; - } - - /// - /// Creates the routes - /// - protected internal void CreateRoutes() - { - var umbracoPath = GlobalSettings.UmbracoMvcArea; - - //Create the front-end route - var defaultRoute = RouteTable.Routes.MapRoute( - "Umbraco_default", - umbracoPath + "/RenderMvc/{action}/{id}", - new { controller = "RenderMvc", action = "Index", id = UrlParameter.Optional } - ); - defaultRoute.RouteHandler = new RenderRouteHandler(ControllerBuilder.Current.GetControllerFactory()); - - //register install routes - RouteTable.Routes.RegisterArea(); - - //register all back office routes - RouteTable.Routes.RegisterArea(); - - //plugin controllers must come first because the next route will catch many things - RoutePluginControllers(); - } - - private void RoutePluginControllers() - { - var umbracoPath = GlobalSettings.UmbracoMvcArea; - - //we need to find the plugin controllers and route them - var pluginControllers = Current.SurfaceControllerTypes - .Concat(Current.UmbracoApiControllerTypes) - .ToArray(); - - //local controllers do not contain the attribute - var localControllers = pluginControllers.Where(x => PluginController.GetMetadata(x).AreaName.IsNullOrWhiteSpace()); - foreach (var s in localControllers) - { - if (TypeHelper.IsTypeAssignableFrom(s)) - { - RouteLocalSurfaceController(s, umbracoPath); - } - else if (TypeHelper.IsTypeAssignableFrom(s)) - { - RouteLocalApiController(s, umbracoPath); - } - } - - //need to get the plugin controllers that are unique to each area (group by) - var pluginSurfaceControlleres = pluginControllers.Where(x => PluginController.GetMetadata(x).AreaName.IsNullOrWhiteSpace() == false); - var groupedAreas = pluginSurfaceControlleres.GroupBy(controller => PluginController.GetMetadata(controller).AreaName); - //loop through each area defined amongst the controllers - foreach (var g in groupedAreas) - { - //create an area for the controllers (this will throw an exception if all controllers are not in the same area) - var pluginControllerArea = new PluginControllerArea(g.Select(PluginController.GetMetadata)); - //register it - RouteTable.Routes.RegisterArea(pluginControllerArea); - } - } - - private void RouteLocalApiController(Type controller, string umbracoPath) - { - var meta = PluginController.GetMetadata(controller); - - //url to match - var routePath = meta.IsBackOffice == false - ? umbracoPath + "/Api/" + meta.ControllerName + "/{action}/{id}" - : umbracoPath + "/BackOffice/Api/" + meta.ControllerName + "/{action}/{id}"; - - var route = RouteTable.Routes.MapHttpRoute( - string.Format("umbraco-{0}-{1}", "api", meta.ControllerName), - routePath, - new { controller = meta.ControllerName, id = UrlParameter.Optional }, - new[] { meta.ControllerNamespace }); - //web api routes don't set the data tokens object - if (route.DataTokens == null) - { - route.DataTokens = new RouteValueDictionary(); - } - route.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, "api"); //ensure the umbraco token is set - } - - private void RouteLocalSurfaceController(Type controller, string umbracoPath) - { - var meta = PluginController.GetMetadata(controller); - var route = RouteTable.Routes.MapRoute( - string.Format("umbraco-{0}-{1}", "surface", meta.ControllerName), - umbracoPath + "/Surface/" + meta.ControllerName + "/{action}/{id}",//url to match - new { controller = meta.ControllerName, action = "Index", id = UrlParameter.Optional }, - new[] { meta.ControllerNamespace }); //look in this namespace to create the controller - route.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, "surface"); //ensure the umbraco token is set - route.DataTokens.Add("UseNamespaceFallback", false); //Don't look anywhere else except this namespace! - //make it use our custom/special SurfaceMvcHandler - route.RouteHandler = new SurfaceRouteHandler(); - } - - /// - /// Build the core container which contains all core things requird to build an app context - /// - internal override void ConfigureCoreServices(ServiceContainer container) - { - base.ConfigureCoreServices(container); - - // register model mappers - container.RegisterFrom(); - - // support web request scope - // note: everything that is PerRequestLifeTime will be disposed by LightInject at the end of the request - container.EnablePerWebRequestScope(); - - // register the http context and umbraco context accessors - // we *should* use the HttpContextUmbracoContextAccessor, however there are cases when - // we have no http context, eg when booting Umbraco or in background threads, so instead - // let's use an hybrid accessor that can fall back to a ThreadStatic context. - container.RegisterSingleton(); // replaces HttpContext.Current - container.RegisterSingleton(); - - // register a per-request HttpContextBase object - // is per-request so only one wrapper is created per request - container.Register(factory => new HttpContextWrapper(factory.GetInstance().HttpContext), new PerRequestLifeTime()); - - // register the facade accessor - the "current" facade is in the umbraco context - container.RegisterSingleton(); - - // register the umbraco database accessor - // have to use the hybrid thing... - container.RegisterSingleton(); - - // register the XML facade service - //container.RegisterSingleton(factory => new PublishedCache.XmlPublishedCache.FacadeService( - // factory.GetInstance(), - // factory.GetInstance(), - // factory.GetInstance().RequestCache, - // factory.GetAllInstances(), - // factory.GetInstance())); - - // register the NuCache facade service - container.RegisterSingleton(factory => new PublishedCache.NuCache.FacadeService( - new PublishedCache.NuCache.FacadeService.Options { FacadeCacheIsApplicationRequestCache = true }, - factory.GetInstance().MainDom, - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance())); - - // register a per-request UmbracoContext object - // no real need to be per request but assuming it is faster - container.Register(factory => factory.GetInstance().UmbracoContext, new PerRequestLifeTime()); - - // register the umbraco helper - container.RegisterSingleton(); - - // replace some services - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - container.RegisterSingleton(); - - container.RegisterSingleton(); - } - - /// - /// Called to customize the IoC container - /// - /// - internal override void ConfigureApplicationServices(ServiceContainer container) - { - base.ConfigureApplicationServices(container); - - // IoC setup for LightInject for MVC/WebApi - Container.EnableMvc(); - Container.RegisterMvcControllers(PluginManager, GetType().Assembly); - container.EnableWebApi(GlobalConfiguration.Configuration); - container.RegisterApiControllers(PluginManager, GetType().Assembly); - } - - /// - /// Initializes all web based and core resolves - /// - protected override void InitializeResolvers() - { - base.InitializeResolvers(); - - XsltExtensionCollectionBuilder.Register(Container) - .AddExtensionObjectProducer(() => PluginManager.ResolveXsltExtensions()); - - EditorValidatorCollectionBuilder.Register(Container) - .AddProducer(() => PluginManager.ResolveTypes()); - - // set the default RenderMvcController - Current.DefaultRenderMvcControllerType = typeof (RenderMvcController); // fixme WRONG! - - //Override the default server messenger, we need to check if the legacy dist calls is enabled, if that is the - // case, then we'll set the default messenger to be the old one, otherwise we'll set it to the db messenger - // which will always be on. - if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled) - { - //set the legacy one by default - this maintains backwards compat - Container.Register(_ => new BatchedWebServiceServerMessenger(() => - { - //we should not proceed to change this if the app/database is not configured since there will - // be no user, plus we don't need to have server messages sent if this is the case. - if (ApplicationContext.IsConfigured && ApplicationContext.DatabaseContext.IsDatabaseConfigured) - { - //disable if they are not enabled - if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled == false) - { - return null; - } - - try - { - var user = ApplicationContext.Services.UserService.GetUserById(UmbracoConfig.For.UmbracoSettings().DistributedCall.UserId); - return new Tuple(user.Username, user.RawPasswordValue); - } - catch (Exception e) - { - ProfilingLogger.Logger.Error("An error occurred trying to set the IServerMessenger during application startup", e); - return null; - } - } - ProfilingLogger.Logger.Warn("Could not initialize the DefaultServerMessenger, the application is not configured or the database is not configured"); - return null; - }), new PerContainerLifetime()); - } - else - { - - Container.Register(_ => new BatchedDatabaseServerMessenger( - ApplicationContext, - true, - //Default options for web including the required callbacks to build caches - new DatabaseServerMessengerOptions - { - //These callbacks will be executed if the server has not been synced - // (i.e. it is a new server or the lastsynced.txt file has been removed) - InitializingCallbacks = new Action[] - { - //rebuild the xml cache file if the server is not synced - () => - { - // rebuild the facade caches entirely, if the server is not synced - // this is equivalent to DistributedCache RefreshAllFacade but local only - // (we really should have a way to reuse RefreshAllFacade... locally) - // note: refresh all content & media caches does refresh content types too - IFacadeService svc = Current.FacadeService; - bool ignored1, ignored2; - svc.Notify(new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) }); - svc.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out ignored1, out ignored2); - svc.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out ignored1); - }, - //rebuild indexes if the server is not synced - // NOTE: This will rebuild ALL indexes including the members, if developers want to target specific - // indexes then they can adjust this logic themselves. - () => RebuildIndexes(false) - } - }), new PerContainerLifetime()); - } - - ActionCollectionBuilder.Register(Container) - .SetProducer(() => PluginManager.ResolveActions()); - - var surfaceControllerTypes = new SurfaceControllerTypeCollection(PluginManager.ResolveSurfaceControllers()); - Container.RegisterInstance(surfaceControllerTypes); - - var umbracoApiControllerTypes = new UmbracoApiControllerTypeCollection(PluginManager.ResolveUmbracoApiControllers()); - Container.RegisterInstance(umbracoApiControllerTypes); - - // both TinyMceValueConverter (in Core) and RteMacroRenderingValueConverter (in Web) will be - // discovered when CoreBootManager configures the converters. We HAVE to remove one of them - // here because there cannot be two converters for one property editor - and we want the full - // RteMacroRenderingValueConverter that converts macros, etc. So remove TinyMceValueConverter. - // (the limited one, defined in Core, is there for tests) - same for others - Container.GetInstance() - .Remove() - .Remove() - .Remove() - .Remove(); - - // add all known factories, devs can then modify this list on application - // startup either by binding to events or in their own global.asax - FilteredControllerFactoryCollectionBuilder.Register(Container) - .Append(); - - UrlProviderCollectionBuilder.Register(Container) - //.Append() // not enabled by default - .Append() - .Append(); - - Container.Register(); - - ContentFinderCollectionBuilder.Register(Container) - // all built-in finders in the correct order, - // devs can then modify this list on application startup - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); - - Container.Register(); - - ThumbnailProviderCollectionBuilder.Register(Container) - .Add(PluginManager.ResolveThumbnailProviders()); - - ImageUrlProviderCollectionBuilder.Register(Container) - .Append(PluginManager.ResolveImageUrlProviders()); - - Container.RegisterSingleton(); - - HealthCheckCollectionBuilder.Register(Container) - .AddProducer(() => PluginManager.ResolveTypes()) - .Exclude(); // fixme must remove else NuCache dies! - // but we should also have one for NuCache AND NuCache should be a component that does all this - } - - /// - /// Sets up MVC/WebApi services - /// - private void SetupMvcAndWebApi() - { - //don't output the MVC version header (security) - MvcHandler.DisableMvcResponseHeader = true; - - // set master controller factory - var controllerFactory = new MasterControllerFactory(() => Current.FilteredControllerFactories); - ControllerBuilder.Current.SetControllerFactory(controllerFactory); - - // set the render & plugin view engines - ViewEngines.Engines.Add(new RenderViewEngine()); - ViewEngines.Engines.Add(new PluginViewEngine()); - - //set model binder - ModelBinderProviders.BinderProviders.Add(new ContentModelBinder()); // is a provider - - ////add the profiling action filter - //GlobalFilters.Filters.Add(new ProfilingActionFilter()); - - GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), - new NamespaceHttpControllerSelector(GlobalConfiguration.Configuration)); - } - - protected virtual void RebuildIndexes(bool onlyEmptyIndexes) - { - if (ApplicationContext.IsConfigured == false || ApplicationContext.DatabaseContext.IsDatabaseConfigured == false) - { - return; - } - - foreach (var indexer in ExamineManager.Instance.IndexProviders) - { - if (onlyEmptyIndexes == false || indexer.Value.IsIndexNew()) - { - indexer.Value.RebuildIndex(); - } - } - - } - - } -} - +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Configuration; +using System.Linq; +using System.Web; +using System.Web.Configuration; +using System.Web.Http; +using System.Web.Http.Dispatcher; +using System.Web.Mvc; +using System.Web.Routing; +using ClientDependency.Core.Config; +using Examine; +using LightInject; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Dictionary; +using Umbraco.Core.Logging; +using Umbraco.Core.Macros; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.Sync; +using Umbraco.Web.Dictionary; +using Umbraco.Web.Install; +using Umbraco.Web.Media; +using Umbraco.Web.Media.ThumbnailProviders; +using Umbraco.Web.Mvc; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; +using Umbraco.Web.Security; +using Umbraco.Web.UI.JavaScript; +using Umbraco.Web.WebApi; +using Umbraco.Core.Events; +using Umbraco.Core.Cache; +using Umbraco.Core.Services; +using Umbraco.Web.Services; +using Umbraco.Web.Editors; +using Umbraco.Core.DependencyInjection; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Services.Changes; +using Umbraco.Web.Cache; +using Umbraco.Web.DependencyInjection; +using Umbraco.Web.HealthCheck; +using Umbraco.Web.HealthCheck.Checks.DataIntegrity; +using Umbraco.Web._Legacy.Actions; +using UmbracoExamine; +using Action = System.Action; +using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; +using ProfilingViewEngine = Umbraco.Core.Profiling.ProfilingViewEngine; +using TypeHelper = Umbraco.Core.Plugins.TypeHelper; + +namespace Umbraco.Web +{ + /// + /// Represents the Web Umbraco runtime. + /// + /// On top of CoreRuntime, handles all of the web-related aspects of Umbraco (startup, etc). + public class WebRuntime : CoreRuntime + { + public override void Boot(ServiceContainer container) + { + base.Boot(container); + } + + public override void Terminate() + { + base.Terminate(); + } + + protected override void Compose1(ServiceContainer container) + { + base.Compose1(container); + + // register model mappers + container.RegisterFrom(); + + // support web request scope + // note: everything that is PerRequestLifeTime will be disposed by LightInject at the end of the request + // fixme - temp - moved to... find it + //container.EnablePerWebRequestScope(); + + // register the http context and umbraco context accessors + // we *should* use the HttpContextUmbracoContextAccessor, however there are cases when + // we have no http context, eg when booting Umbraco or in background threads, so instead + // let's use an hybrid accessor that can fall back to a ThreadStatic context. + container.RegisterSingleton(); // replaces HttpContext.Current + container.RegisterSingleton(); + + // register a per-request HttpContextBase object + // is per-request so only one wrapper is created per request + container.Register(factory => new HttpContextWrapper(factory.GetInstance().HttpContext), new PerRequestLifeTime()); + + // register the facade accessor - the "current" facade is in the umbraco context + container.RegisterSingleton(); + + // register the umbraco database accessor + // have to use the hybrid thing... + container.RegisterSingleton(); + + // register the XML facade service + //container.RegisterSingleton(factory => new PublishedCache.XmlPublishedCache.FacadeService( + // factory.GetInstance(), + // factory.GetInstance(), + // factory.GetInstance().RequestCache, + // factory.GetAllInstances(), + // factory.GetInstance())); + + // register the NuCache facade service + container.RegisterSingleton(factory => new PublishedCache.NuCache.FacadeService( + new PublishedCache.NuCache.FacadeService.Options { FacadeCacheIsApplicationRequestCache = true }, + factory.GetInstance().MainDom, + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance())); + + // register a per-request UmbracoContext object + // no real need to be per request but assuming it is faster + container.Register(factory => factory.GetInstance().UmbracoContext, new PerRequestLifeTime()); + + // register the umbraco helper + container.RegisterSingleton(); + + // replace some services + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + container.RegisterSingleton(); + + container.RegisterSingleton(); + } + + protected override void Compose2(ServiceContainer container) + { + base.Compose2(container); + + // fixme moved too wtf + //// IoC setup for LightInject for MVC/WebApi + //container.EnableMvc(); + //container.RegisterMvcControllers(PluginManager, GetType().Assembly); + //container.EnableWebApi(GlobalConfiguration.Configuration); + //container.RegisterApiControllers(PluginManager, GetType().Assembly); + + XsltExtensionCollectionBuilder.Register(container) + .AddExtensionObjectProducer(() => PluginManager.ResolveXsltExtensions()); + + EditorValidatorCollectionBuilder.Register(container) + .AddProducer(() => PluginManager.ResolveTypes()); + + // set the default RenderMvcController + Current.DefaultRenderMvcControllerType = typeof(RenderMvcController); // fixme WRONG! + + //Override the default server messenger, we need to check if the legacy dist calls is enabled, if that is the + // case, then we'll set the default messenger to be the old one, otherwise we'll set it to the db messenger + // which will always be on. + if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled) + { + //set the legacy one by default - this maintains backwards compat + container.Register(_ => new BatchedWebServiceServerMessenger(() => + { + //we should not proceed to change this if the app/database is not configured since there will + // be no user, plus we don't need to have server messages sent if this is the case. + if (ApplicationContext.IsConfigured && ApplicationContext.DatabaseContext.IsDatabaseConfigured) + { + //disable if they are not enabled + if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled == false) + { + return null; + } + + try + { + var user = ApplicationContext.Services.UserService.GetUserById(UmbracoConfig.For.UmbracoSettings().DistributedCall.UserId); + return new Tuple(user.Username, user.RawPasswordValue); + } + catch (Exception e) + { + ProfilingLogger.Logger.Error("An error occurred trying to set the IServerMessenger during application startup", e); + return null; + } + } + ProfilingLogger.Logger.Warn("Could not initialize the DefaultServerMessenger, the application is not configured or the database is not configured"); + return null; + }), new PerContainerLifetime()); + } + else + { + container.Register(_ => new BatchedDatabaseServerMessenger( + ApplicationContext, + true, + //Default options for web including the required callbacks to build caches + new DatabaseServerMessengerOptions + { + //These callbacks will be executed if the server has not been synced + // (i.e. it is a new server or the lastsynced.txt file has been removed) + InitializingCallbacks = new Action[] + { + //rebuild the xml cache file if the server is not synced + () => + { + // rebuild the facade caches entirely, if the server is not synced + // this is equivalent to DistributedCache RefreshAllFacade but local only + // (we really should have a way to reuse RefreshAllFacade... locally) + // note: refresh all content & media caches does refresh content types too + var svc = Current.FacadeService; + bool ignored1, ignored2; + svc.Notify(new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) }); + svc.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out ignored1, out ignored2); + svc.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out ignored1); + }, + //rebuild indexes if the server is not synced + // NOTE: This will rebuild ALL indexes including the members, if developers want to target specific + // indexes then they can adjust this logic themselves. + () => RebuildIndexes(false) + } + }), new PerContainerLifetime()); + } + + ActionCollectionBuilder.Register(container) + .SetProducer(() => PluginManager.ResolveActions()); + + var surfaceControllerTypes = new SurfaceControllerTypeCollection(PluginManager.ResolveSurfaceControllers()); + container.RegisterInstance(surfaceControllerTypes); + + var umbracoApiControllerTypes = new UmbracoApiControllerTypeCollection(PluginManager.ResolveUmbracoApiControllers()); + container.RegisterInstance(umbracoApiControllerTypes); + + // both TinyMceValueConverter (in Core) and RteMacroRenderingValueConverter (in Web) will be + // discovered when CoreBootManager configures the converters. We HAVE to remove one of them + // here because there cannot be two converters for one property editor - and we want the full + // RteMacroRenderingValueConverter that converts macros, etc. So remove TinyMceValueConverter. + // (the limited one, defined in Core, is there for tests) - same for others + container.GetInstance() + .Remove() + .Remove() + .Remove() + .Remove(); + + // add all known factories, devs can then modify this list on application + // startup either by binding to events or in their own global.asax + FilteredControllerFactoryCollectionBuilder.Register(container) + .Append(); + + UrlProviderCollectionBuilder.Register(container) + //.Append() // not enabled by default + .Append() + .Append(); + + Container.Register(); + + ContentFinderCollectionBuilder.Register(container) + // all built-in finders in the correct order, + // devs can then modify this list on application startup + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + + container.Register(); + + ThumbnailProviderCollectionBuilder.Register(container) + .Add(PluginManager.ResolveThumbnailProviders()); + + ImageUrlProviderCollectionBuilder.Register(container) + .Append(PluginManager.ResolveImageUrlProviders()); + + container.RegisterSingleton(); + + HealthCheckCollectionBuilder.Register(container) + .AddProducer(() => PluginManager.ResolveTypes()) + .Exclude(); // fixme must remove else NuCache dies! + // but we should also have one for NuCache AND NuCache should be a component that does all this + } + + #region Getters + + protected override IProfiler GetProfiler() => new WebProfiler(); + + protected override CacheHelper GetApplicationCache() => new CacheHelper( + // we need to have the dep clone runtime cache provider to ensure + // all entities are cached properly (cloned in and cloned out) + new DeepCloneRuntimeCacheProvider(new HttpRuntimeCacheProvider(HttpRuntime.Cache)), + new StaticCacheProvider(), + // we need request based cache when running in web-based context + new HttpRequestCacheProvider(), + new IsolatedRuntimeCache(type => + // we need to have the dep clone runtime cache provider to ensure + // all entities are cached properly (cloned in and cloned out) + new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); + + #endregion + + #region Web + + internal static void ConfigureGlobalFilters() + { + GlobalFilters.Filters.Add(new EnsurePartialViewMacroViewContextFilterAttribute()); + } + + internal static void WrapViewEngines(IList viewEngines) + { + if (viewEngines == null || viewEngines.Count == 0) return; + + var originalEngines = viewEngines.Select(e => e).ToArray(); + viewEngines.Clear(); + foreach (var engine in originalEngines) + { + var wrappedEngine = engine is ProfilingViewEngine ? engine : new ProfilingViewEngine(engine); + viewEngines.Add(wrappedEngine); + } + } + + protected internal void CreateRoutes() + { + var umbracoPath = GlobalSettings.UmbracoMvcArea; + + //Create the front-end route + var defaultRoute = RouteTable.Routes.MapRoute( + "Umbraco_default", + umbracoPath + "/RenderMvc/{action}/{id}", + new { controller = "RenderMvc", action = "Index", id = UrlParameter.Optional } + ); + defaultRoute.RouteHandler = new RenderRouteHandler(ControllerBuilder.Current.GetControllerFactory()); + + //register install routes + RouteTable.Routes.RegisterArea(); + + //register all back office routes + RouteTable.Routes.RegisterArea(); + + //plugin controllers must come first because the next route will catch many things + RoutePluginControllers(); + } + + private static void RoutePluginControllers() + { + var umbracoPath = GlobalSettings.UmbracoMvcArea; + + // need to find the plugin controllers and route them + var pluginControllers = Current.SurfaceControllerTypes + .Concat(Current.UmbracoApiControllerTypes) + .ToArray(); + + // local controllers do not contain the attribute + var localControllers = pluginControllers.Where(x => PluginController.GetMetadata(x).AreaName.IsNullOrWhiteSpace()); + foreach (var s in localControllers) + { + if (TypeHelper.IsTypeAssignableFrom(s)) + RouteLocalSurfaceController(s, umbracoPath); + else if (TypeHelper.IsTypeAssignableFrom(s)) + RouteLocalApiController(s, umbracoPath); + } + + // need to get the plugin controllers that are unique to each area (group by) + var pluginSurfaceControlleres = pluginControllers.Where(x => PluginController.GetMetadata(x).AreaName.IsNullOrWhiteSpace() == false); + var groupedAreas = pluginSurfaceControlleres.GroupBy(controller => PluginController.GetMetadata(controller).AreaName); + // loop through each area defined amongst the controllers + foreach (var g in groupedAreas) + { + // create & register an area for the controllers (this will throw an exception if all controllers are not in the same area) + var pluginControllerArea = new PluginControllerArea(g.Select(PluginController.GetMetadata)); + RouteTable.Routes.RegisterArea(pluginControllerArea); + } + } + + private static void RouteLocalApiController(Type controller, string umbracoPath) + { + var meta = PluginController.GetMetadata(controller); + var url = umbracoPath + (meta.IsBackOffice ? "/BackOffice" : "") + "/Api/" + meta.ControllerName + "/{action}/{id}"; + var route = RouteTable.Routes.MapHttpRoute( + $"umbraco-api-{meta.ControllerName}", + url, // url to match + new { controller = meta.ControllerName, id = UrlParameter.Optional }, + new[] { meta.ControllerNamespace }); + if (route.DataTokens == null) // web api routes don't set the data tokens object + route.DataTokens = new RouteValueDictionary(); + route.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, "api"); //ensure the umbraco token is set + } + + private static void RouteLocalSurfaceController(Type controller, string umbracoPath) + { + var meta = PluginController.GetMetadata(controller); + var url = umbracoPath + "/Surface/" + meta.ControllerName + "/{action}/{id}"; + var route = RouteTable.Routes.MapRoute( + $"umbraco-surface-{meta.ControllerName}", + url, // url to match + new { controller = meta.ControllerName, action = "Index", id = UrlParameter.Optional }, + new[] { meta.ControllerNamespace }); // look in this namespace to create the controller + route.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, "surface"); // ensure the umbraco token is set + route.DataTokens.Add("UseNamespaceFallback", false); // don't look anywhere else except this namespace! + // make it use our custom/special SurfaceMvcHandler + route.RouteHandler = new SurfaceRouteHandler(); + } + + #endregion + + public WebRuntime(UmbracoApplicationBase umbracoApplication) + : base(umbracoApplication) + { } + + /// + /// Constructor for unit tests, ensures some resolvers are not initialized + /// + /// + /// + /// + internal WebRuntime(UmbracoApplicationBase umbracoApplication, ProfilingLogger logger, bool isForTesting) + : base(umbracoApplication, logger) + { } + + /// + /// Initialize objects before anything during the boot cycle happens + /// + /// + public override IRuntime Initialize() + { + //TODO: Fix this - we need to manually perform re-indexing on startup when necessary Examine lib no longer does this + ////This is basically a hack for this item: http://issues.umbraco.org/issue/U4-5976 + // // when Examine initializes it will try to rebuild if the indexes are empty, however in many cases not all of Examine's + // // event handlers will be assigned during bootup when the rebuilding starts which is a problem. So with the examine 0.1.58.2941 build + // // it has an event we can subscribe to in order to cancel this rebuilding process, but what we'll do is cancel it and postpone the rebuilding until the + // // boot process has completed. It's a hack but it works. + //ExamineManager.Instance.BuildingEmptyIndexOnStartup += OnInstanceOnBuildingEmptyIndexOnStartup; + + base.Initialize(); + + // fixme - only here so that everything above can have its OWN scope + // this is all completely fucked + Container.EnablePerWebRequestScope(); + Container.EnableMvc(); + Container.RegisterMvcControllers(PluginManager, GetType().Assembly); + Container.EnableWebApi(GlobalConfiguration.Configuration); + Container.RegisterApiControllers(PluginManager, GetType().Assembly); + + //setup mvc and webapi services + SetupMvcAndWebApi(); + + // Backwards compatibility - set the path and URL type for ClientDependency 1.5.1 [LK] + ClientDependency.Core.CompositeFiles.Providers.XmlFileMapper.FileMapVirtualFolder = "~/App_Data/TEMP/ClientDependency"; + ClientDependency.Core.CompositeFiles.Providers.BaseCompositeFileProcessingProvider.UrlTypeDefault = ClientDependency.Core.CompositeFiles.Providers.CompositeUrlType.Base64QueryStrings; + + var section = ConfigurationManager.GetSection("system.web/httpRuntime") as HttpRuntimeSection; + if (section != null) + { + //set the max url length for CDF to be the smallest of the max query length, max request length + ClientDependency.Core.CompositeFiles.CompositeDependencyHandler.MaxHandlerUrlLength = Math.Min(section.MaxQueryStringLength, section.MaxRequestLength); + } + + //Register a custom renderer - used to process property editor dependencies + var renderer = new DependencyPathRenderer(); + renderer.Initialize("Umbraco.DependencyPathRenderer", new NameValueCollection + { + { "compositeFileHandlerPath", ClientDependencySettings.Instance.CompositeFileHandlerPath } + }); + ClientDependencySettings.Instance.MvcRendererCollection.Add(renderer); + + // Disable the X-AspNetMvc-Version HTTP Header + MvcHandler.DisableMvcResponseHeader = true; + + InstallHelper.DeleteLegacyInstaller(); + + return this; + } + + /// + /// Override this method in order to ensure that the UmbracoContext is also created, this can only be + /// created after resolution is frozen! + /// + protected override void FreezeResolution() + { + base.FreezeResolution(); + + //before we do anything, we'll ensure the umbraco context + //see: http://issues.umbraco.org/issue/U4-1717 + var httpContext = new HttpContextWrapper(UmbracoApplication.Context); + UmbracoContext.EnsureContext( + httpContext, ApplicationContext, + Current.FacadeService, + new WebSecurity(httpContext, ApplicationContext), + UmbracoConfig.For.UmbracoSettings(), + Current.UrlProviders, + false); + } + + /// + /// Ensure that the OnApplicationStarted methods of the IApplicationEvents are called + /// + /// + /// + public override IRuntime Complete(Action afterComplete) + { + //Wrap viewengines in the profiling engine + WrapViewEngines(ViewEngines.Engines); + + //add global filters + ConfigureGlobalFilters(); + + //set routes + CreateRoutes(); + + base.Complete(afterComplete); + + //rebuild any empty indexes + //TODO: Do we want to make this optional? otherwise the only way to disable this on startup + // would be to implement a custom WebBootManager and override this method + RebuildIndexes(true); + + //Now ensure webapi is initialized after everything + GlobalConfiguration.Configuration.EnsureInitialized(); + + return this; + } + + + /// + /// Sets up MVC/WebApi services + /// + private void SetupMvcAndWebApi() + { + //don't output the MVC version header (security) + MvcHandler.DisableMvcResponseHeader = true; + + // set master controller factory + var controllerFactory = new MasterControllerFactory(() => Current.FilteredControllerFactories); + ControllerBuilder.Current.SetControllerFactory(controllerFactory); + + // set the render & plugin view engines + ViewEngines.Engines.Add(new RenderViewEngine()); + ViewEngines.Engines.Add(new PluginViewEngine()); + + //set model binder + ModelBinderProviders.BinderProviders.Add(new ContentModelBinder()); // is a provider + + ////add the profiling action filter + //GlobalFilters.Filters.Add(new ProfilingActionFilter()); + + GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), + new NamespaceHttpControllerSelector(GlobalConfiguration.Configuration)); + } + + protected virtual void RebuildIndexes(bool onlyEmptyIndexes) + { + if (ApplicationContext.IsConfigured == false || ApplicationContext.DatabaseContext.IsDatabaseConfigured == false) + { + return; + } + + foreach (var indexer in ExamineManager.Instance.IndexProviders) + { + if (onlyEmptyIndexes == false || indexer.Value.IsIndexNew()) + { + indexer.Value.RebuildIndex(); + } + } + } + } +} +