Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/Umbraco.Core/Cache/IWebsiteOutputCacheDurationProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Umbraco.Cms.Core.Models.PublishedContent;

namespace Umbraco.Cms.Core.Cache;

/// <summary>
/// Provides custom cache durations per content item for website output caching.
/// </summary>
/// <remarks>
/// A single registration of this interface is expected.
/// The default implementation can be replaced to vary duration by content type, path, or content properties.
/// </remarks>
public interface IWebsiteOutputCacheDurationProvider
{
/// <summary>
/// Returns a custom cache duration for the given content, or <c>null</c> to use the configured default.
/// </summary>
/// <param name="content">The published content being cached.</param>
/// <returns>
/// A custom cache duration, <see cref="TimeSpan.Zero"/> to disable caching for this content,
/// or <c>null</c> to use the configured default duration.
/// </returns>
TimeSpan? GetDuration(IPublishedContent content);
}
26 changes: 26 additions & 0 deletions src/Umbraco.Core/Cache/IWebsiteOutputCacheEvictionProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace Umbraco.Cms.Core.Cache;

/// <summary>
/// Provides additional cache tags to evict when content changes.
/// </summary>
/// <remarks>
/// <para>
/// Multiple implementations can be registered. The eviction handler iterates all providers
/// to collect additional tags to evict beyond the built-in content key tag.
/// </para>
/// <para>
/// Works as a pair with <see cref="IWebsiteOutputCacheTagProvider"/>: the tag provider adds
/// custom tags when caching a response, and this provider maps content changes back to those
/// tags at eviction time.
/// </para>
/// </remarks>
public interface IWebsiteOutputCacheEvictionProvider
{
/// <summary>
/// Returns additional cache tags to evict when the specified content changes.
/// </summary>
/// <param name="context">Details of the content change.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>Additional cache tags to evict.</returns>
Task<IEnumerable<string>> GetAdditionalEvictionTagsAsync(OutputCacheContentChangedContext context, CancellationToken cancellationToken = default);
}
31 changes: 31 additions & 0 deletions src/Umbraco.Core/Cache/IWebsiteOutputCacheManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace Umbraco.Cms.Core.Cache;

/// <summary>
/// Provides programmatic eviction of website output cache entries.
/// </summary>
/// <remarks>
/// Use this to evict cached pages from custom code, for example when external data changes
/// that affects a rendered page. All methods are no-ops when output caching is not enabled.
/// </remarks>
public interface IWebsiteOutputCacheManager
{
/// <summary>
/// Evicts the cached page for a specific content item.
/// </summary>
/// <param name="contentKey">The key of the content item to evict.</param>
/// <param name="cancellationToken">A cancellation token.</param>
Task EvictContentAsync(Guid contentKey, CancellationToken cancellationToken = default);

/// <summary>
/// Evicts all cached pages matching a custom tag.
/// </summary>
/// <param name="tag">The cache tag to evict.</param>
/// <param name="cancellationToken">A cancellation token.</param>
Task EvictByTagAsync(string tag, CancellationToken cancellationToken = default);

/// <summary>
/// Evicts all cached website pages.
/// </summary>
/// <param name="cancellationToken">A cancellation token.</param>
Task EvictAllAsync(CancellationToken cancellationToken = default);
}
22 changes: 22 additions & 0 deletions src/Umbraco.Core/Cache/IWebsiteOutputCacheTagProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Umbraco.Cms.Core.Models.PublishedContent;

namespace Umbraco.Cms.Core.Cache;

/// <summary>
/// Provides additional cache tags when caching a website content page.
/// </summary>
/// <remarks>
/// Multiple implementations can be registered; the output cache policy iterates all of them
/// to collect tags at cache-write time. Works as a pair with <see cref="IWebsiteOutputCacheEvictionProvider"/>:
/// the tag provider adds custom tags when caching, and the eviction provider maps content changes
/// back to those tags at eviction time.
/// </remarks>
public interface IWebsiteOutputCacheTagProvider
{
/// <summary>
/// Returns additional cache tags for the given published content.
/// </summary>
/// <param name="content">The published content being cached.</param>
/// <returns>Additional cache tags to associate with the cached response.</returns>
IEnumerable<string> GetTags(IPublishedContent content);
}
42 changes: 42 additions & 0 deletions src/Umbraco.Core/Cache/OutputCacheContentChangedContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace Umbraco.Cms.Core.Cache;

