Kentico Xperience Design Patterns: Protecting Hard Coded Identifiers
Tags: xperience, kentico, csharp, dotnet
Published: 2022-06-13 15:32:23

---
title: Kentico Xperience Design Patterns: Protecting Hard Coded Identifiers
published: true
description: Learn why we hard code identifiers in our Kentico Xperience applications, and how to use and protect that data from deletion to guaranteed content and data quality.
tags: xperience, kentico, csharp, dotnet
series: Kentico Xperience Design Patterns
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/hcusgcjdsp4aozej50g9.jpg
---

When we need access to specific values or data in our applications, we might inject them through configuration (especially if they are different for each environment), or store them in a database with an identifier and access them by this identifier in our code.

The first approach is often thought of as being architecturally sound 😎 because the application doesn't need to re-built (or even re-deployed) if the data changes. We only need to update the configuration, and maybe restart the app.

The second is considered bad 😒, because an accidental or incorrect change to the database means an application needs rebuilt and redeployed.

But, what if hard coding an identifier wasn't so risky and the identifier wasn't environment specific 😮? That sounds great, but we'll need a way of guaranteeing these things!

## 📚 What Will We Learn?

- When would we hard code identifiers in Xperience?
- How do we ensure they are stable between environments?
- How do we protect them from problematic modifications?

## 💎 Hard Coding Identifiers

