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>(); | |
| } | |
| } |
![Tamás Tárnok = [ C#, Sitecore, … ]](https://trnktms.com/wp-content/uploads/2021/11/pxart_white-small.png)
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>();
LikeLike
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?
LikeLike
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
LikeLiked by 1 person