/// <summary>
/// Describes a content change that triggered output cache eviction.
/// </summary>
public class OutputCacheContentChangedContext
{
/// <summary>
/// Initializes a new instance of the <see cref="OutputCacheContentChangedContext"/> class.
/// </summary>
/// <param name="contentId">The integer ID of the content that changed.</param>
/// <param name="contentKey">The key of the content that changed.</param>
/// <param name="publishedCultures">The cultures that were published, if applicable.</param>
/// <param name="unpublishedCultures">The cultures that were unpublished, if applicable.</param>
public OutputCacheContentChangedContext(int contentId, Guid contentKey, IReadOnlyCollection<string> publishedCultures, IReadOnlyCollection<string> unpublishedCultures)
{
ContentId = contentId;
ContentKey = contentKey;
PublishedCultures = publishedCultures;
UnpublishedCultures = unpublishedCultures;
}

/// <summary>
/// Gets the integer ID of the content that changed.
/// </summary>
public int ContentId { get; }

/// <summary>
/// Gets the key of the content that changed.
/// </summary>
public Guid ContentKey { get; }

/// <summary>
/// Gets the cultures that were published, if applicable. Empty for invariant content.
/// </summary>
public IReadOnlyCollection<string> PublishedCultures { get; }

/// <summary>
/// Gets the cultures that were unpublished, if applicable. Empty for invariant content.
/// </summary>
public IReadOnlyCollection<string> UnpublishedCultures { get; }
}
46 changes: 46 additions & 0 deletions src/Umbraco.Core/Configuration/Models/WebsiteSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.ComponentModel;

namespace Umbraco.Cms.Core.Configuration.Models;

/// <summary>
/// Typed configuration options for website rendering settings.
/// </summary>
[UmbracoOptions(Constants.Configuration.ConfigWebsite)]
public class WebsiteSettings
{
private const bool StaticEnabled = false;

/// <summary>
/// Gets or sets the output cache settings for website rendering.
/// </summary>
public OutputCacheSettings OutputCache { get; set; } = new();

/// <summary>
/// Typed configuration options for output caching of website rendering.
/// </summary>
public class OutputCacheSettings
{
private const string StaticDuration = "00:00:10"; // Ten seconds.

/// <summary>
/// Gets or sets a value indicating whether website output caching is enabled.
/// </summary>
/// <value><c>true</c> if website output caching should be enabled; otherwise, <c>false</c>.</value>
/// <remarks>
/// The default value is <c>false</c>.
/// </remarks>
[DefaultValue(StaticEnabled)]
public bool Enabled { get; set; } = StaticEnabled;

/// <summary>
/// Gets or sets the duration for which rendered content pages are cached.
/// </summary>
/// <value>Cache lifetime.</value>
/// <remarks>
/// The default cache duration is ten seconds, if this configuration value is not provided.
/// Can be overridden per content item via <c>IWebsiteOutputCacheDurationProvider</c>.
/// </remarks>
[DefaultValue(StaticDuration)]
public TimeSpan ContentDuration { get; set; } = TimeSpan.Parse(StaticDuration);
}
}
5 changes: 5 additions & 0 deletions src/Umbraco.Core/Constants-Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,11 @@ public static class Configuration
public const string ConfigBackOfficeTokenCookie = ConfigSecurity + ":BackOfficeTokenCookie";
public const string ConfigDictionary = ConfigPrefix + "Dictionary";

/// <summary>
/// The configuration key for website settings.
/// </summary>
public const string ConfigWebsite = ConfigPrefix + "Website";

/// <summary>
/// Contains constants for named options used in configuration.
/// </summary>
Expand Down
41 changes: 41 additions & 0 deletions src/Umbraco.Core/Constants-Website.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace Umbraco.Cms.Core;

