Implements two subscribers for the PublishingStrategy for updating cache after publish and unpublish.
With this in place it should only be necessary to call Publish in the ContentService, and not worry about refreshing the cache.
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
//Publishing Events
|
||||
public class PublishingEventArgs : System.ComponentModel.CancelEventArgs { }
|
||||
public class PublishingEventArgs : System.ComponentModel.CancelEventArgs
|
||||
{
|
||||
public bool IsAllRepublished { get; set; }
|
||||
}
|
||||
public class SendToPublishEventArgs : System.ComponentModel.CancelEventArgs { }
|
||||
|
||||
//Moving object Events
|
||||
|
||||
@@ -114,7 +114,8 @@ namespace Umbraco.Core.Publishing
|
||||
/// Call to fire event that updating the published content has finalized.
|
||||
/// </summary>
|
||||
/// <param name="content">An enumerable list of <see cref="IContent"/> thats being published</param>
|
||||
public abstract void PublishingFinalized(IEnumerable<IContent> content);
|
||||
/// <param name="isAllRepublished">Boolean indicating whether its all content that is republished</param>
|
||||
public abstract void PublishingFinalized(IEnumerable<IContent> content, bool isAllRepublished);
|
||||
|
||||
/// <summary>
|
||||
/// Call to fire event that updating the unpublished content has finalized.
|
||||
|
||||
@@ -54,7 +54,8 @@ namespace Umbraco.Core.Publishing
|
||||
/// Call to fire event that updating the published content has finalized.
|
||||
/// </summary>
|
||||
/// <param name="content">An enumerable list of <see cref="IContent"/> thats being published</param>
|
||||
void PublishingFinalized(IEnumerable<IContent> content);
|
||||
/// <param name="isAllRepublished">Boolean indicating whether its all content that is republished</param>
|
||||
void PublishingFinalized(IEnumerable<IContent> content, bool isAllRepublished);
|
||||
|
||||
/// <summary>
|
||||
/// Call to fire event that updating the unpublished content has finalized.
|
||||
|
||||
@@ -252,9 +252,10 @@ namespace Umbraco.Core.Publishing
|
||||
/// Call to fire event that updating the published content has finalized.
|
||||
/// </summary>
|
||||
/// <param name="content">An enumerable list of <see cref="IContent"/> thats being published</param>
|
||||
public override void PublishingFinalized(IEnumerable<IContent> content)
|
||||
/// <param name="isAllRepublished">Boolean indicating whether its all content that is republished</param>
|
||||
public override void PublishingFinalized(IEnumerable<IContent> content, bool isAllRepublished)
|
||||
{
|
||||
OnPublished(content, new PublishingEventArgs());
|
||||
OnPublished(content, new PublishingEventArgs{ IsAllRepublished = isAllRepublished});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -362,7 +362,7 @@ namespace Umbraco.Core.Services
|
||||
_unitOfWork.Commit();
|
||||
|
||||
//Updating content to published state is finished, so we fire event through PublishingStrategy to have cache updated
|
||||
_publishingStrategy.PublishingFinalized(updated);
|
||||
_publishingStrategy.PublishingFinalized(updated, true);
|
||||
|
||||
Audit.Add(AuditTypes.Publish, "RePublish All performed by user", userId == -1 ? 0 : userId, -1);
|
||||
}
|
||||
@@ -433,7 +433,7 @@ namespace Umbraco.Core.Services
|
||||
_unitOfWork.Commit();
|
||||
|
||||
//Save xml to db and call following method to fire event:
|
||||
_publishingStrategy.PublishingFinalized(updated);
|
||||
_publishingStrategy.PublishingFinalized(updated, false);
|
||||
|
||||
Audit.Add(AuditTypes.Publish, "Publish with Children performed by user", userId == -1 ? 0 : userId, content.Id);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Web;
|
||||
using System.Xml;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -11,7 +9,6 @@ using Umbraco.Core.ObjectResolution;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
using Umbraco.Web.Strategies;
|
||||
using umbraco.editorControls.tinyMCE3;
|
||||
using umbraco.interfaces;
|
||||
|
||||
@@ -66,25 +63,7 @@ namespace Umbraco.Tests.Publishing
|
||||
[Test]
|
||||
public void Can_Publish_And_Update_Xml_Cache()
|
||||
{
|
||||
// Arrange
|
||||
var httpContext = base.GetUmbracoContext("/test", 1234).HttpContext;
|
||||
var updateContentCache = new UpdateContentCache(httpContext);
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var content = contentService.GetById(1046);
|
||||
|
||||
// Act
|
||||
bool published = contentService.Publish(content, 0);
|
||||
|
||||
// Assert
|
||||
Assert.That(published, Is.True);
|
||||
Assert.That(content.Published, Is.True);
|
||||
Assert.IsTrue(httpContext.Items.Contains("UmbracoXmlContextContent"));
|
||||
|
||||
var document = httpContext.Items["UmbracoXmlContextContent"] as XmlDocument;
|
||||
Console.Write(document.OuterXml);
|
||||
document.Save("umbraco.config");
|
||||
|
||||
updateContentCache.Unsubscribe();
|
||||
//TODO Make new test
|
||||
}
|
||||
|
||||
public void CreateTestData()
|
||||
|
||||
100
src/Umbraco.Web/Strategies/UpdateCacheAfterPublish.cs
Normal file
100
src/Umbraco.Web/Strategies/UpdateCacheAfterPublish.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Publishing;
|
||||
using umbraco;
|
||||
using umbraco.cms.businesslogic.web;
|
||||
using umbraco.interfaces;
|
||||
using umbraco.presentation.cache;
|
||||
|
||||
namespace Umbraco.Web.Strategies
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the UpdateCacheAfterPublish class, which subscribes to the Published event
|
||||
/// of the <see cref="PublishingStrategy"/> class and is responsible for doing the actual
|
||||
/// cache refresh after a content item has been published.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This implementation is meant as a seperation of the cache refresh from the ContentService
|
||||
/// and PublishingStrategy.
|
||||
/// </remarks>
|
||||
public class UpdateCacheAfterPublish : IApplicationStartupHandler
|
||||
{
|
||||
public UpdateCacheAfterPublish()
|
||||
{
|
||||
PublishingStrategy.Published += PublishingStrategy_Published;
|
||||
}
|
||||
|
||||
void PublishingStrategy_Published(object sender, Core.PublishingEventArgs e)
|
||||
{
|
||||
if (sender is IContent)
|
||||
{
|
||||
var content = sender as IContent;
|
||||
UpdateSingleContentCache(content);
|
||||
}
|
||||
else if (sender is IEnumerable<IContent>)
|
||||
{
|
||||
if (e.IsAllRepublished)
|
||||
{
|
||||
var content = sender as IEnumerable<IContent>;
|
||||
UpdateMultipleContentCache(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateEntireCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the xml cache for all nodes
|
||||
/// </summary>
|
||||
private void UpdateEntireCache()
|
||||
{
|
||||
if (UmbracoSettings.UseDistributedCalls)
|
||||
{
|
||||
dispatcher.RefreshAll(new Guid("27ab3022-3dfa-47b6-9119-5945bc88fd66"));
|
||||
}
|
||||
else
|
||||
{
|
||||
content.Instance.RefreshContentFromDatabaseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the xml cache for nodes in list
|
||||
/// </summary>
|
||||
private void UpdateMultipleContentCache(IEnumerable<IContent> content)
|
||||
{
|
||||
if (UmbracoSettings.UseDistributedCalls)
|
||||
{
|
||||
foreach (var c in content)
|
||||
{
|
||||
dispatcher.Refresh(new Guid("27ab3022-3dfa-47b6-9119-5945bc88fd66"), c.Id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var documents = content.Select(x => new Document(x)).ToList();
|
||||
global::umbraco.content.Instance.UpdateDocumentCache(documents);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the xml cache for a single node
|
||||
/// </summary>
|
||||
private void UpdateSingleContentCache(IContent content)
|
||||
{
|
||||
if (UmbracoSettings.UseDistributedCalls)
|
||||
{
|
||||
dispatcher.Refresh(new Guid("27ab3022-3dfa-47b6-9119-5945bc88fd66"), content.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
var doc = new Document(content);
|
||||
global::umbraco.content.Instance.UpdateDocumentCache(doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/Umbraco.Web/Strategies/UpdateCacheAfterUnPublish.cs
Normal file
59
src/Umbraco.Web/Strategies/UpdateCacheAfterUnPublish.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Publishing;
|
||||
using umbraco;
|
||||
using umbraco.interfaces;
|
||||
using umbraco.presentation.cache;
|
||||
|
||||
namespace Umbraco.Web.Strategies
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the UpdateCacheAfterUnPublish class, which subscribes to the UnPublished event
|
||||
/// of the <see cref="PublishingStrategy"/> class and is responsible for doing the actual
|
||||
/// cache refresh after a content item has been unpublished.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This implementation is meant as a seperation of the cache refresh from the ContentService
|
||||
/// and PublishingStrategy.
|
||||
/// </remarks>
|
||||
public class UpdateCacheAfterUnPublish : IApplicationStartupHandler
|
||||
{
|
||||
public UpdateCacheAfterUnPublish()
|
||||
{
|
||||
PublishingStrategy.UnPublished += PublishingStrategy_UnPublished;
|
||||
}
|
||||
|
||||
void PublishingStrategy_UnPublished(object sender, Core.PublishingEventArgs e)
|
||||
{
|
||||
if (sender is IContent)
|
||||
{
|
||||
var content = sender as IContent;
|
||||
UnPublishSingle(content);
|
||||
}
|
||||
else if (sender is IEnumerable<IContent>)
|
||||
{
|
||||
var content = sender as IEnumerable<IContent>;
|
||||
foreach (var c in content)
|
||||
{
|
||||
UnPublishSingle(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the xml cache for a single node by removing it
|
||||
/// </summary>
|
||||
private void UnPublishSingle(IContent content)
|
||||
{
|
||||
if (UmbracoSettings.UseDistributedCalls)
|
||||
{
|
||||
dispatcher.Remove(new Guid("27ab3022-3dfa-47b6-9119-5945bc88fd66"), content.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
global::umbraco.content.Instance.ClearDocumentCache(content.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Web;
|
||||
using System.Web.Caching;
|
||||
using System.Xml;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Publishing;
|
||||
using Umbraco.Core.Services;
|
||||
using umbraco.interfaces;
|
||||
using umbraco.presentation.nodeFactory;
|
||||
using Node = umbraco.NodeFactory.Node;
|
||||
|
||||
namespace Umbraco.Web.Strategies
|
||||
{
|
||||
internal class UpdateContentCache : IApplicationStartupHandler
|
||||
{
|
||||
// Sync access to internal cache
|
||||
private static readonly object XmlContentInternalSyncLock = new object();
|
||||
private const string XmlContextContentItemKey = "UmbracoXmlContextContent";
|
||||
private readonly HttpContextBase _httpContext;
|
||||
private readonly ServiceContext _serviceContext;
|
||||
|
||||
public UpdateContentCache()
|
||||
{
|
||||
_httpContext = new HttpContextWrapper(HttpContext.Current);
|
||||
_serviceContext = ServiceContext.Current;
|
||||
|
||||
PublishingStrategy.Published += PublishingStrategy_Published;
|
||||
}
|
||||
|
||||
public UpdateContentCache(HttpContextBase httpContext)
|
||||
{
|
||||
_httpContext = httpContext;
|
||||
_serviceContext = ServiceContext.Current;
|
||||
|
||||
PublishingStrategy.Published += PublishingStrategy_Published;
|
||||
}
|
||||
|
||||
void PublishingStrategy_Published(object sender, PublishingEventArgs e)
|
||||
{
|
||||
if((sender is IContent) == false) return;
|
||||
|
||||
//var e = new DocumentCacheEventArgs();
|
||||
//FireBeforeUpdateDocumentCache(d, e);
|
||||
|
||||
var content = sender as IContent;
|
||||
|
||||
// lock the xml cache so no other thread can write to it at the same time
|
||||
// note that some threads could read from it while we hold the lock, though
|
||||
lock (XmlContentInternalSyncLock)
|
||||
{
|
||||
XmlDocument wip = XmlContent;
|
||||
|
||||
ClearContextCache();
|
||||
|
||||
XmlContent = UpdateXmlAndSitemap(content, wip, false);//Update sitemap is usually set to true
|
||||
}
|
||||
|
||||
// clear cached field values
|
||||
if (_httpContext != null)
|
||||
{
|
||||
Cache httpCache = _httpContext.Cache;
|
||||
string cachedFieldKeyStart = String.Format("contentItem{0}_", content.Id);
|
||||
var foundKeys = new List<string>();
|
||||
foreach (DictionaryEntry cacheItem in httpCache)
|
||||
{
|
||||
string key = cacheItem.Key.ToString();
|
||||
if (key.StartsWith(cachedFieldKeyStart))
|
||||
foundKeys.Add(key);
|
||||
}
|
||||
foreach (string foundKey in foundKeys)
|
||||
{
|
||||
httpCache.Remove(foundKey);
|
||||
}
|
||||
}
|
||||
|
||||
//Action.RunActionHandlers(d, ActionPublish.Instance);
|
||||
//FireAfterUpdateDocumentCache(d, e);
|
||||
}
|
||||
|
||||
private XmlDocument UpdateXmlAndSitemap(IContent content, XmlDocument xmlContentCopy, bool updateSitemapProvider)
|
||||
{
|
||||
// check if document *is* published, it could be unpublished by an event
|
||||
if (content.Published)
|
||||
{
|
||||
int parentId = content.Level == 1 ? -1 : content.ParentId;
|
||||
var contentXmlNode = content.ToXml(false).GetXmlNode();
|
||||
var xmlNode = xmlContentCopy.ImportNode(contentXmlNode, true);
|
||||
xmlContentCopy = AppendContentXml(content.Id, content.Level, parentId, xmlNode, xmlContentCopy);
|
||||
|
||||
// update sitemapprovider
|
||||
if (updateSitemapProvider && SiteMap.Provider is UmbracoSiteMapProvider)
|
||||
{
|
||||
try
|
||||
{
|
||||
var prov = (UmbracoSiteMapProvider)SiteMap.Provider;
|
||||
var n = new Node(content.Id, true);
|
||||
if (!String.IsNullOrEmpty(n.Url) && n.Url != "/#")
|
||||
{
|
||||
prov.UpdateNode(n);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Log.Add(LogTypes.Error, content.Id, "Can't update Sitemap Provider due to empty Url in node");
|
||||
}
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
//Log.Add(LogTypes.Error, content.Id, string.Format("Error adding node to Sitemap Provider in PublishNodeDo(): {0}", ee));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return xmlContentCopy;
|
||||
}
|
||||
|
||||
private XmlDocument AppendContentXml(int id, int level, int parentId, XmlNode docNode, XmlDocument xmlContentCopy)
|
||||
{
|
||||
// Find the document in the xml cache
|
||||
XmlNode x = xmlContentCopy.GetElementById(id.ToString());
|
||||
|
||||
// if the document is not there already then it's a new document
|
||||
// we must make sure that its document type exists in the schema
|
||||
var xmlContentCopy2 = xmlContentCopy;
|
||||
if (x == null && UmbracoSettings.UseLegacyXmlSchema == false)
|
||||
{
|
||||
//TODO Look into the validate schema method - seems a bit odd
|
||||
//Move to Contract ?
|
||||
xmlContentCopy = ValidateSchema(docNode.Name, xmlContentCopy);
|
||||
if (xmlContentCopy != xmlContentCopy2)
|
||||
docNode = xmlContentCopy.ImportNode(docNode, true);
|
||||
}
|
||||
|
||||
// Find the parent (used for sortering and maybe creation of new node)
|
||||
XmlNode parentNode;
|
||||
if (level == 1)
|
||||
parentNode = xmlContentCopy.DocumentElement;
|
||||
else
|
||||
parentNode = xmlContentCopy.GetElementById(parentId.ToString());
|
||||
|
||||
if (parentNode != null)
|
||||
{
|
||||
if (x == null)
|
||||
{
|
||||
x = docNode;
|
||||
parentNode.AppendChild(x);
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO
|
||||
//TransferValuesFromDocumentXmlToPublishedXml(docNode, x);
|
||||
}
|
||||
|
||||
// TODO: Update with new schema!
|
||||
string xpath = UmbracoSettings.UseLegacyXmlSchema ? "./node" : "./* [@id]";
|
||||
XmlNodeList childNodes = parentNode.SelectNodes(xpath);
|
||||
|
||||
// Maybe sort the nodes if the added node has a lower sortorder than the last
|
||||
if (childNodes.Count > 0)
|
||||
{
|
||||
int siblingSortOrder =
|
||||
int.Parse(childNodes[childNodes.Count - 1].Attributes.GetNamedItem("sortOrder").Value);
|
||||
int currentSortOrder = int.Parse(x.Attributes.GetNamedItem("sortOrder").Value);
|
||||
if (childNodes.Count > 1 && siblingSortOrder > currentSortOrder)
|
||||
{
|
||||
//SortNodes(ref parentNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return xmlContentCopy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear HTTPContext cache if any
|
||||
/// </summary>
|
||||
private void ClearContextCache()
|
||||
{
|
||||
// If running in a context very important to reset context cache or else new nodes are missing
|
||||
if (_httpContext != null && _httpContext.Items.Contains(XmlContextContentItemKey))
|
||||
_httpContext.Items.Remove(XmlContextContentItemKey);
|
||||
}
|
||||
|
||||
private XmlDocument ValidateSchema(string docTypeAlias, XmlDocument xmlDoc)
|
||||
{
|
||||
// check if doctype is defined in schema else add it
|
||||
// can't edit the doctype of an xml document, must create a new document
|
||||
|
||||
var doctype = xmlDoc.DocumentType;
|
||||
var subset = doctype.InternalSubset;
|
||||
if (!subset.Contains(string.Format("<!ATTLIST {0} id ID #REQUIRED>", docTypeAlias)))
|
||||
{
|
||||
subset = string.Format("<!ELEMENT {0} ANY>\r\n<!ATTLIST {0} id ID #REQUIRED>\r\n{1}", docTypeAlias, subset);
|
||||
var xmlDoc2 = new XmlDocument();
|
||||
doctype = xmlDoc2.CreateDocumentType("root", null, null, subset);
|
||||
xmlDoc2.AppendChild(doctype);
|
||||
var root = xmlDoc2.ImportNode(xmlDoc.DocumentElement, true);
|
||||
xmlDoc2.AppendChild(root);
|
||||
|
||||
// apply
|
||||
xmlDoc = xmlDoc2;
|
||||
}
|
||||
|
||||
return xmlDoc;
|
||||
}
|
||||
|
||||
private XmlDocument XmlContent
|
||||
{
|
||||
get
|
||||
{
|
||||
var content = _httpContext.Items[XmlContextContentItemKey] as XmlDocument;
|
||||
if (content == null)
|
||||
{
|
||||
//content = global::umbraco.content.Instance.XmlContent;
|
||||
var dtd = _serviceContext.ContentTypeService.GetDtd();
|
||||
|
||||
content = new XmlDocument();
|
||||
content.LoadXml(
|
||||
String.Format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>{0}{1}{0}<root id=\"-1\"/>",
|
||||
Environment.NewLine,
|
||||
dtd));
|
||||
|
||||
_httpContext.Items[XmlContextContentItemKey] = content;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
set { _httpContext.Items[XmlContextContentItemKey] = value; }
|
||||
}
|
||||
|
||||
internal void Unsubscribe()
|
||||
{
|
||||
PublishingStrategy.Published -= PublishingStrategy_Published;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Publishing;
|
||||
|
||||
namespace Umbraco.Web.Strategies
|
||||
{
|
||||
public class UpdateMultipleContentCache
|
||||
{
|
||||
public UpdateMultipleContentCache()
|
||||
{
|
||||
PublishingStrategy.Published += PublishingStrategy_Published;
|
||||
}
|
||||
|
||||
void PublishingStrategy_Published(object sender, Core.PublishingEventArgs e)
|
||||
{
|
||||
if ((sender is IEnumerable<IContent>) == false) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,9 +327,9 @@
|
||||
<Compile Include="RouteCollectionExtensions.cs" />
|
||||
<Compile Include="Routing\LookupByPageIdQuery.cs" />
|
||||
<Compile Include="Mvc\SurfaceControllerResolver.cs" />
|
||||
<Compile Include="Strategies\UpdateCacheAfterPublish.cs" />
|
||||
<Compile Include="Strategies\UpdateCacheAfterUnPublish.cs" />
|
||||
<Compile Include="Templates\TemplateRenderer.cs" />
|
||||
<Compile Include="Strategies\UpdateContentCache.cs" />
|
||||
<Compile Include="Strategies\UpdateMultipleContentCache.cs" />
|
||||
<Compile Include="Templates\TemplateUtilities.cs" />
|
||||
<Compile Include="umbraco.presentation\Default.aspx.cs">
|
||||
<SubType>ASPXCodeBehind</SubType>
|
||||
|
||||
Reference in New Issue
Block a user