Skip to content

Commit

Permalink
Fixes slow legacy routes and added async overloads for delivery api (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
bergmania authored Oct 29, 2024
1 parent d1799ec commit f0a1d62
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
Expand Down Expand Up @@ -46,19 +47,17 @@ public async Task<IActionResult> ByIdV20(Guid id)

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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public async Task<IActionResult> ItemsV20([FromQuery(Name = "id")] HashSet<Guid>

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)
Expand Down
52 changes: 51 additions & 1 deletion src/Umbraco.Core/DeliveryApi/ApiPublishedContentCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,35 @@ public ApiPublishedContentCache(
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)
{
Expand Down Expand Up @@ -64,16 +93,37 @@ public ApiPublishedContentCache(
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();
Expand Down
4 changes: 4 additions & 0 deletions src/Umbraco.Core/DeliveryApi/IApiPublishedContentCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
54 changes: 34 additions & 20 deletions src/Umbraco.Core/Services/DocumentUrlService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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;
Expand All @@ -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;
Expand All @@ -49,8 +50,8 @@ public DocumentUrlService(
IKeyValueService keyValueService,
IIdKeyMap idKeyMap,
IDocumentNavigationQueryService documentNavigationQueryService,
IDomainService domainService,
IPublishStatusQueryService publishStatusQueryService)
IPublishStatusQueryService publishStatusQueryService,
IDomainCacheService domainCacheService)
{
_logger = logger;
_documentUrlRepository = documentUrlRepository;
Expand All @@ -64,8 +65,8 @@ public DocumentUrlService(
_keyValueService = keyValueService;
_idKeyMap = idKeyMap;
_documentNavigationQueryService = documentNavigationQueryService;
_domainService = domainService;
_publishStatusQueryService = publishStatusQueryService;
_domainCacheService = domainCacheService;
}

public async Task InitAsync(bool forceEmpty, CancellationToken cancellationToken)
Expand Down Expand Up @@ -443,19 +444,26 @@ public string GetLegacyRouteFormat(Guid docuemntKey, string? culture, bool isDra
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)
{
Expand All @@ -478,7 +486,7 @@ public string GetLegacyRouteFormat(Guid docuemntKey, string? culture, bool isDra
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();
Expand Down Expand Up @@ -510,24 +518,30 @@ public async Task<IEnumerable<UrlInfo>> ListUrlsAsync(Guid contentKey)
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;
Expand Down Expand Up @@ -562,14 +576,14 @@ public async Task<IEnumerable<UrlInfo>> ListUrlsAsync(Guid contentKey)
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));
Expand Down

0 comments on commit f0a1d62

Please sign in to comment.