public static partial class Constants
{
/// <summary>
/// Constants related to the website rendering pipeline.
/// </summary>
public static class Website
{
/// <summary>
/// Constants related to website output caching.
/// </summary>
public static class OutputCache
{
/// <summary>
/// The named output cache policy for Umbraco website content.
/// </summary>
public const string ContentCachePolicy = "UmbracoWebsiteContent";

/// <summary>
/// Tag prefix for a specific content item (followed by the content key).
/// </summary>
public const string ContentTagPrefix = "umb-content-";

/// <summary>
/// Tag prefix for ancestor-based eviction (followed by the ancestor content key).
/// </summary>
public const string AncestorTagPrefix = "umb-content-ancestor-";

/// <summary>
/// Tag prefix for content type-based eviction (followed by the content type alias).
/// </summary>
public const string ContentTypeTagPrefix = "umb-content-type-";

/// <summary>
/// Tag that matches all cached website content pages.
/// </summary>
public const string AllContentTag = "umb-content-all";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@
.AddUmbracoOptions<CacheSettings>()
.AddUmbracoOptions<SystemDateMigrationSettings>()
.AddUmbracoOptions<DistributedJobSettings>()
.AddUmbracoOptions<BackOfficeTokenCookieSettings>();
.AddUmbracoOptions<BackOfficeTokenCookieSettings>()
.AddUmbracoOptions<WebsiteSettings>();

Check warning on line 106 in src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ Getting worse: Large Method

AddConfiguration increases from 70 to 71 lines of code, threshold = 70. Large functions with many lines of code are generally harder to understand and lower the code health. Avoid adding more lines to this function.

// Configure connection string and ensure it's updated when the configuration changes
builder.Services.AddSingleton<IConfigureOptions<ConnectionStrings>, ConfigureConnectionStrings>();
Expand Down
2 changes: 2 additions & 0 deletions src/Umbraco.Web.Common/Controllers/RenderController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.OutputCaching;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Routing;
Expand Down Expand Up @@ -47,6 +48,7 @@ protected IUmbracoContext UmbracoContext
/// <summary>
/// The default action to render the front-end view.
/// </summary>
[OutputCache(PolicyName = Cms.Core.Constants.Website.OutputCache.ContentCachePolicy)]
public virtual IActionResult Index() => CurrentTemplate(new ContentModel(CurrentPage));

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models.PublishedContent;

namespace Umbraco.Cms.Web.Website.Caching;

/// <summary>
/// Tags cached pages with their content type alias, enabling eviction by content type.
/// </summary>
internal sealed class ContentTypeOutputCacheTagProvider : IWebsiteOutputCacheTagProvider
{
/// <inheritdoc />
public IEnumerable<string> GetTags(IPublishedContent content)
{
yield return Constants.Website.OutputCache.ContentTypeTagPrefix + content.ContentType.Alias;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models.PublishedContent;

namespace Umbraco.Cms.Web.Website.Caching;

/// <summary>
/// Default implementation of <see cref="IWebsiteOutputCacheDurationProvider"/> that always returns <c>null</c>,
/// deferring to the configured <c>ContentDuration</c> for all content.
/// </summary>
internal sealed class DefaultWebsiteOutputCacheDurationProvider : IWebsiteOutputCacheDurationProvider
{
/// <inheritdoc />
public TimeSpan? GetDuration(IPublishedContent content) => null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Http;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Web;

namespace Umbraco.Cms.Web.Website.Caching;

/// <summary>
/// Default implementation of <see cref="IWebsiteOutputCacheRequestFilter"/> that prevents caching
/// for preview mode and authenticated member requests.
/// </summary>
public class DefaultWebsiteOutputCacheRequestFilter : IWebsiteOutputCacheRequestFilter
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;

/// <summary>
/// Initializes a new instance of the <see cref="DefaultWebsiteOutputCacheRequestFilter"/> class.
/// </summary>
/// <param name="umbracoContextAccessor">The Umbraco context accessor.</param>
public DefaultWebsiteOutputCacheRequestFilter(IUmbracoContextAccessor umbracoContextAccessor)
=> _umbracoContextAccessor = umbracoContextAccessor;

/// <inheritdoc />
public virtual bool IsCacheable(HttpContext context, IPublishedContent content)
=> ShouldExcludePreview() is false && ShouldExcludeAuthenticated(context) is false;

/// <summary>
/// Gets a value indicating whether preview mode requests should be excluded from caching.
/// By default returns <c>true</c> when the current request is in Umbraco preview mode.
/// </summary>
protected virtual bool ShouldExcludePreview()
=> _umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)
&& umbracoContext.InPreviewMode;

/// <summary>
/// Gets a value indicating whether authenticated requests should be excluded from caching.
/// By default returns <c>true</c> when the current request is from an authenticated user.
/// </summary>
protected virtual bool ShouldExcludeAuthenticated(HttpContext context)
=> context.User.Identity?.IsAuthenticated == true;
}
Loading
Loading