Search code examples
c#asp.net-core.net-5asp.net-core-5.0

Why does .NET 5 ASP.NET Core catch-all route supersede a defined route, when in .NET Core 3 it did not?


Migrating from .NET Core 3.1 to .NET 5, we encountered peculiar behavior with a catch-all route configuration.

The relevant part of the startup configuration looks like this:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseStaticFiles();
    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
                 name: "default",
                 pattern: "home",
                 defaults: new { controller = "Home", action = "Index" });

        //catch-all endpoint
        endpoints.Map("{*.}", async (t) =>
        {
            System.Console.WriteLine("hello");

            await Task.CompletedTask;
        });
    });
}

Calling the url: http://localhost:port/home hits the catch-all route instead of the home controller. If the catch-all endpoint map is commented out, the home controller endpoint is hit - which is what we would expect in both cases, reading the MSDN docs. The bahavior before migration was the most specific route called first (i.e. the home controller endpoint), and the catch-all only responded when no route could be matched.

Were there breaking changes in .NET 5, or are we missing something?


Solution

  • The behavior was due to the ASP.NET Core team introducing a change to the routing mechanism in 5.0 - the problematic behavior is actually by design.

    From javiercn on GitHub:

    I looked more in depth and I can conclude that the behavior you are seeing is by design and caused by the fix we did in 5.0 to the routing bug we had in 3.1

    Routes have order and precedence and order wins over precedence.

    Conventional routes have an Order=1 (so that they are less specific than attribute routes and don't interfere with them when combined).

    The second endpoint you are mapping doesn't have an order, so the order is 0.

    The conventional route has more precedence than the catch-all route, but order is evaluated over the precedence.

    That's why the route with the catch-all wins over the conventional route.

    Conventional routes have a starting order of 1 and the order increases with each definition (to reflect the same behavior as classic mvc).

    See more in the GitHub issue here: https://github.com/dotnet/aspnetcore/issues/29594