From 13f7f96101f66821e8f4d465803caf29a397f69e Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 28 Nov 2016 09:38:50 +0100 Subject: [PATCH] Refactor app event resolver weights --- .../ApplicationEventsResolver.cs | 82 ++++++++----------- .../ObjectResolution/WeightAttribute.cs | 17 +--- .../Cache/CacheRefresherEventHandler.cs | 48 +++++------ 3 files changed, 59 insertions(+), 88 deletions(-) diff --git a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs index 6cb60e2df0..eda1b94e37 100644 --- a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs +++ b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs @@ -11,7 +11,13 @@ namespace Umbraco.Core.ObjectResolution /// A resolver to return all IApplicationEvents objects /// /// - /// This is disposable because after the app has started it should be disposed to release any memory being occupied by instances. + /// This is disposable because after the app has started it should be disposed to release any memory being occupied by instances. + /// Ordering handlers: handlers are ordered by (ascending) weight. By default, handlers from the Umbraco.* or Concorde.* + /// assemblies have a -100 weight whereas any other handler has a weight of +100. A custom weight can be assigned to a handler + /// by marking the class with the WeightAttribute. For example, the CacheRefresherEventHandler is marked with [Weight(int.MinValue)] + /// because its events need to run before anything else. Positive weights are considered "user-space" while negative weights are + /// "core". Finally, users can register a filter to process the list (after it has been ordered) and re-order it, or remove handlers. + /// BEWARE! handlers order is an important thing, and removing handlers or reordering handlers can have unexpected consequences. /// public sealed class ApplicationEventsResolver : ManyObjectsResolverBase, IDisposable { @@ -25,9 +31,9 @@ namespace Umbraco.Core.ObjectResolution /// internal ApplicationEventsResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable applicationEventHandlers) : base(serviceProvider, logger, applicationEventHandlers) - { + { //create the legacy resolver and only include the legacy types - _legacyResolver = new LegacyStartupHandlerResolver( + _legacyResolver = new LegacyStartupHandlerResolver( serviceProvider, logger, applicationEventHandlers.Where(x => TypeHelper.IsTypeAssignableFrom(x) == false)); } @@ -52,26 +58,29 @@ namespace Umbraco.Core.ObjectResolution { if (_orderedAndFiltered == null) { - _resolved = true; - _orderedAndFiltered = GetSortedValues().ToList(); - OnCollectionResolved(_orderedAndFiltered); + _orderedAndFiltered = GetSortedValues().ToList(); + if (FilterCollection != null) + FilterCollection(_orderedAndFiltered); } return _orderedAndFiltered; } } - /// - /// A delegate that can be set in the pre-boot phase in order to filter or re-order the event handler collection - /// - /// - /// This can be set on startup in the pre-boot process in either a custom boot manager or global.asax (UmbracoApplication) - /// - public Action> FilterCollection + /// + /// Gets or sets a delegate to filter the event handler list (EXPERT!). + /// + /// + /// This can be set on startup in the pre-boot process in either a custom boot manager or global.asax (UmbracoApplication). + /// Allows custom logic to execute in order to filter and/or re-order the event handlers prior to executing. + /// To be used by custom boot sequences where the boot loader needs to remove some handlers, or raise their priority. + /// Filtering the event handler collection can have ugly consequences. Use with care. + /// + public Action> FilterCollection { get { return _filterCollection; } set { - if (_resolved) + if (_orderedAndFiltered != null) throw new InvalidOperationException("Cannot set the FilterCollection delegate once the ApplicationEventHandlers are resolved"); if (_filterCollection != null) throw new InvalidOperationException("Cannot set the FilterCollection delegate once it's already been specified"); @@ -80,40 +89,16 @@ namespace Umbraco.Core.ObjectResolution } } - /// - /// Allow any filters to be applied to the event handler list - /// - /// - /// - /// This allows custom logic to execute in order to filter or re-order the event handlers prior to executing, - /// however this also ensures that any core handlers are executed first to ensure the stabiliy of Umbraco. - /// - private void OnCollectionResolved(List handlers) - { - if (FilterCollection != null) - { - FilterCollection(handlers); - } + protected override int GetObjectWeight(object o) + { + var type = o.GetType(); + var attr = type.GetCustomAttribute(true); + if (attr != null) return attr.Weight; + var name = type.Assembly.FullName; - //find all of the core handlers and their weight, remove them from the main list - var coreItems = new List>(); - foreach (var handler in handlers.ToArray()) - { - //Yuck, but not sure what else we can do - if ( - handler.GetType().Assembly.FullName.StartsWith("Umbraco.", StringComparison.OrdinalIgnoreCase) - || handler.GetType().Assembly.FullName.StartsWith("Concorde.")) - { - coreItems.Add(new Tuple(handler, GetObjectWeight(handler))); - handlers.Remove(handler); - } - } - - //re-add the core handlers to the beginning of the list ordered by their weight - foreach (var coreHandler in coreItems.OrderBy(x => x.Item2)) - { - handlers.Insert(0, coreHandler.Item1); - } + // we should really attribute all our Core handlers, so this is temp + var core = name.InvariantStartsWith("Umbraco.") || name.InvariantStartsWith("Concorde."); + return core ? -DefaultPluginWeight : DefaultPluginWeight; } /// @@ -157,7 +142,6 @@ namespace Umbraco.Core.ObjectResolution private bool _disposed; private readonly ReaderWriterLockSlim _disposalLocker = new ReaderWriterLockSlim(); private Action> _filterCollection; - private bool _resolved = false; /// /// Gets a value indicating whether this instance is disposed. @@ -215,7 +199,7 @@ namespace Umbraco.Core.ObjectResolution _legacyResolver.Dispose(); ResetCollections(); _orderedAndFiltered.Clear(); - _orderedAndFiltered = null; + _orderedAndFiltered = null; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/WeightAttribute.cs b/src/Umbraco.Core/ObjectResolution/WeightAttribute.cs index 35f10a4111..16d18fb7b7 100644 --- a/src/Umbraco.Core/ObjectResolution/WeightAttribute.cs +++ b/src/Umbraco.Core/ObjectResolution/WeightAttribute.cs @@ -12,26 +12,11 @@ namespace Umbraco.Core.ObjectResolution /// Initializes a new instance of the class with a weight. /// /// The object type weight. - /// - /// This internal constructor allows for internal Umbraco products to set a negative number weight - /// - internal WeightAttribute(int weight) + public WeightAttribute(int weight) { Weight = weight; } - /// - /// Initializes a new instance of the class with a weight. - /// - /// The object type weight. - /// - /// The weight must be a positive number - /// - public WeightAttribute(uint weight) - { - Weight = Convert.ToInt32(weight); - } - /// /// Gets or sets the weight of the object type. /// diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index 791318d8ab..78b211ee7c 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -11,6 +11,7 @@ using umbraco.cms.businesslogic; using System.Linq; using umbraco.cms.businesslogic.web; using Umbraco.Core.Logging; +using Umbraco.Core.ObjectResolution; using Umbraco.Core.Publishing; using Content = Umbraco.Core.Models.Content; using ApplicationTree = Umbraco.Core.Models.ApplicationTree; @@ -21,6 +22,7 @@ namespace Umbraco.Web.Cache /// /// Class which listens to events on business level objects in order to invalidate the cache amongst servers when data changes /// + [Weight(int.MinValue)] public class CacheRefresherEventHandler : ApplicationEventHandler { protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) @@ -104,14 +106,14 @@ namespace Umbraco.Web.Cache //Bind to media events - MediaService.Saved += MediaServiceSaved; + MediaService.Saved += MediaServiceSaved; MediaService.Deleted += MediaServiceDeleted; MediaService.Moved += MediaServiceMoved; MediaService.Trashed += MediaServiceTrashed; MediaService.EmptiedRecycleBin += MediaServiceEmptiedRecycleBin; //Bind to content events - this is for unpublished content syncing across servers (primarily for examine) - + ContentService.Saved += ContentServiceSaved; ContentService.Deleted += ContentServiceDeleted; ContentService.Copied += ContentServiceCopied; @@ -231,7 +233,7 @@ namespace Umbraco.Web.Cache DistributedCache.Instance.RemoveUnpublishedCachePermanently(e.Ids.ToArray()); } } - + /// /// Handles cache refreshing for when content is trashed /// @@ -253,7 +255,7 @@ namespace Umbraco.Web.Cache /// /// /// - /// When an entity is copied new permissions may be assigned to it based on it's parent, if that is the + /// When an entity is copied new permissions may be assigned to it based on it's parent, if that is the /// case then we need to clear all user permissions cache. /// static void ContentServiceCopied(IContentService sender, CopyEventArgs e) @@ -285,10 +287,10 @@ namespace Umbraco.Web.Cache /// /// /// - /// When an entity is saved we need to notify other servers about the change in order for the Examine indexes to + /// When an entity is saved we need to notify other servers about the change in order for the Examine indexes to /// stay up-to-date for unpublished content. - /// - /// When an entity is created new permissions may be assigned to it based on it's parent, if that is the + /// + /// When an entity is created new permissions may be assigned to it based on it's parent, if that is the /// case then we need to clear all user permissions cache. /// static void ContentServiceSaved(IContentService sender, SaveEventArgs e) @@ -303,8 +305,8 @@ namespace Umbraco.Web.Cache var permissionsChanged = ((Content)x).WasPropertyDirty("PermissionsChanged"); if (permissionsChanged) { - clearUserPermissions = true; - } + clearUserPermissions = true; + } } }); @@ -337,7 +339,7 @@ namespace Umbraco.Web.Cache static void ApplicationTreeDeleted(ApplicationTree sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationTreeCache(); - } + } #endregion #region Application event handlers @@ -349,7 +351,7 @@ namespace Umbraco.Web.Cache static void ApplicationDeleted(Section sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationCache(); - } + } #endregion #region UserType event handlers @@ -362,9 +364,9 @@ namespace Umbraco.Web.Cache { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserTypeCache(x.Id)); } - + #endregion - + #region Dictionary event handlers static void LocalizationServiceSavedDictionaryItem(ILocalizationService sender, SaveEventArgs e) @@ -390,11 +392,11 @@ namespace Umbraco.Web.Cache e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDataTypeCache(x)); } - + #endregion #region Stylesheet and stylesheet property event handlers - + static void FileServiceDeletedStylesheet(IFileService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveStylesheetCache(x)); @@ -441,7 +443,7 @@ namespace Umbraco.Web.Cache { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshLanguageCache(x)); } - + #endregion #region Content/media/member Type event handlers @@ -505,9 +507,9 @@ namespace Umbraco.Web.Cache e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshMemberTypeCache(x)); } - + #endregion - + #region User/permissions event handlers static void CacheRefresherEventHandler_AssignedPermissions(PermissionRepository sender, SaveEventArgs e) @@ -540,7 +542,7 @@ namespace Umbraco.Web.Cache { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserCache(x.Id)); } - + private static void InvalidateCacheForPermissionsChange(UserPermission sender) { if (sender.User != null) @@ -580,7 +582,7 @@ namespace Umbraco.Web.Cache { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshTemplateCache(x.Id)); } - + #endregion #region Macro event handlers @@ -600,7 +602,7 @@ namespace Umbraco.Web.Cache DistributedCache.Instance.RefreshMacroCache(entity); } } - + #endregion #region Media event handlers @@ -631,14 +633,14 @@ namespace Umbraco.Web.Cache static void MediaServiceSaved(IMediaService sender, SaveEventArgs e) { DistributedCache.Instance.RefreshMediaCache(e.SavedEntities.ToArray()); - } + } #endregion #region Member event handlers static void MemberServiceDeleted(IMemberService sender, DeleteEventArgs e) { - DistributedCache.Instance.RemoveMemberCache(e.DeletedEntities.ToArray()); + DistributedCache.Instance.RemoveMemberCache(e.DeletedEntities.ToArray()); } static void MemberServiceSaved(IMemberService sender, SaveEventArgs e)