Umbraco Content Delivery API – Wildcard item resolver

I came across the problem, that Umbraco does not support wildcard item resolving out of the box – neither MVC nor Content Delivery API. In this post I am focusing on the Content Delivery API solution, which just has been released with Umbraco 12, this year.

What is a wildcard item?

Wildcard items are useful when you would like to request the same page item for different URLs. Let’s say, in my head application I want to show the product details page – but the product data itself is coming from an external source:

  • /products/t-shirt
  • /products/hat
  • /products/socks

Without wildcard item resolver, I would need to create items in Umbraco for each URL, but with the implemented resolver I just need to create one wildcard item which is resolved for each of the URLs above.

Solution

Firstly, I had to look at the Content Delivery API implementation and I saw, it’s using the IApiPublishedContentCache. Cool-cool, so I just have to inherit from the original class and define a new implementation for the method which is resolving the items by route – namely the GetByRoute method. Sounds like a good plan, but Umbraco stopped me ⛔. No-no-no, because the ApiPublishedContentCache is a sealed class. Alright, then I came up with the idea to not inherit from the original implementation, but inject it as a dependency and just use the default implementations where I don’t want to overwrite the logic, this is a good enough solution in this case, because this service contains just a few methods. Here is the implementation I came up with:

public class WildcardApiPublishedContentCache : IApiPublishedContentCache
{
private const char SplitChar = '/';
private const string WildcardName = "star";
private readonly ApiPublishedContentCache _apiPublishedContentCache;
public WildcardApiPublishedContentCache(ApiPublishedContentCache apiPublishedContentCache)
{
_apiPublishedContentCache = apiPublishedContentCache;
}
public IPublishedContent? GetById(Guid contentId)
{
return _apiPublishedContentCache.GetById(contentId);
}
public IEnumerable<IPublishedContent> GetByIds(IEnumerable<Guid> contentIds)
{
return _apiPublishedContentCache.GetByIds(contentIds);
}
public IPublishedContent? GetByRoute(string route)
{
var content = _apiPublishedContentCache.GetByRoute(route);
if (content == null)
{
// Try to get a wildcard item if exists
var routePath = route.Split(SplitChar);
var wildcardRoute = string.Join(SplitChar, new List<string>(routePath.Take(routePath.Length - 1)) { WildcardName });
content = _apiPublishedContentCache.GetByRoute(wildcardRoute);
}
return content;
}
}

To able to inject the default class ApiPublishedContentCache, I had to register the class without the interface – this would be much more elegant if I could just inherit from this class in my own implementation. So the service registration looks like the following:

public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
// Overwrite the default ApiPublishedContentCache to support wildcard item resolving
services.AddSingleton<ApiPublishedContentCache>();
services.AddSingleton<IApiPublishedContentCache, WildcardApiPublishedContentCache>();
}
}
view raw Startup.cs hosted with ❤ by GitHub

3 thoughts on “Umbraco Content Delivery API – Wildcard item resolver

  1. Some good information here, but it seems that this is no longer valid (after only a few months).

    Using this example and Umbraco 12 the WildcardApiPublishedContentCache is never instantiated or called.

    I even tried forcably removing the built-in ApiPublishedContentCache and instantiating it myself inside the WildcardApiPublishedContentCache, but I guess there’s something somewhere that inserts it again and uses it.

    services.RemoveAll<IApiPublishedContentCache>();
    services.AddSingleton<IApiPublishedContentCache, WildcardApiPublishedContentCache>();

    Like

    1. Thank you for the feedback, I really appreciate it! It is working on an Umbraco 13 environment. It is important to invoke the replacement after the default Umbraco DI registrations. How your `ConfigureServices` method look like? Can you try to move DI replacement part to the end of this method?

      Like

      1. The DI replacement was already at the end of `ConfigureServices()`.

        I did, however, find another solution that worked just fine. Could even use some of your example code in it 🙂

        Create a new `IContentFinder`, put your code into the `TryFindContent()` implementation, and append the contentfinder to the list of content finders in `ConfigureServices()`: `umbracoBuilder.ContentFinders().Append<YourContentFinder>()`

        I was testing your example on Umbraco 12, though. I’m surprised there are such big changes between the two, but I believe the ContentFinder way is more “according to spec” 🙂

        https://docs.umbraco.com/umbraco-cms/reference/routing/request-pipeline/icontentfinder

        Liked by 1 person

Leave a comment