Skip to content

Commit e9c97f8

Browse files
Allow multiple URL segments per document (#18603)
* Code tidy - XML header comments, method ordering, warning resolution. * Add extension method for retrieving all URL segments for a document. * Cache and persist multiple URL segments per document. * Allowed segment providers to terminate or allow additional segments. Set up currently failing integration test for expected routes. * Resolved cache issue to ensure passing new integration tests. * Fixed failing integration test. * Test class naming tidy up. * Added resolution and persistance of a primary segment, to retain previous behaviour when a single segment is retrieved. * Further integration tests. * Resolved backward compatibility of interface. * Supress amends made to integration tests. * Aligned naming of integration tests. * Removed unused using, added XML header comment. * Throw on missing table in migration. * Code clean-up. * Fix multiple enumeration * Used default on migrated column. * Use 1 over true for default value. * Remove unused logger --------- Co-authored-by: mole <[email protected]>
1 parent da5669d commit e9c97f8

13 files changed

+780
-360
lines changed

src/Umbraco.Core/Models/ContentBaseExtensions.cs

+58-14
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,81 @@ public static class ContentBaseExtensions
1111
private static DefaultUrlSegmentProvider? _defaultUrlSegmentProvider;
1212

1313
/// <summary>
14-
/// Gets the URL segment for a specified content and culture.
14+
/// Gets a single URL segment for a specified content and culture.
1515
/// </summary>
1616
/// <param name="content">The content.</param>
1717
/// <param name="shortStringHelper"></param>
1818
/// <param name="urlSegmentProviders"></param>
1919
/// <param name="culture">The culture.</param>
2020
/// <param name="published">Whether to get the published or draft.</param>
2121
/// <returns>The URL segment.</returns>
22+
/// <remarks>
23+
/// If more than one URL segment provider is available, the first one that returns a non-null value will be returned.
24+
/// </remarks>
2225
public static string? GetUrlSegment(this IContentBase content, IShortStringHelper shortStringHelper, IEnumerable<IUrlSegmentProvider> urlSegmentProviders, string? culture = null, bool published = true)
2326
{
24-
if (content == null)
25-
{
26-
throw new ArgumentNullException(nameof(content));
27-
}
27+
var urlSegment = GetUrlSegments(content, urlSegmentProviders, culture, published).FirstOrDefault();
28+
29+
// Ensure we have at least the segment from the default URL provider returned.
30+
urlSegment ??= GetDefaultUrlSegment(shortStringHelper, content, culture, published);
2831

29-
if (urlSegmentProviders == null)
32+
return urlSegment;
33+
}
34+
35+
/// <summary>
36+
/// Gets all URL segments for a specified content and culture.
37+
/// </summary>
38+
/// <param name="content">The content.</param>
39+
/// <param name="shortStringHelper"></param>
40+
/// <param name="urlSegmentProviders"></param>
41+
/// <param name="culture">The culture.</param>
42+
/// <param name="published">Whether to get the published or draft.</param>
43+
/// <returns>The collection of URL segments.</returns>
44+
public static IEnumerable<string> GetUrlSegments(this IContentBase content, IShortStringHelper shortStringHelper, IEnumerable<IUrlSegmentProvider> urlSegmentProviders, string? culture = null, bool published = true)
45+
{
46+
var urlSegments = GetUrlSegments(content, urlSegmentProviders, culture, published).Distinct().ToList();
47+
48+
// Ensure we have at least the segment from the default URL provider returned.
49+
if (urlSegments.Count == 0)
3050
{
31-
throw new ArgumentNullException(nameof(urlSegmentProviders));
51+
var defaultUrlSegment = GetDefaultUrlSegment(shortStringHelper, content, culture, published);
52+
if (defaultUrlSegment is not null)
53+
{
54+
urlSegments.Add(defaultUrlSegment);
55+
}
3256
}
3357

34-
var url = urlSegmentProviders.Select(p => p.GetUrlSegment(content, published, culture)).FirstOrDefault(u => u != null);
35-
if (url == null)
58+
return urlSegments;
59+
}
60+
61+
private static IEnumerable<string> GetUrlSegments(
62+
IContentBase content,
63+
IEnumerable<IUrlSegmentProvider> urlSegmentProviders,
64+
string? culture,
65+
bool published)
66+
{
67+
foreach (IUrlSegmentProvider urlSegmentProvider in urlSegmentProviders)
3668
{
37-
if (_defaultUrlSegmentProvider == null)
69+
var segment = urlSegmentProvider.GetUrlSegment(content, published, culture);
70+
if (string.IsNullOrEmpty(segment) == false)
3871
{
39-
_defaultUrlSegmentProvider = new DefaultUrlSegmentProvider(shortStringHelper);
40-
}
72+
yield return segment;
4173

42-
url = _defaultUrlSegmentProvider.GetUrlSegment(content, culture); // be safe
74+
if (urlSegmentProvider.AllowAdditionalSegments is false)
75+
{
76+
yield break;
77+
}
78+
}
4379
}
80+
}
4481

45-
return url;
82+
private static string? GetDefaultUrlSegment(
83+
IShortStringHelper shortStringHelper,
84+
IContentBase content,
85+
string? culture,
86+
bool published)
87+
{
88+
_defaultUrlSegmentProvider ??= new DefaultUrlSegmentProvider(shortStringHelper);
89+
return _defaultUrlSegmentProvider.GetUrlSegment(content, published, culture);
4690
}
4791
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
11
namespace Umbraco.Cms.Core.Models;
22

3+
/// <summary>
4+
/// Represents a URL segment for a published document.
5+
/// </summary>
36
public class PublishedDocumentUrlSegment
47
{
8+
/// <summary>
9+
/// Gets or sets the document key.
10+
/// </summary>
511
public required Guid DocumentKey { get; set; }
12+
13+
/// <summary>
14+
/// Gets or sets the language Id.
15+
/// </summary>
616
public required int LanguageId { get; set; }
17+
18+
/// <summary>
19+
/// Gets or sets the URL segment string.
20+
/// </summary>
721
public required string UrlSegment { get; set; }
22+
23+
/// <summary>
24+
/// Gets or sets a value indicating whether the URL segment is for a draft.
25+
/// </summary>
826
public required bool IsDraft { get; set; }
27+
28+
/// <summary>
29+
/// Gets or sets a value indicating whether the URL segment is the primary one (first resolved from the collection of URL providers).
30+
/// </summary>
31+
public required bool IsPrimary { get; set; }
932
}

0 commit comments

Comments
 (0)