V10: fix build warnings infrastructure (#12369)

* Run code cleanup

* Run dotnet format

* Start manual fixes

* Manual fixing of warnings

* Fix nullability in columnalias

* Fix tests

* Fix up after merge

* Start updating after review

* Update editorconfig to contain new static & const rules

* Fix up editorconfig to not contain duplicate rules

* Fix up static member names

* Fix up according to review

* Update src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Examine/ExamineUmbracoIndexingHandler.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Examine/PublishedContentIndexPopulator.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Extensions/InstanceIdentifiableExtensions.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Macros/MacroTagParser.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Macros/MacroTagParser.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Macros/MacroTagParser.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Migrations/Expressions/Alter/Table/IAlterTableColumnOptionBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Migrations/Upgrade/V_10_0_0/AddMemberPropertiesAsColumns.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Dtos/ExternalLoginDto.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/AccessMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/AuditEntryMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/MediaMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/MemberMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/PropertyGroupMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/PropertyGroupMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/PropertyTypeMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/PropertyTypeMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/NPocoMapperCollectionBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Querying/ExpressionVisitorBase.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Fix [..] to substring

* Fix after merge with 10/dev

* Fox ContentValueSetValidator.cs

* Update LoggerConfigExtensions

Co-authored-by: Nikolaj Geisle <niko737@edu.ucl.dk>
Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
This commit is contained in:
Nikolaj Geisle
2022-06-02 08:18:31 +02:00
committed by GitHub
parent adcc9a0e1f
commit f4e333c178
838 changed files with 64052 additions and 61173 deletions

View File

@@ -1,7 +1,4 @@
using System;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Examine;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -10,131 +7,132 @@ using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Infrastructure;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Routing
namespace Umbraco.Cms.Core.Routing;
/// <summary>
/// Provides an implementation of <see cref="IContentFinder" /> that runs the legacy 404 logic.
/// </summary>
public class ContentFinderByConfigured404 : IContentLastChanceFinder
{
private readonly IEntityService _entityService;
private readonly IExamineManager _examineManager;
private readonly ILogger<ContentFinderByConfigured404> _logger;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly IVariationContextAccessor _variationContextAccessor;
private ContentSettings _contentSettings;
/// <summary>
/// Provides an implementation of <see cref="IContentFinder"/> that runs the legacy 404 logic.
/// Initializes a new instance of the <see cref="ContentFinderByConfigured404" /> class.
/// </summary>
public class ContentFinderByConfigured404 : IContentLastChanceFinder
public ContentFinderByConfigured404(
ILogger<ContentFinderByConfigured404> logger,
IEntityService entityService,
IOptionsMonitor<ContentSettings> contentSettings,
IExamineManager examineManager,
IVariationContextAccessor variationContextAccessor,
IUmbracoContextAccessor umbracoContextAccessor)
{
private readonly ILogger<ContentFinderByConfigured404> _logger;
private readonly IEntityService _entityService;
private ContentSettings _contentSettings;
private readonly IExamineManager _examineManager;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly IVariationContextAccessor _variationContextAccessor;
_logger = logger;
_entityService = entityService;
_contentSettings = contentSettings.CurrentValue;
_examineManager = examineManager;
_variationContextAccessor = variationContextAccessor;
_umbracoContextAccessor = umbracoContextAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="ContentFinderByConfigured404"/> class.
/// </summary>
public ContentFinderByConfigured404(
ILogger<ContentFinderByConfigured404> logger,
IEntityService entityService,
IOptionsMonitor<ContentSettings> contentSettings,
IExamineManager examineManager,
IVariationContextAccessor variationContextAccessor,
IUmbracoContextAccessor umbracoContextAccessor)
contentSettings.OnChange(x => _contentSettings = x);
}
/// <summary>
/// Tries to find and assign an Umbraco document to a <c>PublishedRequest</c>.
/// </summary>
/// <param name="frequest">The <c>PublishedRequest</c>.</param>
/// <returns>A value indicating whether an Umbraco document was found and assigned.</returns>
public Task<bool> TryFindContent(IPublishedRequestBuilder frequest)
{
if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext))
{
_logger = logger;
_entityService = entityService;
_contentSettings = contentSettings.CurrentValue;
_examineManager = examineManager;
_variationContextAccessor = variationContextAccessor;
_umbracoContextAccessor = umbracoContextAccessor;
contentSettings.OnChange(x => _contentSettings = x);
return Task.FromResult(false);
}
/// <summary>
/// Tries to find and assign an Umbraco document to a <c>PublishedRequest</c>.
/// </summary>
/// <param name="frequest">The <c>PublishedRequest</c>.</param>
/// <returns>A value indicating whether an Umbraco document was found and assigned.</returns>
public async Task<bool> TryFindContent(IPublishedRequestBuilder frequest)
if (_logger.IsEnabled(LogLevel.Debug))
{
if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext))
{
return false;
}
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("Looking for a page to handle 404.");
}
_logger.LogDebug("Looking for a page to handle 404.");
}
int? domainContentId = null;
int? domainContentId = null;
// try to find a culture as best as we can
string? errorCulture = CultureInfo.CurrentUICulture.Name;
if (frequest.Domain != null)
// try to find a culture as best as we can
var errorCulture = CultureInfo.CurrentUICulture.Name;
if (frequest.Domain != null)
{
errorCulture = frequest.Domain.Culture;
domainContentId = frequest.Domain.ContentId;
}
else
{
var route = frequest.AbsolutePathDecoded;
var pos = route.LastIndexOf('/');
IPublishedContent? node = null;
while (pos > 1)
{
errorCulture = frequest.Domain.Culture;
domainContentId = frequest.Domain.ContentId;
}
else
{
var route = frequest.AbsolutePathDecoded;
var pos = route.LastIndexOf('/');
IPublishedContent? node = null;
while (pos > 1)
{
route = route.Substring(0, pos);
node = umbracoContext.Content?.GetByRoute(route, culture: frequest?.Culture);
if (node != null)
{
break;
}
pos = route.LastIndexOf('/');
}
route = route.Substring(0, pos);
node = umbracoContext.Content?.GetByRoute(route, culture: frequest?.Culture);
if (node != null)
{
Domain? d = DomainUtilities.FindWildcardDomainInPath(umbracoContext.PublishedSnapshot.Domains?.GetAll(true), node.Path, null);
if (d != null)
{
errorCulture = d.Culture;
}
break;
}
pos = route.LastIndexOf('/');
}
var error404 = NotFoundHandlerHelper.GetCurrentNotFoundPageId(
_contentSettings.Error404Collection.ToArray(),
_entityService,
new PublishedContentQuery(umbracoContext.PublishedSnapshot, _variationContextAccessor, _examineManager),
errorCulture,
domainContentId);
IPublishedContent? content = null;
if (error404.HasValue)
if (node != null)
{
if (_logger.IsEnabled(LogLevel.Debug))
Domain? d = DomainUtilities.FindWildcardDomainInPath(
umbracoContext.PublishedSnapshot.Domains?.GetAll(true), node.Path, null);
if (d != null)
{
_logger.LogDebug("Got id={ErrorNodeId}.", error404.Value);
errorCulture = d.Culture;
}
content = umbracoContext.Content?.GetById(error404.Value);
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug(content == null
}
}
var error404 = NotFoundHandlerHelper.GetCurrentNotFoundPageId(
_contentSettings.Error404Collection.ToArray(),
_entityService,
new PublishedContentQuery(umbracoContext.PublishedSnapshot, _variationContextAccessor, _examineManager),
errorCulture,
domainContentId);
IPublishedContent? content = null;
if (error404.HasValue)
{
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("Got id={ErrorNodeId}.", error404.Value);
}
content = umbracoContext.Content?.GetById(error404.Value);
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug(content == null
? "Could not find content with that id."
: "Found corresponding content.");
}
}
else
{
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("Got nothing.");
}
}
frequest?
.SetPublishedContent(content)
.SetIs404();
return content != null;
}
else
{
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("Got nothing.");
}
}
frequest?
.SetPublishedContent(content)
.SetIs404();
return Task.FromResult(content != null);
}
}

