How to Create a URL- and SEO-Friendly String in C#

Build readable, stable ASCII URL slugs in C# with Unicode normalization, transliteration, separator cleanup, and a safe length limit.

Article guideContents, topics, tags, and RSS
Get new notes via RSS

A slug turns a title such as ICH MUẞ EINIGE CRÈME BRÛLÉE HABEN into a readable URL segment such as ich-muss-einige-creme-brulee-haben.

The important part is not only replacing spaces. A useful slug function needs an explicit policy for Unicode, repeated separators, empty input, and maximum length.

Decide what your slugs support

This example creates lowercase ASCII slugs. It uses .NET Unicode normalization to separate many Latin letters from their diacritics, removes the combining marks, transliterates a small set of letters, and converts every other boundary to a hyphen.

ASCII is a product choice, not an SEO requirement. Modern URLs can contain Unicode. If your content spans languages that cannot be represented sensibly with Latin characters, keep Unicode slugs or use a tested transliteration library instead of silently dropping letters.

The slug function

using System.Globalization;
using System.Text;

public static class Slug
{
    public static string Create(string? value, int maxLength = 80)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return string.Empty;
        }

        if (maxLength < 1)
        {
            throw new ArgumentOutOfRangeException(nameof(maxLength));
        }

        var normalized = value
            .Trim()
            .ToLowerInvariant()
            .Normalize(NormalizationForm.FormD);

        var result = new StringBuilder(normalized.Length);

        foreach (var character in normalized)
        {
            var category = CharUnicodeInfo.GetUnicodeCategory(character);
            if (category == UnicodeCategory.NonSpacingMark)
            {
                continue;
            }

            var replacement = character switch
            {
                'ß' => "ss",
                'æ' => "ae",
                'ø' => "o",
                'ð' => "d",
                'þ' => "th",
                'ł' => "l",
                _ when character <= 127 && char.IsLetterOrDigit(character) =>
                    character.ToString(),
                _ => null,
            };

            if (replacement is not null)
            {
                foreach (var replacementCharacter in replacement)
                {
                    if (result.Length == maxLength)
                    {
                        break;
                    }

                    result.Append(replacementCharacter);
                }
            }
            else if (result.Length > 0 && result[^1] != '-' && result.Length < maxLength)
            {
                result.Append('-');
            }

            if (result.Length == maxLength)
            {
                break;
            }
        }

        return result.ToString().Trim('-');
    }
}

The normalization step uses Form D, which decomposes characters such as è into a base letter and a combining mark. The code discards the combining mark while retaining the base letter. The explicit switch handles letters that do not decompose into the ASCII result we want.

Check the behavior

var slug = Slug.Create("ICH MUẞ EINIGE CRÈME BRÛLÉE HABEN");

Console.WriteLine(slug);
// ich-muss-einige-creme-brulee-haben

Add tests for the rules your application depends on:

[Theory]
[InlineData("Hello, world!", "hello-world")]
[InlineData("  repeated   spaces  ", "repeated-spaces")]
[InlineData("Crème brûlée", "creme-brulee")]
[InlineData("Straße", "strasse")]
[InlineData("", "")]
public void Creates_expected_slug(string input, string expected)
{
    Assert.Equal(expected, Slug.Create(input));
}

If you use generated slugs as permanent public URLs, store the chosen slug with the record. Changing the title or transliteration rules later should not silently change an already published URL. ASP.NET Core can also apply slug rules during link generation with an outbound parameter transformer, but the same stability rule still applies.

Johan Boström smiling in a navy jacket and white shirt.
Author portrait

About the author

Johan Boström

Technology leader · Solution architect · Developer

My primary work is leading developers and working as a solution architect. I still develop when hands-on work helps the team or the solution.

I mainly work across systems, integrations, and AI. This archive keeps the practical details, lessons, and implementation notes worth finding again.