Consistent Kebab Cased Controller Routes in ASP.NET Core

Lloyd Atkinson
3,638
Consistent Kebab Cased Controller Routes in ASP.NET Core

While I like the naming conventions in .NET, URLs should not follow those conventions. I’ve often wondered why the ASP.NET team has historically chosen to create some disagreeable default routing names. For example, before ASP.NET Core, it was very common to see sites with .aspx in the URL. Fortunately, that convention has been phased out, but one remains: pascal case routes.

Instead of /WeatherForecast, I prefer /weather-forecast. This is also more consistent with other web frameworks. While URL’s are mostly treated as case insensitive, that is not actually the case.

How about enforcing lower-case, kebab-cased routes and avoiding all of that? Plus, it’s just more readable.

Surprisingly there are scarce details on achieving this reliably and consistently.

Fortunately, the .NET team provided a solution (and one that should really be built into ASP.NET Core!) for this in their reference architecture eShopOnWeb. This is the primary reason I’m writing this post - searching online did not surface this solution at all (at least not in the first few pages of results).

public sealed class SlugifyParameterTransformer : IOutboundParameterTransformer
{
public string? TransformOutbound(object? value)
{
if (value == null) { return null; }
string? str = value.ToString();
if (string.IsNullOrEmpty(str)) { return null; }
return Regex.Replace(str, "([a-z])([A-Z])", "$1-$2").ToLower();
}
}

Unfortunately there are no unit tests in the repository for this, so I wrote one for it.

public class SlugifyParameterTransformerTests
{
[Theory]
[InlineData(null, null)]
[InlineData("", null)]
[InlineData("TypicalControllerName", "typical-controller-name")]
public void ShouldTransformMixedCaseString(object value, string expected)
{
var transformer = new SlugifyParameterTransformer();
var result = transformer.TransformOutbound(value);
result.ShouldBe(expected);
}
}

This is then used when adding the controllers in the composition root.

public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.Conventions.Add(
new RouteTokenTransformerConvention(new SlugifyParameterTransformer()));
});
}

You should now see the following in the OpenAPI UI:

OpenAPI UI

Share:

Spotted a typo or want to leave a comment? Send feedback

Stay up to date

Subscribe to my newsletter to stay up to date on my articles and projects