Fixes slow legacy routes and added async overloads for delivery api (#17371)

This commit is contained in:
Bjarke Berg
2024-10-29 10:01:52 +01:00
committed by GitHub
parent d1799ecdd2
commit f0a1d62247
5 changed files with 93 additions and 26 deletions

View File

@@ -1,3 +1,4 @@
using System.Diagnostics;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -46,19 +47,17 @@ public class ByIdContentApiController : ContentApiItemControllerBase
private async Task<IActionResult> HandleRequest(Guid id)
{
IPublishedContent? contentItem = ApiPublishedContentCache.GetById(id);
IPublishedContent? contentItem = await ApiPublishedContentCache.GetByIdAsync(id).ConfigureAwait(false);
if (contentItem is null)
{
return NotFound();
}
IActionResult? deniedAccessResult = await HandleMemberAccessAsync(contentItem, _requestMemberAccessService);
IActionResult? deniedAccessResult = await HandleMemberAccessAsync(contentItem, _requestMemberAccessService).ConfigureAwait(false);
if (deniedAccessResult is not null)
{
return deniedAccessResult;
}
IApiContentResponse? apiContentResponse = ApiContentResponseBuilder.Build(contentItem);
if (apiContentResponse is null)
{

View File

@@ -45,7 +45,7 @@ public class ByIdsContentApiController : ContentApiItemControllerBase
private async Task<IActionResult> HandleRequest(HashSet<Guid> ids)
{
IPublishedContent[] contentItems = ApiPublishedContentCache.GetByIds(ids).ToArray();
IPublishedContent[] contentItems = (await ApiPublishedContentCache.GetByIdsAsync(ids).ConfigureAwait(false)).ToArray();
IActionResult? deniedAccessResult = await HandleMemberAccessAsync(contentItems, _requestMemberAccessService);
if (deniedAccessResult is not null)

View File

@@ -32,6 +32,35 @@ public sealed class ApiPublishedContentCache : IApiPublishedContentCache
deliveryApiSettings.OnChange(settings => _deliveryApiSettings = settings);
}
public async Task<IPublishedContent?> GetByRouteAsync(string route)
{
var isPreviewMode = _requestPreviewService.IsPreview();
// Handle the nasty logic with domain document ids in front of paths.
int? documentStartNodeId = null;
if (route.StartsWith("/") is false)
{
var index = route.IndexOf('/');
if (index > -1 && int.TryParse(route.Substring(0, index), out var nodeId))
{
documentStartNodeId = nodeId;
route = route.Substring(index);
}
}
Guid? documentKey = _documentUrlService.GetDocumentKeyByRoute(
route,
_requestCultureService.GetRequestedCulture(),
documentStartNodeId,
_requestPreviewService.IsPreview()
);
IPublishedContent? content = documentKey.HasValue
? await _publishedContentCache.GetByIdAsync(documentKey.Value, isPreviewMode)
: null;
return ContentOrNullIfDisallowed(content);
}
public IPublishedContent? GetByRoute(string route)
{
@@ -64,16 +93,37 @@ public sealed class ApiPublishedContentCache : IApiPublishedContentCache
return ContentOrNullIfDisallowed(content);
}
public async Task<IPublishedContent?> GetByIdAsync(Guid contentId)
{
IPublishedContent? content = await _publishedContentCache.GetByIdAsync(contentId, _requestPreviewService.IsPreview()).ConfigureAwait(false);
return ContentOrNullIfDisallowed(content);
}
public IPublishedContent? GetById(Guid contentId)
{
IPublishedContent? content = _publishedContentCache.GetById(_requestPreviewService.IsPreview(), contentId);
return ContentOrNullIfDisallowed(content);
}
public async Task<IEnumerable<IPublishedContent>> GetByIdsAsync(IEnumerable<Guid> contentIds)
{
var isPreviewMode = _requestPreviewService.IsPreview();
IEnumerable<Task<IPublishedContent?>> tasks = contentIds
.Select(contentId => _publishedContentCache.GetByIdAsync(contentId, isPreviewMode));
IPublishedContent?[] allContent = await Task.WhenAll(tasks);
return allContent
.WhereNotNull()
.Where(IsAllowedContentType)
.ToArray();
}
public IEnumerable<IPublishedContent> GetByIds(IEnumerable<Guid> contentIds)
{
var isPreviewMode = _requestPreviewService.IsPreview();
return contentIds
.Select(contentId => _publishedContentCache.GetById(_requestPreviewService.IsPreview(), contentId))
.Select(contentId => _publishedContentCache.GetById(isPreviewMode, contentId))
.WhereNotNull()
.Where(IsAllowedContentType)
.ToArray();

View File

@@ -9,4 +9,8 @@ public interface IApiPublishedContentCache
IPublishedContent? GetById(Guid contentId);
IEnumerable<IPublishedContent> GetByIds(IEnumerable<Guid> contentIds);
Task<IPublishedContent?> GetByIdAsync(Guid contentId) => Task.FromResult(GetById(contentId));
Task<IPublishedContent?> GetByRouteAsync(string route) => Task.FromResult(GetByRoute(route));
Task<IEnumerable<IPublishedContent>> GetByIdsAsync(IEnumerable<Guid> contentIds) => Task.FromResult(GetByIds(contentIds));
}

View File

@@ -6,6 +6,7 @@ using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services.Navigation;
@@ -30,8 +31,8 @@ public class DocumentUrlService : IDocumentUrlService
private readonly IKeyValueService _keyValueService;
private readonly IIdKeyMap _idKeyMap;
private readonly IDocumentNavigationQueryService _documentNavigationQueryService;
private readonly IDomainService _domainService;
private readonly IPublishStatusQueryService _publishStatusQueryService;
private readonly IDomainCacheService _domainCacheService;
private readonly ConcurrentDictionary<string, PublishedDocumentUrlSegment> _cache = new();
private bool _isInitialized;
@@ -49,8 +50,8 @@ public class DocumentUrlService : IDocumentUrlService
IKeyValueService keyValueService,
IIdKeyMap idKeyMap,
IDocumentNavigationQueryService documentNavigationQueryService,
IDomainService domainService,
IPublishStatusQueryService publishStatusQueryService)
IPublishStatusQueryService publishStatusQueryService,
IDomainCacheService domainCacheService)
{
_logger = logger;
_documentUrlRepository = documentUrlRepository;
@@ -64,8 +65,8 @@ public class DocumentUrlService : IDocumentUrlService
_keyValueService = keyValueService;
_idKeyMap = idKeyMap;
_documentNavigationQueryService = documentNavigationQueryService;
_domainService = domainService;
_publishStatusQueryService = publishStatusQueryService;
_domainCacheService = domainCacheService;
}
public async Task InitAsync(bool forceEmpty, CancellationToken cancellationToken)
@@ -443,19 +444,26 @@ public class DocumentUrlService : IDocumentUrlService
var cultureOrDefault = string.IsNullOrWhiteSpace(culture) is false ? culture : _languageService.GetDefaultIsoCodeAsync().GetAwaiter().GetResult();
Guid[] ancestorsOrSelfKeysArray = ancestorsOrSelfKeys as Guid[] ?? ancestorsOrSelfKeys.ToArray();
IDictionary<Guid, IDomain?> ancestorOrSelfKeyToDomains = ancestorsOrSelfKeysArray.ToDictionary(x => x, ancestorKey =>
IDictionary<Guid, Domain?> ancestorOrSelfKeyToDomains = ancestorsOrSelfKeysArray.ToDictionary(x => x, ancestorKey =>
{
IEnumerable<IDomain> domains = _domainService.GetAssignedDomainsAsync(ancestorKey, false).GetAwaiter().GetResult();
return domains.FirstOrDefault(x=>x.LanguageIsoCode == cultureOrDefault);
Attempt<int> idAttempt = _idKeyMap.GetIdForKey(ancestorKey, UmbracoObjectTypes.Document);
if(idAttempt.Success is false)
{
return null;
}
IEnumerable<Domain> domains = _domainCacheService.GetAssigned(idAttempt.Result, false);
return domains.FirstOrDefault(x=>x.Culture == cultureOrDefault);
});
var urlSegments = new List<string>();
IDomain? foundDomain = null;
Domain? foundDomain = null;
foreach (Guid ancestorOrSelfKey in ancestorsOrSelfKeysArray)
{
if (ancestorOrSelfKeyToDomains.TryGetValue(ancestorOrSelfKey, out IDomain? domain))
if (ancestorOrSelfKeyToDomains.TryGetValue(ancestorOrSelfKey, out Domain? domain))
{
if (domain is not null)
{
@@ -478,7 +486,7 @@ public class DocumentUrlService : IDocumentUrlService
if (foundDomain is not null)
{
//we found a domain, and not to construct the route in the funny legacy way
return foundDomain.RootContentId + "/" + string.Join("/", urlSegments);
return foundDomain.ContentId + "/" + string.Join("/", urlSegments);
}
var isRootFirstItem = GetTopMostRootKey(isDraft, cultureOrDefault) == ancestorsOrSelfKeysArray.Last();
@@ -510,24 +518,30 @@ public class DocumentUrlService : IDocumentUrlService
var cultures = languages.ToDictionary(x=>x.IsoCode);
Guid[] ancestorsOrSelfKeysArray = ancestorsOrSelfKeys as Guid[] ?? ancestorsOrSelfKeys.ToArray();
Dictionary<Guid, Task<Dictionary<string, IDomain>>> ancestorOrSelfKeyToDomains = ancestorsOrSelfKeysArray.ToDictionary(x => x, async ancestorKey =>
Dictionary<Guid, Task<Dictionary<string, Domain>>> ancestorOrSelfKeyToDomains = ancestorsOrSelfKeysArray.ToDictionary(x => x, async ancestorKey =>
{
IEnumerable<IDomain> domains = await _domainService.GetAssignedDomainsAsync(ancestorKey, false);
return domains.ToDictionary(x => x.LanguageIsoCode!);
});
Attempt<int> idAttempt = _idKeyMap.GetIdForKey(ancestorKey, UmbracoObjectTypes.Document);
if(idAttempt.Success is false)
{
return null;
}
IEnumerable<Domain> domains = _domainCacheService.GetAssigned(idAttempt.Result, false);
return domains.ToDictionary(x => x.Culture!);
})!;
foreach ((string culture, ILanguage language) in cultures)
{
var urlSegments = new List<string>();
IDomain? foundDomain = null;
Domain? foundDomain = null;
var hasUrlInCulture = true;
foreach (Guid ancestorOrSelfKey in ancestorsOrSelfKeysArray)
{
if (ancestorOrSelfKeyToDomains.TryGetValue(ancestorOrSelfKey, out Task<Dictionary<string, IDomain>>? domainDictionaryTask))
if (ancestorOrSelfKeyToDomains.TryGetValue(ancestorOrSelfKey, out Task<Dictionary<string, Domain>>? domainDictionaryTask))
{
Dictionary<string, IDomain> domainDictionary = await domainDictionaryTask;
if (domainDictionary.TryGetValue(culture, out IDomain? domain))
Dictionary<string, Domain> domainDictionary = await domainDictionaryTask;
if (domainDictionary.TryGetValue(culture, out Domain? domain))
{
foundDomain = domain;
break;
@@ -562,14 +576,14 @@ public class DocumentUrlService : IDocumentUrlService
return result;
}
private string GetFullUrl(bool isRootFirstItem, List<string> reversedUrlSegments, IDomain? foundDomain)
private string GetFullUrl(bool isRootFirstItem, List<string> reversedUrlSegments, Domain? foundDomain)
{
var urlSegments = new List<string>(reversedUrlSegments);
urlSegments.Reverse();
if (foundDomain is not null)
{
return foundDomain.DomainName.EnsureEndsWith("/") + string.Join('/', urlSegments);
return foundDomain.Name.EnsureEndsWith("/") + string.Join('/', urlSegments);
}
return '/' + string.Join('/', urlSegments.Skip(_globalSettings.HideTopLevelNodeFromPath && isRootFirstItem ? 1 : 0));