Custom Controller to intercept routes
c
Hi all, I've got a page called
'Accounts'
in a multilingual size, and I've made a custom controller. Now, when you go to
/en-us/accounts/
it hits the below the custom controller. but I also want to hit the controller for
/en-us/accounts/{some name}
with it being multilingual the route isn't always account, as an example in a french translation it would be
/fr/comptes/
and
/fr/comptes/{some name}
How do I intercept the call to the child routes, and have I even done this correctly, as just because it works doesn't means it right? 😄 here is the custom controller:
Copy code
csharp
public class AccountsController(
    IUmbracoContextAccessor umbracoContextAccessor,
    ILogger<RenderController> logger,
    ICompositeViewEngine compositeViewEngine) : RenderController(logger, compositeViewEngine, umbracoContextAccessor)
{
    public override IActionResult Index()
    {
        var accountPage = CurrentPage as Accounts;
        if (accountPage == null)
        {
            return NotFound();
        }
        
        //some custom logic to do some stuff here
        
        
        return CurrentTemplate(new ContentModel(accountPage));
    }
}
m
Hi, As the url varies so much Id suggest using a ContentFinder - https://docs.umbraco.com/umbraco-cms/reference/routing/request-pipeline/icontentfinder This will let you check the parent uri see if its /account etc then set the appropriate IPublishedCotent and template
c
Ive read that page several times today and do not understand how to implement it or how it works 😦 I might just be being stupid 😦
m
Nah they are a pain but all good once u get ur head round em
I actually have the code you need but on my work laptop 😦
s
The {some name}, is this also a doctype or indeed just an url that needs to be redirected to the accounts page? If it is "just" a redirect, the content finder would be the solution. If the some name is a doctype itself that lives under the accounts node, you could create a render controller for that route (multi language is just the route name, so /accounts and comptes are the same doctype and should hit the same controller)
c
Some name will be account names, so it's like a dynamic location, I want {some value} to be passed into the controller as a variable l, if possible.
m
I these are virtual pages (not real umbraco nodes) you can also use the
IVirtualPageController
approach https://docs.umbraco.com/umbraco-cms/v/13.latest-lts/reference/routing/custom-routes#custom-route-with-ivirtualpagecontroller
c
I assume these can't work with mutlilingual setups as the account page isnt called account on every language, and every time we add a new language we'd need to add another mapping?
m
maybe something like... (completely untested 🙂 )
Copy code
csharp
Endpoints = app => app.UseEndpoints(endpoints =>
                {

                    IEnumerable<ILanguage> languages = _localizationService.GetAllLanguages();
                    var accountsNode = umbracoContext.Content.GetById({GUID});

                    foreach (ILanguage language in languages)
                    {
                        CultureInfo cultureInfo = language.CultureInfo;

                        var accountsNodeCulturalName = accountsNode.Name(cultureInfo.IsoCode);

                        endpoints.MapControllerRoute(
                        "Accounts Controller",
                        $"/{accountsNodeCulturalName}/{{action}}/{{id?}}",
                        new {Controller = "Accounts", Action = "Index"});
                    }
                    
                })
Actually you might want
accountsNode.Url(cultureInfo.IsoCode)
to construct the endpoints.. (and I'm assuming here that you multilanguage setup is using language variants, and not a duplicate node tree)
m
Caching ofc can be added to avoid this look up, but its all NuCache
c
Thanks all, i'll give these a go later today and let you know if they worked for me, thanks for you're help.
@Matt Wise do I need to make umbraco aware of the finder or will it be automatically picked up, I've put the code into the project, changed up some the names etc, but I'm not hitting any breakpoints, im still hitting the 404 page
m
Yes you do 🙂 let me find it 🙂
builder.ContentFinders().InsertBefore<ContentFinderByUrl, YourFinder>();
in a composer
you can do before or after
After probably makes most sense tbh
c
a few tweaks to the code, and it works 😄 thanks for the help
m
what needed changing out of interest?
(a fair bit was done in dotnet fiddle) 😄
c
it was around segements, to get the right segements, and then urldecode due to urlencoded characters in chinese routes (yay! multilingual)
Copy code
csharp
public class AccountFinder : IContentFinder
{
    private readonly IAppPolicyCache _runtimeCache;
    private readonly IUmbracoContextAccessor _umbracoContextAccessor;
    private readonly IShortStringHelper _shortStringHelper;

    public AccountFinder(AppCaches appCaches,
        IUmbracoContextAccessor umbracoContextAccessor,
        IShortStringHelper shortStringHelper)
    {
        _runtimeCache = appCaches.RuntimeCache;
        _umbracoContextAccessor = umbracoContextAccessor;
        _shortStringHelper = shortStringHelper;
    }

    public Task<bool> TryFindContent(IPublishedRequestBuilder request)
    {
        if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext))
        {
            return Task.FromResult(false);
        }

        if (request.Domain?.Name is not null)
        {
            var countryIndex = request.Uri.ToString().IndexOf(request.Domain?.Name!, StringComparison.OrdinalIgnoreCase);

            if (countryIndex < 0)
            {
                return Task.FromResult(false);
            }
        }

        var route = HttpUtility.UrlDecode(string.Concat(request.Uri.Segments.SkipLast(1).TakeLast(1)));
        route = request.Domain!.ContentId.ToString("0/") + route;

        var accountPage = umbracoContext.Content?.GetByRoute(route);
        if (accountPage is { ContentType.Alias: Accounts.ModelTypeAlias })
        {
            
            var displayName = string.Concat(request.Uri.Segments.TakeLast(1));
            
            request.SetPublishedContent(accountPage);
            request.SetTemplate(new Template(_shortStringHelper, "Account", "account"));
            return Task.FromResult(true);
        }
        else
        {
            return Task.FromResult(false);
        }
    }
}
m
Thankfully the multilang urls I have dont have to deal with Chinese just yet 😄
30 Views