Public profile pages for your Umbraco members [Part 2]

Handling the Custom Routes for your public profile pages.

While writing Part One, I realised that this might be better as a mini-series so here we are with Part Two.

Introduction to the series

If you have a social orientated website which allows members to register an account, it's quite likely you will want to have a public profile page for your members. However, out of the box Umbraco doesn't make this "easy". Member's don't have Url's (as far as I know) which means there is no way to route traffic.

The quickest way to over come this is to create a profile node for each member by hooking into member saved event and wire one up, but this isn't particularly good for long term maintenance. If your site ends up with lots of members your content tree will grow uncontrollably. Please don't do this!

There is, however, alternative ways that can make things much more sustainable. The approach this article series will cover is by using a Custom Route and a Render Mvc Controller, and results in profiles having their own Unique URL.

Setup

The code shared in this article is the basis for a real world solution and has been tested on Umbraco v8.9, using Models Builder in AppData mode. The reason I'm specifically sharing this information is that I've chosen to extend one of the generated models.

What will be covered?

  1. [Part 1] Generating a unique slug based on a members name
  2. [Part 2]Creating a Custom route and a custom UmbracoVirtualNodeRouteHandler
  3. Creating a RenderMVC Controller and handling the inbound requests - coming soon
  4. Automatic creation of a "base node" with a fixed name. - coming soon

There might well be more things covered but the above are the 4 main areas that come together to allow this to work.

Creating a Custom route

Okay, so in Part 1 of this series we created a URL slug to represent a members profile, but the slug is only part of our request URLs. In this example we want the public profile to be accessible via a path with the following format "/members/". The thing with this is that "/members" will be a node in the content tree. I'll cover more about that in part 3, but for now we need to create a custom route handler.

Our is a wonderful resource for finding out about things you can do with Umbraco. There is an entire section on Routing and in there you can find a page talking about custom MVC Routes

Why do we need a custom route handler? Well, by default all the route handling in Umbraco is looking for Content nodes, media items, static files, or Umbraco Surface/Api controllers. In this instance though we are trying to get to something that isn't currently available.

In order to create this custom route, we need three components.

  1. A component class that implements IComponent
  2. A composer for registering the component
  3. A custom implementation of UmbracoVirtualNodeRouteHandler

Why do we need a custom implementation of UmbracoVirtualNodeRouteHandler? This is because we will be looking up nodes based on a URL and all of the existing core implementations don't do this.

The Component and Composer

These two items go hand in hand, you need the composer to register the component, but it is the component that does the heavy lifting and actually register our custom route.

The component will register a route that follows our planned pattern, "/members/" and it will direct requests to a controller called members, and an action called Profile.

First up the code for the Component:


public class RegisterMemberProfileRouteComponent : IComponent
{
    public void Initialize()
    {
        RouteTable.Routes.MapUmbracoRoute("ProfileCustomRoute", "members/{memberAlias}", new
        {
            controller = "Members",
            action = "Profile",
            memberAlias = UrlParameter.Optional
        }, new UmbracoVirtualNodeByUrlRouteHandler("/members"));
    }

    public void Terminate()
    {
        // Nothing to terminate
    }
}

Next we have the Composer; unlike others this time I've opted to inherit from the ComponentComposer. There is no real reason for this other than this is what the offial documentation does, normally I'd inherit from IUserComposer and register the component myself.

[RuntimeLevel(MinLevel = Umbraco.Core.RuntimeLevel.Run)]
public class RegisterMemberProfileRouteComposer : ComponentComposer<RegisterMemberProfileRouteComponent>
{ }

The custom route handler

Now, if you were to use the code from above you'd get an error. UmbracoVirtualNodeByUrlRouteHandler doesn't exist, it isn't a part of Umbraco. The two Virtual Node Route Handlers that ship with Umbraco are capable of finding content based on their Id or their UDI, which personally I feel isn't appropriate for most scenarios as ID's vary across environments, as might UDI's).

So I have created my own to take a different approach. So, what is it doing? Well it is searching for content based on it's URL, the limitation this does have is that it makes the URL fixed.

public class UmbracoVirtualNodeByUrlRoutHandler :
                                 UmbracoVirtualNodeRouteHandler
{
    private readonly string url;

    public UmbracoVirtualNodeByUrlRoutHandler(string url)
    {
        if (string.IsNullOrWhiteSpace(url))
        {
            throw new ArgumentException($"'{nameof(url)}' cannot be null or whitespace", nameof(url));
        }

        this.url = url;
    }
    protected override IPublishedContent FindContent(RequestContext requestContext, UmbracoContext umbracoContext)
    {
        return umbracoContext.Content.GetByRoute(url);
    }
}

As you can see from the code, it's fairly "straight forward" but it's using the content cache GetByRoute method to find the content node we want to associate with the request.

Summary

So there we have it, part two of the series is complete. The components discussed above work together to route requests for a members profile through to a controller. What does this controller look like? That will be the focus of part 3.

An important thing to note is that in order for this to work you must have content node in your content tree that has the name Members and the url or /members. If you want to use a different name for your content node remember to update the route details in the Component.


So, this is where we will end this second post of the series. You can now get your Umbraco Member set up nicely with a Url Slug [Part 1], and set up a route on which they can be accessed [Part 2]. I hope you find this post useful, if you do or have any questions, please reach out to me on Twitter