using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using Umbraco.Core; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Services; using umbraco.BusinessLogic; using umbraco.cms.businesslogic; using System.Linq; using System.Reflection; using System.Web; using System.Web.Hosting; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Publishing; using Umbraco.Web.Routing; using Umbraco.Web.Security; using Content = Umbraco.Core.Models.Content; using ApplicationTree = Umbraco.Core.Models.ApplicationTree; using DeleteEventArgs = umbraco.cms.businesslogic.DeleteEventArgs; 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 { public CacheRefresherEventHandler() { } public CacheRefresherEventHandler(bool supportUnbinding) { if (supportUnbinding) _unbinders = new List(); } protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { LogHelper.Info("Initializing Umbraco internal event handlers for cache refreshing"); // bind to application tree events Bind(() => ApplicationTreeService.Deleted += ApplicationTreeService_Deleted, () => ApplicationTreeService.Deleted -= ApplicationTreeService_Deleted); Bind(() => ApplicationTreeService.Updated += ApplicationTreeService_Updated, () => ApplicationTreeService.Updated -= ApplicationTreeService_Updated); Bind(() => ApplicationTreeService.New += ApplicationTreeService_New, () => ApplicationTreeService.New -= ApplicationTreeService_New); // bind to application events Bind(() => SectionService.Deleted += SectionService_Deleted, () => SectionService.Deleted -= SectionService_Deleted); Bind(() => SectionService.New += SectionService_New, () => SectionService.New -= SectionService_New); // bind to user and user type events Bind(() => UserService.SavedUserType += UserService_SavedUserType, () => UserService.SavedUserType -= UserService_SavedUserType); Bind(() => UserService.DeletedUserType += UserService_DeletedUserType, () => UserService.DeletedUserType -= UserService_DeletedUserType); Bind(() => UserService.SavedUser += UserService_SavedUser, () => UserService.SavedUser -= UserService_SavedUser); Bind(() => UserService.DeletedUser += UserService_DeletedUser, () => UserService.DeletedUser -= UserService_DeletedUser); // bind to dictionary events Bind(() => LocalizationService.DeletedDictionaryItem += LocalizationService_DeletedDictionaryItem, () => LocalizationService.DeletedDictionaryItem -= LocalizationService_DeletedDictionaryItem); Bind(() => LocalizationService.SavedDictionaryItem += LocalizationService_SavedDictionaryItem, () => LocalizationService.SavedDictionaryItem -= LocalizationService_SavedDictionaryItem); // bind to data type events Bind(() => DataTypeService.Deleted += DataTypeService_Deleted, () => DataTypeService.Deleted -= DataTypeService_Deleted); Bind(() => DataTypeService.Saved += DataTypeService_Saved, () => DataTypeService.Saved -= DataTypeService_Saved); // bind to stylesheet events Bind(() => FileService.SavedStylesheet += FileService_SavedStylesheet, () => FileService.SavedStylesheet -= FileService_SavedStylesheet); Bind(() => FileService.DeletedStylesheet += FileService_DeletedStylesheet, () => FileService.DeletedStylesheet -= FileService_DeletedStylesheet); // bind to domain events Bind(() => DomainService.Saved += DomainService_Saved, () => DomainService.Saved -= DomainService_Saved); Bind(() => DomainService.Deleted += DomainService_Deleted, () => DomainService.Deleted -= DomainService_Deleted); // bind to language events Bind(() => LocalizationService.SavedLanguage += LocalizationService_SavedLanguage, () => LocalizationService.SavedLanguage -= LocalizationService_SavedLanguage); Bind(() => LocalizationService.DeletedLanguage += LocalizationService_DeletedLanguage, () => LocalizationService.DeletedLanguage -= LocalizationService_DeletedLanguage); // bind to content type events Bind(() => ContentTypeService.SavedContentType += ContentTypeService_SavedContentType, () => ContentTypeService.SavedContentType -= ContentTypeService_SavedContentType); Bind(() => ContentTypeService.SavedMediaType += ContentTypeService_SavedMediaType, () => ContentTypeService.SavedMediaType -= ContentTypeService_SavedMediaType); Bind(() => ContentTypeService.DeletedContentType += ContentTypeService_DeletedContentType, () => ContentTypeService.DeletedContentType -= ContentTypeService_DeletedContentType); Bind(() => ContentTypeService.DeletedMediaType += ContentTypeService_DeletedMediaType, () => ContentTypeService.DeletedMediaType -= ContentTypeService_DeletedMediaType); Bind(() => MemberTypeService.Saved += MemberTypeService_Saved, () => MemberTypeService.Saved -= MemberTypeService_Saved); Bind(() => MemberTypeService.Deleted += MemberTypeService_Deleted, () => MemberTypeService.Deleted -= MemberTypeService_Deleted); // bind to permission events // we should wrap legacy permissions so we can get rid of this // fixme - the method names here (PermissionNew...) are not supported // by the event handling mechanism for scopes and deploy, and not sure // how to fix with the generic repository Bind(() => Permission.New += PermissionNew, () => Permission.New -= PermissionNew); Bind(() => Permission.Updated += PermissionUpdated, () => Permission.Updated -= PermissionUpdated); Bind(() => Permission.Deleted += PermissionDeleted, () => Permission.Deleted -= PermissionDeleted); Bind(() => PermissionRepository.AssignedPermissions += CacheRefresherEventHandler_AssignedPermissions, () => PermissionRepository.AssignedPermissions -= CacheRefresherEventHandler_AssignedPermissions); // bind to template events Bind(() => FileService.SavedTemplate += FileService_SavedTemplate, () => FileService.SavedTemplate -= FileService_SavedTemplate); Bind(() => FileService.DeletedTemplate += FileService_DeletedTemplate, () => FileService.DeletedTemplate -= FileService_DeletedTemplate); // bind to macro events Bind(() => MacroService.Saved += MacroService_Saved, () => MacroService.Saved -= MacroService_Saved); Bind(() => MacroService.Deleted += MacroService_Deleted, () => MacroService.Deleted -= MacroService_Deleted); // bind to member events Bind(() => MemberService.Saved += MemberService_Saved, () => MemberService.Saved -= MemberService_Saved); Bind(() => MemberService.Deleted += MemberService_Deleted, () => MemberService.Deleted -= MemberService_Deleted); Bind(() => MemberGroupService.Saved += MemberGroupService_Saved, () => MemberGroupService.Saved -= MemberGroupService_Saved); Bind(() => MemberGroupService.Deleted += MemberGroupService_Deleted, () => MemberGroupService.Deleted -= MemberGroupService_Deleted); // bind to media events Bind(() => MediaService.Saved += MediaService_Saved, () => MediaService.Saved -= MediaService_Saved); Bind(() => MediaService.Deleted += MediaService_Deleted, () => MediaService.Deleted -= MediaService_Deleted); Bind(() => MediaService.Moved += MediaService_Moved, () => MediaService.Moved -= MediaService_Moved); Bind(() => MediaService.Trashed += MediaService_Trashed, () => MediaService.Trashed -= MediaService_Trashed); Bind(() => MediaService.EmptiedRecycleBin += MediaService_EmptiedRecycleBin, () => MediaService.EmptiedRecycleBin -= MediaService_EmptiedRecycleBin); // bind to content events // this is for unpublished content syncing across servers (primarily for examine) Bind(() => ContentService.Saved += ContentService_Saved, () => ContentService.Saved -= ContentService_Saved); Bind(() => ContentService.Deleted += ContentService_Deleted, () => ContentService.Deleted -= ContentService_Deleted); Bind(() => ContentService.Copied += ContentService_Copied, () => ContentService.Copied -= ContentService_Copied); // the Move method of the content service fires Saved/Published events during its // execution so we don't need to listen to moved - this will probably change in due time //Bind(() => ContentService.Moved += ContentServiceMoved, // () => ContentService.Moved -= ContentServiceMoved); Bind(() => ContentService.Trashed += ContentService_Trashed, () => ContentService.Trashed -= ContentService_Trashed); Bind(() => ContentService.EmptiedRecycleBin += ContentService_EmptiedRecycleBin, () => ContentService.EmptiedRecycleBin -= ContentService_EmptiedRecycleBin); Bind(() => ContentService.Published += ContentService_Published, () => ContentService.Published -= ContentService_Published); Bind(() => ContentService.UnPublished += ContentService_UnPublished, () => ContentService.UnPublished -= ContentService_UnPublished); // bind to public access events Bind(() => PublicAccessService.Saved += PublicAccessService_Saved, () => PublicAccessService.Saved -= PublicAccessService_Saved); Bind(() => PublicAccessService.Deleted += PublicAccessService_Deleted, () => PublicAccessService.Deleted -= PublicAccessService_Deleted); // bind to relation type events Bind(() => RelationService.SavedRelationType += RelationService_SavedRelationType, () => RelationService.SavedRelationType -= RelationService_SavedRelationType); Bind(() => RelationService.DeletedRelationType += RelationService_DeletedRelationType, () => RelationService.DeletedRelationType -= RelationService_DeletedRelationType); } private List _unbinders; private void Bind(Action binder, Action unbinder) { // bind now binder(); // abd register unbinder for later, if needed if (_unbinders == null) return; _unbinders.Add(unbinder); } // for tests internal void Unbind() { if (_unbinders == null) throw new NotSupportedException(); foreach (var unbinder in _unbinders) unbinder(); _unbinders = null; } #region Publishing // IPublishingStrategy (obsolete) events are proxied into ContentService, which works fine when // events are actually raised, but not when they are handled by HandleEvents, so we have to have // these proxy methods that are *not* registered against any event *but* used by HandleEvents. // ReSharper disable once UnusedMember.Local static void PublishingStrategy_UnPublished(IPublishingStrategy sender, PublishEventArgs e) { ContentService_UnPublished(sender, e); } // ReSharper disable once UnusedMember.Local static void PublishingStrategy_Published(IPublishingStrategy sender, PublishEventArgs e) { ContentService_Published(sender, e); } static void ContentService_UnPublished(IPublishingStrategy sender, PublishEventArgs e) { if (e.PublishedEntities.Any()) { if (e.PublishedEntities.Count() > 1) { foreach (var c in e.PublishedEntities) { UnPublishSingle(c); } } else { var content = e.PublishedEntities.FirstOrDefault(); UnPublishSingle(content); } } } /// /// Refreshes the xml cache for a single node by removing it /// private static void UnPublishSingle(IContent content) { DistributedCache.Instance.RemovePageCache(content); } static void ContentService_Published(IPublishingStrategy sender, PublishEventArgs e) { if (e.PublishedEntities.Any()) { if (e.IsAllRepublished) { UpdateEntireCache(); return; } if (e.PublishedEntities.Count() > 1) { UpdateMultipleContentCache(e.PublishedEntities); } else { var content = e.PublishedEntities.FirstOrDefault(); UpdateSingleContentCache(content); } } } /// /// Refreshes the xml cache for all nodes /// private static void UpdateEntireCache() { DistributedCache.Instance.RefreshAllPageCache(); } /// /// Refreshes the xml cache for nodes in list /// private static void UpdateMultipleContentCache(IEnumerable content) { DistributedCache.Instance.RefreshPageCache(content.ToArray()); } /// /// Refreshes the xml cache for a single node /// private static void UpdateSingleContentCache(IContent content) { DistributedCache.Instance.RefreshPageCache(content); } #endregion #region Public access event handlers static void PublicAccessService_Saved(IPublicAccessService sender, SaveEventArgs e) { DistributedCache.Instance.RefreshPublicAccess(); } static void PublicAccessService_Deleted(IPublicAccessService sender, DeleteEventArgs e) { DistributedCache.Instance.RefreshPublicAccess(); } #endregion #region Content service event handlers static void ContentService_EmptiedRecycleBin(IContentService sender, RecycleBinEventArgs e) { if (e.RecycleBinEmptiedSuccessfully && e.IsContentRecycleBin) { DistributedCache.Instance.RemoveUnpublishedCachePermanently(e.Ids.ToArray()); } } /// /// Handles cache refreshing for when content is trashed /// /// /// /// /// This is for the unpublished page refresher - the entity will be unpublished before being moved to the trash /// and the unpublished event will take care of remove it from any published caches /// static void ContentService_Trashed(IContentService sender, MoveEventArgs e) { DistributedCache.Instance.RefreshUnpublishedPageCache( e.MoveInfoCollection.Select(x => x.Entity).ToArray()); } /// /// Handles cache refreshing for when content is copied /// /// /// /// /// 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 ContentService_Copied(IContentService sender, CopyEventArgs e) { //check if permissions have changed var permissionsChanged = ((Content)e.Copy).WasPropertyDirty("PermissionsChanged"); if (permissionsChanged) { DistributedCache.Instance.RefreshAllUserPermissionsCache(); } //run the un-published cache refresher since copied content is not published DistributedCache.Instance.RefreshUnpublishedPageCache(e.Copy); } /// /// Handles cache refreshing for when content is deleted (not unpublished) /// /// /// static void ContentService_Deleted(IContentService sender, DeleteEventArgs e) { DistributedCache.Instance.RemoveUnpublishedPageCache(e.DeletedEntities.ToArray()); } /// /// Handles cache refreshing for when content is saved (not published) /// /// /// /// /// 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 /// case then we need to clear all user permissions cache. /// static void ContentService_Saved(IContentService sender, SaveEventArgs e) { var clearUserPermissions = false; e.SavedEntities.ForEach(x => { //check if it is new if (x.IsNewEntity()) { //check if permissions have changed var permissionsChanged = ((Content)x).WasPropertyDirty("PermissionsChanged"); if (permissionsChanged) { clearUserPermissions = true; } } }); if (clearUserPermissions) { DistributedCache.Instance.RefreshAllUserPermissionsCache(); } //filter out the entities that have only been saved (not newly published) since // newly published ones will be synced with the published page cache refresher var unpublished = e.SavedEntities.Where(x => x.JustPublished() == false); //run the un-published cache refresher DistributedCache.Instance.RefreshUnpublishedPageCache(unpublished.ToArray()); } #endregion #region ApplicationTree event handlers static void ApplicationTreeService_New(ApplicationTree sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationTreeCache(); } static void ApplicationTreeService_Updated(ApplicationTree sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationTreeCache(); } static void ApplicationTreeService_Deleted(ApplicationTree sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationTreeCache(); } #endregion #region Application event handlers static void SectionService_New(Section sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationCache(); } static void SectionService_Deleted(Section sender, EventArgs e) { DistributedCache.Instance.RefreshAllApplicationCache(); } #endregion #region UserType event handlers static void UserService_DeletedUserType(IUserService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserTypeCache(x.Id)); } static void UserService_SavedUserType(IUserService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserTypeCache(x.Id)); } #endregion #region Dictionary event handlers static void LocalizationService_SavedDictionaryItem(ILocalizationService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDictionaryCache(x.Id)); } static void LocalizationService_DeletedDictionaryItem(ILocalizationService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDictionaryCache(x.Id)); } #endregion #region DataType event handlers static void DataTypeService_Saved(IDataTypeService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDataTypeCache(x)); } static void DataTypeService_Deleted(IDataTypeService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDataTypeCache(x)); } #endregion #region Stylesheet and stylesheet property event handlers static void FileService_DeletedStylesheet(IFileService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveStylesheetCache(x)); } static void FileService_SavedStylesheet(IFileService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshStylesheetCache(x)); } #endregion #region Domain event handlers static void DomainService_Saved(IDomainService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshDomainCache(x)); } static void DomainService_Deleted(IDomainService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveDomainCache(x)); } #endregion #region Language event handlers /// /// Fires when a langauge is deleted /// /// /// static void LocalizationService_DeletedLanguage(ILocalizationService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveLanguageCache(x)); } /// /// Fires when a langauge is saved /// /// /// static void LocalizationService_SavedLanguage(ILocalizationService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshLanguageCache(x)); } #endregion #region Content/media/member Type event handlers /// /// Fires when a media type is deleted /// /// /// static void ContentTypeService_DeletedMediaType(IContentTypeService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveMediaTypeCache(x)); } /// /// Fires when a content type is deleted /// /// /// static void ContentTypeService_DeletedContentType(IContentTypeService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(contentType => DistributedCache.Instance.RemoveContentTypeCache(contentType)); } /// /// Fires when a member type is deleted /// /// /// static void MemberTypeService_Deleted(IMemberTypeService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(contentType => DistributedCache.Instance.RemoveMemberTypeCache(contentType)); } /// /// Fires when a media type is saved /// /// /// static void ContentTypeService_SavedMediaType(IContentTypeService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshMediaTypeCache(x)); } /// /// Fires when a content type is saved /// /// /// static void ContentTypeService_SavedContentType(IContentTypeService sender, SaveEventArgs e) { e.SavedEntities.ForEach(contentType => DistributedCache.Instance.RefreshContentTypeCache(contentType)); } /// /// Fires when a member type is saved /// /// /// static void MemberTypeService_Saved(IMemberTypeService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshMemberTypeCache(x)); } #endregion #region User/permissions event handlers static void CacheRefresherEventHandler_AssignedPermissions(PermissionRepository sender, SaveEventArgs e) { var userIds = e.SavedEntities.Select(x => x.UserId).Distinct(); userIds.ForEach(x => DistributedCache.Instance.RefreshUserPermissionsCache(x)); } static void PermissionDeleted(UserPermission sender, DeleteEventArgs e) { InvalidateCacheForPermissionsChange(sender); } static void PermissionUpdated(UserPermission sender, SaveEventArgs e) { InvalidateCacheForPermissionsChange(sender); } static void PermissionNew(UserPermission sender, NewEventArgs e) { InvalidateCacheForPermissionsChange(sender); } static void UserService_SavedUser(IUserService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserCache(x.Id)); } static void UserService_DeletedUser(IUserService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserCache(x.Id)); } private static void InvalidateCacheForPermissionsChange(UserPermission sender) { if (sender.User != null) { DistributedCache.Instance.RefreshUserPermissionsCache(sender.User.Id); } else if (sender.UserId > -1) { DistributedCache.Instance.RefreshUserPermissionsCache(sender.UserId); } else if (sender.NodeIds.Any()) { DistributedCache.Instance.RefreshAllUserPermissionsCache(); } } #endregion #region Template event handlers /// /// Removes cache for template /// /// /// static void FileService_DeletedTemplate(IFileService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveTemplateCache(x.Id)); } /// /// Refresh cache for template /// /// /// static void FileService_SavedTemplate(IFileService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshTemplateCache(x.Id)); } #endregion #region Macro event handlers static void MacroService_Deleted(IMacroService sender, DeleteEventArgs e) { foreach (var entity in e.DeletedEntities) { DistributedCache.Instance.RemoveMacroCache(entity); } } static void MacroService_Saved(IMacroService sender, SaveEventArgs e) { foreach (var entity in e.SavedEntities) { DistributedCache.Instance.RefreshMacroCache(entity); } } #endregion #region Media event handlers static void MediaService_EmptiedRecycleBin(IMediaService sender, RecycleBinEventArgs e) { if (e.RecycleBinEmptiedSuccessfully && e.IsMediaRecycleBin) { DistributedCache.Instance.RemoveMediaCachePermanently(e.Ids.ToArray()); } } static void MediaService_Trashed(IMediaService sender, MoveEventArgs e) { DistributedCache.Instance.RemoveMediaCacheAfterRecycling(e.MoveInfoCollection.ToArray()); } static void MediaService_Moved(IMediaService sender, MoveEventArgs e) { DistributedCache.Instance.RefreshMediaCacheAfterMoving(e.MoveInfoCollection.ToArray()); } static void MediaService_Deleted(IMediaService sender, DeleteEventArgs e) { DistributedCache.Instance.RemoveMediaCachePermanently(e.DeletedEntities.Select(x => x.Id).ToArray()); } static void MediaService_Saved(IMediaService sender, SaveEventArgs e) { DistributedCache.Instance.RefreshMediaCache(e.SavedEntities.ToArray()); } #endregion #region Member event handlers static void MemberService_Deleted(IMemberService sender, DeleteEventArgs e) { DistributedCache.Instance.RemoveMemberCache(e.DeletedEntities.ToArray()); } static void MemberService_Saved(IMemberService sender, SaveEventArgs e) { DistributedCache.Instance.RefreshMemberCache(e.SavedEntities.ToArray()); } #endregion #region Member group event handlers static void MemberGroupService_Deleted(IMemberGroupService sender, DeleteEventArgs e) { foreach (var m in e.DeletedEntities.ToArray()) { DistributedCache.Instance.RemoveMemberGroupCache(m.Id); } } static void MemberGroupService_Saved(IMemberGroupService sender, SaveEventArgs e) { foreach (var m in e.SavedEntities.ToArray()) { DistributedCache.Instance.RemoveMemberGroupCache(m.Id); } } #endregion #region Relation type event handlers static void RelationService_SavedRelationType(IRelationService sender, SaveEventArgs args) { var dc = DistributedCache.Instance; foreach (var e in args.SavedEntities) dc.RefreshRelationTypeCache(e.Id); } static void RelationService_DeletedRelationType(IRelationService sender, DeleteEventArgs args) { var dc = DistributedCache.Instance; foreach (var e in args.DeletedEntities) dc.RemoveRelationTypeCache(e.Id); } #endregion /// /// This will inspect the event metadata and execute it's affiliated handler if one is found /// /// internal static void HandleEvents(IEnumerable events) { //TODO: We should remove this in v8, this is a backwards compat hack and is needed because when we are using Deploy, the events will be raised on a background //thread which means that cache refreshers will also execute on a background thread and in many cases developers may be using UmbracoContext.Current in their //cache refresher handlers, so before we execute all of the events, we'll ensure a context UmbracoContext tempContext = null; if (UmbracoContext.Current == null) { var httpContext = new HttpContextWrapper(HttpContext.Current ?? new HttpContext(new SimpleWorkerRequest("temp.aspx", "", new StringWriter()))); tempContext = UmbracoContext.EnsureContext( httpContext, ApplicationContext.Current, new WebSecurity(httpContext, ApplicationContext.Current), UmbracoConfig.For.UmbracoSettings(), UrlProviderResolver.Current.Providers, true); } try { foreach (var e in events) { var handler = FindHandler(e); if (handler == null) continue; handler.Invoke(null, new[] { e.Sender, e.Args }); } } finally { if (tempContext != null) tempContext.Dispose(); } } /// /// Used to cache all candidate handlers /// private static readonly Lazy CandidateHandlers = new Lazy(() => { var underscore = new[] { '_' }; return typeof (CacheRefresherEventHandler) .GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) .Select(x => { if (x.Name.Contains("_") == false) return null; var parts = x.Name.Split(underscore, StringSplitOptions.RemoveEmptyEntries).Length; if (parts != 2) return null; var parameters = x.GetParameters(); if (parameters.Length != 2) return null; if (typeof (EventArgs).IsAssignableFrom(parameters[1].ParameterType) == false) return null; return x; }) .WhereNotNull() .ToArray(); }); /// /// Used to cache all found event handlers /// private static readonly ConcurrentDictionary FoundHandlers = new ConcurrentDictionary(); internal static MethodInfo FindHandler(IEventDefinition eventDefinition) { var name = eventDefinition.Sender.GetType().Name + "_" + eventDefinition.EventName; return FoundHandlers.GetOrAdd(eventDefinition, _ => CandidateHandlers.Value.FirstOrDefault(x => x.Name == name)); } } }