Resvolution - Runtime components, fixing, cleanup

This commit is contained in:
Stephan
2016-09-01 11:25:00 +02:00
parent f32d984fbb
commit 18f3a7fbab
23 changed files with 773 additions and 528 deletions

View File

@@ -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);
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -0,0 +1,5 @@
namespace Umbraco.Core.Components
{
public interface IRuntimeComponent : IUmbracoComponent
{ }
}

View File

@@ -1,5 +1,6 @@
namespace Umbraco.Core.Components
{
[RequireComponent(typeof(IRuntimeComponent))]
public interface IUmbracoCoreComponent : IUmbracoComponent
{ }
}

View File

@@ -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; }
}
}

View File

@@ -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()
{ }
}
}

View 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);
}
});
}
}
}

View File

@@ -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>();

View File

@@ -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);

View File

@@ -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();
}
}
}

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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 { }

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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" />

View File

@@ -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

View 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();
}
}
}
}
}