Robware Software by Rob

Conditional controller route (alias) in ASP.NET

I recently had to move some clients of an API over from an old endpoint URL to a new one. The new endpoint had completely replaced the old one, with the old endpoint simply being a Route attribute acting as an alias. Having two endpoints for identical functionality was causing confusion, so I couldn't just leave it there despite how harmless it seemed. I also wanted to make this switch at different times for each environment to get people to ensure it's fixed in our pre-production environments before it was fully turned off in prod. In order to facilitate this switch I created a ASP.NET "convention" to allow me to switch this off with a feature flag in appsettings.json.

Here's the "convention" code:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace Glorious.Api;

public class ConditionalControllerAliasConvention<T> : IApplicationModelConvention where T : ControllerBase
{
	private readonly string _aliasRoute;
	private readonly bool _enabled;

	public ConditionalControllerAliasConvention(string aliasRoute, bool enabled)
	{
		_aliasRoute = aliasRoute;
		_enabled = enabled;
	}

	public void Apply(ApplicationModel application)
	{
		if (!_enabled)
		{
			return;
		}

		var controller = application.Controllers.Single(model => model.ControllerType == typeof(T));

		var aliasRouteModel = new AttributeRouteModel(
			new RouteAttribute(_aliasRoute)
		);

		controller.Selectors.Add(new SelectorModel
		{
			AttributeRouteModel = aliasRouteModel
		});
	}
}

Here's how to use it:

builder.Services.AddControllers(options =>
	options.Conventions.Add(new ConditionalControllerAliasConvention<AwesomeController>("sadOldEndpoint",
		builder.Configuration.GetSection("oldSadEndpointEnableFlag").Get<bool>())));

As you can see, we use the AddControllers options to add our ConditionalControllerAliasConvention convention. The generic parameter is our controller we want to point to. The first parameter is the alias or endpoint we want to use. The last parameter is a simple boolean switch, which in this instance is fed from our config.

Posted on Friday the 9th of May 2025