and use either an Umbraco builder extension, or a composer to add it to it to the ContentFindersCollection.
Umbraco runs all content finders in the collection 'in order', until one of the IContentFinders returns true. Once this occurs, the request is then handled by that finder, and no further IContentFinders are executed. Therefore the order in which ContentFinders are added to the ContentFinderCollection is important.
The ContentFinder can set the PublishedContent item for the request, or template or even execute a redirect.
Example
This IContentFinders will find a document with id 1234, when the Url begins with /woot.
publicclassMyContentFinder:IContentFinder{privatereadonlyIUmbracoContextAccessor _umbracoContextAccessor;publicMyContentFinder(IUmbracoContextAccessor umbracoContextAccessor) { _umbracoContextAccessor = umbracoContextAccessor; }publicTask<bool> TryFindContent(IPublishedRequestBuilder contentRequest) {var path =contentRequest.Uri.GetAbsolutePathDecoded();if (path.StartsWith("/woot") isfalse) {returnTask.FromResult(false); // Not found }if (!_umbracoContextAccessor.TryGetUmbracoContext(outvar umbracoContext)) {returnTask.FromResult(false); } // Have we got a node with ID 1234var content =umbracoContext.Content.GetById(1234);if (content isnull) { // If not found, let another IContentFinder in the collection try.returnTask.FromResult(false); } // If content is found, then render that nodecontentRequest.SetPublishedContent(content);returnTask.FromResult(true); }}
Adding and removing IContentFinders
You can use either an extension on the Umbraco builder or a composer to access the ContentFinderCollection and add or remove specific ContentFinders.
Learn more about registering dependencies and when to use which method in the Dependency Injection article.
Umbraco builder extension
First create the extension method:
usingRoutingDocs.ContentFinders;usingUmbraco.Cms.Core.DependencyInjection;usingUmbraco.Cms.Core.Routing;namespaceRoutingDocs.Extensions;publicstaticclassUmbracoBuilderExtensions{publicstaticIUmbracoBuilderAddCustomContentFinders(thisIUmbracoBuilder builder) { // Add our custom content finder just before the core ContentFinderByUrlbuilder.ContentFinders().InsertBefore<ContentFinderByUrl,MyContentFinder>(); // You can also remove content finders, this is not required here though, since our finder runs before the url one
builder.ContentFinders().Remove<ContentFinderByUrl>(); // You use Append to add to the end of the collectionbuilder.ContentFinders().Append<AnotherContentFinderExample>(); // or Insert for a specific position in the collectionbuilder.ContentFinders().Insert<AndAnotherContentFinder>(3);return builder; }}
usingUmbraco.Cms.Core.Composing;usingUmbraco.Cms.Core.DependencyInjection;usingUmbraco.Cms.Core.Routing;namespaceRoutingDocs.ContentFinders;publicclassUpdateContentFindersComposer:IComposer{publicvoidCompose(IUmbracoBuilder builder) { // Add our custom content finder just before the core ContentFinderByUrlbuilder.ContentFinders().InsertBefore<ContentFinderByUrl,MyContentFinder>(); // You can also remove content finders, this is not required here though, since our finder runs before the url one
builder.ContentFinders().Remove<ContentFinderByUrl>(); // You use Append to add to the end of the collectionbuilder.ContentFinders().Append<AnotherContentFinderExample>(); // or Insert for a specific position in the collectionbuilder.ContentFinders().Insert<AndAnotherContentFinder>(3); }}
NotFoundHandlers
To set your own 404 finder create an IContentLastChanceFinder and set it as the ContentLastChanceFinder. (perhaps you have a multilingual site and need to find the appropriate 404 page in the correct language).
A IContentLastChanceFinder will always return a 404 status code. This example creates a new implementation of the IContentLastChanceFinder and gets the 404 page for the current language of the request.
usingSystem.Linq;usingSystem.Threading.Tasks;usingUmbraco.Cms.Core.Models.PublishedContent;usingUmbraco.Cms.Core.Routing;usingUmbraco.Cms.Core.Services;usingUmbraco.Cms.Core.Web;namespaceRoutingDocs.ContentFinders;publicclassMy404ContentFinder:IContentLastChanceFinder{privatereadonlyIDomainService _domainService;privatereadonlyIUmbracoContextAccessor _umbracoContextAccessor;publicMy404ContentFinder(IDomainService domainService,IUmbracoContextAccessor umbracoContextAccessor) { _domainService = domainService; _umbracoContextAccessor = umbracoContextAccessor; }publicTask<bool> TryFindContent(IPublishedRequestBuilder contentRequest) { // Find the root node with a matching domain to the incoming requestvar allDomains =_domainService.GetAll(true).ToList();var domain = allDomains? .FirstOrDefault(f =>f.DomainName==contentRequest.Uri.Authority||f.DomainName==$"https://{contentRequest.Uri.Authority}"||f.DomainName==$"http://{contentRequest.Uri.Authority}"); var siteId = domain != null ? domain.RootContentId : allDomains.Any() ? allDomains.FirstOrDefault()?.RootContentId : null;
if (!_umbracoContextAccessor.TryGetUmbracoContext(outvar umbracoContext)) {returnTask.FromResult(false); } public Task<bool> TryFindContent(IPublishedRequestBuilder contentRequest) { // Find the root node with a matching domain to the incoming requestvar allDomains =_domainService.GetAll(true).ToList();var domain = allDomains? .FirstOrDefault(f =>f.DomainName==contentRequest.Uri.Authority||f.DomainName==$"https://{contentRequest.Uri.Authority}"||f.DomainName==$"http://{contentRequest.Uri.Authority}"); var siteId = domain != null ? domain.RootContentId : allDomains.Any() ? allDomains.FirstOrDefault()?.RootContentId : null;
if (!_umbracoContextAccessor.TryGetUmbracoContext(outvar umbracoContext)) {returnTask.FromResult(false); }if (umbracoContext.Content==null)returnnewTask<bool>(() =>contentRequest.PublishedContentisnotnull);var siteRoot =umbracoContext.Content.GetById(false, siteId ??-1);if (siteRoot isnull) {returnTask.FromResult(false); } // Assuming the 404 page is in the root of the language site with alias fourOhFourPageAliasvar notFoundNode =siteRoot.Children?.FirstOrDefault(f =>f.ContentType.Alias=="fourOhFourPageAlias");if (notFoundNode isnotnull) {contentRequest.SetPublishedContent(notFoundNode); } // Return true or false depending on whether our custom 404 page was foundreturnTask.FromResult(contentRequest.PublishedContentisnotnull); }}
You can configure Umbraco to use your own implementation in the Program.cs file:
builder.CreateUmbracoBuilder() .AddBackOffice() .AddWebsite() .AddDeliveryApi() .AddComposers() // If you need to add something Umbraco specific, do it in the "AddUmbraco" builder chain, using the IUmbracoBuilder extension methods.
.SetContentLastChanceFinder<RoutingDocs.ContentFinders.My404ContentFinder>() .Build();
When adding a custom IContentLastChanceFinder to the pipeline any Error404Collection-settings in appSettings.json will be ignored.