Fixes slow legacy routes and added async overloads for delivery api (#17371)
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user