From 84f6239c982b1eba5b1d095fdf365d2d027446bf Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 7 Jan 2019 09:30:47 +0100 Subject: [PATCH] Components Initialize and Terminate --- .../Components/AuditEventsComponent.cs | 6 ++ .../Components/ComponentCollection.cs | 16 ++++ src/Umbraco.Core/Components/IComponent.cs | 16 +++- .../Components/ManifestWatcherComponent.cs | 20 +++-- .../Components/RelateOnCopyComponent.cs | 5 +- .../Components/RelateOnTrashComponent.cs | 5 +- src/Umbraco.Core/Runtime/CoreRuntime.cs | 3 +- .../Runtime/CoreRuntimeComponent.cs | 12 ++- .../Components/ComponentTests.cs | 75 +++++++++++++++---- .../Runtimes/CoreRuntimeTests.cs | 16 +++- .../Cache/DistributedCacheBinderComponent.cs | 12 ++- .../BackOfficeUserAuditEventsComponent.cs | 18 +++-- ...aseServerRegistrarAndMessengerComponent.cs | 6 ++ .../Components/NotificationsComponent.cs | 26 +++++-- .../Components/PublicAccessComponent.cs | 5 +- .../Logging/WebProfilerComponent.cs | 31 +++++--- .../PropertyEditorsComponent.cs | 15 +++- .../NuCache/NuCacheComponent.cs | 6 ++ .../Routing/RedirectTrackingComponent.cs | 6 +- .../Runtime/WebRuntimeComponent.cs | 46 +++++++++--- .../Scheduling/SchedulerComponent.cs | 28 ++++--- src/Umbraco.Web/Search/ExamineComponent.cs | 39 ++++++---- .../SignalR/PreviewHubComponent.cs | 47 +++++++----- 23 files changed, 340 insertions(+), 119 deletions(-) diff --git a/src/Umbraco.Core/Components/AuditEventsComponent.cs b/src/Umbraco.Core/Components/AuditEventsComponent.cs index f592613e0d..08d4702afa 100644 --- a/src/Umbraco.Core/Components/AuditEventsComponent.cs +++ b/src/Umbraco.Core/Components/AuditEventsComponent.cs @@ -23,7 +23,10 @@ namespace Umbraco.Core.Components _auditService = auditService; _userService = userService; _entityService = entityService; + } + public void Initialize() + { UserService.SavedUserGroup += OnSavedUserGroupWithUsers; UserService.SavedUser += OnSavedUser; @@ -37,6 +40,9 @@ namespace Umbraco.Core.Components MemberService.Exported += OnMemberExported; } + public void Terminate() + { } + private IUser CurrentPerformingUser { get diff --git a/src/Umbraco.Core/Components/ComponentCollection.cs b/src/Umbraco.Core/Components/ComponentCollection.cs index abe493b524..4fa81b9760 100644 --- a/src/Umbraco.Core/Components/ComponentCollection.cs +++ b/src/Umbraco.Core/Components/ComponentCollection.cs @@ -20,6 +20,21 @@ namespace Umbraco.Core.Components _logger = logger; } + public void Initialize() + { + using (_logger.DebugDuration($"Initializing. (log components when >{LogThresholdMilliseconds}ms)", "Initialized.")) + { + foreach (var component in this.Reverse()) // terminate components in reverse order + { + var componentType = component.GetType(); + using (_logger.DebugDuration($"Initializing {componentType.FullName}.", $"Initialized {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + { + component.Initialize(); + } + } + } + } + public void Terminate() { using (_logger.DebugDuration($"Terminating. (log components when >{LogThresholdMilliseconds}ms)", "Terminated.")) @@ -29,6 +44,7 @@ namespace Umbraco.Core.Components var componentType = component.GetType(); using (_logger.DebugDuration($"Terminating {componentType.FullName}.", $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) { + component.Terminate(); component.DisposeIfDisposable(); } } diff --git a/src/Umbraco.Core/Components/IComponent.cs b/src/Umbraco.Core/Components/IComponent.cs index 28ca539387..b1954d821b 100644 --- a/src/Umbraco.Core/Components/IComponent.cs +++ b/src/Umbraco.Core/Components/IComponent.cs @@ -5,11 +5,21 @@ /// /// /// Components are created by DI and therefore must have a public constructor. - /// All components which are also disposable, will be disposed in reverse - /// order, when Umbraco terminates. + /// All components are terminated in reverse order when Umbraco terminates, and + /// disposable components are disposed. /// The Dispose method may be invoked more than once, and components /// should ensure they support this. /// public interface IComponent - { } + { + /// + /// Initializes the component. + /// + void Initialize(); + + /// + /// Terminates the component. + /// + void Terminate(); + } } diff --git a/src/Umbraco.Core/Components/ManifestWatcherComponent.cs b/src/Umbraco.Core/Components/ManifestWatcherComponent.cs index a96dd516e7..2420e1d5bb 100644 --- a/src/Umbraco.Core/Components/ManifestWatcherComponent.cs +++ b/src/Umbraco.Core/Components/ManifestWatcherComponent.cs @@ -1,20 +1,28 @@ -using System; -using System.IO; +using System.IO; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; namespace Umbraco.Core.Components { - public sealed class ManifestWatcherComponent : IComponent, IDisposable + public sealed class ManifestWatcherComponent : IComponent { + private readonly IRuntimeState _runtimeState; + private readonly ILogger _logger; + // if configured and in debug mode, a ManifestWatcher watches App_Plugins folders for // package.manifest chances and restarts the application on any change private ManifestWatcher _mw; public ManifestWatcherComponent(IRuntimeState runtimeState, ILogger logger) { - if (runtimeState.Debug == false) return; + _runtimeState = runtimeState; + _logger = logger; + } + + public void Initialize() + { + if (_runtimeState.Debug == false) return; //if (ApplicationContext.Current.IsConfigured == false || GlobalSettings.DebugMode == false) // return; @@ -22,11 +30,11 @@ namespace Umbraco.Core.Components var appPlugins = IOHelper.MapPath("~/App_Plugins/"); if (Directory.Exists(appPlugins) == false) return; - _mw = new ManifestWatcher(logger); + _mw = new ManifestWatcher(_logger); _mw.Start(Directory.GetDirectories(appPlugins)); } - public void Dispose() + public void Terminate() { if (_mw == null) return; diff --git a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs index 41ae3c70dc..404d385680 100644 --- a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs +++ b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs @@ -8,11 +8,14 @@ namespace Umbraco.Core.Components //TODO: This should just exist in the content service/repo! public sealed class RelateOnCopyComponent : IComponent { - public RelateOnCopyComponent() + public void Initialize() { ContentService.Copied += ContentServiceCopied; } + public void Terminate() + { } + private static void ContentServiceCopied(IContentService sender, Events.CopyEventArgs e) { if (e.RelateToOriginal == false) return; diff --git a/src/Umbraco.Core/Components/RelateOnTrashComponent.cs b/src/Umbraco.Core/Components/RelateOnTrashComponent.cs index 93803d4fae..6279bb98ba 100644 --- a/src/Umbraco.Core/Components/RelateOnTrashComponent.cs +++ b/src/Umbraco.Core/Components/RelateOnTrashComponent.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Components { public sealed class RelateOnTrashComponent : IComponent { - public RelateOnTrashComponent() + public void Initialize() { ContentService.Moved += ContentService_Moved; ContentService.Trashed += ContentService_Trashed; @@ -17,6 +17,9 @@ namespace Umbraco.Core.Components MediaService.Trashed += MediaService_Trashed; } + public void Terminate() + { } + private static void ContentService_Moved(IContentService sender, MoveEventArgs e) { foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinContent.ToInvariantString()))) diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 3a698dd30d..7a4314c968 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -146,8 +146,9 @@ namespace Umbraco.Core.Runtime // create the factory _factory = Current.Factory = composition.CreateFactory(); - // create the components + // create & initialize the components _components = _factory.GetInstance(); + _components.Initialize(); } catch (Exception e) { diff --git a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs b/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs index 9e61feb15d..b9efdd6432 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntimeComponent.cs @@ -7,12 +7,19 @@ namespace Umbraco.Core.Runtime { public class CoreRuntimeComponent : IComponent { + private readonly IEnumerable _mapperProfiles; + public CoreRuntimeComponent(IEnumerable mapperProfiles) + { + _mapperProfiles = mapperProfiles; + } + + public void Initialize() { // mapper profiles have been registered & are created by the container Mapper.Initialize(configuration => { - foreach (var profile in mapperProfiles) + foreach (var profile in _mapperProfiles) configuration.AddProfile(profile); }); @@ -24,5 +31,8 @@ namespace Umbraco.Core.Runtime IOHelper.EnsurePathExists(SystemDirectories.MvcViews + "/Partials"); IOHelper.EnsurePathExists(SystemDirectories.MvcViews + "/MacroPartials"); } + + public void Terminate() + { } } } diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 903c53292c..a04636f919 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -18,7 +18,8 @@ namespace Umbraco.Tests.Components public class ComponentTests { private static readonly List Composed = new List(); - private static readonly List Initialized = new List(); + private static readonly List Initialized = new List(); + private static readonly List Terminated = new List(); private static IFactory MockFactory(Action> setup = null) { @@ -64,13 +65,36 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); - var components = new Core.Components.Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 3 is User - goes away with RuntimeLevel.Unknown // => reorder components accordingly - components.Compose(); + composers.Compose(); AssertTypeArray(TypeArray(), Composed); + + var factory = MockFactory(m => + { + m.Setup(x => x.TryGetInstance(It.Is(t => t == typeof(ISomeResource)))).Returns(() => new SomeResource()); + m.Setup(x => x.GetInstance(It.IsAny())).Returns((type) => + { + if (type == typeof(Composer1)) return new Composer1(); + if (type == typeof(Composer5)) return new Composer5(); + if (type == typeof(Component5)) return new Component5(new SomeResource()); + if (type == typeof(IProfilingLogger)) return new ProfilingLogger(Mock.Of(), Mock.Of()); + throw new NotSupportedException(type.FullName); + }); + }); + + var builder = composition.WithCollectionBuilder(); + builder.RegisterWith(register); + var components = builder.CreateCollection(factory); + + Assert.IsEmpty(components); + components.Initialize(); + Assert.IsEmpty(Initialized); + components.Terminate(); + Assert.IsEmpty(Terminated); } [Test] @@ -164,6 +188,10 @@ namespace Umbraco.Tests.Components [Test] public void Initialize() { + Composed.Clear(); + Initialized.Clear(); + Terminated.Clear(); + var register = MockRegister(); var factory = MockFactory(m => { @@ -181,17 +209,22 @@ namespace Umbraco.Tests.Components var types = new[] { typeof(Composer1), typeof(Composer5) }; var composers = new Composers(composition, types, Mock.Of()); - Composed.Clear(); - Initialized.Clear(); + + Assert.IsEmpty(Composed); composers.Compose(); + AssertTypeArray(TypeArray(), Composed); + var builder = composition.WithCollectionBuilder(); builder.RegisterWith(register); var components = builder.CreateCollection(factory); - Assert.AreEqual(2, Composed.Count); - Assert.AreEqual(typeof(Composer1), Composed[0]); - Assert.AreEqual(typeof(Composer5), Composed[1]); - Assert.AreEqual(1, Initialized.Count); - Assert.AreEqual("Umbraco.Tests.Components.ComponentTests+SomeResource", Initialized[0]); + + Assert.IsEmpty(Initialized); + components.Initialize(); + AssertTypeArray(TypeArray(), Initialized); + + Assert.IsEmpty(Terminated); + components.Terminate(); + AssertTypeArray(TypeArray(), Terminated); } [Test] @@ -301,9 +334,6 @@ namespace Umbraco.Tests.Components } } - public class TestComponentBase : IComponent - { } - public class Composer1 : TestComposerBase { } @@ -326,11 +356,26 @@ namespace Umbraco.Tests.Components } } - public class Component5 : IComponent + public class TestComponentBase : IComponent { + public virtual void Initialize() + { + Initialized.Add(GetType()); + } + + public virtual void Terminate() + { + Terminated.Add(GetType()); + } + } + + public class Component5 : TestComponentBase + { + private readonly ISomeResource _resource; + public Component5(ISomeResource resource) { - Initialized.Add(resource.GetType().FullName); + _resource = resource; } } diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs index ca70ac5b6a..5be0617f52 100644 --- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs @@ -191,8 +191,7 @@ namespace Umbraco.Tests.Runtimes { // test flags public static bool Ctored; - public static bool Initialized1; - public static bool Initialized2; + public static bool Initialized; public static bool Terminated; public static IProfilingLogger ProfilingLogger; @@ -200,7 +199,7 @@ namespace Umbraco.Tests.Runtimes public static void Reset() { - Ctored = Initialized1 = Initialized2 = Terminated = false; + Ctored = Initialized = Terminated = false; ProfilingLogger = null; } @@ -210,10 +209,19 @@ namespace Umbraco.Tests.Runtimes ProfilingLogger = proflog; } + public void Initialize() + { + Initialized = true; + } + + public void Terminate() + { + Terminated = true; + } + public void Dispose() { Disposed = true; - Terminated = true; } } } diff --git a/src/Umbraco.Web/Cache/DistributedCacheBinderComponent.cs b/src/Umbraco.Web/Cache/DistributedCacheBinderComponent.cs index 91646e3624..94d274731d 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheBinderComponent.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheBinderComponent.cs @@ -4,9 +4,19 @@ namespace Umbraco.Web.Cache { public class DistributedCacheBinderComponent : IComponent { + private readonly IDistributedCacheBinder _binder; + public DistributedCacheBinderComponent(IDistributedCacheBinder distributedCacheBinder) { - distributedCacheBinder.BindEvents(); + _binder = distributedCacheBinder; } + + public void Initialize() + { + _binder.BindEvents(); + } + + public void Terminate() + { } } } diff --git a/src/Umbraco.Web/Components/BackOfficeUserAuditEventsComponent.cs b/src/Umbraco.Web/Components/BackOfficeUserAuditEventsComponent.cs index 81b900fc3a..545a781a8d 100644 --- a/src/Umbraco.Web/Components/BackOfficeUserAuditEventsComponent.cs +++ b/src/Umbraco.Web/Components/BackOfficeUserAuditEventsComponent.cs @@ -12,17 +12,14 @@ namespace Umbraco.Web.Components private readonly IAuditService _auditService; private readonly IUserService _userService; - private IUser GetPerformingUser(int userId) - { - var found = userId >= 0 ? _userService.GetUserById(userId) : null; - return found ?? new User {Id = 0, Name = "SYSTEM", Email = ""}; - } - public BackOfficeUserAuditEventsComponent(IAuditService auditService, IUserService userService) { _auditService = auditService; _userService = userService; + } + public void Initialize() + { //BackOfficeUserManager.AccountLocked += ; //BackOfficeUserManager.AccountUnlocked += ; BackOfficeUserManager.ForgotPasswordRequested += OnForgotPasswordRequest; @@ -34,7 +31,15 @@ namespace Umbraco.Web.Components BackOfficeUserManager.PasswordChanged += OnPasswordChanged; BackOfficeUserManager.PasswordReset += OnPasswordReset; //BackOfficeUserManager.ResetAccessFailedCount += ; + } + public void Terminate() + { } + + private IUser GetPerformingUser(int userId) + { + var found = userId >= 0 ? _userService.GetUserById(userId) : null; + return found ?? new User { Id = 0, Name = "SYSTEM", Email = "" }; } private static string FormatEmail(IMembershipUser user) @@ -42,7 +47,6 @@ namespace Umbraco.Web.Components return user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; } - private void OnLoginSuccess(object sender, EventArgs args) { if (args is IdentityAuditEventArgs identityArgs) diff --git a/src/Umbraco.Web/Components/DatabaseServerRegistrarAndMessengerComponent.cs b/src/Umbraco.Web/Components/DatabaseServerRegistrarAndMessengerComponent.cs index bd3b50bd9a..2e3132a82c 100644 --- a/src/Umbraco.Web/Components/DatabaseServerRegistrarAndMessengerComponent.cs +++ b/src/Umbraco.Web/Components/DatabaseServerRegistrarAndMessengerComponent.cs @@ -121,7 +121,10 @@ namespace Umbraco.Web.Components new BackgroundTaskRunnerOptions { AutoStart = true }, logger); _processTaskRunner = new BackgroundTaskRunner("ServerInstProcess", new BackgroundTaskRunnerOptions { AutoStart = true }, logger); + } + public void Initialize() + { //We will start the whole process when a successful request is made UmbracoModule.RouteAttempt += RegisterBackgroundTasksOnce; @@ -129,6 +132,9 @@ namespace Umbraco.Web.Components _messenger.Startup(); } + public void Terminate() + { } + /// /// Handle when a request is made /// diff --git a/src/Umbraco.Web/Components/NotificationsComponent.cs b/src/Umbraco.Web/Components/NotificationsComponent.cs index 9314181c15..4b447598a3 100644 --- a/src/Umbraco.Web/Components/NotificationsComponent.cs +++ b/src/Umbraco.Web/Components/NotificationsComponent.cs @@ -18,27 +18,39 @@ namespace Umbraco.Web.Components { public sealed class NotificationsComponent : IComponent { - public NotificationsComponent(INotificationService notificationService, Notifier notifier, ActionCollection actions) + private readonly Notifier _notifier; + private readonly ActionCollection _actions; + + public NotificationsComponent(Notifier notifier, ActionCollection actions) + { + _notifier = notifier; + _actions = actions; + } + + public void Initialize() { //Send notifications for the send to publish action - ContentService.SentToPublish += (sender, args) => notifier.Notify(actions.GetAction(), args.Entity); + ContentService.SentToPublish += (sender, args) => _notifier.Notify(_actions.GetAction(), args.Entity); //Send notifications for the published action - ContentService.Published += (sender, args) => notifier.Notify(actions.GetAction(), args.PublishedEntities.ToArray()); + ContentService.Published += (sender, args) => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); //Send notifications for the saved action - ContentService.Sorted += (sender, args) => ContentServiceSorted(notifier, sender, args, actions); + ContentService.Sorted += (sender, args) => ContentServiceSorted(_notifier, sender, args, _actions); //Send notifications for the update and created actions - ContentService.Saved += (sender, args) => ContentServiceSaved(notifier, sender, args, actions); + ContentService.Saved += (sender, args) => ContentServiceSaved(_notifier, sender, args, _actions); //Send notifications for the delete action - ContentService.Deleted += (sender, args) => notifier.Notify(actions.GetAction(), args.DeletedEntities.ToArray()); + ContentService.Deleted += (sender, args) => _notifier.Notify(_actions.GetAction(), args.DeletedEntities.ToArray()); //Send notifications for the unpublish action - ContentService.Unpublished += (sender, args) => notifier.Notify(actions.GetAction(), args.PublishedEntities.ToArray()); + ContentService.Unpublished += (sender, args) => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); } + public void Terminate() + { } + private void ContentServiceSorted(Notifier notifier, IContentService sender, Core.Events.SaveEventArgs args, ActionCollection actions) { var parentId = args.SavedEntities.Select(x => x.ParentId).Distinct().ToList(); diff --git a/src/Umbraco.Web/Components/PublicAccessComponent.cs b/src/Umbraco.Web/Components/PublicAccessComponent.cs index 09c1108295..e08d42d961 100644 --- a/src/Umbraco.Web/Components/PublicAccessComponent.cs +++ b/src/Umbraco.Web/Components/PublicAccessComponent.cs @@ -8,11 +8,14 @@ namespace Umbraco.Web.Components { public sealed class PublicAccessComponent : IComponent { - public PublicAccessComponent() + public void Initialize() { MemberGroupService.Saved += MemberGroupService_Saved; } + public void Terminate() + { } + static void MemberGroupService_Saved(IMemberGroupService sender, Core.Events.SaveEventArgs e) { foreach (var grp in e.SavedEntities) diff --git a/src/Umbraco.Web/Logging/WebProfilerComponent.cs b/src/Umbraco.Web/Logging/WebProfilerComponent.cs index 418ea66d23..b61bcd372c 100755 --- a/src/Umbraco.Web/Logging/WebProfilerComponent.cs +++ b/src/Umbraco.Web/Logging/WebProfilerComponent.cs @@ -1,6 +1,5 @@ using System; using System.Web; -using Umbraco.Core; using Umbraco.Core.Components; using Umbraco.Core.Logging; @@ -9,30 +8,38 @@ namespace Umbraco.Web.Logging internal sealed class WebProfilerComponent : IComponent { private readonly WebProfiler _profiler; + private readonly bool _profile; - public WebProfilerComponent(IProfiler profiler, ILogger logger, IRuntimeState runtime) + public WebProfilerComponent(IProfiler profiler, ILogger logger) { + _profile = true; + // although registered in WebRuntime.Compose, ensure that we have not // been replaced by another component, and we are still "the" profiler _profiler = profiler as WebProfiler; - if (_profiler == null) - { - // if VoidProfiler was registered, let it be known - var vp = profiler as VoidProfiler; - if (vp != null) - logger.Info("Profiler is VoidProfiler, not profiling (must run debug mode to profile)."); - return; - } + if (_profiler != null) return; + + // if VoidProfiler was registered, let it be known + if (profiler is VoidProfiler) + logger.Info("Profiler is VoidProfiler, not profiling (must run debug mode to profile)."); + _profile = false; + } + + public void Initialize() + { + if (!_profile) return; // bind to ApplicationInit - ie execute the application initialization for *each* application // it would be a mistake to try and bind to the current application events UmbracoApplicationBase.ApplicationInit += InitializeApplication; } + public void Terminate() + { } + private void InitializeApplication(object sender, EventArgs args) { - var app = sender as HttpApplication; - if (app == null) return; + if (!(sender is HttpApplication app)) return; // for *each* application (this will run more than once) app.BeginRequest += (s, a) => _profiler.UmbracoApplicationBeginRequest(s, a); diff --git a/src/Umbraco.Web/PropertyEditors/PropertyEditorsComponent.cs b/src/Umbraco.Web/PropertyEditors/PropertyEditorsComponent.cs index e719a72160..2fc3badb85 100644 --- a/src/Umbraco.Web/PropertyEditors/PropertyEditorsComponent.cs +++ b/src/Umbraco.Web/PropertyEditors/PropertyEditorsComponent.cs @@ -1,5 +1,4 @@ using System.Linq; -using Umbraco.Core; using Umbraco.Core.Components; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -9,18 +8,26 @@ namespace Umbraco.Web.PropertyEditors { internal sealed class PropertyEditorsComponent : IComponent { + private readonly PropertyEditorCollection _propertyEditors; + public PropertyEditorsComponent(PropertyEditorCollection propertyEditors) { - var fileUpload = propertyEditors.OfType().FirstOrDefault(); + _propertyEditors = propertyEditors; + } + + public void Initialize() + { + var fileUpload = _propertyEditors.OfType().FirstOrDefault(); if (fileUpload != null) Initialize(fileUpload); - var imageCropper = propertyEditors.OfType().FirstOrDefault(); + var imageCropper = _propertyEditors.OfType().FirstOrDefault(); if (imageCropper != null) Initialize(imageCropper); // grid/examine moved to ExamineComponent } - // as long as these methods are private+static they won't be executed by the boot loader + public void Terminate() + { } private static void Initialize(FileUploadPropertyEditor fileUpload) { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs b/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs index fcb2f016b0..089ff6a8ea 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs @@ -8,5 +8,11 @@ namespace Umbraco.Web.PublishedCache.NuCache { // nothing - this just ensures that the service is created at boot time } + + public void Initialize() + { } + + public void Terminate() + { } } } diff --git a/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs b/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs index e0af787e84..6cebf857ac 100644 --- a/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs +++ b/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs @@ -69,8 +69,7 @@ namespace Umbraco.Web.Redirects } } - public RedirectTrackingComponent() - //protected void Initialize(ContentFinderCollectionBuilder contentFinderCollectionBuilder) + public void Initialize() { // don't let the event handlers kick in if Redirect Tracking is turned off in the config if (UmbracoConfig.GetConfig("umbracoConfiguration/settings").WebRouting.DisableRedirectUrlTracking) return; @@ -103,6 +102,9 @@ namespace Umbraco.Web.Redirects // rolled back items have to be published, so publishing will take care of that } + public void Terminate() + { } + private static void ContentCacheRefresher_CacheUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args) { diff --git a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs index 8aff0457ea..0199a34579 100644 --- a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs +++ b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs @@ -34,8 +34,17 @@ namespace Umbraco.Web.Runtime { public sealed class WebRuntimeComponent : IComponent { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly SurfaceControllerTypeCollection _surfaceControllerTypes; + private readonly UmbracoApiControllerTypeCollection _apiControllerTypes; + private readonly IPublishedSnapshotService _publishedSnapshotService; + private readonly IUserService _userService; + private readonly IUmbracoSettingsSection _umbracoSettings; + private readonly IGlobalSettings _globalSettings; + private readonly IVariationContextAccessor _variationContextAccessor; + private readonly UrlProviderCollection _urlProviders; + public WebRuntimeComponent( - IRuntimeState runtime, IUmbracoContextAccessor umbracoContextAccessor, SurfaceControllerTypeCollection surfaceControllerTypes, UmbracoApiControllerTypeCollection apiControllerTypes, @@ -43,15 +52,27 @@ namespace Umbraco.Web.Runtime IUserService userService, IUmbracoSettingsSection umbracoSettings, IGlobalSettings globalSettings, - IEntityService entityService, IVariationContextAccessor variationContextAccessor, UrlProviderCollection urlProviders) { + _umbracoContextAccessor = umbracoContextAccessor; + _surfaceControllerTypes = surfaceControllerTypes; + _apiControllerTypes = apiControllerTypes; + _publishedSnapshotService = publishedSnapshotService; + _userService = userService; + _umbracoSettings = umbracoSettings; + _globalSettings = globalSettings; + _variationContextAccessor = variationContextAccessor; + _urlProviders = urlProviders; + } + + public void Initialize() + { // setup mvc and webapi services SetupMvcAndWebApi(); // client dependency - ConfigureClientDependency(globalSettings); + ConfigureClientDependency(_globalSettings); // Disable the X-AspNetMvc-Version HTTP Header MvcHandler.DisableMvcResponseHeader = true; @@ -65,7 +86,7 @@ namespace Umbraco.Web.Runtime ConfigureGlobalFilters(); // set routes - CreateRoutes(umbracoContextAccessor, globalSettings, surfaceControllerTypes, apiControllerTypes); + CreateRoutes(_umbracoContextAccessor, _globalSettings, _surfaceControllerTypes, _apiControllerTypes); // get an http context // at that moment, HttpContext.Current != null but its .Request property is null @@ -75,19 +96,22 @@ namespace Umbraco.Web.Runtime // (also sets the accessor) // this is a *temp* UmbracoContext UmbracoContext.EnsureContext( - umbracoContextAccessor, + _umbracoContextAccessor, new HttpContextWrapper(HttpContext.Current), - publishedSnapshotService, - new WebSecurity(httpContext, userService, globalSettings), - umbracoSettings, - urlProviders, - globalSettings, - variationContextAccessor); + _publishedSnapshotService, + new WebSecurity(httpContext, _userService, _globalSettings), + _umbracoSettings, + _urlProviders, + _globalSettings, + _variationContextAccessor); // ensure WebAPI is initialized, after everything GlobalConfiguration.Configuration.EnsureInitialized(); } + public void Terminate() + { } + private static void ConfigureGlobalFilters() { GlobalFilters.Filters.Add(new EnsurePartialViewMacroViewContextFilterAttribute()); diff --git a/src/Umbraco.Web/Scheduling/SchedulerComponent.cs b/src/Umbraco.Web/Scheduling/SchedulerComponent.cs index efd9d60fe8..acfb2b4608 100644 --- a/src/Umbraco.Web/Scheduling/SchedulerComponent.cs +++ b/src/Umbraco.Web/Scheduling/SchedulerComponent.cs @@ -24,11 +24,11 @@ namespace Umbraco.Web.Scheduling private readonly HealthCheckCollection _healthChecks; private readonly HealthCheckNotificationMethodCollection _notifications; - private readonly BackgroundTaskRunner _keepAliveRunner; - private readonly BackgroundTaskRunner _publishingRunner; - private readonly BackgroundTaskRunner _tasksRunner; - private readonly BackgroundTaskRunner _scrubberRunner; - private readonly BackgroundTaskRunner _healthCheckRunner; + private BackgroundTaskRunner _keepAliveRunner; + private BackgroundTaskRunner _publishingRunner; + private BackgroundTaskRunner _tasksRunner; + private BackgroundTaskRunner _scrubberRunner; + private BackgroundTaskRunner _healthCheckRunner; private bool _started; private object _locker = new object(); @@ -47,18 +47,26 @@ namespace Umbraco.Web.Scheduling _healthChecks = healthChecks; _notifications = notifications; + } + public void Initialize() + { // backgrounds runners are web aware, if the app domain dies, these tasks will wind down correctly - _keepAliveRunner = new BackgroundTaskRunner("KeepAlive", logger); - _publishingRunner = new BackgroundTaskRunner("ScheduledPublishing", logger); - _tasksRunner = new BackgroundTaskRunner("ScheduledTasks", logger); - _scrubberRunner = new BackgroundTaskRunner("LogScrubber", logger); - _healthCheckRunner = new BackgroundTaskRunner("HealthCheckNotifier", logger); + _keepAliveRunner = new BackgroundTaskRunner("KeepAlive", _logger); + _publishingRunner = new BackgroundTaskRunner("ScheduledPublishing", _logger); + _tasksRunner = new BackgroundTaskRunner("ScheduledTasks", _logger); + _scrubberRunner = new BackgroundTaskRunner("LogScrubber", _logger); + _healthCheckRunner = new BackgroundTaskRunner("HealthCheckNotifier", _logger); // we will start the whole process when a successful request is made UmbracoModule.RouteAttempt += RegisterBackgroundTasksOnce; } + public void Terminate() + { + // the appdomain / maindom / whatever takes care of stopping background task runners + } + private void RegisterBackgroundTasksOnce(object sender, RoutableAttemptEventArgs e) { switch (e.Outcome) diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index 81543c6dcc..09d0c555da 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web.Search { private readonly IExamineManager _examineManager; private readonly IContentValueSetBuilder _contentValueSetBuilder; - public IPublishedContentValueSetBuilder _publishedContentValueSetBuilder; + private readonly IPublishedContentValueSetBuilder _publishedContentValueSetBuilder; private readonly IValueSetBuilder _mediaValueSetBuilder; private readonly IValueSetBuilder _memberValueSetBuilder; private static bool _disableExamineIndexing = false; @@ -36,6 +36,10 @@ namespace Umbraco.Web.Search private readonly ServiceContext _services; private static BackgroundTaskRunner _rebuildOnStartupRunner; private static readonly object RebuildLocker = new object(); + private readonly IMainDom _mainDom; + private readonly IProfilingLogger _logger; + private readonly IUmbracoIndexesCreator _indexCreator; + private readonly IndexRebuilder _indexRebuilder; // the default enlist priority is 100 // enlist with a lower priority to ensure that anything "default" runs after us @@ -59,6 +63,14 @@ namespace Umbraco.Web.Search _mediaValueSetBuilder = mediaValueSetBuilder; _memberValueSetBuilder = memberValueSetBuilder; + _mainDom = mainDom; + _logger = profilingLogger; + _indexCreator = indexCreator; + _indexRebuilder = indexRebuilder; + } + + public void Initialize() + { //we want to tell examine to use a different fs lock instead of the default NativeFSFileLock which could cause problems if the appdomain //terminates and in some rare cases would only allow unlocking of the file if IIS is forcefully terminated. Instead we'll rely on the simplefslock //which simply checks the existence of the lock file @@ -69,9 +81,9 @@ namespace Umbraco.Web.Search }; //let's deal with shutting down Examine with MainDom - var examineShutdownRegistered = mainDom.Register(() => + var examineShutdownRegistered = _mainDom.Register(() => { - using (profilingLogger.TraceDuration("Examine shutting down")) + using (_logger.TraceDuration("Examine shutting down")) { _examineManager.Dispose(); } @@ -79,23 +91,23 @@ namespace Umbraco.Web.Search if (!examineShutdownRegistered) { - profilingLogger.Debug("Examine shutdown not registered, this appdomain is not the MainDom, Examine will be disabled"); + _logger.Debug("Examine shutdown not registered, this appdomain is not the MainDom, Examine will be disabled"); //if we could not register the shutdown examine ourselves, it means we are not maindom! in this case all of examine should be disabled! - Suspendable.ExamineEvents.SuspendIndexers(profilingLogger); + Suspendable.ExamineEvents.SuspendIndexers(_logger); _disableExamineIndexing = true; return; //exit, do not continue } //create the indexes and register them with the manager - foreach(var index in indexCreator.Create()) + foreach(var index in _indexCreator.Create()) _examineManager.AddIndex(index); - profilingLogger.Debug("Examine shutdown registered with MainDom"); + _logger.Debug("Examine shutdown registered with MainDom"); - var registeredIndexers = examineManager.Indexes.OfType().Count(x => x.EnableDefaultEventHandler); + var registeredIndexers = _examineManager.Indexes.OfType().Count(x => x.EnableDefaultEventHandler); - profilingLogger.Info("Adding examine event handlers for {RegisteredIndexers} index providers.", registeredIndexers); + _logger.Info("Adding examine event handlers for {RegisteredIndexers} index providers.", registeredIndexers); // don't bind event handlers if we're not suppose to listen if (registeredIndexers == 0) @@ -108,12 +120,15 @@ namespace Umbraco.Web.Search MediaCacheRefresher.CacheUpdated += MediaCacheRefresherUpdated; MemberCacheRefresher.CacheUpdated += MemberCacheRefresherUpdated; - EnsureUnlocked(profilingLogger, examineManager); + EnsureUnlocked(_logger, _examineManager); //TODO: Instead of waiting 5000 ms, we could add an event handler on to fulfilling the first request, then start? - RebuildIndexes(indexRebuilder, profilingLogger, true, 5000); + RebuildIndexes(_indexRebuilder, _logger, true, 5000); } + public void Terminate() + { } + /// /// Called to rebuild empty indexes on startup /// @@ -146,8 +161,6 @@ namespace Umbraco.Web.Search } } - - /// /// Must be called to each index is unlocked before any indexing occurs /// diff --git a/src/Umbraco.Web/SignalR/PreviewHubComponent.cs b/src/Umbraco.Web/SignalR/PreviewHubComponent.cs index 1ca8ad0176..328f5a06ed 100644 --- a/src/Umbraco.Web/SignalR/PreviewHubComponent.cs +++ b/src/Umbraco.Web/SignalR/PreviewHubComponent.cs @@ -1,5 +1,6 @@ using System; using Microsoft.AspNet.SignalR; +using Umbraco.Core.Cache; using Umbraco.Core.Components; using Umbraco.Core.Sync; using Umbraco.Web.Cache; @@ -8,31 +9,39 @@ namespace Umbraco.Web.SignalR { public class PreviewHubComponent : IComponent { + private readonly Lazy> _hubContext; + // using a lazy arg here means that we won't create the hub until necessary // and therefore we won't have too bad an impact on boot time public PreviewHubComponent(Lazy> hubContext) { - // ContentService.Saved is too soon - the content cache is not ready yet - // try using the content cache refresher event, because when it triggers - // the cache has already been notified of the changes - //ContentService.Saved += (sender, args) => - //{ - // var entity = args.SavedEntities.FirstOrDefault(); - // if (entity != null) - // _previewHub.Clients.All.refreshed(entity.Id); - //}; + _hubContext = hubContext; + } - ContentCacheRefresher.CacheUpdated += (sender, args) => + public void Initialize() + { + // ContentService.Saved is too soon - the content cache is not ready yet, + // so use the content cache refresher event, because when it triggers + // the cache has already been notified of the changes + + ContentCacheRefresher.CacheUpdated += HandleCacheUpdated; + } + + public void Terminate() + { + ContentCacheRefresher.CacheUpdated -= HandleCacheUpdated; + } + + private void HandleCacheUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args) + { + if (args.MessageType != MessageType.RefreshByPayload) return; + var payloads = (ContentCacheRefresher.JsonPayload[])args.MessageObject; + var hubContextInstance = _hubContext.Value; + foreach (var payload in payloads) { - if (args.MessageType != MessageType.RefreshByPayload) return; - var payloads = (ContentCacheRefresher.JsonPayload[])args.MessageObject; - var hubContextInstance = hubContext.Value; - foreach (var payload in payloads) - { - var id = payload.Id; // keep it simple for now, ignore ChangeTypes - hubContextInstance.Clients.All.refreshed(id); - } - }; + var id = payload.Id; // keep it simple for now, ignore ChangeTypes + hubContextInstance.Clients.All.refreshed(id); + } } } }