Typically, when using a [DXP](https://xperience.io/simplicity-through-a-unified-dxp), we will let content managers and marketers customize content for a site 😉, but sometimes it's more convenient to code content directly into an HTML template.

An example of this could be product pages on a site with thousands of products, each with a link to the Contact Us page (`/contact-us`) at the bottom of the layout. This scenario isn't appropriate for the [Page Builder](https://docs.xperience.io/developing-websites/page-builder-development) and there's not much benefit to adding a custom Page Type field to a page in the content tree for the "Contact Us" link.

What's the best way to add these links to our templates 🤷‍♀️?

### Generating Page Links

Hard-coding links in templates is a scenario where we want to reference an identifier in code, because we don't want to maintain a hard-coded URL over time.

Yes, Xperience's [former URLs feature](https://docs.xperience.io/managing-website-content/working-with-pages/managing-page-urls#ManagingpageURLs-FormerURLs) helps to ensure that moving pages around in the Content Tree or changing a URL slug will never break a URL 😅. However, there's several benefits to not hard-coding URLs:

- SEO: crawlers won't need to follow 301 redirects of moved pages
- UX: visitors won't need to follow 301 redirects and will have faster page loads
- Developers: it will be easier to maintain a site when links aren't hard-coded
  - An innocent looking `/special-product` link could redirect to something completely unexpected 😑!
- Content Management: Multi-lingual sites will have a different URL per culture, which means a hard-coded path won't work 😞.

Ok, we've decided we do want to sometimes hard-code links, but we don't want to hard-code URLs. So, instead, we'll use an identifier of the page we are linking to.

In Xperience we have a couple of options to pick from:
- `NodeID`
- `DocumentID`
- `DocumentGUID`
- `NodeGUID`

Which should we choose 🧐?

## 🏛 Keeping Identifiers Stable Between Environments

If we want our URLs to be correctly generated on a multi-lingual site, we'll want to use one of the Node values, since the Document ones reference a single specific culture for a page - it wouldn't be a very friendly site if we always generated URLs to the Spanish site for all the other cultures 😁.

Xperience's SQL Server database will auto-increment page integer IDs, which means creating a page in one environment could associate it with a `NodeID` that is being used by another page in a different environment.

If we have "Local", "Test", and "Production" environments, we don't want our code to fail to generate URLs 😬 (or generate incorrect URLs) because the identifier we reference isn't correct for that environment.

When using [content staging](https://docs.xperience.io/deploying-websites/content-staging) Xperience guarantees pages synced from one environment to another will keep their same `GUID` values (`NodeGUID` and `DocumentGUID`) while their `ID` values can change.

So, given the option of `NodeID` and `NodeGUID`, we are going to select `NodeGUID` because it's stable between environments 🙌🏾!

## 🏗 Generating URLs

Now that we've decided on our stable page identifier, how are we going to generate URLs for those pages?

We could use Page Type `Guid` fields combined with the Page Selector Form Control, and then retrieve the URL in code:

```csharp
Guid nodeGUID = productPage.Fields.CTALinkPageNodeGUID;

TreeNode? linkedPage = pageRetrieve
    .Retrieve<TreeNode>(
        query => query
            .WhereEquals(nameof(TreeNode.NodeGUID), nodeGUID),
        cache => cache.Key($"CTA|{nodeGUID}"))
    .FirstOrDefault();

if (linkedPage is null)
{
    return null;
}

PageUrl linkedPageUrl = pageUrlRetriever.Retrieve(linkedPage);

return ProductViewModel(productPage, linkedPageUrl);
```

This isn't too complicated, but the whole idea here was to generate the URLs without content management customization, so a Page Type field doesn't align with our goal 🙁.

### Page Link Tag Helper

Instead, I'm recommending we use an [ASP.NET Core Tag Helper](https://docs.microsoft.com/en-US/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-6.0) that will enhance `<a>` elements in our Views:

```html
<a href="" xp-page-link="..."></a>
```

This Tag Helper will use one of our identifiers to perform a very similar series of data retrieval steps to what we outlined above, and then populate the `href` and link text of our `<a>`.

But what do we pass to the `xp-page-link` attribute? A hardcoded `Guid`, while technically correct, is going to be very difficult to understand when reading the `.cshtml` file:

- Which page does it represent 😵?
- Did we accidentally typo it somewhere 😖? 
- What if we need to delete and then recreate the page we want to link to and have to replace the `Guid` everywhere ☠?

### LinkablePage

Instead, let's create a class with friendly names to encapsulate our identifiers:

```csharp
public class LinkablePage
{
    public static LinkablePage Home { get; } = 
        new LinkablePage(new Guid("..."));

    public static LinkablePage ContactUs { get; } = 
        new LinkablePage(new Guid("..."));

    public static LinkablePage PrivacyPolicy { get; } = 
        new LinkablePage(new Guid("..."));

    public Guid NodeGUID { get; }

    protected LinkablePage(Guid nodeGUID) => NodeGUID = nodeGUID;

    public static All LinkablePage[] { get; } = new[]
    {
        Home,
        ContactUs,
        PrivacyPolicy
    };
}
```

This `LinkablePage` class contains the full set of any pages we might want to generate URLs for, stores the `NodeGUID` values of each of those pages, and makes them immutable. The constructor is also `protected`, so no new objects can be created elsewhere in the application.

> This last point is important because these values need to be known at development time, not runtime.

Here's how we'd use one with our Tag Helper:

```html
<a href="" xp-page-link="LinkablePage.PrivacyPolicy"></a>
```

Because these identifiers are `static` properties, we could get fancy with our Razor and add a `@using static` at the top of the Razor file to access the `LinkablePage` instances directly:

```
@using static Sandbox.Data.LinkablePage

<a href="" xp-page-link="PrivacyPolicy"></a>
```

These are both readable, and easy to author 👏🏼.

Assuming the Tag Helper caches the URLs of links it generates for each of the different pages, it's performant as well 💪🏽!

> The implementation for this tag helper can be found in the NuGet package for [Xperience Page Link Tag Helpers](https://github.com/wiredviews/xperience-page-link-tag-helpers/). Try it out!

## 👮🏽‍♀️ Protecting Identifiers

Now that we've leveraged this convenient and maintainable approach for generating links to pages in the content tree, how do we guarantee that these don't suddenly stop working because pages get deleted?

Kentico Xperience has the perfect tool for us to leverage to guarantee the consistency of our data while the application is running - [Global Events](https://docs.xperience.io/custom-development/handling-global-events).

We can register an event handler for the `DocumentEvents.Delete.Before` event and then cancel the event if the Page being deleted matches a `LinkablePage`:

```csharp
public class LinkablePageProtectionModule : Module
{
    public LinkablePageProtectionModule 
        : base(nameof(LinkablePageProtectionModule));

    protected override void OnInit()
    {
        base.OnInit();

        DocumentEvents.Delete.Before += Delete_Before;
    }
 
    private void Delete_Before(object sender, DocumentEventArgs e)
    {
        if (LinkablePage.All.Any(
            p => p.NodeGUID == e.Node.NodeGUID))
        {
            e.Cancel();

            var log = Service.Resolve<IEventLogService>();

            string message = $"Cannot delete Linkable Page [{e.Node.NodeAliasPath}], as it might be in use. Please first remove the Linkable Page in the application code and re-deploy the application.";

            log.LogError(
                nameof(LinkablePageEventHandler),
                "DELETE_PAGE",
                message);

            return;
        }
    }
}
```

This class's `Delete_Before` method will be called each time a page is deleted and it will check to make sure that page doesn't have a `NodeGUID` that matches one of the values we've hard coded in the application. If it does match, the deletion is canceled and we log a helpful message explaining why 😉.

The final step is to register our custom module in our application (preferably somewhere in our `CMSApp` project, like a `DependencyRegistrations.cs` file):

```csharp
[assembly: RegisterModule(typeof(LinkablePageProtectionModule))]
```

This pattern for protecting hard coded identifiers can be used for _anything_ in Xperience that supports content staging:

- E-Commerce data (payment methods, shipping options)
- Pages
- Custom Module class records
- Users/roles
- Categories

All data our application code directly depends on through hard-coded identifiers is probably worth protecting with this approach 🤓.

## 🏁 Conclusion

Usually we want to leave content management up to marketers and content managers (that's the whole point of a DXP!) 👍🏿.

But sometimes it's far more convenient to manage some of the content ourselves - especially for links to pages that don't need to change frequently (or ever).

However, we still want to make sure that these links are correct and don't break between environments or while the site is running.

By storing a page's `NodeGUID` value in the application code, and using both content staging and a custom module to prevent accidental page deletions, we can achieve all of our goals 🤗.

We've created a readable, maintainable, scalable, performant, environment-consistent, and robust way of generating links to pages in our application 🥳.

As always, thanks for reading 🙏!

## References

- [Kentico Xperience - DXP](https://xperience.io/simplicity-through-a-unified-dxp)
- [Kentico Xperience Docs - Page Builder Development](https://docs.xperience.io/developing-websites/page-builder-development)
- [Kentico Xperience Docs - Former URLs](https://docs.xperience.io/managing-website-content/working-with-pages/managing-page-urls#ManagingpageURLs-FormerURLs)
- [Kentico Xperience Docs - Content Staging](https://docs.xperience.io/deploying-websites/content-staging)
- [ASP.NET Core - Tag Helpers](https://docs.microsoft.com/en-US/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-6.0)
- [Xperience Page Link Tag Helpers](https://github.com/wiredviews/xperience-page-link-tag-helpers/)
- [Kentico Xperience Docs - Global Events](https://docs.xperience.io/custom-development/handling-global-events)

---

We've put together a list over on [Kentico's GitHub account](https://github.com/Kentico/Home/blob/master/RESOURCES.md) of developer resources. Go check it out!

If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.

{% tag kentico %}

{% tag xperience %}

Or my [Kentico Xperience blog series](https://dev.to/seangwright/series), like:

- [Kentico Xperience Xplorations](https://dev.to/seangwright/series/8185)
- [Kentico Xperience MVC Widget Experiments](https://dev.to/seangwright/series/9483)
- [Kentico Xperience Design Patterns](https://dev.to/seangwright/series/10963)