Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V13: Set request culture for VirtualPageController #16572

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/Umbraco.Core/Routing/IPublishedRouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,37 @@ public interface IPublishedRouter
/// </para>
/// </remarks>
Task<IPublishedRequest> UpdateRequestAsync(IPublishedRequest request, IPublishedContent? publishedContent);

/// <summary>
/// Finds the site root (if any) matching the http request, and updates the PublishedRequest and VariationContext accordingly.
/// <remarks>
/// <para>
/// This method is used for VirtualPage routing.
/// </para>
/// <para>
/// In this case we do not want to run the entire routing pipeline since ContentFinders are not needed here.
/// However, we do want to set the culture on VariationContext and PublishedRequest to the values specified by the domains.
/// </para>
/// </remarks>
/// </summary>
/// <param name="request">The request to update the culture on domain on</param>
/// <returns>True if a domain was found otherwise false.</returns>
bool RouteDomain(IPublishedRequestBuilder request) => false;

/// <summary>
/// Finds the site root (if any) matching the http request, and updates the VariationContext accordingly.
/// </summary>
/// <remarks>
/// <para>
/// This is used for VirtualPage routing.
/// </para>
/// <para>
/// This is required to set the culture on VariationContext to the values specified by the domains, before the FindContent method is called.
/// In order to allow the FindContent implementer to correctly find content based off the culture. Before the PublishedRequest is built.
/// </para>
/// </remarks>
/// <param name="uri">The URI to resolve the domain from.</param>
/// <returns>True if a domain was found, otherwise false.</returns>
bool UpdateVariationContext(Uri uri) => false;

}
43 changes: 33 additions & 10 deletions src/Umbraco.Core/Routing/PublishedRouter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Globalization;

Check notice on line 1 in src/Umbraco.Core/Routing/PublishedRouter.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v13/dev)

✅ Getting better: Overall Code Complexity

The mean cyclomatic complexity decreases from 7.06 to 6.19, threshold = 4. This file has many conditional statements (e.g. if, for, while) across its implementation, leading to lower code health. Avoid adding more conditionals.
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
Expand Down Expand Up @@ -108,7 +108,7 @@
// find domain
if (builder.Domain == null)
{
FindDomain(builder);
FindAndSetDomain(builder);
}

await RouteRequestInternalAsync(builder);
Expand Down Expand Up @@ -185,7 +185,7 @@

private async Task<IPublishedRequest> TryRouteRequest(IPublishedRequestBuilder request)
{
FindDomain(request);
FindAndSetDomain(request);

if (request.IsRedirect())
{
Expand Down Expand Up @@ -270,55 +270,78 @@
// to find out the appropriate template
}

/// <summary>
/// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly.
/// </summary>
/// <returns>A value indicating whether a domain was found.</returns>
internal bool FindDomain(IPublishedRequestBuilder request)
/// <inheritdoc />
public bool RouteDomain(IPublishedRequestBuilder request)
{
var found = FindAndSetDomain(request);
HandleWildcardDomains(request);
SetVariationContext(request.Culture);
return found;
}

/// <inheritdoc />
public bool UpdateVariationContext(Uri uri)
{
DomainAndUri? domain = FindDomain(uri, out _);
SetVariationContext(domain?.Culture);
return domain?.Culture is not null;
}

private DomainAndUri? FindDomain(Uri uri, out string? defaultCulture)
{
const string tracePrefix = "FindDomain: ";

// note - we are not handling schemes nor ports here.
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri);
_logger.LogDebug("{TracePrefix}Uri={RequestUri}", tracePrefix, uri);
}

IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
IDomainCache? domainsCache = umbracoContext.PublishedSnapshot.Domains;
var domains = domainsCache?.GetAll(false).ToList();

// determines whether a domain corresponds to a published document, since some
// domains may exist but on a document that has been unpublished - as a whole - or
// that is not published for the domain's culture - in which case the domain does
// not apply
bool IsPublishedContentDomain(Domain domain)
{
// just get it from content cache - optimize there, not here
IPublishedContent? domainDocument = umbracoContext.PublishedSnapshot.Content?.GetById(domain.ContentId);

// not published - at all
if (domainDocument == null)
{
return false;
}

// invariant - always published
if (!domainDocument.ContentType.VariesByCulture())
{
return true;
}

// variant, ensure that the culture corresponding to the domain's language is published
return domain.Culture is not null && domainDocument.Cultures.ContainsKey(domain.Culture);
}

domains = domains?.Where(IsPublishedContentDomain).ToList();

var defaultCulture = domainsCache?.DefaultCulture;
defaultCulture = domainsCache?.DefaultCulture;

return DomainUtilities.SelectDomain(domains, uri, defaultCulture: defaultCulture);
}

