简体   繁体   中英

Endpoint Routing in ASP.NET Core 2.2 is Not Working

Update (2)

@poke seems to have figured it out and it looks to be an issue with endpoint routing itself favoring the {*url} if there's perceived ambiguity with other higher routes.

Update (1)

@poke commented that I had a typo on the {*url} route where the t in controller was missing. After fixing that the {*url} route started working and the DefaultController.Gone action was working.

BUT! Now weird behavior is starting to crop up again. After the {*url} was fixed, navigating to /settings which is supposed to match the {controller}/{action} route fails and falls back to the {*url} route.

If I remove the {*url} route from the registrations then /settings works again. The {action} route continues to not work.

Original

Please forgive the length of the question, but I am trying to offer as much information as possible.

I am working on an ASP.NET Core 2.2 blogging app for myself, and I'm having inexplicable problems getting routing to work. After spending half the day yelling at my screens, I decided to take a step back and start a new project that was completely isolated. Somehow the problems persisted in the new project. I've stripped it down pretty much to a starved skeleton and I still can't get the routes to work. The routes I'm trying to set up are:

settings/{controller}/{id:int}/{action} - works
settings/{controller}/{action} - works
blog/{*slug} - works
blog/{skip:int?} - works
{controller}/{action} - works
{action} - doesn't work
{*url} - doesn't work

Specifically, I'm having problems with the last two routes.

The {action} route is not generating for simple actions like DefaultController.About even though it has no constraints, all it has is defaults for PostsController.List because I want a list of posts to be shown for the root URL.

The {*url} just doesn't seem to work at all. I want to use it as my final fallback and it's defaulted to DefaultController.Gone , but if I just bash on the keyboard for some nonsense URL all I get is a 404 error.

I feel that the issue is with the DefaultController since both the About and Gone actions are in it and neither one seems to be working, but I just can't seem to figure out how. It literally does nothing but renders views, just like the other controllers.

Below is the code of the stripped down project. I'd really appreciate it if someone can spin it up and tell me where I'm failing because I certainly can't seem to figure it.

Program.cs

public sealed class Program {
    public static async Task Main(
        string[] args) => await WebHost.CreateDefaultBuilder(args)
                                       .UseStartup<Startup>()
                                       .Build()
                                       .RunAsync();
}

Startup.cs

public class Startup {
    public void ConfigureServices(
        IServiceCollection services) {
        services.AddMvc()
                .SetCompatibilityVersion(CompatibilityVersion.Latest);
    }

    public void Configure(
        IApplicationBuilder app) {
        app.UseMvc(
            r => {
                //  /settings/{controller}/{id}/{action}
                r.MapRoute("600", "settings/{controller}/{id:int}/{action}", null, new {
                    controller = "Categories|Tags"
                });

                //  /settings/{controller}/{action}
                r.MapRoute("500", "settings/{controller}/{action}", null, new {
                    controller = "Categories|Tags"
                });

                //  /blog/*
                r.MapRoute("400", "blog/{*slug}", new {
                    action = "Show",
                    controller = "Posts"
                });

                //  /blog/{skip}
                r.MapRoute("300", "blog/{skip:int?}", new {
                    action = "List",
                    controller = "Posts"
                });

                //  /{controller}/{action}
                r.MapRoute("200", "{controller}/{action=Default}", null, new {
                    controller = "Settings|Tools"
                });

                //  /{action}
                r.MapRoute("100", "{action}", new {
                    action = "List",
                    controller = "Posts"
                });

                //  /*
                r.MapRoute("-1", "{*url}", new {
                    action = "Gone",
                    conroller = "Default"
                });
            });
    }
}

CategoriesController.cs

public sealed class CategoriesController :
    Controller {
    [HttpGet]
    public IActionResult Add() => Content("Category added");

    [HttpGet]
    public IActionResult Remove(
        int id) => Content($"Category {id} removed");
}

