Kentico Xperience 13: How to programmatically update the URL slug of a page
Tags: kentico, kentico13, xperience
Published: 2021-07-04 15:42:43

I recently wrote a console app to migrate data from Drupal to Kentico Xperience 13 using the Xperience APIs.

As part of this, I was required to recreate pages in Xperience so that they had the same URL as their counterpart in Drupal.

My approach was to, for each page in my Drupal data source, create a new instance of the `TreeNode` object and set its values based on the values stored for that page in Drupal.

I also set the `NodeAlias` value of the `TreeNode` object at this point so that it matched the page's "page alias" (aka slug) in Drupal.

I then called Insert() on my object instance to create the page in Xperience.


## Page URLs work differently in Xperience 13

Now in the old days of Kentico CMS, this would have been good enough as each page's URL was based off of its `NodeAliasPath` but in Xperience 13 the URL of a page is controlled separately and is managed by an editor via the URLs tab in Kentico.

![Updating URL slug in Kentico Xperience 13](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z2qgb0hig0mh0rlyqic6.JPG)

I found that when I called `Insert()`, the slug was being auto generated based on the `DocumentName` of the page, rather than just copying the `NodeAlias`.

I now needed a way to update the slug.


## In search of a solution for updating the slug

Off I went in search of slugs (that's the first time I've ever said that...I promise) and I found they were stored in the `PageUrlPathUrlPath` column (try and say that 10 times in quick succession) within the `CMS_PageUrlPath` database table with a foreign key to the `NodeID` of the page in question.

My immediate thought was that I could retrieve the relevant `PageUrlPathInfo` object, update the slug et voilà! But no luck.

It's then that I noticed there is a hash stored against each `PageUrlPath` record and that this must be updated in conjunction with updating the slug.

Usually when you see a hash against a record in Kentico, it means there's some helper method somewhere which you can use to update the record and manage the hash at the same time. It's just a case of finding this helper method.

Having exhausted my friends Google and Kentico devnet, I guessed that the underlying aspx page which is providing the functionality for editors to update the slug in the URLs tab must be using such a method.


## How to update the URL slug

Having found said aspx page in the CMS Visual Studio project `CMSModules/Content/CMSDesk/Properties/Urls.aspx` I followed the code through and found the `PageUrlPathSlugUpdater` which does exactly the job I needed.

Here's the code to update the slug:

```C#
PageUrlPathSlugUpdater pageUrlPathSlugUpdater = new PageUrlPathSlugUpdater(page, Culture);

IEnumerable<CollisionData> collidingPaths;

pageUrlPathSlugUpdater.TryUpdate(newSlug, out collidingPaths);
                    
if (collidingPaths.Count() == 0)
{
     // Slug was updated
}
else
{
     // Slug wasn't updated
}
```


## Pros and Cons of separating page URL from NodeAliasPath

One of the limitations of the `NodeAlias` property is that there is a 70 character max limit. This was a problem as some of the page aliases for pages in Drupal were longer than this so they were being truncated. 

Luckily, there is no such limit on URL slugs, so I was able to replicate the URLs from Drupal into Kentico without issue.

One disadvantage now is that when you retrieve the data for a page in code, you now need to, in addition, use `PageUrlRetriever.Retrieve(nodeAliasPath)` to get the URL of the page, which generates another (if not more) calls to the database. Previously, you just needed to ensure you were retrieving the NodeAliasPath when getting the page data. 

There's no way to retrieve URLs for pages in bulk either, so you can imagine in code that creates, say, website navigation where you are in a foreach loop scenario, some developers may end up calling this method for each iteration and, subsequently, generating a tonne of SQL queries.

Luckily there is a way to get around this. More on that in my next post.