/// <summary>
/// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly.
/// </summary>
/// <returns>A value indicating whether a domain was found.</returns>
internal bool FindAndSetDomain(IPublishedRequestBuilder request)
{
const string tracePrefix = "FindDomain: ";
// try to find a domain matching the current request
DomainAndUri? domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture);
DomainAndUri? domainAndUri = FindDomain(request.Uri, out var defaultCulture);

Check notice on line 344 in src/Umbraco.Core/Routing/PublishedRouter.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v13/dev)

✅ No longer an issue: Bumpy Road Ahead

FindDomain is no longer above the threshold for logical blocks with deeply nested code. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

Check notice on line 344 in src/Umbraco.Core/Routing/PublishedRouter.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v13/dev)

ℹ New issue: Bumpy Road Ahead

FindAndSetDomain has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

// handle domain - always has a contentId and a culture
if (domainAndUri != null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Web.Common.Controllers;
using Umbraco.Cms.Web.Common.Routing;

Expand Down Expand Up @@ -40,6 +42,12 @@ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionE
if (endpoint != null)
{
IUmbracoVirtualPageRoute umbracoVirtualPageRoute = context.HttpContext.RequestServices.GetRequiredService<IUmbracoVirtualPageRoute>();
IPublishedRouter publishedRouter = context.HttpContext.RequestServices.GetRequiredService<IPublishedRouter>();
UriUtility uriUtility = context.HttpContext.RequestServices.GetRequiredService<UriUtility>();

var originalRequestUrl = new Uri(context.HttpContext.Request.GetEncodedUrl());
Uri cleanedUri = uriUtility.UriToUmbraco(originalRequestUrl);
publishedRouter.UpdateVariationContext(cleanedUri);

IPublishedContent? publishedContent = umbracoVirtualPageRoute.FindContent(endpoint, context);

Expand Down
1 change: 1 addition & 0 deletions src/Umbraco.Web.Common/Routing/UmbracoVirtualPageRoute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ public async Task<IPublishedRequest> CreatePublishedRequest(HttpContext httpCont

IPublishedRequestBuilder requestBuilder = await _publishedRouter.CreateRequestAsync(cleanedUrl);
requestBuilder.SetPublishedContent(publishedContent);
_publishedRouter.RouteDomain(requestBuilder);

return requestBuilder.Build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public async Task Lookup_By_Url_Alias_And_Domain(string inputUrl, string expecte
var request = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);

// must lookup domain
publishedRouter.FindDomain(request);
publishedRouter.FindAndSetDomain(request);

if (expectedNode > 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public async Task Lookup_SingleDomain(string url, int expectedId)
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);

// must lookup domain else lookup by URL fails
publishedRouter.FindDomain(frequest);
publishedRouter.FindAndSetDomain(frequest);

var lookup = new ContentFinderByUrl(Mock.Of<ILogger<ContentFinderByUrl>>(), umbracoContextAccessor);
var result = await lookup.TryFindContent(frequest);
Expand Down Expand Up @@ -245,7 +245,7 @@ public async Task Lookup_NestedDomains(string url, int expectedId, string expect
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);

// must lookup domain else lookup by URL fails
publishedRouter.FindDomain(frequest);
publishedRouter.FindAndSetDomain(frequest);
Assert.AreEqual(expectedCulture, frequest.Culture);

var lookup = new ContentFinderByUrl(Mock.Of<ILogger<ContentFinderByUrl>>(), umbracoContextAccessor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ public async Task DomainAndCulture(string inputUrl, string expectedCulture, int
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);

// lookup domain
publishedRouter.FindDomain(frequest);
publishedRouter.FindAndSetDomain(frequest);

Assert.AreEqual(expectedCulture, frequest.Culture);

Expand Down Expand Up @@ -310,7 +310,7 @@ public async Task DomainAndCultureWithWildcards(string inputUrl, string expected
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);

// lookup domain
publishedRouter.FindDomain(frequest);
publishedRouter.FindAndSetDomain(frequest);

// find document
var finder = new ContentFinderByUrl(Mock.Of<ILogger<ContentFinderByUrl>>(), umbracoContextAccessor);
Expand Down Expand Up @@ -345,7 +345,7 @@ public async Task DomainGeneric(string inputUrl, string expectedCulture, int exp
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);

// lookup domain
publishedRouter.FindDomain(frequest);
publishedRouter.FindAndSetDomain(frequest);
Assert.IsNotNull(frequest.Domain);

Assert.AreEqual(expectedCulture, frequest.Culture);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public async Task DoNotPolluteCache()
var publishedRouter = CreatePublishedRouter(umbracoContextAccessor);
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);

publishedRouter.FindDomain(frequest);
publishedRouter.FindAndSetDomain(frequest);
Assert.IsTrue(frequest.HasDomain());

// check that it's been routed
Expand Down
Loading