Enabling non-unique node names in Umbraco 9
Tags: umbraco
Published: 2022-03-16 09:41:45

By default Umbraco doesn't let you have two documents with the same name underneath the same parent. I needed to change that.

## If you're wondering why...
(if not, [skip to the how](#how))

In my particular case this is for news articles and event pages. 

My news and event pages use a custom `IUrlProvider` and `IContentFinder` ([as per the docs](https://our.umbraco.com/documentation/Reference/Routing/Request-Pipeline/)) so that my news and events pages can have URLs like this:

`/events/2022/03/16/my-awesome-event/`

I have multiple events with the same name, but I want to keep all of my events underneath a single parent node in the backoffice. By default, if I create two events called "My Awesome Event" beneath the same node in the backoffice I'll end up with "My Awesome Event" and "My Awesome Event (1)", like this:

![enforced unique node names in the Umbraco backoffice](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ny23g4yfzpglxmi6tmac.png)
 
The URL changes too:
`/events/2022/03/16/my-awesome-event/`
`/events/2022/04/20/my-awesome-event-1/`

I could use date "folders", and automate this by moving the articles/events on save, but this adds extra nodes and hierarchy that I don't want or need. Having them all in one list view will make for a much nicer editor experience (in this site).

A bit of googling reminded me that in v7 we had the option to set `ensureUniqueNaming` to false in the config. That's not available in v8, and was a bit of a blunt instrument anyway.

Turns out that in v9 (and probably in v8 too, but I haven't checked) there's a smarter way...

## Here's how <a name="how"></a>

`EnsureUniqueNaming` is [still a property](https://github.com/umbraco/Umbraco-CMS/blob/bc80892f5114e7c284a61e86da36b777535aef68/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs#L90) on the `DocumentRepository` and it's used to determine if the repository allows non-unique names. It's set to false, and I thought about inheriting this class and simply setting this to true - but that would set it to true for _all_ content, and that seems risky. I have routing in place to make sure that non-unique names for my news/events don't matter, but ~~if~~ _when_ an editor creates a page with a duplicate name elsewhere it just wont work.

Fortunately, there's an [overridable method](https://github.com/umbraco/Umbraco-CMS/blob/bc80892f5114e7c284a61e86da36b777535aef68/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs#L1623-L1626) that means we can be a bit smarter - `EnsureUniqueNodeName`.

In this method I have access to the parent id, so I can check where I'm saving content and conditionally decide to allow duplicate node names or not. 

Here's what I ended up doing:

```csharp
public class CustomDocumentRepository : DocumentRepository
{
    private static readonly string[] allowNonUniqueChildNames = { EventsPage.ModelTypeAlias, NewsPage.ModelTypeAlias };

    // constructor ommited for brevity

    protected override string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0)
    {
        var parent = this.Get(parentId);

        if (allowNonUniqueChildNames.InvariantContains(parent.ContentType.Alias))
        {
            return nodeName;
        }
            
        return base.EnsureUniqueNodeName(parentId, nodeName, id);
    }
}
```
In this case the content type aliases are taken from my ModelsBuilder models.

Thanks to Dependency Injection, it's super easy to replace the existing repository with our custom one. Here's an example using startup.cs, make sure to either add the service _after_ the `AddUmbraco` extension or add it to [your own builder extension method](https://our.umbraco.com/documentation/reference/using-ioc/#builder-extension-methods) which is what I did.

```csharp
public void ConfigureServices(IServiceCollection services)
{
    services.AddUmbraco(_env, _config)
        .AddBackOffice()
        .AddWebsite()
        .AddComposers()
        .Build();
        .Build();

    services.AddUnique<IDocumentRepository, CustomDocumentRepository>();
}

```

The result:
![duplicate node names in the Umbraco backoffice](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r1xf24noqto1p5hmkg5k.png)

### This could be better...

This approach only really works where it makes sense to scope non-unique names based on the parent node - though that doesn't have to be based on type, it could be a property value etc. In my case, an "events" node only has child events and a "news" node only child news articles, so this is fine. 

Ideally, I'd want to take more things into account such as the type or even content of the node that I am saving, but this method doesn't know anything about the content other than the name. I could override other methods but that would result in a lot of code duplication (just look at all those private methods). Perhaps there's scope for adding a notification here to allow control over how and when unique names are applied, or at least overloading the method to take the content itself into account. If I get round to making a PR for that I'll update this post.