JustNik

Public profile pages for your Umbraco members [Part 3]

Umbracologoblue05

While writing Part One, I realised that this might be better as a mini-series. We've had Part Two, so here is Part Three.

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. [Part 3] Creating a RenderMVC Controller and handling the inbound requests
  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.

Rendering out the results

Okay, so in Part 1 of this series we created a URL slug to represent a members profile; in Part 2 we talked about how to handle the inboud request from a route perspective. Now we need to look into rendering out the result, or more importantly how we fully hydrate our view model in order to do so.

Incase you missed it, in Part 2, I mentioned that our profiles will be accessed via a fixed format URL, in this case the base part will always be /members. In order to achieve this, I made the decision that this point in the URL would represent a content node in the Umbraco content tree. By doing this, it means there will be a doc type that represents this part of the URL and that it must exist with a fixed name in the content tree. In the final part of this series I'll cover how I automatically create this node and fix it's name to prevent content editors from changing it. I'll also highlight limitations to this as well.

What is a RenderMvcController?

A RenderMvcController is an Umbraco construct based on the traditional concept of an MVC Controller. Umbraco's routing works by having a default controller that gets hit upon every page request. However, Umbraco allows you to intercept this (they call it route hijacking) and use your own instance of a RenderMvcController instead of their own. The documentation on Our is pretty good for explaing why you might go about it, but it's imporant to note there are rules around how you go about achieve this.

The important things to note are as follows:

  1. The controllers name must match the alias of the document type you want to hijack. e.g. Document type alias of home would be hijacked by a RenderMvcController with the name HomeController.
  2. The Action "normally" represents the template that is being rendered, appart from "index" which is the default template. - Something to note with this, it is possible to use tricks from custom routes to hit specific actions and make your own decisions on which view file / template is used so this isn't a hard and fast rule.

In summary, a RenderMvcController is an MVC Controller that hijacks part of the Umbraco Routing process for all instances of an associated document type.

Now let's get onto the nitty gritty!

Creating our RenderMvcController

As mentioned in previous parts of this series, we have a fixed part of our path member profile, /members. In my example, this node exists in Umbraco and its document type is called Members (alias: members). So let's create a RenderMvcController to hijack our route for this document type:


public class MembersController : RenderMvcController
{

    public override ActionResult Index(ContentModel model)
    {
        return base.Index(model);
    }
}

Although it's not needed, for clarity the example above overides the default Index method. This is useful if you want to make modifications to the content model before passing it through to the template. However in our scenario we don't need to, instead the next bit we are going to look at is linking our custom route from Part 2. In part two we've already started directing traffic to this controller, but currently it isn't going to recieve it. Instead it will most likely blow up saying there is no valid route found.

Why is this?

Well, in Part 2 we had this section of code:


RouteTable.Routes.MapUmbracoRoute("ProfileCustomRoute", "members/{memberAlias}", new
    {
        controller = "Members",
        action = "Profile",
        memberAlias = UrlParameter.Optional
    }, new UmbracoVirtualNodeByUrlRouteHandler("/members"));

What this bit of code is doing is pushing the traffic to the controller above, but it is looking for an action called Profile, which we need to create.

Before we look at this action, it's important to note that our custom Route Handler plays an important part here as it should have set the current page to be our Members page (although this isn't what we will be displaying). This helps with our wiring up of the controller.


public ActionResult Profile(ContentModel model, string memberAlias)
{

    var members = memberService.GetMembersByPropertyValue(ContentModels.Member.GetModelPropertyType(p => p.ProfileSlug).Alias, memberAlias, StringPropertyMatchType.Exact);
    if (model.Content is ContentModels.Members membersNode && members.Count() == 1)
    {
        membersNode.MemberProfile = Members.GetById(members.FirstOrDefault().Id) as ContentModels.Member);
        return View("Profile", membersNode);
    }
    else members.Count() != 1
        return View("InvalidProfile", model.Content);
}

In our Profile action, we attempt to retrieve our member's profile based on the slug that has been passed in via our custom route. If it finds an exact match then it returns an error view saying that the member does not exist. In this example the actual message is controlled by a property on the Members page, which is why the custom controller and having reference to the Members node is important. It also allows other Umbraco features to work in the views such as accessing root nodes for menus and settings.

To achieve this the Members node's model has been extended with a custom property to store the identified Member profile and pass both to the Profile view. Why do this you may ask? Well the Members profile is not part of the Umbraco content tree. On the view, in most cases, you are going to want to render navigations, breadcrumbs, or other data reliant on the Umbraco content tree and the "percieved" location on the site. As such, having a real node associated with the request makes this much easier.

Once you have all of this set up, you can go about creating your Profile view as required by your project. This series won't cover rendering out the view as this is standard Umbraco Razor and View generation at this point, the only difference being that the actual content you will be wanting to render exists in a property of the view's Model, instead of the Model itself being what you need. You can easily modify this if you don't want that to be the case though.

Summary

So there we have it, part three of the series is complete, my appolgies for the delay in getting it out for you eager readers. The controller we've discussed receives the inbound traffic from the route handler in Part 2. This approach is essentially taking advantage of features built into Umbraco with the bare minimal amount of coding being required to get out the member profile so we can render it.

Although I didn't highlight this, a critical function that allows us to get the member profile is this one GetMembersByPropertyValue. This allows us to get all Members where a specific property has a specific value, in the case of this series, we are checking the Member profile's Profile Slug property for the url segment that was passed into our controller action on the memberAlias parameter.

As a reminder from Part 2, 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, and it must be a document type of Members (see how I like to keep things named the same for consistency).


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

JustNik
Connect with me
Twitter GitHub LinkedIn
© JustNik 2024