diff --git a/src/Umbraco.Core/ObjectResolution/Resolution.cs b/src/Umbraco.Core/ObjectResolution/Resolution.cs index 0b478d54cf..31661f6f7b 100644 --- a/src/Umbraco.Core/ObjectResolution/Resolution.cs +++ b/src/Umbraco.Core/ObjectResolution/Resolution.cs @@ -68,44 +68,6 @@ namespace Umbraco.Core.ObjectResolution // about using it for anything else. Also, while the backdoor is open, the resolution system is locked so nothing // can work properly => deadlocks. Therefore, open the backdoor, do resolution changes EXCLUSIVELY, and close the door! - /// - /// Returns a disposable object that reprents dirty access to temporarily unfrozen resolution configuration. - /// - /// - /// Should not be used. - /// Should be used in a using(Resolution.DirtyBackdoorToConfiguration) { ... } mode. - /// Because we just lift the frozen state, and we don't actually re-freeze, the Frozen event does not trigger. - /// - internal static IDisposable DirtyBackdoorToConfiguration - { - get { return new DirtyBackdoor(); } - } - - // keep the class here because it needs write-access to Resolution.IsFrozen - private class DirtyBackdoor : IDisposable - { - - private readonly IDisposable _lock; - private readonly bool _frozen; - - public DirtyBackdoor() - { - LogHelper.Debug(typeof(DirtyBackdoor), "Creating back door for resolution"); - - _lock = new WriteLock(ConfigurationLock); - _frozen = _isFrozen; - _isFrozen = false; - } - - public void Dispose() - { - LogHelper.Debug(typeof(DirtyBackdoor), "Disposing back door for resolution"); - - _isFrozen = _frozen; - _lock.Dispose(); - } - } - /// /// Freezes resolution. /// diff --git a/src/Umbraco.Tests/Resolvers/ActionsResolverTests.cs b/src/Umbraco.Tests/Resolvers/ActionCollectionTests.cs similarity index 72% rename from src/Umbraco.Tests/Resolvers/ActionsResolverTests.cs rename to src/Umbraco.Tests/Resolvers/ActionCollectionTests.cs index 3ddffaa694..dc75cd0431 100644 --- a/src/Umbraco.Tests/Resolvers/ActionsResolverTests.cs +++ b/src/Umbraco.Tests/Resolvers/ActionCollectionTests.cs @@ -1,184 +1,177 @@ -using System.Linq; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.ObjectResolution; -using Umbraco.Web.UI.Pages; -using Umbraco.Web; -using Umbraco.Web._Legacy.Actions; - -namespace Umbraco.Tests.Resolvers -{ - [TestFixture] - public class ActionsResolverTests : ResolverBaseTest - { - - [TearDown] - public void TearDown() - { - ActionsResolver.Reset(); - } - - // NOTE - // ManyResolverTests ensure that we'll get our actions back and ActionsResolver works, - // so all we're testing here is that plugin manager _does_ find our actions - // which should be ensured by PlugingManagerTests anyway, so this is useless? - // maybe not as it seems to handle the "instance" thing... so we test that we respect the singleton? - [Test] - public void FindAllActions() - { - ActionsResolver.Current = new ActionsResolver( - new ActivatorServiceProvider(), ProfilingLogger.Logger, - () => PluginManager.ResolveActions()); - - Resolution.Freeze(); - - var actions = ActionsResolver.Current.Actions; - Assert.AreEqual(2, actions.Count()); - - // order is unspecified, but both must be there - bool hasAction1 = actions.ElementAt(0) is SingletonAction || actions.ElementAt(1) is SingletonAction; - bool hasAction2 = actions.ElementAt(0) is NonSingletonAction || actions.ElementAt(1) is NonSingletonAction; - Assert.IsTrue(hasAction1); - Assert.IsTrue(hasAction2); - - SingletonAction action = (SingletonAction)(actions.ElementAt(0) is SingletonAction ? actions.ElementAt(0) : actions.ElementAt(1)); - - // ensure we respect the singleton - Assert.AreSame(SingletonAction.Instance, action); - } - - #region Classes for tests - - public class SingletonAction : IAction - { - //create singleton - private static readonly SingletonAction instance = new SingletonAction(); - - public static SingletonAction Instance - { - get { return instance; } - } - - #region IAction Members - - public char Letter - { - get - { - return 'I'; - } - } - - public string JsFunctionName - { - get - { - return string.Format("{0}.actionAssignDomain()", ClientTools.Scripts.GetAppActions); - } - } - - public string JsSource - { - get - { - return null; - } - } - - public string Alias - { - get - { - return "assignDomain"; - } - } - - public string Icon - { - get - { - return ".sprDomain"; - } - } - - public bool ShowInNotifier - { - get - { - return false; - } - } - public bool CanBePermissionAssigned - { - get - { - return true; - } - } - #endregion - } - - public class NonSingletonAction : IAction - { - #region IAction Members - - public char Letter - { - get - { - return 'Q'; - } - } - - public string JsFunctionName - { - get - { - return string.Format("{0}.actionAssignDomain()", ClientTools.Scripts.GetAppActions); - } - } - - public string JsSource - { - get - { - return null; - } - } - - public string Alias - { - get - { - return "asfasdf"; - } - } - - public string Icon - { - get - { - return ".sprDomain"; - } - } - - public bool ShowInNotifier - { - get - { - return false; - } - } - public bool CanBePermissionAssigned - { - get - { - return true; - } - } - #endregion - } - - #endregion - } +using System.Linq; +using LightInject; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.DependencyInjection; +using Umbraco.Core.ObjectResolution; +using Umbraco.Web.UI.Pages; +using Umbraco.Web; +using Umbraco.Web._Legacy.Actions; + +namespace Umbraco.Tests.Resolvers +{ + [TestFixture] + public class ActionCollectionTests : ResolverBaseTest + { + // NOTE + // ManyResolverTests ensure that we'll get our actions back and ActionsResolver works, + // so all we're testing here is that plugin manager _does_ find our actions + // which should be ensured by PlugingManagerTests anyway, so this is useless? + // maybe not as it seems to handle the "instance" thing... so we test that we respect the singleton? + [Test] + public void FindAllActions() + { + var collectionBuilder = new ActionCollectionBuilder(); + collectionBuilder.SetProducer(() => PluginManager.ResolveActions()); + + var actions = collectionBuilder.CreateCollection(); + Assert.AreEqual(2, actions.Count()); + + // order is unspecified, but both must be there + var hasAction1 = actions.ElementAt(0) is SingletonAction || actions.ElementAt(1) is SingletonAction; + var hasAction2 = actions.ElementAt(0) is NonSingletonAction || actions.ElementAt(1) is NonSingletonAction; + Assert.IsTrue(hasAction1); + Assert.IsTrue(hasAction2); + + var action = (SingletonAction)(actions.ElementAt(0) is SingletonAction ? actions.ElementAt(0) : actions.ElementAt(1)); + + // ensure we respect the singleton + Assert.AreSame(SingletonAction.Instance, action); + } + + #region Classes for tests + + public class SingletonAction : IAction + { + //create singleton + private static readonly SingletonAction instance = new SingletonAction(); + + public static SingletonAction Instance + { + get { return instance; } + } + + #region IAction Members + + public char Letter + { + get + { + return 'I'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionAssignDomain()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "assignDomain"; + } + } + + public string Icon + { + get + { + return ".sprDomain"; + } + } + + public bool ShowInNotifier + { + get + { + return false; + } + } + public bool CanBePermissionAssigned + { + get + { + return true; + } + } + #endregion + } + + public class NonSingletonAction : IAction + { + #region IAction Members + + public char Letter + { + get + { + return 'Q'; + } + } + + public string JsFunctionName + { + get + { + return string.Format("{0}.actionAssignDomain()", ClientTools.Scripts.GetAppActions); + } + } + + public string JsSource + { + get + { + return null; + } + } + + public string Alias + { + get + { + return "asfasdf"; + } + } + + public string Icon + { + get + { + return ".sprDomain"; + } + } + + public bool ShowInNotifier + { + get + { + return false; + } + } + public bool CanBePermissionAssigned + { + get + { + return true; + } + } + #endregion + } + + #endregion + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Resolvers/XsltExtensionsResolverTests.cs b/src/Umbraco.Tests/Resolvers/XsltExtensionsResolverTests.cs index b0f834bf89..fe17fd9b6b 100644 --- a/src/Umbraco.Tests/Resolvers/XsltExtensionsResolverTests.cs +++ b/src/Umbraco.Tests/Resolvers/XsltExtensionsResolverTests.cs @@ -17,12 +17,6 @@ namespace Umbraco.Tests.Resolvers [TestFixture] public class XsltExtensionsResolverTests : ResolverBaseTest { - [TearDown] - public void TearDown() - { - ActionsResolver.Reset(); - } - // NOTE // ManyResolverTests ensure that we'll get our actions back and ActionsResolver works, // so all we're testing here is that plugin manager _does_ find our actions diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index fda799fd40..493e92e5ea 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -479,7 +479,7 @@ - + diff --git a/src/Umbraco.Web/Current.cs b/src/Umbraco.Web/Current.cs index b1b65cae7b..f77159e819 100644 --- a/src/Umbraco.Web/Current.cs +++ b/src/Umbraco.Web/Current.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Strings; using Umbraco.Web.HealthCheck; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; +using Umbraco.Web._Legacy.Actions; using CoreCurrent = Umbraco.Core.DependencyInjection.Current; namespace Umbraco.Web @@ -118,6 +119,12 @@ namespace Umbraco.Web public static HealthCheckCollectionBuilder HealthCheckCollectionBuilder => Container.GetInstance(); + public static ActionCollectionBuilder ActionCollectionBuilder + => Container.GetInstance(); + + public static ActionCollection Actions + => Container.GetInstance(); + #endregion #region Core Getters diff --git a/src/Umbraco.Web/Models/Trees/MenuItemList.cs b/src/Umbraco.Web/Models/Trees/MenuItemList.cs index 76be2e4c00..36d596817a 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItemList.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItemList.cs @@ -122,7 +122,7 @@ namespace Umbraco.Web.Models.Trees internal MenuItem CreateMenuItem(string name, bool hasSeparator = false, IDictionary additionalData = null) where T : IAction { - var item = ActionsResolver.Current.GetAction(); + var item = Current.Actions.GetAction(); if (item != null) { var menuItem = new MenuItem(item, name) diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index f46218deb5..eaf6faae58 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -18,8 +18,8 @@ namespace Umbraco.Web.Trees //We will not allow the tree to render unless the user has access to any of the sections that the tree gets rendered // this is not ideal but until we change permissions to be tree based (not section) there's not much else we can do here. [UmbracoApplicationAuthorize( - Constants.Applications.Content, - Constants.Applications.Media, + Constants.Applications.Content, + Constants.Applications.Media, Constants.Applications.Users, Constants.Applications.Settings, Constants.Applications.Developer, @@ -29,10 +29,10 @@ namespace Umbraco.Web.Trees [CoreTree] public class ContentTreeController : ContentTreeControllerBase { - + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) { - var node = base.CreateRootNode(queryStrings); + var node = base.CreateRootNode(queryStrings); //if the user's start node is not default, then ensure the root doesn't have a menu if (Security.CurrentUser.StartContentId != Constants.System.Root) { @@ -56,7 +56,7 @@ namespace Umbraco.Web.Trees { get { return Security.CurrentUser.StartContentId; } } - + /// /// Creates a tree node for a content item based on an UmbracoEntity /// @@ -90,7 +90,7 @@ namespace Umbraco.Web.Trees node.AdditionalData.Add("isContainer", true); node.SetContainerStyle(); } - + if (entity.IsPublished == false) node.SetNotPublishedStyle(); @@ -142,7 +142,7 @@ namespace Umbraco.Web.Trees // add default actions for *all* users menu.Items.Add(Services.TextService.Localize("actions", ActionRePublish.Instance.Alias)).ConvertLegacyMenuItem(null, "content", "content"); menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); - + return menu; } @@ -161,7 +161,7 @@ namespace Umbraco.Web.Trees var nodeMenu = GetAllNodeMenuItems(item); var allowedMenuItems = GetAllowedUserMenuItemsForNode(item); - + FilterUserAllowedMenuItems(nodeMenu, allowedMenuItems); //if the media item is in the recycle bin, don't have a default menu, just show the regular menu @@ -173,9 +173,9 @@ namespace Umbraco.Web.Trees else { //set the default to create - nodeMenu.DefaultMenuAlias = ActionNew.Instance.Alias; + nodeMenu.DefaultMenuAlias = ActionNew.Instance.Alias; } - + return nodeMenu; } @@ -209,31 +209,45 @@ namespace Umbraco.Web.Trees protected MenuItemCollection GetAllNodeMenuItems(IUmbracoEntity item) { var menu = new MenuItemCollection(); - menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)); - menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias)); - + AddActionNode(item, menu); + AddActionNode(item, menu); + //need to ensure some of these are converted to the legacy system - until we upgrade them all to be angularized. - menu.Items.Add(Services.TextService.Localize("actions", ActionMove.Instance.Alias), true); - menu.Items.Add(Services.TextService.Localize("actions", ActionCopy.Instance.Alias)); - menu.Items.Add(Services.TextService.Localize("actions", ActionChangeDocType.Instance.Alias)).ConvertLegacyMenuItem(item, "content", "content"); + AddActionNode(item, menu, true); + AddActionNode(item, menu); + AddActionNode(item, menu, convert: true); - menu.Items.Add(Services.TextService.Localize("actions", ActionSort.Instance.Alias), true).ConvertLegacyMenuItem(item, "content", "content"); + AddActionNode(item, menu, true, true); - menu.Items.Add(Services.TextService.Localize("actions", ActionRollback.Instance.Alias)).ConvertLegacyMenuItem(item, "content", "content"); - menu.Items.Add(Services.TextService.Localize("actions", ActionAudit.Instance.Alias)).ConvertLegacyMenuItem(item, "content", "content"); - menu.Items.Add(Services.TextService.Localize("actions", ActionPublish.Instance.Alias), true).ConvertLegacyMenuItem(item, "content", "content"); - menu.Items.Add(Services.TextService.Localize("actions", ActionToPublish.Instance.Alias)).ConvertLegacyMenuItem(item, "content", "content"); - menu.Items.Add(Services.TextService.Localize("actions", ActionAssignDomain.Instance.Alias)).ConvertLegacyMenuItem(item, "content", "content"); - menu.Items.Add(Services.TextService.Localize("actions", ActionRights.Instance.Alias)).ConvertLegacyMenuItem(item, "content", "content"); - menu.Items.Add(Services.TextService.Localize("actions", ActionProtect.Instance.Alias), true).ConvertLegacyMenuItem(item, "content", "content"); - - menu.Items.Add(Services.TextService.Localize("actions", ActionNotify.Instance.Alias), true).ConvertLegacyMenuItem(item, "content", "content"); - menu.Items.Add(Services.TextService.Localize("actions", ActionSendToTranslate.Instance.Alias)).ConvertLegacyMenuItem(item, "content", "content"); + AddActionNode(item, menu, convert: true); + AddActionNode(item, menu, convert: true); + AddActionNode(item, menu, true, true); + AddActionNode(item, menu, convert: true); + AddActionNode(item, menu, convert: true); + AddActionNode(item, menu, convert: true); + AddActionNode(item, menu, true, true); - menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + AddActionNode(item, menu, true, true); + AddActionNode(item, menu, convert: true); + + AddActionNode(item, menu, true); return menu; } + private void AddActionNode(IUmbracoEntity item, MenuItemCollection menu, bool hasSeparator = false, bool convert = false) + where TAction : IAction + { + var menuItem = menu.Items.Add(Services.TextService.Localize("actions", Current.Actions.GetAction().Alias), hasSeparator); + if (convert) menuItem.ConvertLegacyMenuItem(item, "content", "content"); + } + + private void AddActionNode(IUmbracoEntity item, MenuItemCollection menu, bool hasSeparator = false, bool convert = false) + where TItem : MenuItem, new() + where TAction : IAction + { + var menuItem = menu.Items.Add(Services.TextService.Localize("actions", Current.Actions.GetAction().Alias), hasSeparator); + if (convert) menuItem.ConvertLegacyMenuItem(item, "content", "content"); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs b/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs index 17716724c1..30e677455e 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs @@ -47,7 +47,7 @@ namespace Umbraco.Web.Trees public static string GetLegacyIActionJavascript() { var js = new StringBuilder(); - foreach (var a in ActionsResolver.Current.Actions.ToList()) + foreach (var a in Current.Actions) { // NH: Added a try/catch block to this as an error in a 3rd party action can crash the whole menu initialization try diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index d07069dea6..277fd3b0be 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -476,9 +476,8 @@ namespace Umbraco.Web })); } - ActionsResolver.Current = new ActionsResolver( - ServiceProvider, ProfilingLogger.Logger, - () => PluginManager.ResolveActions()); + ActionCollectionBuilder.Register(Container) + .SetProducer(() => PluginManager.ResolveActions()); SurfaceControllerResolver.Current = new SurfaceControllerResolver( ServiceProvider, ProfilingLogger.Logger, diff --git a/src/Umbraco.Web/_Legacy/Actions/Action.cs b/src/Umbraco.Web/_Legacy/Actions/Action.cs index 611e45bafd..32a808664b 100644 --- a/src/Umbraco.Web/_Legacy/Actions/Action.cs +++ b/src/Umbraco.Web/_Legacy/Actions/Action.cs @@ -19,8 +19,8 @@ namespace Umbraco.Web._Legacy.Actions /// /// The Action class itself has responsibility for registering actions and actionhandlers, /// and contains methods which will be invoked whenever a change is made to ex. a document, media or member - /// - /// An action/actionhandler will automatically be registered, using reflection + /// + /// An action/actionhandler will automatically be registered, using reflection /// which is enabling thirdparty developers to extend the core functionality of /// umbraco without changing the codebase. /// @@ -47,20 +47,8 @@ namespace Umbraco.Web._Legacy.Actions { lock (Lock) { - // NOTE use the DirtyBackdoor to change the resolution configuration EXCLUSIVELY - // ie do NOT do ANYTHING else while holding the backdoor, because while it is open - // the whole resolution system is locked => nothing can work properly => deadlocks - - var newResolver = new ActionsResolver( - new ActivatorServiceProvider(), LoggerResolver.Current.Logger, - () => TypeFinder.FindClassesOfType(PluginManager.Current.AssembliesToScan)); - - using (Umbraco.Core.ObjectResolution.Resolution.DirtyBackdoorToConfiguration) - { - ActionsResolver.Reset(false); // and do NOT reset the whole resolution! - ActionsResolver.Current = newResolver; - } - + // this will reset the collection + Current.ActionCollectionBuilder.SetProducer(() => TypeFinder.FindClassesOfType(PluginManager.Current.AssembliesToScan)); } } @@ -81,7 +69,7 @@ namespace Umbraco.Web._Legacy.Actions /// public static List GetJavaScriptFileReferences() { - return ActionsResolver.Current.Actions + return Current.Actions .Where(x => !string.IsNullOrWhiteSpace(x.JsSource)) .Select(x => x.JsSource).ToList(); //return ActionJsReference; @@ -90,7 +78,7 @@ namespace Umbraco.Web._Legacy.Actions /// /// Javascript menuitems - tree contextmenu /// Umbraco console - /// + /// /// Suggestion: this method should be moved to the presentation layer. /// /// @@ -101,7 +89,7 @@ namespace Umbraco.Web._Legacy.Actions { string _actionJsList = ""; - foreach (IAction action in ActionsResolver.Current.Actions) + foreach (IAction action in Current.Actions) { // Adding try/catch so this rutine doesn't fail if one of the actions fail // Add to language JsList @@ -139,7 +127,7 @@ namespace Umbraco.Web._Legacy.Actions List list = new List(); foreach (var c in entityPermission.AssignedPermissions.Where(x => x.Length == 1).Select(x => x.ToCharArray()[0])) { - IAction action = ActionsResolver.Current.Actions.ToList().Find( + IAction action = Current.Actions.ToList().Find( delegate (IAction a) { return a.Letter == c; @@ -162,7 +150,7 @@ namespace Umbraco.Web._Legacy.Actions List list = new List(); foreach (char c in actions.ToCharArray()) { - IAction action = ActionsResolver.Current.Actions.ToList().Find( + IAction action = Current.Actions.ToList().Find( delegate(IAction a) { return a.Letter == c; @@ -190,7 +178,7 @@ namespace Umbraco.Web._Legacy.Actions /// public static List GetPermissionAssignable() { - return ActionsResolver.Current.Actions.ToList().FindAll( + return Current.Actions.ToList().FindAll( delegate(IAction a) { return (a.CanBePermissionAssigned); diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionAssignDomain.cs b/src/Umbraco.Web/_Legacy/Actions/ActionAssignDomain.cs index 5f87feaf50..70601cf959 100644 --- a/src/Umbraco.Web/_Legacy/Actions/ActionAssignDomain.cs +++ b/src/Umbraco.Web/_Legacy/Actions/ActionAssignDomain.cs @@ -2,74 +2,83 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using Umbraco.Core.Logging; -using Umbraco.Core.ObjectResolution; -using Umbraco.Core._Legacy.PackageActions; +using LightInject; +using Umbraco.Core.DependencyInjection; using Umbraco.Web.UI.Pages; namespace Umbraco.Web._Legacy.Actions { - /// - /// A resolver to return all IAction objects - /// - public sealed class ActionsResolver : LazyManyObjectsResolverBase + public class ActionCollectionBuilder : ICollectionBuilder { - /// - /// Constructor - /// - /// - /// - /// - internal ActionsResolver(IServiceProvider serviceProvider, ILogger logger, Func> packageActions) - : base(serviceProvider, logger, packageActions) - { + private static Func> _producer; + public static ActionCollectionBuilder Register(IServiceContainer container) + { + // register the builder - per container + var builderLifetime = new PerContainerLifetime(); + container.Register(builderLifetime); + + // get the builder, get the collection lifetime + var builder = container.GetInstance(); + var collectionLifetime = builder.CollectionLifetime; + + // register the collection - special lifetime + container.Register(factory => factory.GetInstance().CreateCollection(), collectionLifetime); + + return builder; } - /// - /// Gets the implementations. - /// - public IEnumerable Actions - { - get - { - return Values; - } - } - - /// - /// Gets an Action if it exists. - /// - /// - /// - internal IAction GetAction() - where T : IAction - { - return Actions.SingleOrDefault(x => x.GetType() == typeof(T)); - } - - protected override IEnumerable CreateInstances() + public ActionCollection CreateCollection() { var actions = new List(); - var foundIActions = InstanceTypes; - foreach (var type in foundIActions) + foreach (var type in _producer()) { - IAction typeInstance; - var instance = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static); - //if the singletone initializer is not found, try simply creating an instance of the IAction if it supports public constructors - if (instance == null) - typeInstance = ServiceProvider.GetService(type) as IAction; - else - typeInstance = instance.GetValue(null, null) as IAction; - - if (typeInstance != null) - { - actions.Add(typeInstance); - } + var getter = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static); + var instance = getter == null + ? Activator.CreateInstance(type) as IAction + : getter.GetValue(null, null) as IAction; + if (instance == null) continue; + actions.Add(instance); } - return actions; + return new ActionCollection(actions); } + public void SetProducer(Func> producer) + { + _producer = producer; + CollectionLifetime.Reset(); + } + + private ResettablePerContainerLifetime CollectionLifetime { get; } = new ResettablePerContainerLifetime(); + + private class ResettablePerContainerLifetime : ILifetime + { + private object _instance; + + public object GetInstance(Func createInstance, Scope scope) + { + // not dealing with disposable instances, actions are not disposable + return _instance ?? (_instance = createInstance()); + } + + public void Reset() + { + _instance = null; + } + } + } + + public class ActionCollection : BuilderCollectionBase + { + public ActionCollection(IEnumerable items) + : base(items) + { } + + internal T GetAction() + where T : IAction + { + return this.OfType().SingleOrDefault(); + } } public interface IAction @@ -91,11 +100,6 @@ namespace Umbraco.Web._Legacy.Actions /// public class ActionAssignDomain : IAction { - //create singleton -#pragma warning disable 612,618 - private static readonly ActionAssignDomain m_instance = new ActionAssignDomain(); -#pragma warning restore 612,618 - /// /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. /// All Umbraco assemblies should use the singleton instantiation (this.Instance) @@ -104,12 +108,9 @@ namespace Umbraco.Web._Legacy.Actions [Obsolete("Use the singleton instantiation instead of a constructor")] public ActionAssignDomain() { } - public static ActionAssignDomain Instance - { - get { return m_instance; } - } + public static ActionAssignDomain Instance { get; } = new ActionAssignDomain(); - #region IAction Members + #region IAction Members public char Letter { diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionAudit.cs b/src/Umbraco.Web/_Legacy/Actions/ActionAudit.cs index a1a2384702..06a66e8e73 100644 --- a/src/Umbraco.Web/_Legacy/Actions/ActionAudit.cs +++ b/src/Umbraco.Web/_Legacy/Actions/ActionAudit.cs @@ -8,11 +8,6 @@ namespace Umbraco.Web._Legacy.Actions /// public class ActionAudit : IAction { - //create singleton -#pragma warning disable 612,618 - private static readonly ActionAudit m_instance = new ActionAudit(); -#pragma warning restore 612,618 - /// /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. /// All Umbraco assemblies should use the singleton instantiation (this.Instance) @@ -21,10 +16,7 @@ namespace Umbraco.Web._Legacy.Actions [Obsolete("Use the singleton instantiation instead of a constructor")] public ActionAudit() { } - public static ActionAudit Instance - { - get { return m_instance; } - } + public static ActionAudit Instance { get; } = new ActionAudit(); #region IAction Members diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionNew.cs b/src/Umbraco.Web/_Legacy/Actions/ActionNew.cs index 211697462b..16d89a3035 100644 --- a/src/Umbraco.Web/_Legacy/Actions/ActionNew.cs +++ b/src/Umbraco.Web/_Legacy/Actions/ActionNew.cs @@ -8,9 +8,6 @@ namespace Umbraco.Web._Legacy.Actions /// public class ActionNew : IAction { - //create singleton - private static readonly ActionNew InnerInstance = new ActionNew(); - /// /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. /// All Umbraco assemblies should use the singleton instantiation (this.Instance) @@ -19,10 +16,7 @@ namespace Umbraco.Web._Legacy.Actions [Obsolete("Use the singleton instantiation instead of a constructor")] public ActionNew() { } - public static ActionNew Instance - { - get { return InnerInstance; } - } + public static ActionNew Instance { get; } = new ActionNew(); #region IAction Members diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenu.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenu.cs index cfaf6bb2cd..60109160f9 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenu.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenu.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Web.Script.Serialization; using Umbraco.Core.Logging; using Umbraco.Core; +using Umbraco.Web; using Umbraco.Web._Legacy.Actions; using Action = Umbraco.Web._Legacy.Actions.Action; @@ -21,7 +22,7 @@ namespace umbraco.controls.Tree }); List allActions = new List(); - foreach (var a in ActionsResolver.Current.Actions) + foreach (var a in Current.Actions) { // NH: Added a try/catch block to this as an error in a 3rd party action can crash the whole menu initialization try diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenuItem.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenuItem.cs index c624c429c2..402d25a5a4 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenuItem.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenuItem.cs @@ -4,6 +4,7 @@ using System.Web.Script.Serialization; using System.Text; using Umbraco.Core; using Umbraco.Core.Services; +using Umbraco.Web; using Umbraco.Web._Legacy.Actions; namespace umbraco.controls.Tree @@ -67,7 +68,7 @@ namespace umbraco.controls.Tree get { List types = new List(); - foreach (var a in ActionsResolver.Current.Actions) + foreach (var a in Current.Actions) { types.Add(a.GetType()); } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs index fc6149808d..27eadf265b 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/cruds.aspx.cs @@ -48,7 +48,7 @@ namespace umbraco.dialogs corner.Style.Add("border", "none"); names.Cells.Add(corner); - foreach (var a in ActionsResolver.Current.Actions) + foreach (var a in Current.Actions) { if (a.CanBePermissionAssigned == false) continue; @@ -77,7 +77,7 @@ namespace umbraco.dialogs hc.Style.Add("border", "none"); names.Cells.Add(hc); - foreach (var a in ActionsResolver.Current.Actions) + foreach (var a in Current.Actions) { var chk = new CheckBox { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/notifications.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/notifications.aspx.cs index 0b5bebc114..e0f6f7b22b 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/notifications.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/notifications.aspx.cs @@ -43,7 +43,7 @@ namespace umbraco.dialogs node = Services.EntityService.Get(int.Parse(Request.GetItemAsString("id"))); - var actionList = ActionsResolver.Current.Actions; + var actionList = Current.Actions; foreach (var a in actionList) { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/viewAuditTrail.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/viewAuditTrail.aspx.cs index a4b099b5e5..79db369f04 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/viewAuditTrail.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/viewAuditTrail.aspx.cs @@ -10,6 +10,7 @@ using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; using Umbraco.Core; using Umbraco.Core.Services; +using Umbraco.Web; using Umbraco.Web._Legacy.Actions; using Action = Umbraco.Web._Legacy.Actions.Action; @@ -41,7 +42,7 @@ namespace umbraco.presentation.umbraco.dialogs action = action.ToLower(); if (action == "new") action = "create"; - var actions = ActionsResolver.Current.Actions; + var actions = Current.Actions; foreach (var a in actions) { return Services.TextService.Localize(action);