Kentico 12: Design Patterns Part 13 - Generating Page URLs
Tags: kentico, mvc, dotnet, csharp
Published: 2019-09-16 13:18:46

---
title: Kentico 12: Design Patterns Part 13 - Generating Page URLs
published: true
description: Learn how to use MVC's APIs to generate valid URLs and a custom approach for linking to content in the CMS tree.
tags: kentico, mvc, dotnet, csharp
series: Kentico 12 - Design Patterns
---

![Chain links](https://thepracticaldev.s3.amazonaws.com/i/4uvnhe00in49zknbhxu7.jpg)

<figcaption>Photo by <a href="https://unsplash.com/@markusspiske">Markus Spiske
</a> on <a href="https://unsplash.com">Unsplash</a></figcaption>

The web applications we build using Kentico CMS generate pages that are full of content (otherwise, why would we be using a CMS 🤷‍♀️?!).

That content can be text, markup, images, or links (to name a few possibilities).

Any links to our own site (internal links) need to be treated with care...

Due to the flexibility of the CMS platform, and the MVC framework, it's possible to create URLs that either don't navigate to any controllers or actions in your application, or don't represent any real content in the CMS 😮.

Let's look at the various ways MVC helps us generate URLs, and my current method for generating URLs to content in the CMS tree.

Hopefully our URLs can be as flexible as our code and content management 👍!

---

## Generating URLs with MVC APIs

So much of the MVC framework is based around routes, controllers, and actions - they are the foundation of how we identify and separate one request for content from another.

Fortunately, MVC exposes various methods of using these parts of the framework to consistently, and (in some cases) with strong typing support, generate URLs.

The simplest way to generate URLs in MVC is to use any of the helper methods in the `System.Web.Mvc.UrlHelper` class.

Imagine we have a controller as follows:

```csharp
public class HomeController : Controller
{
    public ActionResult Home(int id)
    {
        // ...

        return View();
    }
}
```

To generate a url for the above action and controller we can call `UrlHelper.Action()` as follows:

```csharp
string url = urlHelper.Action("Index", "Home", new { id = 5 });
```

We can also do this in our Razor files using the following call:

```csharp
<a href="@(Url.Action("Index", "Home", new { id = 5 }))"></a>
```

If we know the name of our route (which would have been specified when calling `System.Web.Mvc.RouteCollection.MapRoute()`) and the parameters that the route requires we can use `UrlHelper.RouteUrl()` as follows:

```csharp
// Assume "Index" and "Home" are the default
//   action and controller values for this route

string url = urlHelper.RouteUrl(
    routeName: "Default Route", 
    routeValues: new { id = 5 });
```

While these are the official APIs for generating URLs in MVC, they are lacking in my opinion 😕.

They take strings and anonymous objects as parameters, which are very easy to typo 😒!

Not only that, but these parameters are values that are supposed to exactly match the names of C# classes, methods, and parameters.

What happens when rename something 🤔?

Our URL generation breaks - but only at runtime! Time to go check all those links in the application 😞!

---

## T4MVC

There are alternatives to this standard approach of URL generation, like [T4MVC](https://github.com/T4MVC/T4MVC).

T4MVC generates static classes with names of all the controllers and actions we might want to route to.

We would use T4MVC as follows (taken from their docs):

```csharp
@Html.ActionLink("Dinner Details", MVC.Dinners.Details(Model.DinnerID))
```

My problem with this approach is it requires a re-build of the project to generate the templated classes, which means our code could be type-safe one minute, and then only after a compile show a bunch of errors.

This isn't what most developers expect from working with C#/.NET in Visual Studio.

That said, I think for extremely large MVC driven applications, it can help manage the increased complexity of URL generation.

---

## Using MVC Futures

If we install the [Microsoft.AspNet.Mvc.Futures](https://www.nuget.org/packages/Microsoft.AspNet.Mvc.Futures) NuGet package, we get access to some type safe URL generation from the `Microsoft.Web.Mvc.LinkExtensions` class.

> `Microsoft.AspNet.Mvc.Futures` is a library of ['prototypes of features' being considered for 'future versions of MVC'](https://weblogs.asp.net/leftslipper/asp-net-mvc-release-candidate-2-i-declare-myself-to-be-declarative).

> Obviously, since ASP.NET Core has usurped classic MVC development, the Futures code will never make it into MVC 5, but you can still use it in your projects by installing the package 👏.

Below we can see how these extensions use Expressions to generate an HTML anchor using the Controller classes in our application:

```csharp
@Html.ActionLink<HomeController>(c => c.Index(5), "Home")
```

Unfortunately, this API doesn't give us a raw URL. If we wanted something like this on the `UrlHelper` class we'd need use the method `Microsoft.Web.Mvc.Internal.ExpressionHelper.GetRouteValuesFromExpression<T>()` in our own code.

What's doubly unfortunate is that these APIs were not built for action methods that return `Task<T>` 😑.

If you use it with `Task` returning actions, you'll get the following warning:

```
The current method calls an async method that returns a Task
or a Task<TResult> and doesn't apply the await operator to the result.
The call to the async method starts an asynchronous task.
However, because no await operator is applied, the program continues
without waiting for the task to complete...
```

This doesn't prevent the code from working, because we aren't actually executing the action.

But if you do choose to use this method, I hope you aren't writing a bunch of `async` controller actions 😢, because those warnings are gonna get annoying.

---

I have seen older comments from people concerned about the performance impact of generating URLs with Expressions instead of the built-in methods I detailed at the beginning.

There is definitely going to be an impact on performance from using the Expression approach - and it's also definitely not going to matter when comparing that to things like database access time 😄.

With any of these things, we should:

- Choose an approach that helps us manage the complexity of our apps
- Measure our performance
- Set goals of what level of performance is acceptable
- Make small changes
- Measure again to see if our goals were met, and repeat

... We should be using caching anyway, right 🤓?

---

## Simpler Type-Safe URLs with `Func<>` and `nameof()`

I created my own extension on `UrlHelper` that gives some type safety without going down the rabbit 🐇 hole of tearing apart Expressions:

```csharp
public static string Action<T>(
    this UrlHelper helper, 
    Func<T, string> nameOfAction, 
    object routeValues = default) where T : Controller
{
    string controllerTypeName = typeof(T).Name;

    string controllerName = controllerTypeName
        .Substring(0, controllerTypeName.Length - 10);

    return helper.Action(nameOfAction(null), controllerName, routeValues);
}
```

This can be called like so (either in Razor or C#):

```csharp
string url = urlHelper.Action<HomeController>(
    nameOfAction: c => nameof(c.Index), 
    routeValues: new { id = 5 });
```

The benefit here is that if we rename `HomeController.Index` to `OtherController.Blah`, we will get a compile error if our rename didn't refactor the above code automatically ⚡.

At the same time, it doesn't matter if `HomeController.Index()` returns an `ActionResult` or `Task<ActionResult>` 🙂.

This method doesn't handle renaming parameters or ensuring their type-safety - Ya can't win 'em all!

I use it mostly for parameterless `Index()` action methods, so the lack of parameter type-safety isn't as much of an issue.

---

## NodeAliasPath Link

I previously wrote about using `NodeAliasPath` as a routing mechanism:

{% link seangwright/kentico-12-design-patterns-part-10-mvc-routing-with-nodealiaspath-22l3 %}

For any pages that are directly represented by specific nodes in the CMS content tree, this routing approach is my preferred one.

You might be wondering how we can use the above MVC utility methods to generate URLs for this kinds of routes...

Well, we can't 😣.

We instead need to reverse the process used for routing in my previous post.

> To quickly summarize, the Kentico MVC application receives a request and checks to see if the path of the request matches a `NodeAliasPath` of a given `TreeNode`.

> If there is a match, it sends the request to the controller action that handles requests for the Page Type associated with the `ClassName` of that `TreeNode`.

> Otherwise, the request is handled by MVC's standard routing approaches (convention based or attribute routing).

---

### LinkablePage

I've come up with a pattern I'm calling 'Linkable Pages' wherein we make links in our application code to specific nodes in the CMS content tree that can be routed to.

I'm calling these routable nodes 'Pages', but there's only a subset of nodes I actually want to explicitly create links to (ex: Home, Contact Us, Products Landing Page)

We then rely on content synchronization to ensure the connection between the CMS content nodes and the 'Linkable Pages', in application code, remain consistent no matter what environment our application runs in 🤔.

### Value Object

I use a Value Object pattern to ensure only an immutable and discrete set of pages can be linked to 🤓.

Let's look at example code for the `LinkablePage` class:

```csharp
public class LinkablePage
{
    static LinkablePage()
    {
        Home = new LinkablePage(
            new Guid("65bc6106-74c5-456d-bc5b-71ae0d89c92a"));

        ContactUs = new LinkablePage(
            new Guid("2a955262-1fd2-44d6-b0f2-9329b42eefa3"));

        Products = new LinkablePage(
            new Guid("61a5f388-1c19-4645-a628-4a0e4b70fbac"));
    }

    public static LinkablePage Home { get; protected set; }
    public static LinkablePage ContactUs { get; protected set; }
    public static LinkablePage Products { get; protected set; }

    public Guid NodeGuid { get; }

    protected LinkablePage(Guid nodeGuid) => NodeGuid = nodeGuid;
}
```

The class only has 1 constructor, which is `protected`, meaning instances can only be created within the class itself (or a child class).

The instances are static properties on the class, which means they are well defined and easy to access 👍.

The class is also a non-primitive type (ex: `int`, `string`, `Guid`) so it can be the parameter of methods, which gives more domain meaning to those methods 🙂.

The class instances and the entire set of possible instance values is immutable, which makes validating the correct set of possible values easier 😎.

> I'm leaving out some of the equality comparison functionality of Value Objects here since I'm not comparing `LinkablePage` instances, but if you want to read more on this topic check out [Value Objects explained](https://enterprisecraftsmanship.com/posts/value-objects-explained/).

### Html Extension

To render out links in Razor views I use an `HtmlHelper` extension method:

```csharp
public static void RenderPageLink(
    this HtmlHelper htmlHelper, 
    LinkablePage page, 
    string linkText, 
    string elementClasses = "")
{
    var request = new PageLinkRequest(page, linkText, elementClasses);

    htmlHelper.RenderAction(
        nameof(NavigationController.PageLink),
        "Navigation", 
        new { request });
}
```

### Child Action

The Razor child action rendered by this call is as follows:

```csharp
[ChildActionOnly]
public ActionResult PageLink(PageLinkRequest request)
{
    // The following call is wrapped in a caching layer to prevent
    //   repeat trips to the database to generate the same links

    string nodeAliasPath = queryDispatcher
        .Dispatch(new NodeAliasPathByNodeGuidQuery(request.Page));

    string linkUrl = string.IsNullOrWhiteSpace(nodeAliasPath)
        ? ""
        : nodeAliasPath.ToLower();

    var viewModel = new PageLinkViewModel(
        label: request.Text, 
        linkUrl: linkUrl, 
        classes: request.Classes);

    return PartialView(viewModel);
}
```

### Razor View

And the Razor view is as simple as the few lines below:

```html
@{ 
    if (string.IsNullOrWhiteSpace(Model.LinkUrl)
      || string.IsNullOrWhiteSpace(Model.Label))
    {
        return;
    }
}

<a href="@Model.LinkUrl" class="@Model.Classes">@Model.Label</a>
```

### Generating the Link

So, to pull all of this together we use the `HtmlHelper` extension in another Razor view as follows:

```html
<li>
  @{ Html.RenderPageLink(
         LinkablePage.Home, 
         "Home", 
         "btn btn-primary"); 
  }
</li>
<li>
  @{ Html.RenderPageLink(
         LinkablePage.ContactUs, 
         "Contact Us", 
         "btn btn-primary"); 
  }
</li>
```

The call to `queryDispatcher.Dispatch(new NodeAliasPathByNodeGuidQuery(nodeGuid));` is wrapped in caching, with all the correct dependency keys and the SQL query itself is minimal and selects only the `NodeAliasPath` column.

> If you are looking for ideas on how to apply consistent caching across all your database queries, checkout the following post 👍:

> {% link seangwright/kentico-12-design-patterns-part-12-database-query-caching-patterns-43hc %}

> If you are interested in a simple, readable way to create Kentico cache dependency keys, try this post 👍:

> {% link wiredviews/kentico-cms-quick-tip-fluentcachekeys-consistent-cache-dependency-key-generation-9ek %}

### Generating Page URLs in Code

We can also use the above approach to generate URLs in our C# code. These can be used in redirect responses from controllers or in emails sent from the MVC application.

Below is an example of generating a redirect url in a `LogoutController`:

```csharp
public ActionResult Index()
{
    authenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);

    string nodeAliasPath = queryDispatcher
        .Dispatch(new NodeAliasPathByNodeGuidQuery(LinkablePage.Home));

    string homePageUrl = string.IsNullOrWhiteSpace(nodeAliasPath)
        ? "/"
        : nodeAliasPath.ToLower();

    return Redirect(homePageUrl);
}
```

Now, I can see how you might think this scenario is a little contrived 😋, but I believe the readability of `LinkablePage.Home` is better and provides more business meaning than `"/"` by itself.

In my code I've hidden this querying logic behind an `IUrlBuilder.BuildNodeAliasPath(LinkablePage page);` method which keeps the above methods even leaner and easier to test 😎.

## Summary

We took a look at all of the out-of-the-box URL generation options that MVC provides.

Unfortunately, they lack a major feature most C# developers rely on - type safety 👎. These work, but don't scale with larger applications.

Projects like T4MVC and Mvc.Futures help fill the gaps somewhat, each taking different approachs (templating and Expressions) to create type-safe URLs.

They also come with their own caveats - but for your team and project the trade-offs might be worth it.

I showed how we can get part of the way to type-safe URL generation using the `nameof()` operator and `Func<T, string>`.

It's simple, which, in my opinion, is the best part 😉!

Finally, we took a look at how we can generate URLs for pages based on `NodeAliasPath` routes using the `LinkablePage` value object class.

This approach requires us to rely on content synchronization between environments, and also some caching infrastructure to ensure we don't overload our database with queries.

However, it let's us treat content items in the CMS tree as specific domain objects in our code and use the path to those items in the tree as the generated URLs.

If those `NodeAliasPath` values change, all of our links will update 🤗.

If those nodes are removed from the CMS, and we remove the specific `LinkablePage` instance (ex: `LinkablePage.ContactUs`), our code won't compile until we change all references to it 😅.

I'm interested to know what techniques you and your team have used to create URLs consistently and reliably in your Kentico 12 MVC code bases.

Let me know in the comments below!

Thanks for reading 🙏.

---

If you are looking for additional Kentico content, checkout the Kentico tag here on DEV:

{% tag kentico %}


Or my Kentico blog series:

- [Kentico 12: Design Patterns](https://dev.to/search?q=Kentico%2012%3A%20Design%20Patterns)
- [Kentico CMS Quick Tips](https://dev.to/search?q=Kentico%20CMS%20Quick%20Tip)