View File

@@ -1,5 +1,3 @@
using System;
using System.Linq;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
@@ -9,99 +7,99 @@ using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Xml;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Routing
{
/// <summary>
/// Used to determine the node to display when content is not found based on the configured error404 elements in umbracoSettings.config
/// </summary>
internal class NotFoundHandlerHelper
{
internal static int? GetCurrentNotFoundPageId(
ContentErrorPage[] error404Collection,
IEntityService entityService,
IPublishedContentQuery publishedContentQuery,
string? errorCulture,
int? domainContentId)
{
if (error404Collection.Length > 1)
{
// test if a 404 page exists with current culture thread
ContentErrorPage? cultureErr = error404Collection.FirstOrDefault(x => x.Culture.InvariantEquals(errorCulture))
?? error404Collection.FirstOrDefault(x => x.Culture == "default"); // there should be a default one!
namespace Umbraco.Cms.Core.Routing;
if (cultureErr != null)
{
return GetContentIdFromErrorPageConfig(cultureErr, entityService, publishedContentQuery, domainContentId);
}
}
else if (error404Collection.Length == 1)
/// <summary>
/// Used to determine the node to display when content is not found based on the configured error404 elements in
/// umbracoSettings.config
/// </summary>
internal class NotFoundHandlerHelper
{
internal static int? GetCurrentNotFoundPageId(
ContentErrorPage[] error404Collection,
IEntityService entityService,
IPublishedContentQuery publishedContentQuery,
string? errorCulture,
int? domainContentId)
{
if (error404Collection.Length > 1)
{
// test if a 404 page exists with current culture thread
ContentErrorPage? cultureErr =
error404Collection.FirstOrDefault(x => x.Culture.InvariantEquals(errorCulture))
?? error404Collection.FirstOrDefault(x => x.Culture == "default"); // there should be a default one!
if (cultureErr != null)
{
return GetContentIdFromErrorPageConfig(error404Collection.First(), entityService, publishedContentQuery, domainContentId);
return GetContentIdFromErrorPageConfig(cultureErr, entityService, publishedContentQuery, domainContentId);
}
}
else if (error404Collection.Length == 1)
{
return GetContentIdFromErrorPageConfig(error404Collection.First(), entityService, publishedContentQuery, domainContentId);
}
return null;
}
/// <summary>
/// Returns the content id based on the configured ContentErrorPage section.
/// </summary>
internal static int? GetContentIdFromErrorPageConfig(
ContentErrorPage errorPage,
IEntityService entityService,
IPublishedContentQuery publishedContentQuery,
int? domainContentId)
{
if (errorPage.HasContentId)
{
return errorPage.ContentId;
}
if (errorPage.HasContentKey)
{
// need to get the Id for the GUID
// TODO: When we start storing GUIDs into the IPublishedContent, then we won't have to look this up
// but until then we need to look it up in the db. For now we've implemented a cached service for
// converting Int -> Guid and vice versa.
Attempt<int> found = entityService.GetId(errorPage.ContentKey, UmbracoObjectTypes.Document);
if (found.Success)
{
return found.Result;
}
return null;
}
/// <summary>
/// Returns the content id based on the configured ContentErrorPage section.
/// </summary>
internal static int? GetContentIdFromErrorPageConfig(
ContentErrorPage errorPage,
IEntityService entityService,
IPublishedContentQuery publishedContentQuery,
int? domainContentId)
if (errorPage.ContentXPath.IsNullOrWhiteSpace() == false)
{
if (errorPage.HasContentId)
try
{
return errorPage.ContentId;
}
// we have an xpath statement to execute
var xpathResult = UmbracoXPathPathSyntaxParser.ParseXPathQuery(
errorPage.ContentXPath!,
domainContentId,
nodeid =>
{
IEntitySlim? ent = entityService.Get(nodeid);
return ent?.Path.Split(',').Reverse();
},
i => publishedContentQuery.Content(i) != null);
if (errorPage.HasContentKey)
{
// need to get the Id for the GUID
// TODO: When we start storing GUIDs into the IPublishedContent, then we won't have to look this up
// but until then we need to look it up in the db. For now we've implemented a cached service for
// converting Int -> Guid and vice versa.
Attempt<int> found = entityService.GetId(errorPage.ContentKey, UmbracoObjectTypes.Document);
if (found.Success)
// now we'll try to execute the expression
IPublishedContent? nodeResult = publishedContentQuery.ContentSingleAtXPath(xpathResult);
if (nodeResult != null)
{
return found.Result;
return nodeResult.Id;
}
}
catch (Exception ex)
{
StaticApplicationLogging.Logger.LogError(ex, "Could not parse xpath expression: {ContentXPath}", errorPage.ContentXPath);
return null;
}
if (errorPage.ContentXPath.IsNullOrWhiteSpace() == false)
{
try
{
// we have an xpath statement to execute
var xpathResult = UmbracoXPathPathSyntaxParser.ParseXPathQuery(
xpathExpression: errorPage.ContentXPath!,
nodeContextId: domainContentId,
getPath: nodeid =>
{
IEntitySlim? ent = entityService.Get(nodeid);
return ent?.Path.Split(',').Reverse();
},
publishedContentExists: i => publishedContentQuery.Content(i) != null);
// now we'll try to execute the expression
IPublishedContent? nodeResult = publishedContentQuery.ContentSingleAtXPath(xpathResult);
if (nodeResult != null)
{
return nodeResult.Id;
}
}
catch (Exception ex)
{
StaticApplicationLogging.Logger.LogError(ex, "Could not parse xpath expression: {ContentXPath}", errorPage.ContentXPath);
return null;
}
}
return null;
}
return null;
}
}

View File

@@ -1,9 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
@@ -15,177 +12,186 @@ using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Routing
namespace Umbraco.Cms.Core.Routing;
/// Implements a notification handler for managing redirect URLs tracking.
/// <para>when content is renamed or moved, we want to create a permanent 301 redirect from it's old URL</para>
/// <para>
/// not managing domains because we don't know how to do it - changing domains => must create a higher level
/// strategy using rewriting rules probably
/// </para>
/// <para>recycle bin = moving to and from does nothing: to = the node is gone, where would we redirect? from = same</para>
public sealed class RedirectTrackingHandler :
INotificationHandler<ContentPublishingNotification>,
INotificationHandler<ContentPublishedNotification>,
INotificationHandler<ContentMovingNotification>,
INotificationHandler<ContentMovedNotification>
{
/// Implements a notification handler for managing redirect URLs tracking.
/// <para>when content is renamed or moved, we want to create a permanent 301 redirect from it's old URL</para>
/// <para>
/// not managing domains because we don't know how to do it - changing domains => must create a higher level
/// strategy using rewriting rules probably
/// </para>
/// <para>recycle bin = moving to and from does nothing: to = the node is gone, where would we redirect? from = same</para>
public sealed class RedirectTrackingHandler :
INotificationHandler<ContentPublishingNotification>,
INotificationHandler<ContentPublishedNotification>,
INotificationHandler<ContentMovingNotification>,
INotificationHandler<ContentMovedNotification>
private const string NotificationStateKey = "Umbraco.Cms.Core.Routing.RedirectTrackingHandler";
private readonly ILogger<RedirectTrackingHandler> _logger;
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private readonly IRedirectUrlService _redirectUrlService;
private readonly IVariationContextAccessor _variationContextAccessor;
private readonly IOptionsMonitor<WebRoutingSettings> _webRoutingSettings;
public RedirectTrackingHandler(
ILogger<RedirectTrackingHandler> logger,
IOptionsMonitor<WebRoutingSettings> webRoutingSettings,
IPublishedSnapshotAccessor publishedSnapshotAccessor,
IRedirectUrlService redirectUrlService,
IVariationContextAccessor variationContextAccessor)
{
private readonly ILogger<RedirectTrackingHandler> _logger;
private readonly IOptionsMonitor<WebRoutingSettings> _webRoutingSettings;
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private readonly IRedirectUrlService _redirectUrlService;
private readonly IVariationContextAccessor _variationContextAccessor;
_logger = logger;
_webRoutingSettings = webRoutingSettings;
_publishedSnapshotAccessor = publishedSnapshotAccessor;
_redirectUrlService = redirectUrlService;
_variationContextAccessor = variationContextAccessor;
}
private const string NotificationStateKey = "Umbraco.Cms.Core.Routing.RedirectTrackingHandler";
public void Handle(ContentMovedNotification notification) => CreateRedirectsForOldRoutes(notification);
public RedirectTrackingHandler(
ILogger<RedirectTrackingHandler> logger,
IOptionsMonitor<WebRoutingSettings> webRoutingSettings,
IPublishedSnapshotAccessor publishedSnapshotAccessor,
IRedirectUrlService redirectUrlService,
IVariationContextAccessor variationContextAccessor)
public void Handle(ContentMovingNotification notification) =>
StoreOldRoutes(notification.MoveInfoCollection.Select(m => m.Entity), notification);
public void Handle(ContentPublishedNotification notification) => CreateRedirectsForOldRoutes(notification);
public void Handle(ContentPublishingNotification notification) =>
StoreOldRoutes(notification.PublishedEntities, notification);
private static bool IsNotRoute(string? route) =>
// null if content not found
// err/- if collision or anomaly or ...
route == null || route.StartsWith("err/");
private void StoreOldRoutes(IEnumerable<IContent> entities, IStatefulNotification notification)
{
// don't let the notification handlers kick in if Redirect Tracking is turned off in the config
if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking)
{
_logger = logger;
_webRoutingSettings = webRoutingSettings;
_publishedSnapshotAccessor = publishedSnapshotAccessor;
_redirectUrlService = redirectUrlService;
_variationContextAccessor = variationContextAccessor;
return;
}
public void Handle(ContentPublishingNotification notification) => StoreOldRoutes(notification.PublishedEntities, notification);
public void Handle(ContentPublishedNotification notification) => CreateRedirectsForOldRoutes(notification);
public void Handle(ContentMovingNotification notification) => StoreOldRoutes(notification.MoveInfoCollection.Select(m => m.Entity), notification);
public void Handle(ContentMovedNotification notification) => CreateRedirectsForOldRoutes(notification);
private void StoreOldRoutes(IEnumerable<IContent> entities, IStatefulNotification notification)
OldRoutesDictionary oldRoutes = GetOldRoutes(notification);
foreach (IContent entity in entities)
{
// don't let the notification handlers kick in if Redirect Tracking is turned off in the config
if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking)
return;
StoreOldRoute(entity, oldRoutes);
}
}
var oldRoutes = GetOldRoutes(notification);
foreach (var entity in entities)
{
StoreOldRoute(entity, oldRoutes);
}
private void CreateRedirectsForOldRoutes(IStatefulNotification notification)
{
// don't let the notification handlers kick in if Redirect Tracking is turned off in the config
if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking)
{
return;
}
private void CreateRedirectsForOldRoutes(IStatefulNotification notification)
{
// don't let the notification handlers kick in if Redirect Tracking is turned off in the config
if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking)
return;
OldRoutesDictionary oldRoutes = GetOldRoutes(notification);
CreateRedirects(oldRoutes);
}
var oldRoutes = GetOldRoutes(notification);
CreateRedirects(oldRoutes);
private OldRoutesDictionary GetOldRoutes(IStatefulNotification notification)
{
if (notification.State.ContainsKey(NotificationStateKey) == false)
{
notification.State[NotificationStateKey] = new OldRoutesDictionary();
}
private OldRoutesDictionary GetOldRoutes(IStatefulNotification notification)
{
if (notification.State.ContainsKey(NotificationStateKey) == false)
{
notification.State[NotificationStateKey] = new OldRoutesDictionary();
}
return (OldRoutesDictionary?)notification.State[NotificationStateKey] ?? new OldRoutesDictionary();
}
return (OldRoutesDictionary?)notification.State[NotificationStateKey] ?? new OldRoutesDictionary();
private void StoreOldRoute(IContent entity, OldRoutesDictionary oldRoutes)
{
if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot))
{
return;
}
private void StoreOldRoute(IContent entity, OldRoutesDictionary oldRoutes)
IPublishedContentCache? contentCache = publishedSnapshot?.Content;
IPublishedContent? entityContent = contentCache?.GetById(entity.Id);
if (entityContent is null)
{
if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out var publishedSnapshot))
{
return;
}
IPublishedContentCache? contentCache = publishedSnapshot?.Content;
IPublishedContent? entityContent = contentCache?.GetById(entity.Id);
if (entityContent is null)
{
return;
}
// get the default affected cultures by going up the tree until we find the first culture variant entity (default to no cultures)
var defaultCultures = entityContent.AncestorsOrSelf()?.FirstOrDefault(a => a.Cultures.Any())?.Cultures.Keys.ToArray()
?? Array.Empty<string>();
foreach (IPublishedContent publishedContent in entityContent.DescendantsOrSelf(_variationContextAccessor))
{
// if this entity defines specific cultures, use those instead of the default ones
IEnumerable<string> cultures = publishedContent.Cultures.Any() ? publishedContent.Cultures.Keys : defaultCultures;
foreach (var culture in cultures)
{
var route = contentCache?.GetRouteById(publishedContent.Id, culture);
if (IsNotRoute(route))
{
continue;
}
oldRoutes[new ContentIdAndCulture(publishedContent.Id, culture)] = new ContentKeyAndOldRoute(publishedContent.Key, route!);
}
}
return;
}
private void CreateRedirects(OldRoutesDictionary oldRoutes)
// get the default affected cultures by going up the tree until we find the first culture variant entity (default to no cultures)
var defaultCultures = entityContent.AncestorsOrSelf().FirstOrDefault(a => a.Cultures.Any())?.Cultures.Keys
.ToArray()
?? Array.Empty<string>();
foreach (IPublishedContent publishedContent in entityContent.DescendantsOrSelf(_variationContextAccessor))
{
if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out var publishedSnapshot))
{
return;
}
// if this entity defines specific cultures, use those instead of the default ones
IEnumerable<string> cultures =
publishedContent.Cultures.Any() ? publishedContent.Cultures.Keys : defaultCultures;
var contentCache = publishedSnapshot?.Content;
if (contentCache == null)
foreach (var culture in cultures)
{
_logger.LogWarning("Could not track redirects because there is no current published snapshot available.");
return;
}
foreach (KeyValuePair<ContentIdAndCulture, ContentKeyAndOldRoute> oldRoute in oldRoutes)
{
var newRoute = contentCache.GetRouteById(oldRoute.Key.ContentId, oldRoute.Key.Culture);
if (IsNotRoute(newRoute) || oldRoute.Value.OldRoute == newRoute)
var route = contentCache?.GetRouteById(publishedContent.Id, culture);
if (IsNotRoute(route))
{
continue;
}
_redirectUrlService.Register(oldRoute.Value.OldRoute, oldRoute.Value.ContentKey, oldRoute.Key.Culture);
oldRoutes[new ContentIdAndCulture(publishedContent.Id, culture)] =
new ContentKeyAndOldRoute(publishedContent.Key, route!);
}
}
private static bool IsNotRoute(string? route)
{
// null if content not found
// err/- if collision or anomaly or ...
return route == null || route.StartsWith("err/");
}
private class ContentIdAndCulture : Tuple<int, string>
{
public ContentIdAndCulture(int contentId, string culture) : base(contentId, culture)
{
}
public int ContentId => Item1;
public string Culture => Item2;
}
private class ContentKeyAndOldRoute : Tuple<Guid, string>
{
public ContentKeyAndOldRoute(Guid contentKey, string oldRoute) : base(contentKey, oldRoute)
{
}
public Guid ContentKey => Item1;
public string OldRoute => Item2;
}
private class OldRoutesDictionary : Dictionary<ContentIdAndCulture, ContentKeyAndOldRoute>
{
}
}
private void CreateRedirects(OldRoutesDictionary oldRoutes)
{
if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot))
{
return;
}
IPublishedContentCache? contentCache = publishedSnapshot?.Content;
if (contentCache == null)
{
_logger.LogWarning("Could not track redirects because there is no current published snapshot available.");
return;
}
foreach (KeyValuePair<ContentIdAndCulture, ContentKeyAndOldRoute> oldRoute in oldRoutes)
{
var newRoute = contentCache.GetRouteById(oldRoute.Key.ContentId, oldRoute.Key.Culture);
if (IsNotRoute(newRoute) || oldRoute.Value.OldRoute == newRoute)
{
continue;
}
_redirectUrlService.Register(oldRoute.Value.OldRoute, oldRoute.Value.ContentKey, oldRoute.Key.Culture);
}
}
private class ContentIdAndCulture : Tuple<int, string>
{
public ContentIdAndCulture(int contentId, string culture)
: base(contentId, culture)
{
}
public int ContentId => Item1;
public string Culture => Item2;
}
private class ContentKeyAndOldRoute : Tuple<Guid, string>
{
public ContentKeyAndOldRoute(Guid contentKey, string oldRoute)
: base(contentKey, oldRoute)
{
}
public Guid ContentKey => Item1;
public string OldRoute => Item2;
}
private class OldRoutesDictionary : Dictionary<ContentIdAndCulture, ContentKeyAndOldRoute>
{
}
}