As far as I know it is not possible to perform route hijacking using a custom RenderController with an async Index method. I think it is very important to be able to do so because you usually need to load the view model with data from a DB source which in most frameworks uses async operations. You can make the async functions run sync but that is not a good practice. Is there something I'm missing? Will this be looked into in a further release?
Meanwhile, while overriding the async Index is not possible, I'm thinking of putting my async DB functions on OnActionExecutionAsync, loading the models I need into a field on the controller and then just grabbing them in the Index method. But it would be much cleaner to do it all on Index().
I will give this some more testing. Failing that this either needs logging as a bug or feature request. I would see more of a feature request than a bug.
As you show in your blog post Route Hijacking is still possible and easy to do in Umbraco 9, pretty much similar to Umbraco 8. The problem is if you want an async Index() method. The solution I mentioned in the orignal post works great and is not much of an hassle after all. But of course, it would be much better if we could just have an async Index() method.
If you can put the feature request in it would be great as I have no experience doing so and I'm afraid I would mess up.
Ran into the same issue recently and created the following abstract class to work around the issue:
public abstract class AsyncRenderController : RenderController
{
protected AsyncRenderController(ILogger<RenderController> logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, compositeViewEngine, umbracoContextAccessor)
{
}
[NonAction]
public sealed override IActionResult Index() => throw new NotImplementedException();
public abstract Task<IActionResult> Index(CancellationToken cancellationToken);
}
The abstract class is of course not mandatory, but helps to keep the "hacky" construct hidden away. While not perfect it seems better than overriding OnActionExecutionAsync.
Hi Victor, are you able to explain exactly what this does / how it works. By marking the default, sync Index() as a [NonAction] does that route Index calls to the async Index() version?
We mark the existing sync Index() as a [NonAction] so it isn't routable, and create a new async Index overload that will be used instead.
The new overload needs to have a different method signature, so that's why we take a CancellationToken as a parameter, but it's fine to just not use it if not needed.
You can also implement an asynchronous controller method with the same name as the template alias. In that case the synchronous Index() method will not be used. For instance when the template alias is 'HomePage' the controller would look like this:
public class HomePageController : RenderController
{
public HomePageController(
ILogger<HomePageController> logger,
ICompositeViewEngine compositeViewEngine,
IUmbracoContextAccessor umbracoContextAccessor) : base(logger, compositeViewEngine, umbracoContextAccessor)
{
}
public override IActionResult Index()
{
return base.Index();
}
public async Task<IActionResult> HomePage()
{
await SomeAsyncMethod();
return CurrentTemplate(new ContentModel(CurrentPage));
}
}
I've included the override for the Index() method for clarity, if you debug this controller you'll notice that the async HomePage() method is being used for the HomePage template instead of the Index() method.
Edit: The name of the controller method should match the name of template alias (not the document type alias). I'm not sure, but this may not work when DisableAlternativeTemplates is set to true (see Web routing config).
I love this answer. For the past couple years I've been doing the abstract class and inheriting from this in my controllers, then overriding the async Index ActionResult, as indicated earlier on this topic. It works, but I hate boilerplate and prefer to avoid it when possible.
This idea of making an async ActionResult with a method name that matches the template alias works like a charm for me.
Async Index method in Render Controller
Hello,
As far as I know it is not possible to perform route hijacking using a custom RenderController with an async Index method. I think it is very important to be able to do so because you usually need to load the view model with data from a DB source which in most frameworks uses async operations. You can make the async functions run sync but that is not a good practice. Is there something I'm missing? Will this be looked into in a further release?
Meanwhile, while overriding the async Index is not possible, I'm thinking of putting my async DB functions on OnActionExecutionAsync, loading the models I need into a field on the controller and then just grabbing them in the Index method. But it would be much cleaner to do it all on Index().
Thanks,
João
Hi Joao,
Sorry I just saw this post. I wrote a blog about this a while back.
This should be exactly what you are looking for. This is a guide how to do Route hijacking in Umbraco 9. https://www.umbrajobs.com/blog/posts/2021/june/umbraco-9-route-hijacking-custom-rendercontrollers-how-and-why/
Let me know if you have any issues.
Regards
David
Actually sorry. I didn't read correctly.
This is more related to Async Root Hijacking.
I will give this some more testing. Failing that this either needs logging as a bug or feature request. I would see more of a feature request than a bug.
https://github.com/umbraco/Umbraco-CMS/issues?fbclid=IwAR2L5Dd-fjNtW251yi5Fzd-F1O6s2zhBizuhsqyptiAg4nIPDRvwVHfO6n8
I will let you know if I manage to get this up and running.
Regards
David
Thanks for the answer David.
As you show in your blog post Route Hijacking is still possible and easy to do in Umbraco 9, pretty much similar to Umbraco 8. The problem is if you want an async Index() method. The solution I mentioned in the orignal post works great and is not much of an hassle after all. But of course, it would be much better if we could just have an async Index() method.
If you can put the feature request in it would be great as I have no experience doing so and I'm afraid I would mess up.
Regards
Hi João,
Ran into the same issue recently and created the following abstract class to work around the issue:
The abstract class is of course not mandatory, but helps to keep the "hacky" construct hidden away. While not perfect it seems better than overriding OnActionExecutionAsync.
Hi Victor, are you able to explain exactly what this does / how it works. By marking the default, sync Index() as a [NonAction] does that route Index calls to the async Index() version?
That is exactly what happens.
We mark the existing sync Index() as a [NonAction] so it isn't routable, and create a new async Index overload that will be used instead.
The new overload needs to have a different method signature, so that's why we take a CancellationToken as a parameter, but it's fine to just not use it if not needed.
You can also implement an asynchronous controller method with the same name as the template alias. In that case the synchronous Index() method will not be used. For instance when the template alias is 'HomePage' the controller would look like this:
I've included the override for the Index() method for clarity, if you debug this controller you'll notice that the async HomePage() method is being used for the HomePage template instead of the Index() method.
Edit: The name of the controller method should match the name of template alias (not the document type alias). I'm not sure, but this may not work when
DisableAlternativeTemplates
is set to true (see Web routing config).I love this answer. For the past couple years I've been doing the abstract class and inheriting from this in my controllers, then overriding the async Index ActionResult, as indicated earlier on this topic. It works, but I hate boilerplate and prefer to avoid it when possible.
This idea of making an async ActionResult with a method name that matches the template alias works like a charm for me.
is working on a reply...