Resvolution - Runtime components, fixing, cleanup
This commit is contained in:
@@ -17,7 +17,7 @@ namespace Umbraco.Core.Components
|
||||
private IUmbracoComponent[] _components;
|
||||
private bool _booted;
|
||||
|
||||
private const int LogThresholdMilliseconds = 200;
|
||||
private const int LogThresholdMilliseconds = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BootLoader"/> class.
|
||||
@@ -30,37 +30,6 @@ namespace Umbraco.Core.Components
|
||||
_proflog = container.GetInstance<ProfilingLogger>();
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -74,6 +43,7 @@ namespace Umbraco.Core.Components
|
||||
using (_proflog.TraceDuration<BootLoader>($"Booting Umbraco {UmbracoVersion.GetSemanticVersion().ToSemanticString()} on {NetworkHelper.MachineName}.", "Booted."))
|
||||
{
|
||||
var orderedComponentTypes = PrepareComponentTypes(componentTypes);
|
||||
|
||||
InstanciateComponents(orderedComponentTypes);
|
||||
ComposeComponents();
|
||||
InitializeComponents();
|
||||
@@ -95,19 +65,20 @@ namespace Umbraco.Core.Components
|
||||
{
|
||||
var componentTypeList = componentTypes.ToList();
|
||||
|
||||
// cannot remove that one - ever
|
||||
if (componentTypeList.Contains(typeof(UmbracoCoreComponent)) == false)
|
||||
componentTypeList.Add(typeof(UmbracoCoreComponent));
|
||||
|
||||
var enabled = new Dictionary<Type, EnableInfo>();
|
||||
|
||||
// process the enable/disable attributes
|
||||
// these two attributes are *not* inherited and apply to *classes* only (not interfaces).
|
||||
// remote declarations (when a component enables/disables *another* component)
|
||||
// have priority over local declarations (when a component disables itself) so that
|
||||
// ppl can enable components that, by default, are disabled
|
||||
// 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...
|
||||
// declarations depends on the type finder and is unspecified.
|
||||
foreach (var componentType in componentTypeList)
|
||||
{
|
||||
foreach (var attr in componentType.GetCustomAttributes<EnableComponentAttribute>())
|
||||
@@ -146,29 +117,52 @@ namespace Umbraco.Core.Components
|
||||
{
|
||||
temp.Clear();
|
||||
|
||||
//// for tests
|
||||
//Console.WriteLine("Components & Dependencies:");
|
||||
//Console.WriteLine(type.FullName);
|
||||
//foreach (var attribute in type.GetCustomAttributes<RequireComponentAttribute>())
|
||||
// Console.WriteLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue ? (attribute.Weak.Value ? " (weak)" : " (strong)") : ""));
|
||||
//foreach (var i in type.GetInterfaces())
|
||||
//{
|
||||
// Console.WriteLine(" " + i.FullName);
|
||||
// foreach (var attribute in i.GetCustomAttributes<RequireComponentAttribute>())
|
||||
// Console.WriteLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue ? (attribute.Weak.Value ? " (weak)" : " (strong)") : ""));
|
||||
//}
|
||||
//Console.WriteLine("/");
|
||||
//Console.WriteLine();
|
||||
|
||||
// get attributes
|
||||
// these attributes are *not* inherited because we want to custom-inherit for interfaces only
|
||||
// these attributes are *not* inherited because we want to "custom-inherit" for interfaces only
|
||||
var attributes = type
|
||||
.GetInterfaces().SelectMany(x => x.GetCustomAttributes<RequireComponentAttribute>())
|
||||
.Concat(type.GetCustomAttributes<RequireComponentAttribute>());
|
||||
|
||||
// requiring an interface => require any enabled component implementing that interface
|
||||
// requiring a class => require only that class
|
||||
// what happens in case of conflicting attributes (different strong/weak for same type) is not specified.
|
||||
foreach (var attr in attributes)
|
||||
{
|
||||
// requiring an interface = require any enabled component implementing that interface
|
||||
// unless strong, and then require at least one enabled component implementing that interface
|
||||
if (attr.RequiredType.IsInterface)
|
||||
temp.AddRange(componentTypeList.Where(x => attr.RequiredType.IsAssignableFrom(x)));
|
||||
{
|
||||
var implems = componentTypeList.Where(x => attr.RequiredType.IsAssignableFrom(x)).ToList();
|
||||
if (implems.Count > 0)
|
||||
temp.AddRange(implems);
|
||||
else if (attr.Weak == false) // if explicitely set to !weak, is strong, else is weak
|
||||
throw new Exception($"Broken component dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
|
||||
}
|
||||
// requiring a class = require that the component is enabled
|
||||
// unless weak, and then requires it if it is enabled
|
||||
else
|
||||
temp.Add(attr.RequiredType);
|
||||
{
|
||||
if (componentTypeList.Contains(attr.RequiredType))
|
||||
temp.Add(attr.RequiredType);
|
||||
else if (attr.Weak != true) // if not explicitely set to weak, is strong
|
||||
throw new Exception($"Broken component dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
|
||||
}
|
||||
}
|
||||
|
||||
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>(type.FullName, dependsOn.Select(x => x.FullName).ToArray(), new Lazy<Type>(() => type)));
|
||||
var dependsOn = temp.Distinct().Select(x => x.FullName).ToArray();
|
||||
items.Add(new TopologicalSorter.DependencyField<Type>(type.FullName, dependsOn, new Lazy<Type>(() => type)));
|
||||
}
|
||||
return TopologicalSorter.GetSortedItems(items);
|
||||
}
|
||||
|
||||
@@ -2,17 +2,37 @@
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a component should be disabled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If a type is specified, disables the component of that type, else disables the component marked with the attribute.</para>
|
||||
/// <para>This attribute is *not* inherited.</para>
|
||||
/// <para>This attribute applies to classes only, it is not possible to enable/disable interfaces.</para>
|
||||
/// <para>If a component ends up being both enabled and disabled: attributes marking the component itself have lower priority
|
||||
/// than attributes on *other* components, eg if a component declares itself as disabled it is possible to enable it from
|
||||
/// another component. Anything else is unspecified.</para>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public class DisableComponentAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DisableComponentAttribute"/> class.
|
||||
/// </summary>
|
||||
public DisableComponentAttribute()
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DisableComponentAttribute"/> class.
|
||||
/// </summary>
|
||||
public DisableComponentAttribute(Type disabledType)
|
||||
{
|
||||
DisabledType = disabledType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the disabled type, or null if it is the component marked with the attribute.
|
||||
/// </summary>
|
||||
public Type DisabledType { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,37 @@
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a component should be enabled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If a type is specified, enables the component of that type, else enables the component marked with the attribute.</para>
|
||||
/// <para>This attribute is *not* inherited.</para>
|
||||
/// <para>This attribute applies to classes only, it is not possible to enable/disable interfaces.</para>
|
||||
/// <para>If a component ends up being both enabled and disabled: attributes marking the component itself have lower priority
|
||||
/// than attributes on *other* components, eg if a component declares itself as disabled it is possible to enable it from
|
||||
/// another component. Anything else is unspecified.</para>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public class EnableComponentAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EnableComponentAttribute"/> class.
|
||||
/// </summary>
|
||||
public EnableComponentAttribute()
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EnableComponentAttribute"/> class.
|
||||
/// </summary>
|
||||
public EnableComponentAttribute(Type enabledType)
|
||||
{
|
||||
EnabledType = enabledType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the enabled type, or null if it is the component marked with the attribute.
|
||||
/// </summary>
|
||||
public Type EnabledType { get; }
|
||||
}
|
||||
}
|
||||
|
||||
5
src/Umbraco.Core/Components/IRuntimeComponent.cs
Normal file
5
src/Umbraco.Core/Components/IRuntimeComponent.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
public interface IRuntimeComponent : IUmbracoComponent
|
||||
{ }
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
[RequireComponent(typeof(IRuntimeComponent))]
|
||||
public interface IUmbracoCoreComponent : IUmbracoComponent
|
||||
{ }
|
||||
}
|
||||
|
||||
@@ -2,14 +2,58 @@
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = true)]
|
||||
/// <summary>
|
||||
/// Indicates that a component requires another component.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This attribute is *not* inherited. This means that a component class inheriting from
|
||||
/// another component class does *not* inherit its requirements. However, the bootloader checks
|
||||
/// the *interfaces* of every component for their requirements, so requirements declared on
|
||||
/// interfaces are inherited by every component class implementing the interface.</para>
|
||||
/// <para>When targetting a class, indicates a dependency on the component which must be enabled,
|
||||
/// unless the requirement has explicitely been declared as weak (and then, only if the component
|
||||
/// is enabled).</para>
|
||||
/// <para>When targetting an interface, indicates a dependency on enabled components implementing
|
||||
/// the interface. It could be no component at all, unless the requirement has explicitely been
|
||||
/// declared as strong (and at least one component must be enabled).</para>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
|
||||
public class RequireComponentAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequireComponentAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="requiredType">The type of the required component.</param>
|
||||
public RequireComponentAttribute(Type requiredType)
|
||||
{
|
||||
if (typeof(IUmbracoComponent).IsAssignableFrom(requiredType) == false)
|
||||
throw new ArgumentException($"Type {requiredType.FullName} is invalid here because it does not implement {typeof(IUmbracoComponent).FullName}.");
|
||||
RequiredType = requiredType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequireComponentAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="requiredType">The type of the required component.</param>
|
||||
/// <param name="weak">A value indicating whether the requirement is weak.</param>
|
||||
public RequireComponentAttribute(Type requiredType, bool weak)
|
||||
: this(requiredType)
|
||||
{
|
||||
Weak = weak;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the required type.
|
||||
/// </summary>
|
||||
public Type RequiredType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the requirement is weak.
|
||||
/// </summary>
|
||||
/// <remarks>Returns <c>true</c> if the requirement is weak (requires the other component if it
|
||||
/// is enabled), <c>false</c> if the requirement is strong (requires the other component to be
|
||||
/// enabled), and <c>null</c> if unspecified, in which case it is strong for classes and weak for
|
||||
/// interfaces.</remarks>
|
||||
public bool? Weak { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,9 +100,6 @@ namespace Umbraco.Core
|
||||
// then compose
|
||||
Compose(container);
|
||||
|
||||
// fixme!
|
||||
Compose1(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
|
||||
@@ -130,86 +127,6 @@ namespace Umbraco.Core
|
||||
container.RegisterInstance(cache.RuntimeCache);
|
||||
|
||||
container.RegisterInstance(new PluginManager(cache.RuntimeCache, ProfilingLogger));
|
||||
|
||||
// register from roots // fixme - components?!
|
||||
container.RegisterFrom<ConfigurationCompositionRoot>(); // fixme - used to be before caches?
|
||||
container.RegisterFrom<RepositoryCompositionRoot>();
|
||||
container.RegisterFrom<ServicesCompositionRoot>();
|
||||
container.RegisterFrom<CoreModelMappersCompositionRoot>();
|
||||
}
|
||||
|
||||
protected virtual void Compose1(ServiceContainer container)
|
||||
{
|
||||
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);
|
||||
|
||||
//TODO: Don't think we'll need this when the resolvers are all container resolvers
|
||||
container.RegisterSingleton<IServiceProvider, ActivatorServiceProvider>();
|
||||
container.RegisterSingleton<ApplicationContext>();
|
||||
container.Register<MediaFileSystem>(factory => FileSystemProviderManager.Current.GetFileSystemProvider<MediaFileSystem>());
|
||||
|
||||
// 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<ILogger>(), new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), factory.GetInstance<IRuntimeCacheProvider>()));
|
||||
container.RegisterSingleton<ManifestBuilder>();
|
||||
|
||||
PropertyEditorCollectionBuilder.Register(container)
|
||||
.AddProducer(factory => factory.GetInstance<PluginManager>().ResolvePropertyEditors());
|
||||
|
||||
ParameterEditorCollectionBuilder.Register(container)
|
||||
.AddProducer(factory => factory.GetInstance<PluginManager>().ResolveParameterEditors());
|
||||
|
||||
// register our predefined validators
|
||||
ValidatorCollectionBuilder.Register(container)
|
||||
.Add<RequiredManifestValueValidator>()
|
||||
.Add<RegexValidator>()
|
||||
.Add<DelimitedManifestValueValidator>()
|
||||
.Add<EmailValidator>()
|
||||
.Add<IntegerValidator>()
|
||||
.Add<DecimalValidator>();
|
||||
|
||||
// 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(factory => UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled
|
||||
? (IServerRegistrar)new ConfigServerRegistrar(UmbracoConfig.For.UmbracoSettings())
|
||||
: (IServerRegistrar)new DatabaseServerRegistrar(
|
||||
new Lazy<IServerRegistrationService>(() => factory.GetInstance<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<IServerMessenger>(factory
|
||||
=> new DatabaseServerMessenger(factory.GetInstance<ApplicationContext>(), true, new DatabaseServerMessengerOptions()));
|
||||
|
||||
CacheRefresherCollectionBuilder.Register(container)
|
||||
.AddProducer(factory => factory.GetInstance<PluginManager>().ResolveCacheRefreshers());
|
||||
|
||||
PackageActionCollectionBuilder.Register(container)
|
||||
.AddProducer(f => f.GetInstance<PluginManager>().ResolvePackageActions());
|
||||
|
||||
MigrationCollectionBuilder.Register(container)
|
||||
.AddProducer(factory => factory.GetInstance<PluginManager>().ResolveTypes<IMigration>());
|
||||
|
||||
// need to filter out the ones we dont want!! fixme - what does that mean?
|
||||
PropertyValueConverterCollectionBuilder.Register(container)
|
||||
.Append(factory => factory.GetInstance<PluginManager>().ResolveTypes<IPropertyValueConverter>());
|
||||
|
||||
container.RegisterSingleton<IShortStringHelper>(factory
|
||||
=> new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetInstance<IUmbracoSettingsSection>())));
|
||||
|
||||
UrlSegmentProviderCollectionBuilder.Register(container)
|
||||
.Append<DefaultUrlSegmentProvider>();
|
||||
|
||||
// by default, register a noop factory
|
||||
container.RegisterSingleton<IPublishedContentModelFactory, NoopPublishedContentModelFactory>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -225,9 +142,6 @@ namespace Umbraco.Core
|
||||
|
||||
protected ProfilingLogger ProfilingLogger { get; private set; }
|
||||
|
||||
// fixme do we need that one?
|
||||
protected PluginManager PluginManager { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Getters
|
||||
@@ -270,20 +184,6 @@ namespace Umbraco.Core
|
||||
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<IMapperConfiguration>())
|
||||
foreach (var m in Container.GetAllInstances<ModelMapperConfiguration>())
|
||||
{
|
||||
Logger.Debug<CoreRuntime>("FIXME " + m.GetType().FullName);
|
||||
m.ConfigureMappings(configuration, Current.ApplicationContext);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
@@ -302,8 +202,6 @@ namespace Umbraco.Core
|
||||
|
||||
protected ServiceContainer Container => Current.Container; // fixme kill
|
||||
|
||||
protected IServiceProvider ServiceProvider { get; private set; }
|
||||
|
||||
public virtual IRuntime Initialize()
|
||||
{
|
||||
if (_isInitialized)
|
||||
@@ -316,15 +214,6 @@ namespace Umbraco.Core
|
||||
"Umbraco application startup complete");
|
||||
|
||||
|
||||
// register
|
||||
//Compose1(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
|
||||
@@ -414,7 +303,7 @@ namespace Umbraco.Core
|
||||
if (_isComplete)
|
||||
throw new InvalidOperationException("The boot manager has already been completed");
|
||||
|
||||
FreezeResolution();
|
||||
Complete2();
|
||||
|
||||
//Here we need to make sure the db can be connected to
|
||||
EnsureDatabaseConnection();
|
||||
@@ -469,8 +358,7 @@ namespace Umbraco.Core
|
||||
return this;
|
||||
}
|
||||
|
||||
protected virtual void FreezeResolution()
|
||||
{
|
||||
}
|
||||
protected virtual void Complete2()
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
||||
130
src/Umbraco.Core/CoreRuntimeComponent.cs
Normal file
130
src/Umbraco.Core/CoreRuntimeComponent.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
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.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Manifest;
|
||||
using Umbraco.Core.Models.Mapping;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Persistence.Migrations;
|
||||
using Umbraco.Core.Plugins;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Core.Sync;
|
||||
using Umbraco.Core._Legacy.PackageActions;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
public class CoreRuntimeComponent : UmbracoComponentBase, IRuntimeComponent
|
||||
{
|
||||
public override void Compose(ServiceContainer container)
|
||||
{
|
||||
base.Compose(container);
|
||||
|
||||
// register from roots
|
||||
container.RegisterFrom<ConfigurationCompositionRoot>();
|
||||
container.RegisterFrom<RepositoryCompositionRoot>();
|
||||
container.RegisterFrom<ServicesCompositionRoot>();
|
||||
container.RegisterFrom<CoreModelMappersCompositionRoot>();
|
||||
|
||||
//TODO: Don't think we'll need this when the resolvers are all container resolvers
|
||||
container.RegisterSingleton<IServiceProvider, ActivatorServiceProvider>();
|
||||
container.RegisterSingleton<ApplicationContext>();
|
||||
container.Register<MediaFileSystem>(factory => FileSystemProviderManager.Current.GetFileSystemProvider<MediaFileSystem>());
|
||||
|
||||
// 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<ILogger>(), new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), factory.GetInstance<IRuntimeCacheProvider>()));
|
||||
container.RegisterSingleton<ManifestBuilder>();
|
||||
|
||||
PropertyEditorCollectionBuilder.Register(container)
|
||||
.AddProducer(factory => factory.GetInstance<PluginManager>().ResolvePropertyEditors());
|
||||
|
||||
ParameterEditorCollectionBuilder.Register(container)
|
||||
.AddProducer(factory => factory.GetInstance<PluginManager>().ResolveParameterEditors());
|
||||
|
||||
// register our predefined validators
|
||||
ValidatorCollectionBuilder.Register(container)
|
||||
.Add<RequiredManifestValueValidator>()
|
||||
.Add<RegexValidator>()
|
||||
.Add<DelimitedManifestValueValidator>()
|
||||
.Add<EmailValidator>()
|
||||
.Add<IntegerValidator>()
|
||||
.Add<DecimalValidator>();
|
||||
|
||||
// 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(factory => UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled
|
||||
? (IServerRegistrar)new ConfigServerRegistrar(UmbracoConfig.For.UmbracoSettings())
|
||||
: (IServerRegistrar)new DatabaseServerRegistrar(
|
||||
new Lazy<IServerRegistrationService>(() => factory.GetInstance<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<IServerMessenger>(factory
|
||||
=> new DatabaseServerMessenger(factory.GetInstance<ApplicationContext>(), true, new DatabaseServerMessengerOptions()));
|
||||
|
||||
CacheRefresherCollectionBuilder.Register(container)
|
||||
.AddProducer(factory => factory.GetInstance<PluginManager>().ResolveCacheRefreshers());
|
||||
|
||||
PackageActionCollectionBuilder.Register(container)
|
||||
.AddProducer(f => f.GetInstance<PluginManager>().ResolvePackageActions());
|
||||
|
||||
MigrationCollectionBuilder.Register(container)
|
||||
.AddProducer(factory => factory.GetInstance<PluginManager>().ResolveTypes<IMigration>());
|
||||
|
||||
// need to filter out the ones we dont want!! fixme - what does that mean?
|
||||
PropertyValueConverterCollectionBuilder.Register(container)
|
||||
.Append(factory => factory.GetInstance<PluginManager>().ResolveTypes<IPropertyValueConverter>());
|
||||
|
||||
container.RegisterSingleton<IShortStringHelper>(factory
|
||||
=> new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetInstance<IUmbracoSettingsSection>())));
|
||||
|
||||
UrlSegmentProviderCollectionBuilder.Register(container)
|
||||
.Append<DefaultUrlSegmentProvider>();
|
||||
|
||||
// by default, register a noop factory
|
||||
container.RegisterSingleton<IPublishedContentModelFactory, NoopPublishedContentModelFactory>();
|
||||
}
|
||||
|
||||
public void Initialize(IEnumerable<ModelMapperConfiguration> modelMapperConfigurations)
|
||||
{
|
||||
//TODO: Remove these for v8!
|
||||
LegacyPropertyEditorIdToAliasConverter.CreateMappingsForCoreEditors();
|
||||
LegacyParameterEditorAliasConverter.CreateMappingsForCoreEditors();
|
||||
|
||||
InitializeModelMappers(modelMapperConfigurations);
|
||||
|
||||
}
|
||||
|
||||
private void InitializeModelMappers(IEnumerable<ModelMapperConfiguration> modelMapperConfigurations)
|
||||
{
|
||||
Mapper.Initialize(configuration =>
|
||||
{
|
||||
// fixme why ApplicationEventHandler?!
|
||||
//foreach (var m in ApplicationEventsResolver.Current.ApplicationEventHandlers.OfType<IMapperConfiguration>())
|
||||
foreach (var m in modelMapperConfigurations)
|
||||
{
|
||||
//Logger.Debug<CoreRuntime>("FIXME " + m.GetType().FullName);
|
||||
m.ConfigureMappings(configuration, Current.ApplicationContext);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,8 +68,17 @@ namespace Umbraco.Core.DependencyInjection
|
||||
set { _applicationContext = value; }
|
||||
}
|
||||
|
||||
// fixme - refactor
|
||||
// some of our tests did mess with the current plugin manager
|
||||
// so for the time being we support it, however we should fix our tests
|
||||
|
||||
private static PluginManager _pluginManager;
|
||||
|
||||
public static PluginManager PluginManager
|
||||
=> Container.GetInstance<PluginManager>();
|
||||
{
|
||||
get { return _pluginManager ?? (_pluginManager = Container.TryGetInstance<PluginManager>() ?? PluginManager.Default); }
|
||||
set { _pluginManager = value; }
|
||||
}
|
||||
|
||||
public static UrlSegmentProviderCollection UrlSegmentProviders
|
||||
=> Container.GetInstance<UrlSegmentProviderCollection>();
|
||||
|
||||
@@ -27,6 +27,9 @@ namespace Umbraco.Core.DependencyInjection
|
||||
// dependencies for properties that is annotated with the InjectAttribute."
|
||||
container.EnableAnnotatedPropertyInjection();
|
||||
|
||||
// see notes in MixedScopeManagerProvider
|
||||
container.ScopeManagerProvider = new MixedScopeManagerProvider();
|
||||
|
||||
// self-register
|
||||
container.Register<IServiceContainer>(_ => container);
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using LightInject;
|
||||
using LightInject.Web;
|
||||
|
||||
namespace Umbraco.Core.DependencyInjection
|
||||
{
|
||||
// by default, the container's scope manager provider is PerThreadScopeManagerProvider,
|
||||
// and then container.EnablePerWebRequestScope() replaces it with PerWebRequestScopeManagerProvider,
|
||||
// however if any delate has been compiled already at that point, it captures the scope
|
||||
// manager provider and changing it afterwards has no effect for that delegate.
|
||||
//
|
||||
// therefore, Umbraco uses the mixed scope manager provider, which initially wraps an instance
|
||||
// of PerThreadScopeManagerProvider and then can replace that wrapped instance with an instance
|
||||
// of PerWebRequestScopeManagerProvider - but all delegates see is the mixed one - and therefore
|
||||
// they can transition without issues.
|
||||
//
|
||||
// the mixed provider is installed in container.ConfigureUmbracoCore() and then,
|
||||
// when doing eg container.EnableMvc() or anything that does container.EnablePerWebRequestScope()
|
||||
// we need to take great care to preserve the mixed scope manager provider!
|
||||
|
||||
public class MixedScopeManagerProvider : IScopeManagerProvider
|
||||
{
|
||||
private IScopeManagerProvider _provider;
|
||||
|
||||
public MixedScopeManagerProvider()
|
||||
{
|
||||
_provider = new PerThreadScopeManagerProvider();
|
||||
}
|
||||
|
||||
public void EnablePerWebRequestScope()
|
||||
{
|
||||
if (_provider is PerWebRequestScopeManagerProvider) return;
|
||||
_provider = new PerWebRequestScopeManagerProvider();
|
||||
}
|
||||
|
||||
public ScopeManager GetScopeManager()
|
||||
{
|
||||
return _provider.GetScopeManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,10 +29,7 @@ namespace Umbraco.Core.Plugins
|
||||
public class PluginManager
|
||||
{
|
||||
private const string CacheKey = "umbraco-plugins.list";
|
||||
private static object _instanceLocker = new object();
|
||||
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
|
||||
private static PluginManager _instance;
|
||||
private static bool _hasInstance;
|
||||
|
||||
private readonly IRuntimeCacheProvider _runtimeCache;
|
||||
private readonly ProfilingLogger _logger;
|
||||
@@ -96,42 +93,17 @@ namespace Umbraco.Core.Plugins
|
||||
}
|
||||
}
|
||||
|
||||
// fixme - somehow we NEED to get rid of this Current accessor
|
||||
public static PluginManager Current => DependencyInjection.Current.PluginManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current plugin manager.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Ensures that no matter what, only one plugin manager is created, and thus proper caching always takes place.</para>
|
||||
/// <para>The setter is generally only used for unit tests + when creating the master plugin manager in CoreBootManager.</para>
|
||||
/// </remarks>
|
||||
public static PluginManager Current
|
||||
internal static PluginManager Default
|
||||
{
|
||||
get
|
||||
{
|
||||
return LazyInitializer.EnsureInitialized(ref _instance, ref _hasInstance, ref _instanceLocker, () =>
|
||||
{
|
||||
var appctx = ApplicationContext.Current;
|
||||
var cacheProvider = appctx == null // fixme - should Current have an ApplicationCache?
|
||||
? new NullCacheProvider()
|
||||
: appctx.ApplicationCache.RuntimeCache;
|
||||
ProfilingLogger profilingLogger;
|
||||
if (appctx == null)
|
||||
{
|
||||
// fixme - should Current have a ProfilingLogger?
|
||||
profilingLogger = new ProfilingLogger(DependencyInjection.Current.Logger, DependencyInjection.Current.Profiler);
|
||||
}
|
||||
else
|
||||
{
|
||||
profilingLogger = appctx.ProfilingLogger;
|
||||
}
|
||||
return new PluginManager(cacheProvider, profilingLogger);
|
||||
});
|
||||
}
|
||||
internal set
|
||||
{
|
||||
_hasInstance = true;
|
||||
_instance = value;
|
||||
var appctx = ApplicationContext.Current;
|
||||
var cacheProvider = appctx == null // fixme - should Current have an ApplicationCache?
|
||||
? new NullCacheProvider()
|
||||
: appctx.ApplicationCache.RuntimeCache;
|
||||
return new PluginManager(cacheProvider, DependencyInjection.Current.ProfilingLogger);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,112 +421,116 @@ namespace Umbraco.Core.Plugins
|
||||
|
||||
#region Resolve Types
|
||||
|
||||
private IEnumerable<Type> ResolveTypes<T>(
|
||||
Func<IEnumerable<Type>> finder,
|
||||
TypeResolutionKind resolutionType,
|
||||
bool cacheResult)
|
||||
{
|
||||
using (var readLock = new UpgradeableReadLock(Locker))
|
||||
{
|
||||
var typesFound = new List<Type>();
|
||||
private bool _reportedChange;
|
||||
|
||||
private IEnumerable<Type> ResolveTypes<T>(Func<IEnumerable<Type>> finder, TypeResolutionKind resolutionType, bool cacheResult)
|
||||
{
|
||||
using (var rlock = new UpgradeableReadLock(Locker))
|
||||
{
|
||||
using (_logger.DebugDuration<PluginManager>(
|
||||
$"Starting resolution types of {typeof(T).FullName}",
|
||||
$"Completed resolution of types of {typeof(T).FullName}", // cannot contain typesFound.Count as it's evaluated before the find!
|
||||
$"Resolving {typeof(T).FullName}",
|
||||
$"Resolved {typeof(T).FullName}", // cannot contain typesFound.Count as it's evaluated before the find!
|
||||
50))
|
||||
{
|
||||
//check if the TypeList already exists, if so return it, if not we'll create it
|
||||
var typeList = _types.SingleOrDefault(x => x.IsTypeList<T>(resolutionType));
|
||||
// resolve within a lock & timer
|
||||
return ResolveTypes2<T>(finder, resolutionType, cacheResult, rlock);
|
||||
}
|
||||
}
|
||||
|
||||
//need to put some logging here to try to figure out why this is happening: http://issues.umbraco.org/issue/U4-3505
|
||||
if (cacheResult && typeList != null)
|
||||
}
|
||||
|
||||
private IEnumerable<Type> ResolveTypes2<T>(Func<IEnumerable<Type>> finder, TypeResolutionKind resolutionType, bool cacheResult, UpgradeableReadLock rlock)
|
||||
{
|
||||
// check if the TypeList already exists, if so return it, if not we'll create it
|
||||
var typeList = _types.SingleOrDefault(x => x.IsTypeList<T>(resolutionType));
|
||||
|
||||
//need to put some logging here to try to figure out why this is happening: http://issues.umbraco.org/issue/U4-3505
|
||||
if (cacheResult && typeList != null)
|
||||
{
|
||||
_logger.Logger.Debug<PluginManager>($"Resolving {typeof(T).FullName} ({resolutionType}): found a cached type list.");
|
||||
}
|
||||
|
||||
//if we're not caching the result then proceed, or if the type list doesn't exist then proceed
|
||||
if (cacheResult == false || typeList == null)
|
||||
{
|
||||
// upgrade to a write lock since we're adding to the collection
|
||||
rlock.UpgradeToWriteLock();
|
||||
|
||||
typeList = new TypeList<T>(resolutionType);
|
||||
|
||||
var scan = RequiresRescanning || File.Exists(GetPluginListFilePath()) == false;
|
||||
|
||||
if (scan)
|
||||
{
|
||||
// either we have to rescan, or we could not find the cache file:
|
||||
// report (only once) and scan and update the cache file
|
||||
if (_reportedChange == false)
|
||||
{
|
||||
_logger.Logger.Debug<PluginManager>("Existing typeList found for {0} with resolution type {1}", () => typeof(T), () => resolutionType);
|
||||
_logger.Logger.Debug<PluginManager>("Assemblies changes detected, need to rescan everything.");
|
||||
_reportedChange = true;
|
||||
}
|
||||
|
||||
//if we're not caching the result then proceed, or if the type list doesn't exist then proceed
|
||||
if (cacheResult == false || typeList == null)
|
||||
{
|
||||
//upgrade to a write lock since we're adding to the collection
|
||||
readLock.UpgradeToWriteLock();
|
||||
|
||||
typeList = new TypeList<T>(resolutionType);
|
||||
|
||||
//we first need to look into our cache file (this has nothing to do with the 'cacheResult' parameter which caches in memory).
|
||||
//if assemblies have not changed and the cache file actually exists, then proceed to try to lookup by the cache file.
|
||||
if (RequiresRescanning == false && File.Exists(GetPluginListFilePath()))
|
||||
{
|
||||
var fileCacheResult = TryGetCachedPluginsFromFile<T>(resolutionType);
|
||||
|
||||
//here we need to identify if the CachedPluginNotFoundInFile was the exception, if it was then we need to re-scan
|
||||
//in some cases the plugin will not have been scanned for on application startup, but the assemblies haven't changed
|
||||
//so in this instance there will never be a result.
|
||||
if (fileCacheResult.Exception is CachedPluginNotFoundInFileException)
|
||||
{
|
||||
_logger.Logger.Debug<PluginManager>("Tried to find typelist for type {0} and resolution {1} in file cache but the type was not found so loading types by assembly scan ", () => typeof(T), () => resolutionType);
|
||||
|
||||
//we don't have a cache for this so proceed to look them up by scanning
|
||||
LoadViaScanningAndUpdateCacheFile<T>(typeList, resolutionType, finder);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fileCacheResult.Success)
|
||||
{
|
||||
var successfullyLoadedFromCache = true;
|
||||
//we have a previous cache for this so we don't need to scan we just load what has been found in the file
|
||||
foreach (var t in fileCacheResult.Result)
|
||||
{
|
||||
try
|
||||
{
|
||||
//we use the build manager to ensure we get all types loaded, this is slightly slower than
|
||||
//Type.GetType but if the types in the assembly aren't loaded yet then we have problems with that.
|
||||
var type = BuildManager.GetType(t, true);
|
||||
typeList.AddType(type);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//if there are any exceptions loading types, we have to exist, this should never happen so
|
||||
//we will need to revert to scanning for types.
|
||||
successfullyLoadedFromCache = false;
|
||||
_logger.Logger.Error<PluginManager>("Could not load a cached plugin type: " + t + " now reverting to re-scanning assemblies for the base type: " + typeof(T).FullName, ex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (successfullyLoadedFromCache == false)
|
||||
{
|
||||
//we need to manually load by scanning if loading from the file was not successful.
|
||||
LoadViaScanningAndUpdateCacheFile<T>(typeList, resolutionType, finder);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Logger.Debug<PluginManager>("Loaded plugin types {0} with resolution {1} from persisted cache", () => typeof(T), () => resolutionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Logger.Debug<PluginManager>("Assembly changes detected, loading types {0} for resolution {1} by assembly scan", () => typeof(T), () => resolutionType);
|
||||
|
||||
//we don't have a cache for this so proceed to look them up by scanning
|
||||
LoadViaScanningAndUpdateCacheFile<T>(typeList, resolutionType, finder);
|
||||
}
|
||||
|
||||
//only add the cache if we are to cache the results
|
||||
if (cacheResult)
|
||||
{
|
||||
//add the type list to the collection
|
||||
var added = _types.Add(typeList);
|
||||
|
||||
_logger.Logger.Debug<PluginManager>("Caching of typelist for type {0} and resolution {1} was successful = {2}", () => typeof(T), () => resolutionType, () => added);
|
||||
|
||||
}
|
||||
}
|
||||
typesFound = typeList.GetTypes().ToList();
|
||||
}
|
||||
|
||||
return typesFound;
|
||||
if (scan == false)
|
||||
{
|
||||
// if we don't have to scan, try the cache file
|
||||
var fileCacheResult = TryGetCachedPluginsFromFile<T>(resolutionType);
|
||||
|
||||
// here we need to identify if the CachedPluginNotFoundInFile was the exception, if it was then we need to re-scan
|
||||
// in some cases the plugin will not have been scanned for on application startup, but the assemblies haven't changed
|
||||
// so in this instance there will never be a result.
|
||||
if (fileCacheResult.Exception is CachedPluginNotFoundInFileException || fileCacheResult.Success == false)
|
||||
{
|
||||
_logger.Logger.Debug<PluginManager>($"Resolving {typeof(T).FullName} ({resolutionType}): failed to load from cache file, must scan assemblies.");
|
||||
scan = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// successfully retrieved types from the file cache: load
|
||||
foreach (var type in fileCacheResult.Result)
|
||||
{
|
||||
try
|
||||
{
|
||||
// we use the build manager to ensure we get all types loaded, this is slightly slower than
|
||||
// Type.GetType but if the types in the assembly aren't loaded yet it would fail whereas
|
||||
// BuildManager will load them
|
||||
typeList.AddType(BuildManager.GetType(type, true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// in case of any exception, we have to exit, and revert to scanning
|
||||
_logger.Logger.Error<PluginManager>($"Resolving {typeof(T).FullName} ({resolutionType}): failed to load cache file type {type}, reverting to scanning assemblies.", ex);
|
||||
scan = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (scan == false)
|
||||
{
|
||||
_logger.Logger.Debug<PluginManager>($"Resolving {typeof(T).FullName} ({resolutionType}): loaded types from cache file.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scan)
|
||||
{
|
||||
// either we had to scan, or we could not resolve the types from the cache file - scan now
|
||||
_logger.Logger.Debug<PluginManager>($"Resolving {typeof(T).FullName} ({resolutionType}): scanning assemblies.");
|
||||
LoadViaScanningAndUpdateCacheFile<T>(typeList, resolutionType, finder);
|
||||
}
|
||||
|
||||
if (scan && cacheResult)
|
||||
{
|
||||
// if we are to cache the results, add the list to the collection
|
||||
var added = _types.Add(typeList);
|
||||
_logger.Logger.Debug<PluginManager>($"Resolved {typeof(T).FullName} ({resolutionType}), caching (added = {added.ToString().ToLowerInvariant()}).");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Logger.Debug<PluginManager>($"Resolved {typeof(T).FullName} ({resolutionType}).");
|
||||
}
|
||||
}
|
||||
|
||||
return typeList.GetTypes().ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="LightInject.Web">
|
||||
<HintPath>C:\Users\Stéphane\.nuget\packages\LightInject.Web\1.0.0.7\lib\net45\LightInject.Web.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Reference Include="System.Configuration" />
|
||||
@@ -112,6 +115,7 @@
|
||||
<Compile Include="Components\BootLoader.cs" />
|
||||
<Compile Include="Components\DisableComponentAttribute.cs" />
|
||||
<Compile Include="Components\EnableComponentAttribute.cs" />
|
||||
<Compile Include="Components\IRuntimeComponent.cs" />
|
||||
<Compile Include="Components\IUmbracoComponent.cs" />
|
||||
<Compile Include="Components\IUmbracoCoreComponent.cs" />
|
||||
<Compile Include="Components\IUmbracoUserComponent.cs" />
|
||||
@@ -218,6 +222,7 @@
|
||||
<Compile Include="Constants-Examine.cs" />
|
||||
<Compile Include="Constants-Icons.cs" />
|
||||
<Compile Include="CoreRuntime.cs" />
|
||||
<Compile Include="CoreRuntimeComponent.cs" />
|
||||
<Compile Include="DependencyInjection\Current.cs" />
|
||||
<Compile Include="DatabaseContext.cs" />
|
||||
<Compile Include="DataTableExtensions.cs" />
|
||||
@@ -231,6 +236,7 @@
|
||||
<Compile Include="DependencyInjection\BuilderCollectionBase.cs" />
|
||||
<Compile Include="DependencyInjection\CollectionBuilderBase.cs" />
|
||||
<Compile Include="DependencyInjection\LazyCollectionBuilderBase.cs" />
|
||||
<Compile Include="DependencyInjection\MixedScopeManagerProvider.cs" />
|
||||
<Compile Include="DependencyInjection\OrderedCollectionBuilderBase.cs" />
|
||||
<Compile Include="DependencyInjection\WeightedCollectionBuilderBase.cs" />
|
||||
<Compile Include="DependencyInjection\RepositoryCompositionRoot.cs" />
|
||||
|
||||
@@ -136,6 +136,59 @@ namespace Umbraco.Tests.Components
|
||||
Assert.AreEqual(typeof(Component9), Composed[2]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WeakDependencies()
|
||||
{
|
||||
var container = new ServiceContainer();
|
||||
container.ConfigureUmbracoCore();
|
||||
|
||||
var logger = Mock.Of<ILogger>();
|
||||
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(Component10) });
|
||||
Assert.AreEqual(1, Composed.Count);
|
||||
Assert.AreEqual(typeof(Component10), Composed[0]);
|
||||
|
||||
thing = new BootLoader(container);
|
||||
Composed.Clear();
|
||||
Assert.Throws<Exception>(() => thing.Boot(new[] { typeof(Component11) }));
|
||||
|
||||
thing = new BootLoader(container);
|
||||
Composed.Clear();
|
||||
Assert.Throws<Exception>(() => thing.Boot(new[] { typeof(Component2) }));
|
||||
|
||||
thing = new BootLoader(container);
|
||||
Composed.Clear();
|
||||
thing.Boot(new[] { typeof(Component12) });
|
||||
Assert.AreEqual(1, Composed.Count);
|
||||
Assert.AreEqual(typeof(Component12), Composed[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DisableMissing()
|
||||
{
|
||||
var container = new ServiceContainer();
|
||||
container.ConfigureUmbracoCore();
|
||||
|
||||
var logger = Mock.Of<ILogger>();
|
||||
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(Component6), typeof(Component8) }); // 8 disables 7 which is not in the list
|
||||
Assert.AreEqual(2, Composed.Count);
|
||||
Assert.AreEqual(typeof(Component6), Composed[0]);
|
||||
Assert.AreEqual(typeof(Component8), Composed[1]);
|
||||
}
|
||||
|
||||
public class TestComponentBase : UmbracoComponentBase
|
||||
{
|
||||
public override void Compose(ServiceContainer container)
|
||||
@@ -184,6 +237,18 @@ namespace Umbraco.Tests.Components
|
||||
public class Component9 : TestComponentBase, ITestComponent
|
||||
{ }
|
||||
|
||||
[RequireComponent(typeof(ITestComponent))]
|
||||
public class Component10 : TestComponentBase
|
||||
{ }
|
||||
|
||||
[RequireComponent(typeof(ITestComponent), false)]
|
||||
public class Component11 : TestComponentBase
|
||||
{ }
|
||||
|
||||
[RequireComponent(typeof(Component4), true)]
|
||||
public class Component12 : TestComponentBase, IUmbracoCoreComponent
|
||||
{ }
|
||||
|
||||
public interface ISomeResource { }
|
||||
|
||||
public class SomeResource : ISomeResource { }
|
||||
|
||||
@@ -5,6 +5,7 @@ using NPoco;
|
||||
using NUnit.Framework;
|
||||
using Semver;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.DependencyInjection;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Migrations;
|
||||
@@ -96,7 +97,7 @@ namespace Umbraco.Tests.Migrations.Upgrades
|
||||
[TearDown]
|
||||
public virtual void TearDown()
|
||||
{
|
||||
PluginManager.Current = null;
|
||||
Current.PluginManager = null;
|
||||
|
||||
TestHelper.CleanContentDirectories();
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
|
||||
// this is so the model factory looks into the test assembly
|
||||
_pluginManager = PluginManager.Current;
|
||||
PluginManager.Current = new PluginManager(new NullCacheProvider(), ProfilingLogger, false)
|
||||
Core.DependencyInjection.Current.PluginManager = new PluginManager(new NullCacheProvider(), ProfilingLogger, false)
|
||||
{
|
||||
AssembliesToScan = _pluginManager.AssembliesToScan
|
||||
.Union(new[] { typeof (PublishedContentMoreTests).Assembly})
|
||||
@@ -76,7 +76,6 @@ namespace Umbraco.Tests.PublishedContent
|
||||
{
|
||||
base.TearDown();
|
||||
|
||||
PluginManager.Current = _pluginManager;
|
||||
Core.DependencyInjection.Current.Reset();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
|
||||
// this is so the model factory looks into the test assembly
|
||||
_pluginManager = PluginManager.Current;
|
||||
PluginManager.Current = new PluginManager(CacheHelper.RuntimeCache, ProfilingLogger, false)
|
||||
Core.DependencyInjection.Current.PluginManager = new PluginManager(CacheHelper.RuntimeCache, ProfilingLogger, false)
|
||||
{
|
||||
AssembliesToScan = _pluginManager.AssembliesToScan
|
||||
.Union(new[] { typeof(PublishedContentTests).Assembly })
|
||||
@@ -58,7 +58,8 @@ namespace Umbraco.Tests.PublishedContent
|
||||
public override void TearDown()
|
||||
{
|
||||
base.TearDown();
|
||||
PluginManager.Current = _pluginManager;
|
||||
// fixme - wtf, restoring? keeping it accross tests for perfs I guess?
|
||||
//PluginManager.Current = _pluginManager;
|
||||
Core.DependencyInjection.Current.Reset();
|
||||
}
|
||||
|
||||
|
||||
@@ -64,17 +64,6 @@ namespace Umbraco.Tests.Runtimes
|
||||
base.Boot(container);
|
||||
}
|
||||
|
||||
protected override void Compose1(ServiceContainer container)
|
||||
{
|
||||
base.Compose1(container);
|
||||
|
||||
container.Register<IUmbracoSettingsSection>(factory => SettingsForTests.GetDefault());
|
||||
container.Register<DatabaseContext>(factory => new DatabaseContext(
|
||||
factory.GetInstance<IDatabaseFactory>(),
|
||||
factory.GetInstance<ILogger>()), new PerContainerLifetime());
|
||||
container.RegisterSingleton<IExamineIndexCollectionAccessor, TestIndexCollectionAccessor>();
|
||||
}
|
||||
|
||||
protected override IEnumerable<Type> GetComponentTypes()
|
||||
{
|
||||
return new[] { typeof(TestComponent) };
|
||||
@@ -99,6 +88,13 @@ namespace Umbraco.Tests.Runtimes
|
||||
public override void Compose(ServiceContainer container)
|
||||
{
|
||||
base.Compose(container);
|
||||
|
||||
container.Register<IUmbracoSettingsSection>(factory => SettingsForTests.GetDefault());
|
||||
container.Register<DatabaseContext>(factory => new DatabaseContext(
|
||||
factory.GetInstance<IDatabaseFactory>(),
|
||||
factory.GetInstance<ILogger>()), new PerContainerLifetime());
|
||||
container.RegisterSingleton<IExamineIndexCollectionAccessor, TestIndexCollectionAccessor>();
|
||||
|
||||
Composed = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ namespace Umbraco.Tests.TestHelpers
|
||||
{
|
||||
if (PluginManagerResetRequired)
|
||||
{
|
||||
PluginManager.Current = null;
|
||||
Core.DependencyInjection.Current.PluginManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,9 +246,10 @@ namespace Umbraco.Tests.TestHelpers
|
||||
/// </summary>
|
||||
protected virtual void SetupPluginManager()
|
||||
{
|
||||
if (PluginManager.Current == null || PluginManagerResetRequired)
|
||||
// fixme - oops
|
||||
if (/*PluginManager.Current == null ||*/ PluginManagerResetRequired)
|
||||
{
|
||||
PluginManager.Current = new PluginManager(CacheHelper.RuntimeCache, ProfilingLogger, false)
|
||||
Core.DependencyInjection.Current.PluginManager = new PluginManager(CacheHelper.RuntimeCache, ProfilingLogger, false)
|
||||
{
|
||||
AssembliesToScan = new[]
|
||||
{
|
||||
@@ -266,8 +267,7 @@ namespace Umbraco.Tests.TestHelpers
|
||||
/// Inheritors can override this to setup any resolvers before resolution is frozen
|
||||
/// </summary>
|
||||
protected virtual void FreezeResolution()
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
protected ApplicationContext ApplicationContext => ApplicationContext.Current;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Umbraco.Tests.TestHelpers
|
||||
container.RegisterSingleton<IProfiler>(factory => Mock.Of<IProfiler>());
|
||||
|
||||
var logger = new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>());
|
||||
var pluginManager = PluginManager.Current = new PluginManager(new NullCacheProvider(),
|
||||
var pluginManager = new PluginManager(new NullCacheProvider(),
|
||||
logger,
|
||||
false);
|
||||
container.RegisterInstance(pluginManager);
|
||||
@@ -61,7 +61,6 @@ namespace Umbraco.Tests.TestHelpers
|
||||
public virtual void TearDown()
|
||||
{
|
||||
//MappingResolver.Reset();
|
||||
PluginManager.Current = null;
|
||||
Current.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,6 +296,7 @@
|
||||
<SubType>ASPXCodeBehind</SubType>
|
||||
</Compile>
|
||||
<Compile Include="WebApi\UmbracoApiControllerTypeCollection.cs" />
|
||||
<Compile Include="WebRuntimeComponent.cs" />
|
||||
<Compile Include="WebServices\FacadeStatusController.cs" />
|
||||
<Compile Include="WebServices\NuCacheStatusController.cs" />
|
||||
<Compile Include="_Legacy\Actions\Action.cs" />
|
||||
|
||||
@@ -72,18 +72,10 @@ namespace Umbraco.Web
|
||||
{
|
||||
base.Boot(container);
|
||||
|
||||
// that one must come *last* because there is no "request scope" during boot,
|
||||
// and components are initialized within a scope so that anything they create
|
||||
// that is per-scope is disposed one they have initialized.
|
||||
container.EnablePerWebRequestScope();
|
||||
|
||||
// fixme - see compose + if it's here then components cannot change anything?
|
||||
// should we use collections? eg MvcControllerCollection and ApiControllerCollection?
|
||||
var pluginManager = container.GetInstance<PluginManager>();
|
||||
container.EnableMvc(); // that one does enable PerWebRequest scope
|
||||
container.RegisterMvcControllers(pluginManager, GetType().Assembly);
|
||||
container.EnableWebApi(GlobalConfiguration.Configuration);
|
||||
container.RegisterApiControllers(pluginManager, GetType().Assembly);
|
||||
// now is the time to switch over to perWebRequest scopes
|
||||
var smp = container.ScopeManagerProvider as MixedScopeManagerProvider;
|
||||
if (smp == null) throw new Exception("Container.ScopeManagerProvider is not MixedScopeManagerProvider.");
|
||||
smp.EnablePerWebRequestScope();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -96,202 +88,6 @@ namespace Umbraco.Web
|
||||
public override void Compose(ServiceContainer container)
|
||||
{
|
||||
base.Compose(container);
|
||||
|
||||
// fixme
|
||||
var pluginManager = container.GetInstance<PluginManager>();
|
||||
|
||||
// fixme - suspecting one of these to enable PerWebRequest scope
|
||||
//container.EnableMvc(); // yes, that one!
|
||||
//container.RegisterMvcControllers(pluginManager, GetType().Assembly);
|
||||
//container.EnableWebApi(GlobalConfiguration.Configuration);
|
||||
//container.RegisterApiControllers(pluginManager, GetType().Assembly);
|
||||
}
|
||||
|
||||
protected override void Compose1(ServiceContainer container)
|
||||
{
|
||||
// register model mappers
|
||||
container.RegisterFrom<WebModelMappersCompositionRoot>();
|
||||
|
||||
base.Compose1(container);
|
||||
|
||||
// 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<IHttpContextAccessor, AspNetHttpContextAccessor>(); // replaces HttpContext.Current
|
||||
container.RegisterSingleton<IUmbracoContextAccessor, HybridUmbracoContextAccessor>();
|
||||
|
||||
// register a per-request HttpContextBase object
|
||||
// is per-request so only one wrapper is created per request
|
||||
container.Register<HttpContextBase>(factory => new HttpContextWrapper(factory.GetInstance<IHttpContextAccessor>().HttpContext), new PerRequestLifeTime());
|
||||
|
||||
// register the facade accessor - the "current" facade is in the umbraco context
|
||||
container.RegisterSingleton<IFacadeAccessor, UmbracoContextFacadeAccessor>();
|
||||
|
||||
// register the umbraco database accessor
|
||||
// have to use the hybrid thing...
|
||||
container.RegisterSingleton<IUmbracoDatabaseAccessor, HybridUmbracoDatabaseAccessor>();
|
||||
|
||||
// register a per-request UmbracoContext object
|
||||
// no real need to be per request but assuming it is faster
|
||||
container.Register(factory => factory.GetInstance<IUmbracoContextAccessor>().UmbracoContext, new PerRequestLifeTime());
|
||||
|
||||
// register the umbraco helper
|
||||
container.RegisterSingleton<UmbracoHelper>();
|
||||
|
||||
// replace some services
|
||||
container.RegisterSingleton<IEventMessagesFactory, DefaultEventMessagesFactory>();
|
||||
container.RegisterSingleton<IEventMessagesAccessor, HybridEventMessagesAccessor>();
|
||||
container.RegisterSingleton<IApplicationTreeService, ApplicationTreeService>();
|
||||
container.RegisterSingleton<ISectionService, SectionService>();
|
||||
|
||||
container.RegisterSingleton<IExamineIndexCollectionAccessor, ExamineIndexCollectionAccessor>();
|
||||
|
||||
// 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<IEditorValidator>());
|
||||
|
||||
// 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<IServerMessenger>(factory => new BatchedWebServiceServerMessenger(() =>
|
||||
{
|
||||
var applicationContext = factory.GetInstance<ApplicationContext>();
|
||||
//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<string, string>(user.Username, user.RawPasswordValue);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProfilingLogger.Logger.Error<WebRuntime>("An error occurred trying to set the IServerMessenger during application startup", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
ProfilingLogger.Logger.Warn<WebRuntime>("Could not initialize the DefaultServerMessenger, the application is not configured or the database is not configured");
|
||||
return null;
|
||||
}), new PerContainerLifetime());
|
||||
}
|
||||
else
|
||||
{
|
||||
container.Register<IServerMessenger>(factory => new BatchedDatabaseServerMessenger(
|
||||
factory.GetInstance<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<PropertyValueConverterCollectionBuilder>()
|
||||
.Remove<TinyMceValueConverter>()
|
||||
.Remove<TextStringValueConverter>()
|
||||
.Remove<MarkdownEditorValueConverter>()
|
||||
.Remove<ImageCropperValueConverter>();
|
||||
|
||||
// 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<RenderControllerFactory>();
|
||||
|
||||
UrlProviderCollectionBuilder.Register(container)
|
||||
//.Append<AliasUrlProvider>() // not enabled by default
|
||||
.Append<DefaultUrlProvider>()
|
||||
.Append<CustomRouteUrlProvider>();
|
||||
|
||||
container.Register<IContentLastChanceFinder, ContentFinderByLegacy404>();
|
||||
|
||||
ContentFinderCollectionBuilder.Register(container)
|
||||
// all built-in finders in the correct order,
|
||||
// devs can then modify this list on application startup
|
||||
.Append<ContentFinderByPageIdQuery>()
|
||||
.Append<ContentFinderByNiceUrl>()
|
||||
.Append<ContentFinderByIdPath>()
|
||||
.Append<ContentFinderByNiceUrlAndTemplate>()
|
||||
.Append<ContentFinderByProfile>()
|
||||
.Append<ContentFinderByUrlAlias>()
|
||||
.Append<ContentFinderByRedirectUrl>();
|
||||
|
||||
container.Register<ISiteDomainHelper, SiteDomainHelper>();
|
||||
|
||||
ThumbnailProviderCollectionBuilder.Register(container)
|
||||
.Add(PluginManager.ResolveThumbnailProviders());
|
||||
|
||||
ImageUrlProviderCollectionBuilder.Register(container)
|
||||
.Append(PluginManager.ResolveImageUrlProviders());
|
||||
|
||||
container.RegisterSingleton<ICultureDictionaryFactory, DefaultCultureDictionaryFactory>();
|
||||
|
||||
HealthCheckCollectionBuilder.Register(container)
|
||||
.AddProducer(() => PluginManager.ResolveTypes<HealthCheck.HealthCheck>())
|
||||
.Exclude<XmlDataIntegrityHealthCheck>(); // 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
|
||||
@@ -476,9 +272,9 @@ namespace Umbraco.Web
|
||||
/// Override this method in order to ensure that the UmbracoContext is also created, this can only be
|
||||
/// created after resolution is frozen!
|
||||
/// </summary>
|
||||
protected override void FreezeResolution()
|
||||
protected override void Complete2()
|
||||
{
|
||||
base.FreezeResolution();
|
||||
base.Complete2();
|
||||
|
||||
//before we do anything, we'll ensure the umbraco context
|
||||
//see: http://issues.umbraco.org/issue/U4-1717
|
||||
|
||||
250
src/Umbraco.Web/WebRuntimeComponent.cs
Normal file
250
src/Umbraco.Web/WebRuntimeComponent.cs
Normal file
@@ -0,0 +1,250 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using Examine;
|
||||
using LightInject;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Components;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.DependencyInjection;
|
||||
using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Macros;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Plugins;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Changes;
|
||||
using Umbraco.Core.Sync;
|
||||
using Umbraco.Web.Cache;
|
||||
using Umbraco.Web.DependencyInjection;
|
||||
using Umbraco.Web.Dictionary;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.HealthCheck;
|
||||
using Umbraco.Web.HealthCheck.Checks.DataIntegrity;
|
||||
using Umbraco.Web.Media;
|
||||
using Umbraco.Web.Media.ThumbnailProviders;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Services;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Web._Legacy.Actions;
|
||||
using UmbracoExamine;
|
||||
using Action = System.Action;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
[RequireComponent(typeof(CoreRuntimeComponent))]
|
||||
public class WebRuntimeComponent : UmbracoComponentBase, IRuntimeComponent
|
||||
{
|
||||
public override void Compose(ServiceContainer container)
|
||||
{
|
||||
base.Compose(container);
|
||||
|
||||
container.RegisterFrom<WebModelMappersCompositionRoot>();
|
||||
|
||||
var pluginManager = container.GetInstance<PluginManager>();
|
||||
var logger = container.GetInstance<ILogger>();
|
||||
|
||||
// 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<IHttpContextAccessor, AspNetHttpContextAccessor>(); // replaces HttpContext.Current
|
||||
container.RegisterSingleton<IUmbracoContextAccessor, HybridUmbracoContextAccessor>();
|
||||
|
||||
// register a per-request HttpContextBase object
|
||||
// is per-request so only one wrapper is created per request
|
||||
container.Register<HttpContextBase>(factory => new HttpContextWrapper(factory.GetInstance<IHttpContextAccessor>().HttpContext), new PerRequestLifeTime());
|
||||
|
||||
// register the facade accessor - the "current" facade is in the umbraco context
|
||||
container.RegisterSingleton<IFacadeAccessor, UmbracoContextFacadeAccessor>();
|
||||
|
||||
// register the umbraco database accessor
|
||||
// have to use the hybrid thing...
|
||||
container.RegisterSingleton<IUmbracoDatabaseAccessor, HybridUmbracoDatabaseAccessor>();
|
||||
|
||||
// register a per-request UmbracoContext object
|
||||
// no real need to be per request but assuming it is faster
|
||||
container.Register(factory => factory.GetInstance<IUmbracoContextAccessor>().UmbracoContext, new PerRequestLifeTime());
|
||||
|
||||
// register the umbraco helper
|
||||
container.RegisterSingleton<UmbracoHelper>();
|
||||
|
||||
// replace some services
|
||||
container.RegisterSingleton<IEventMessagesFactory, DefaultEventMessagesFactory>();
|
||||
container.RegisterSingleton<IEventMessagesAccessor, HybridEventMessagesAccessor>();
|
||||
container.RegisterSingleton<IApplicationTreeService, ApplicationTreeService>();
|
||||
container.RegisterSingleton<ISectionService, SectionService>();
|
||||
|
||||
container.RegisterSingleton<IExamineIndexCollectionAccessor, ExamineIndexCollectionAccessor>();
|
||||
|
||||
// fixme - still, doing it before we get the INITIALIZE scope is a BAD idea
|
||||
// fixme - also note that whatever you REGISTER in a scope, stays registered => create the scope beforehand?
|
||||
// fixme - BUT not enough, once per-request is enabled it WANTS per-request scope even though a scope already exists
|
||||
// IoC setup for LightInject for MVC/WebApi
|
||||
// see comments on MixedScopeManagerProvider for explainations of what we are doing here
|
||||
var smp = container.ScopeManagerProvider as MixedScopeManagerProvider;
|
||||
if (smp == null) throw new Exception("Container.ScopeManagerProvider is not MixedScopeManagerProvider.");
|
||||
container.EnableMvc(); // does container.EnablePerWebRequestScope()
|
||||
container.ScopeManagerProvider = smp; // reverts - we will do it last (in WebRuntime)
|
||||
|
||||
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<IEditorValidator>());
|
||||
|
||||
// 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<IServerMessenger>(factory => new BatchedWebServiceServerMessenger(() =>
|
||||
{
|
||||
var applicationContext = factory.GetInstance<ApplicationContext>();
|
||||
//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<string, string>(user.Username, user.RawPasswordValue);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error<WebRuntime>("An error occurred trying to set the IServerMessenger during application startup", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
logger.Warn<WebRuntime>("Could not initialize the DefaultServerMessenger, the application is not configured or the database is not configured");
|
||||
return null;
|
||||
}), new PerContainerLifetime());
|
||||
}
|
||||
else
|
||||
{
|
||||
container.Register<IServerMessenger>(factory => new BatchedDatabaseServerMessenger(
|
||||
factory.GetInstance<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<PropertyValueConverterCollectionBuilder>()
|
||||
.Remove<TinyMceValueConverter>()
|
||||
.Remove<TextStringValueConverter>()
|
||||
.Remove<MarkdownEditorValueConverter>()
|
||||
.Remove<ImageCropperValueConverter>();
|
||||
|
||||
// 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<RenderControllerFactory>();
|
||||
|
||||
UrlProviderCollectionBuilder.Register(container)
|
||||
//.Append<AliasUrlProvider>() // not enabled by default
|
||||
.Append<DefaultUrlProvider>()
|
||||
.Append<CustomRouteUrlProvider>();
|
||||
|
||||
container.Register<IContentLastChanceFinder, ContentFinderByLegacy404>();
|
||||
|
||||
ContentFinderCollectionBuilder.Register(container)
|
||||
// all built-in finders in the correct order,
|
||||
// devs can then modify this list on application startup
|
||||
.Append<ContentFinderByPageIdQuery>()
|
||||
.Append<ContentFinderByNiceUrl>()
|
||||
.Append<ContentFinderByIdPath>()
|
||||
.Append<ContentFinderByNiceUrlAndTemplate>()
|
||||
.Append<ContentFinderByProfile>()
|
||||
.Append<ContentFinderByUrlAlias>()
|
||||
.Append<ContentFinderByRedirectUrl>();
|
||||
|
||||
container.Register<ISiteDomainHelper, SiteDomainHelper>();
|
||||
|
||||
ThumbnailProviderCollectionBuilder.Register(container)
|
||||
.Add(pluginManager.ResolveThumbnailProviders());
|
||||
|
||||
ImageUrlProviderCollectionBuilder.Register(container)
|
||||
.Append(pluginManager.ResolveImageUrlProviders());
|
||||
|
||||
container.RegisterSingleton<ICultureDictionaryFactory, DefaultCultureDictionaryFactory>();
|
||||
|
||||
HealthCheckCollectionBuilder.Register(container)
|
||||
.AddProducer(() => pluginManager.ResolveTypes<HealthCheck.HealthCheck>())
|
||||
.Exclude<XmlDataIntegrityHealthCheck>(); // fixme must remove else NuCache dies!
|
||||
// but we should also have one for NuCache AND NuCache should be a component that does all this
|
||||
}
|
||||
|
||||
protected virtual void RebuildIndexes(bool onlyEmptyIndexes)
|
||||
{
|
||||
if (Current.ApplicationContext.IsConfigured == false || Current.ApplicationContext.DatabaseContext.IsDatabaseConfigured == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var indexer in ExamineManager.Instance.IndexProviders)
|
||||
{
|
||||
if (onlyEmptyIndexes == false || indexer.Value.IsIndexNew())
|
||||
{
|
||||
indexer.Value.RebuildIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user