DefaultController.cs

public sealed class DefaultController :
    Controller {
    [HttpGet]
    public IActionResult About() => View();

    [HttpGet]
    public IActionResult Gone() => View();
}

About.cshtml (Default)

<h1>DEFAULT.ABOUT</h1>

Gone.cshtml (Default)

<h1>DEFAULT.GONE</h1>

PostsController.cs

public sealed class PostsController :
    Controller {
    [HttpGet]
    public IActionResult List(
        int? skip) => View();

    [HttpGet]
    public IActionResult Show(
        string slug) => View();
}

List.cshtml (Posts)

<h1>POSTS.LIST</h1>
<a asp-action="Show" asp-controller="Posts" asp-route-slug="test-test-test">Show a Post</a>

Show.cshtml (Posts)

<h1>POSTS.SHOW</h1>

SettingsController.cs

public sealed class SettingsController :
    Controller {
    [HttpGet]
    public IActionResult Default() => View();
}

Default.cshtml (Settings)

<h1>SETTINGS.DEFAULT</h1>
<a asp-action="Add" asp-controller="Categories">Add a Category</a>
<br />
<a asp-action="Remove" asp-controller="Categories" asp-route-id="1">Remove a Category</a>
<hr />
<a asp-action="Add" asp-controller="Tags">Add a Tag</a>
<br />
<a asp-action="Remove" asp-controller="Tags" asp-route-id="1">Remove a Tag</a>

TagsController.cs

public sealed class TagsController :
    Controller {
    [HttpGet]
    public IActionResult Add() => Content("Tag added");

    [HttpGet]
    public IActionResult Remove(
        int id) => Content($"Tag {id} removed");
}

ToolsController.cs

public sealed class ToolsController :
    Controller {
    [HttpGet]
    public IActionResult Default() => View();
}

Default.cshtml

<h1>TOOLS.DEFAULT</h1>

_Layout.cshtml

<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
</head>
<body>
    <a asp-action="List" asp-controller="Posts">Blog</a>
    <br />
    <a asp-action="Default" asp-controller="Tools">Tools</a>
    <br />
    <a asp-action="About" asp-controller="Default">About</a>
    <br />
    <a asp-action="Default" asp-controller="Settings">Settings</a>
    <br />
    @RenderBody()
</body>
</html>

{action} - doesn't work

This one doesn't work because it has to match an actual action. So it works for /Show or /List since you are operating on the PostsController . It also works for / since the action defaults to List .

{*url} - doesn't work

This one will work if you set the default controller , instead of the conroller :

r.MapRoute("-1", "{*url}", new
{
    action = "Gone",
    controller = "Default"
});

<a asp-action="About" asp-controller="Default">About</a>

Note that this route will also not match because there is no route to that action. The {controller}/{action} routes are constrained to the SettingsController and ToolsController , so the route won't match. You will need to adjust the constraint or add another route for this to work.

Btw. as a general suggestion: As you probably noticed, managing this many route mapping gets quite complicated. It's often easier to just use attribute routing with explicit routes. You could also mix those with template based routing to get the best of both worlds.


Weirdly /settings which is supposed to match the {controller}/{action} route is now failing and falling back to the {*url} route. If I remove the {*url} route from the registrations then /settings works again.

That appears to be a side effect from comining the settings/{controller}/{action} and the {controller}/{action=Default} routes.

I've been debugging through that now for a bit and it seems that this is a bug with endpoint routing, which favors the catch all route although it is being registered later.

Unfortunately, endpoint routing in ASP.NET Core 2.2 is known to break on a few special cases which is why it is being revamped for 3.0 which will hopefully resolve all issues. That being said, I've opened an issue about this particular problem and reported my findings. Maybe there's a simple solution for this.

One easy workaround would be to change the settings/{controller}/{action} route template to use a prefix other than settings , so that there is no longer an ambiguity. That appears to fix the issues.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM