Skip to content

Commit

Permalink
Fixes #446, #439, #432
Browse files Browse the repository at this point in the history
  • Loading branch information
Shazwazza committed May 3, 2024
1 parent 1e8579d commit ab15543
Show file tree
Hide file tree
Showing 15 changed files with 732 additions and 1,030 deletions.
1,621 changes: 610 additions & 1,011 deletions src/Articulate.Tests.Website/appsettings-schema.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Articulate/Articulate.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<OutputType>Library</OutputType>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
Expand Down
3 changes: 3 additions & 0 deletions src/Articulate/Components/ArticulateComposer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
using Articulate.Routing;
using Articulate.Services;
using Articulate.Syndication;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Notifications;
Expand Down Expand Up @@ -36,6 +38,7 @@ public override void Compose(IUmbracoBuilder builder)
services.AddSingleton<ArticulateRouter>();
services.AddSingleton<RouteCacheRefresherFilter>();
services.AddSingleton<ArticulateFrontEndFilterConvention>();
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, ArticulateDynamicRouteSelectorPolicy>());

builder.UrlProviders().InsertBefore<DefaultUrlProvider, DateFormattedUrlProvider>();

Expand Down
13 changes: 13 additions & 0 deletions src/Articulate/Controllers/ArticulateDynamicRouteAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Articulate.Controllers
{
[AttributeUsage(AttributeTargets.Class)]
public sealed class ArticulateDynamicRouteAttribute : Attribute
{
}
}
1 change: 1 addition & 0 deletions src/Articulate/Controllers/ArticulateRssController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ namespace Articulate.Controllers
#if NET7_0_OR_GREATER
[OutputCache(PolicyName = "Articulate300")]
#endif
[ArticulateDynamicRoute]
public class ArticulateRssController : RenderController
{
private readonly IRssFeedGenerator _feedGenerator;
Expand Down
1 change: 1 addition & 0 deletions src/Articulate/Controllers/ArticulateSearchController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace Articulate.Controllers
/// <summary>
/// Renders search results
/// </summary>
[ArticulateDynamicRoute]
public class ArticulateSearchController : ListControllerBase
{
private readonly IArticulateSearcher _articulateSearcher;
Expand Down
8 changes: 5 additions & 3 deletions src/Articulate/Controllers/ArticulateTagsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
using Articulate.Services;
using Umbraco.Cms.Core.PublishedCache;
using System.Collections.Generic;
#if NET7_0_OR_GREATER
using Umbraco.Cms.Web.Common.Attributes;

#if NET7_0_OR_GREATER
using Microsoft.AspNetCore.OutputCaching;
#endif

Expand All @@ -28,7 +30,7 @@ namespace Articulate.Controllers
#if NET7_0_OR_GREATER
[OutputCache(PolicyName = "Articulate60")]
#endif

[ArticulateDynamicRoute]
public class ArticulateTagsController : ListControllerBase
{
private readonly UmbracoHelper _umbracoHelper;
Expand All @@ -51,7 +53,7 @@ public ArticulateTagsController(
_articulateTagService = articulateTagService;
_tagQuery = tagQuery;
}

/// <summary>
/// Used to render the category listing (virtual node)
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/Articulate/Controllers/MarkdownEditorController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

namespace Articulate.Controllers
{
[ArticulateDynamicRoute]
public class MarkdownEditorController : RenderController
{
private readonly UmbracoApiControllerTypeCollection _apiControllers;
Expand Down
1 change: 1 addition & 0 deletions src/Articulate/Controllers/MetaWeblogController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace Articulate.Controllers
/// middleware but that just supports one endpoint, we are basically wrapping that
/// with our own multi-tenanted version.
/// </remarks>
[ArticulateDynamicRoute]
public class MetaWeblogController : RenderController
{
private readonly IServiceProvider _serviceProvider;
Expand Down
1 change: 1 addition & 0 deletions src/Articulate/Controllers/OpenSearchController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

namespace Articulate.Controllers
{
[ArticulateDynamicRoute]
public class OpenSearchController : RenderController
{
private readonly IPublishedValueFallback _publishedValueFallback;
Expand Down
1 change: 1 addition & 0 deletions src/Articulate/Controllers/RsdController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace Articulate.Controllers
/// <summary>
/// Really simple discovery controller
/// </summary>
[ArticulateDynamicRoute]
public class RsdController : RenderController
{
private readonly UmbracoHelper _umbracoHelper;
Expand Down
1 change: 1 addition & 0 deletions src/Articulate/Controllers/WlwManifestController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace Articulate.Controllers
{
[ArticulateDynamicRoute]
public class WlwManifestController : RenderController
{
private readonly UmbracoHelper _umbraco;
Expand Down
74 changes: 74 additions & 0 deletions src/Articulate/Routing/ArticulateDynamicRouteSelectorPolicy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#nullable enable

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Articulate.Controllers;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matching;
using Umbraco.Cms.Web.Common.Routing;

namespace Articulate.Routing
{
/// <summary>
/// Used when their is ambiguous route candidates due to multiple dynamic routes being assigned.
/// </summary>
/// <remarks>
/// Ambiguous dynamic routes can occur if Umbraco detects a 404 and assigns a route, but sometimes its not
/// actually a 404 because the articulate router occurs after the Umbraco router which handles 404 eagerly.
/// This causes 2x candidates to be resolved and the first (umbraco) is chosen.
/// If we detect that Articulate actually performed the routing, then we use that candidate instead.
/// TODO: Ideally - Umbraco would dynamically route the 404 in a much later state which could be done,
/// by a dynamic router that has a much larger Order so it occurs later in the pipeline instead of eagerly.
/// </remarks>
internal class ArticulateDynamicRouteSelectorPolicy : MatcherPolicy, IEndpointSelectorPolicy
{
public override int Order => 100;

public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
{
// Don't apply this filter to any endpoint group that is a controller route
// i.e. only dynamic routes.
foreach (Endpoint endpoint in endpoints)
{
ControllerAttribute? controller = endpoint.Metadata.GetMetadata<ControllerAttribute>();
if (controller != null)
{
return false;
}
}

// then ensure this is only applied if all endpoints are IDynamicEndpointMetadata
return endpoints.All(x => x.Metadata.GetMetadata<IDynamicEndpointMetadata>() != null);
}

public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
{
var umbracoRouteValues = httpContext.Features.Get<UmbracoRouteValues>();

// If the request has been dynamically routed by articulate to an
// Articulate controller
if (umbracoRouteValues != null
&& umbracoRouteValues.ControllerActionDescriptor.EndpointMetadata.Any(x => x is ArticulateDynamicRouteAttribute))
{
for (var i = 0; i < candidates.Count; i++)
{
// If the candidate is an Articulate dynamic controller, set valid
if (candidates[i].Endpoint.Metadata.GetMetadata<ArticulateDynamicRouteAttribute>() is not null)
{
candidates.SetValidity(i, true);
}
else
{
// else it is invalid
candidates.SetValidity(i, false);
}
}
}

return Task.CompletedTask;
}
}
}
12 changes: 9 additions & 3 deletions src/Articulate/Routing/ArticulateRouteValueTransformer.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#nullable enable

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
Expand Down Expand Up @@ -148,6 +150,8 @@ private async Task WriteRouteValues(IUmbracoContext umbracoContext, HttpContext
// Store the route values as a httpcontext feature
httpContext.Features.Set(umbracoRouteValues);

umbracoContext.PublishedRequest = publishedRequest;

values[ControllerToken] = dynamicRouteValues.ControllerActionDescriptor.ControllerName;
if (string.IsNullOrWhiteSpace(dynamicRouteValues.ControllerActionDescriptor.ActionName) == false)
{
Expand Down Expand Up @@ -175,9 +179,11 @@ private bool ShouldCheck(
return false;
}

// If route values have already been assigned, then Umbraco has
// matched content, we will not proceed.
if (umbracoRouteValues?.PublishedRequest?.PublishedContent != null)
// If route values have already been assigned, then Umbraco has matched content, we will not proceed.
// A 404 can be matched by Umbraco too which will occur for Articulate dynamic routes, so we need to
// proceed to see if it is actually a 404.
if (umbracoRouteValues?.PublishedRequest?.PublishedContent != null
&& umbracoRouteValues?.PublishedRequest?.ResponseStatusCode != 404)
{
return false;
}
Expand Down
22 changes: 10 additions & 12 deletions src/Articulate/Routing/ArticulateRouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,14 @@ public class ArticulateRouter
{
private static readonly object s_locker = new object();
private static readonly string s_searchControllerName = ControllerExtensions.GetControllerName<ArticulateSearchController>();
private static readonly string s_openSearchControllerName = ControllerExtensions.GetControllerName<OpenSearchController>();
private static readonly string s_rsdControllerName = ControllerExtensions.GetControllerName<RsdController>();
private static readonly string s_wlwControllerName = ControllerExtensions.GetControllerName<WlwManifestController>();
private static readonly string s_openSearchControllerName = ControllerExtensions.GetControllerName<OpenSearchController>();
private static readonly string s_rsdControllerName = ControllerExtensions.GetControllerName<RsdController>();
private static readonly string s_wlwControllerName = ControllerExtensions.GetControllerName<WlwManifestController>();
private static readonly string s_tagsControllerName = ControllerExtensions.GetControllerName<ArticulateTagsController>();
private static readonly string s_rssControllerName = ControllerExtensions.GetControllerName<ArticulateRssController>();
private static readonly string s_markdownEditorControllerName = ControllerExtensions.GetControllerName<MarkdownEditorController>();
private static readonly string s_metaWeblogControllerName = ControllerExtensions.GetControllerName<MetaWeblogController>();



private readonly Dictionary<ArticulateRouteTemplate, ArticulateRootNodeCache> _routeCache = new();
private readonly IControllerActionSearcher _controllerActionSearcher;

Expand All @@ -44,7 +42,7 @@ public ArticulateRouter(IControllerActionSearcher controllerActionSearcher)

public bool TryMatch(PathString path, RouteValueDictionary routeValues, out ArticulateRootNodeCache articulateRootNodeCache)
{
foreach(var item in _routeCache)
foreach (var item in _routeCache)
{
var templateMatcher = new TemplateMatcher(item.Key.RouteTemplate, routeValues);
if (templateMatcher.TryMatch(path, routeValues))
Expand Down Expand Up @@ -116,17 +114,17 @@ public void MapRoutes(HttpContext httpContext, IUmbracoContext umbracoContext)
MapAuthorsRssRoute(httpContext, rootNodePath, articulateRootNode, domains);

MapSearchRoute(httpContext, rootNodePath, articulateRootNode, domains);
MapMetaWeblogRoute(httpContext, rootNodePath, articulateRootNode, domains);
MapManifestRoute(httpContext, rootNodePath, articulateRootNode, domains);
MapMetaWeblogRoute(httpContext, rootNodePath, articulateRootNode, domains);
MapManifestRoute(httpContext, rootNodePath, articulateRootNode, domains);
MapRsdRoute(httpContext, rootNodePath, articulateRootNode, domains);
MapOpenSearchRoute(httpContext, rootNodePath, articulateRootNode, domains);

// tags/cats routes are the least specific
MapTagsAndCategoriesRoute(httpContext, rootNodePath, articulateRootNode, domains);
}
}
}
}
}
}

/// <summary>
/// Generically caches a url path for a particular controller
Expand All @@ -153,7 +151,7 @@ private void MapRoute(
_routeCache[art] = dynamicRouteValues;
}

dynamicRouteValues.Add(articulateRootNode.Id, DomainsForContent(articulateRootNode,domains));
dynamicRouteValues.Add(articulateRootNode.Id, DomainsForContent(articulateRootNode, domains));
}

private List<Domain> DomainsForContent(IPublishedContent content, IReadOnlyList<Domain> domains)
Expand All @@ -175,7 +173,7 @@ private void MapOpenSearchRoute(HttpContext httpContext, string rootNodePath, IP
domains);
}

private void MapRsdRoute(HttpContext httpContext, string rootNodePath, IPublishedContent articulateRootNode, List<Domain> domains)
private void MapRsdRoute(HttpContext httpContext, string rootNodePath, IPublishedContent articulateRootNode, List<Domain> domains)
{
RouteTemplate template = TemplateParser.Parse($"{rootNodePath}rsd/{{id}}");
MapRoute(
Expand Down

0 comments on commit ab15543

Please sign in to comment.