148

In ASP.NET 4 this was as easy as routes.LowercaseUrls = true; in the RegisterRoutes handler for the app.

I cannot find an equivalent in ASP.NET Core for achieving this. I'd think it would be here:

app.UseMvc(configureRoutes =>
{
    configureRoutes.MapRoute("Default", "{controller=App}/{action=Index}/{id?}");
});

But nothing in configureRoutes looks to allow it... unless there's an extension method somewhere that I can't find in the docs perhaps?

Amal K
  • 4,359
  • 2
  • 22
  • 44
mariocatch
  • 8,305
  • 8
  • 50
  • 71

7 Answers7

321

For ASP.NET Core:

Add one of the following lines to the ConfigureServices method of the Startup class:

services.AddRouting(options => options.LowercaseUrls = true);

or

services.Configure<RouteOptions>(options => options.LowercaseUrls = true); 

Thanks to Skorunka for the answer as a comment. I thought it was worth promoting to an actual answer.

craftworkgames
  • 9,437
  • 4
  • 41
  • 52
  • 49
    It's worth noting you should put this BEFORE you actually call `AddMvc()` in your `Startup.ConfigureServices()` method. `AddRouting()` which is also called by `AddMvc()`uses the `Try` variants of the methods for adding dependencies to your service collection. So when it sees that the routing dependencies have already been added, it will skip that parts of `AddMvc()` setup logic. – Nick Albrecht Jan 31 '17 at 22:05
  • 2
    Switching this to correct answer since mine was during the transition of asp 4 to core. – mariocatch Apr 30 '17 at 23:22
  • @NickAlbrecht it doesn't seem to make a difference if it's called before or after (as of ASP.NET Core 5.0, at least). AddRouting() will be called twice anyway, so it doesn't matter in which order. – Thomas Levesque Dec 04 '20 at 12:39
  • 1
    I believe that was done around .NET Core 3.x. They changed it so that routing was a stand alone feature instead of bundled with MVC. I don't believe routing is called from the `AddMvc` (or `AddControllersWithViews` if you don't need RazorPages) anymore. So the order only really matters if you're using AspNetCore 2. (Don't recall if this was an option in 1.x). However they *did* split the lowercase behavior into two settings, so if you want fully lower case addresses, you need to set both `LowercaseUrls` and `LowercaseQueryStrings` to `true`. – Nick Albrecht Dec 04 '20 at 22:16
  • Can you clarify that the 2nd option is for Razor Pages and first for MVC? – carlin.scott Jan 31 '22 at 19:00
  • although this will generate lowercase urls, it **will not enforce** lowercase urls. you can still enter urls with uppercase characters. see [this](https://stackoverflow.com/a/50631028/1097123) for how to enforce lowercase urls by rewriting any uppercase urls to lowercase. – kimbaudi Jul 11 '22 at 17:39
66

Update in ASP.NET Core Version >= 2.2

From ASP.NET Core 2.2, along with lowercase you can also make your route dashed using ConstraintMap which will make your route /Employee/EmployeeDetails/1 to /employee/employee-details/1 instead of /employee/employeedetails/1.

To do so, first create the SlugifyParameterTransformer class should be as follows:

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

For ASP.NET Core 2.2 MVC:

In the ConfigureServices method of the Startup class:

services.AddRouting(option =>
{
    option.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
});

And Route configuration should be as follows:

app.UseMvc(routes =>
{
     routes.MapRoute(
        name: "default",
        template: "{controller:slugify}/{action:slugify}/{id?}",
        defaults: new { controller = "Home", action = "Index" });
});

For ASP.NET Core 2.2 Web API:

In the ConfigureServices method of the Startup class:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options => 
    {
        options.Conventions.Add(new RouteTokenTransformerConvention(new SlugifyParameterTransformer()));
    }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

For ASP.NET Core >=3.0 MVC:

In the ConfigureServices method of the Startup class:

services.AddRouting(option =>
{
    option.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
});

And Route configuration should be as follows:

app.UseEndpoints(endpoints =>
{
      endpoints.MapAreaControllerRoute(
          name: "AdminAreaRoute",
          areaName: "Admin",
          pattern: "admin/{controller:slugify=Dashboard}/{action:slugify=Index}/{id:slugify?}");

      endpoints.MapControllerRoute(
          name: "default",
          pattern: "{controller:slugify}/{action:slugify}/{id:slugify?}",
          defaults: new { controller = "Home", action = "Index" });
});

For ASP.NET Core >=3.0 Web API:

In the ConfigureServices method of the Startup class:

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

For ASP.NET Core >=3.0 Razor Pages:

In the ConfigureServices method of the Startup class:

services.AddRazorPages(options => 
{
    options.Conventions.Add(new PageRouteTransformerConvention(new SlugifyParameterTransformer()));
})

This is will make /Employee/EmployeeDetails/1 route to /employee/employee-details/1

TanvirArjel
  • 30,049
  • 14
  • 78
  • 114
  • I tried this code and official Microsoft code too, but "slugify" parameter transformer has no effect. It is just ignored by routing system (so URLs not replaced to dashed ones). To check myself i put logger into TransformOutbound() method, but no calls from there. – Sam Alekseev Jan 10 '19 at 16:58
  • Okay! Let me check please! – TanvirArjel Jan 10 '19 at 16:59
  • @user3172616 I have check it right now! its working as expected! like generating route as `employee-details`. Would you show me your configuration please? – TanvirArjel Jan 10 '19 at 17:03
  • @user3172616 are you using attribute routing on the route that you tested? – TanvirArjel Jan 10 '19 at 17:06
  • I use standard routing approach (not attribute routing) on brand new core v2.2 solution in Visual Studio. There are two screenshots (c# code and .cshtml code). I tried to generate links in different ways with no effect. https://imgur.com/a/L8dCF6c – Sam Alekseev Jan 10 '19 at 17:14
  • Found nothing wrong in your code! Would give a try removing `SlugifyParameterTransformer` class from `startup` class to anywhere? – TanvirArjel Jan 10 '19 at 17:25
  • I moved SlugifyParameterTransformer to separate *.cs file with no success. Generated urls are still the same like "/home/thisisthetest". Also i tried to throw random exception from there, it has no effect. But if i comment code line where i register this Transformer then app crashes because it uses unknown "slugify" placeholder in the route as expected. – Sam Alekseev Jan 10 '19 at 17:40
  • Sorry to hear that! Don't know what wrong happening in case of you while its working fine in my application. – TanvirArjel Jan 10 '19 at 17:42
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/186496/discussion-between-tanvirarjel-and-user3172616). – TanvirArjel Jan 10 '19 at 17:42
27

As other answers indicate, adding:

services.Configure<RouteOptions>(options => options.LowercaseUrls = true);

before

services.AddMvc(...)

works great, but I also want to add that if you use Identity, you will also need:

services.AddIdentity<IdentityUser, IdentityRole>(options =>
{
    var appCookie = options.Cookies.ApplicationCookie;
    appCookie.LoginPath = appCookie.LoginPath.ToString().ToLowerInvariant();
    appCookie.LogoutPath = appCookie.LogoutPath.ToString().ToLowerInvariant();
    appCookie.ReturnUrlParameter = appCookie.ReturnUrlParameter.ToString().ToLowerInvariant();
});

And obviously, replace both IdentityUser, and IdentityRole with your own classes if required.

I just tested this with .NET Core SDK 1.0.4 and the 1.0.5 runtime.

  • 2
    Configure() is the best answer imho: tiny and straight to the point (tested on mvc core 3.1) – T-moty May 09 '20 at 08:31
15

Found the solution.

In the assembly: Microsoft.AspNet.Routing, and the Microsoft.Extensions.DependencyInjection namespace, you can do this in your ConfigureServices(IServiceCollection services) method:

services.ConfigureRouting(setupAction =>
{
    setupAction.LowercaseUrls = true;
});
mariocatch
  • 8,305
  • 8
  • 50
  • 71
8

It is worth noting that setting:

services.Configure<RouteOptions>(options => options.LowercaseUrls = true);

does not affect query strings.

To ensure that query strings are also lowercase, set the options.LowercaseQueryStrings to true:

services.Configure<RouteOptions>(options => 
{ 
    options.LowercaseUrls = true; 
    options.LowercaseQueryStrings = true;
});

However, setting this property to true is only relevant if options.LowercaseUrls is also true. options.LowercaseQueryStrings property is ignored if options.LowercaseUrls is false.

Amal K
  • 4,359
  • 2
  • 22
  • 44
2

For identity, @Jorge Yanes Diez answer doesn't work in ASP.NET Core 2.2 (I think 2.x), so if you use Identity and ASP.NET Core 2.2 (2.x) here is the solution:

services.ConfigureApplicationCookie(options =>
{
    options.LoginPath = "/account/login";
    options.ReturnUrlParameter = "returnurl";
    ...
});

Ref: Configure ASP.NET Core Identity

Mehdi Dehghani
  • 10,970
  • 6
  • 59
  • 64
-1

I had this on RegisterRoutes::RouteConfig:

routes.LowercaseUrls = true;

paburgos
  • 259
  • 2
  • 14