This morning I answered a question on Our Umbraco regarding the best approach to provide a url allowing deep links for virtual nodes in Umbraco. In my answer I demonstrated how to use UmbracoVirtualNodeRouteHandler
as a means of telling Umbraco what node to pass as my RenderModel
.
Shannon talks about the UmbracoVirtualNodeRouteHandler
with custom routing on his blog
I thought I'd expand on some of the functionality available to the developer by documenting a trick I use with implementations of UmbracoVirtualNodeRouteHandler
to provide custom routing.
Use Case
Quite often I find myself reusing a single document type with multiple templates; I'm sure you have also. Most commonly a LoginRegisterPage or similar.
Now I don't know about you but one of the things I miss when using Umbraco is the ability to use Url.Action
to generate urls for my content. I like routing in MVC and I think it's important, especially with the type of page as above to be able to generate the correct urls from within your views or controllers.
To understand what I am going to do here you first need to understand how routing works in Umbraco.
If you have the DocumentType LoginRegisterPage
with the assigned template LoginPage
this will map to a RenderMvcController called LoginRegisterPageController
with an ActionResult LoginPage
. This behaviour is standardised and predictable so we can utilise it.
- DocumentType == Controller
- Template == ActionResult
Example
So let's start by creating some routes.
// Custom routing for the membership pages so we can use
// proper redirects.
RouteTable.Routes.MapUmbracoRoute(
"LoginPage",
"Login/",
new
{
controller = "LoginRegisterPage",
action = "LoginPage"
},
new LoginRegisterPageNodeRouteHandler());
RouteTable.Routes.MapUmbracoRoute(
"RegisterPage",
"Register/",
new
{
controller = "LoginRegisterPage",
action = "RegisterPage"
},
new LoginRegisterPageNodeRouteHandler());
Here I have created two routes that map to the relative urls /Login and /Register. They both map to the same controller but since two different templates are used it will map to two different ActionResult methods.
From here I will create a class that takes advantage of the predictability.
/// <summary>
/// An Umbraco route handler that allows the retrieval of the template alias name from the route action.
/// </summary>
public abstract class TemplatedUmbracoVirtualNodeRouteHandler : UmbracoVirtualNodeRouteHandler
{
/// <summary>
/// Get the template from the current route.
/// </summary>
/// <param name="requestContext">
/// The <see cref="RequestContext"/> containing information about the current HTTP request and route.
/// </param>
/// <returns>
/// The <see cref="string"/> representing the template alias.
/// </returns>
protected virtual string GetTemplateAlias(RequestContext requestContext)
{
return requestContext.RouteData.GetRequiredString("action");
}
}
This class adds an additional method GetTemplateAlias
to our handler pulling in the RouteData action
variable which we know matches the template name of the node we want in the content tree.
And from here we can implement this our handler.
/// <summary>
/// The generic login/register page node route handler.
/// </summary>
public class LoginRegisterPageNodeRouteHandler : TemplatedUmbracoVirtualNodeRouteHandler
{
/// <summary>
/// returns the <see cref="IPublishedContent"/> associated with the route.
/// </summary>
/// <param name="requestContext">
/// The request context.
/// </param>
/// <param name="umbracoContext">
/// The umbraco context.
/// </param>
/// <returns>
/// The <see cref="IPublishedContent"/>.
/// </returns>
protected override IPublishedContent FindContent(RequestContext requestContext, UmbracoContext umbracoContext)
{
UmbracoHelper helper = new UmbracoHelper(umbracoContext);
string alias = typeof(LoginRegisterPage).Name;
string templateAlias = this.GetTemplateAlias(requestContext);
return helper.TypedContentAtRoot()
.First()
.Descendants()
.First(d => d.DocumentTypeAlias.InvariantEquals(alias)
&& d.GetTemplateAlias().InvariantEquals(templateAlias));
}
}
If you look at the code in my handler you will see that I am searching the content for nodes that match both the name and template of the one I want.
It's as simple as that really... I can now use Url.Action("LoginPage", "LoginRegisterPage")
and Url.Action("RegisterPage", "LoginRegisterPage")
throughout my code to provide the correct urls to my respective pages priding me with functionality I sorely missed.