Skip to content

Url.RouteUrl broken after upgrading to asp.net 3.0 endpoint routing #12794

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
dotnetshadow opened this issue Aug 1, 2019 · 8 comments
Closed
Assignees
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates question

Comments

@dotnetshadow
Copy link

dotnetshadow commented Aug 1, 2019

Describe the bug

Upgraded an application from asp.net core 2.2 to 3.0 preview7

[Area("Games")]    
[Route("[area]/{slug}/[controller]")]    
public class FooController : Controller {
        [AjaxOnly]
        [HttpPost("[action]", Name = "GetFooByName")]
        public async Task<JsonResult> GetFooByName(string name)
        {        
        }
}

On my razor page I used to create the following:

<script>
         var endpoints = {
            getFoo: '@Url.RouteUrl("GetFooByName")' <---- no route data passed
        }
</script>

Issue

But since upgrading to asp.net core 3.0 the getFoo
in 2.2 - /area/slug/controller/foo
n 3.0 - blank <----- ????

Using Endpoints - DOESN'T WORK

services.AddRazorPages();
 services.AddControllersWithViews(options =>
            {
                options.Filters.Add(new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()));
                options.Filters.Add(new RequireHttpsAttribute());
                options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
                options.EnableEndpointRouting = true; (doesn't matter true or false)
            });

 app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers(); // attribute routing
                endpoints.MapAreaControllerRoute("areaRoute", "{area:exists}", "{area:exists}/{slug?}/{controller=Home}/{action=Index}/{id?}");
                endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
                endpoints.MapRazorPages();
            });

Using AddMvc - WORKS


services.AddMvc(options =>
            {
                options.Filters.Add(new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()));
                options.Filters.Add(new RequireHttpsAttribute());
                options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());

                // add custom binder to beginning of collection
                options.EnableEndpointRouting = false;
            })

app.UseMvc(routes =>
            {
                // Areas route
                routes.MapRoute(
                    name: "areaRoute",
                    template: "{area:exists}/{slug?}/{controller=Home}/{action=Index}/{id?}");

                // Default route
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });

I even noticed that my links no longer work correctly since the update
<a class="btn btn-sm btn-outline-info" asp-route="BarRoute" asp-route-option="foo">
The route-option value isn't on the generated url

It seems that I can't use the new use.Endpoint

Expected behavior

in 2.2 - /area/slug/controller/foo
in 3.0 - /area/slug/controller/foo

This is a similar issue reported here:
#11320
#11635

@mkArtakMSFT mkArtakMSFT added the area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates label Aug 1, 2019
@mkArtakMSFT
Copy link
Member

Thanks for contacting us, @dotnetshadow.
@rynowak can you please look into this? Thanks!

@rynowak
Copy link
Member

rynowak commented Aug 2, 2019

This was a bug in 2.0-2.2 attribute routing that's been fixed. Ambient route values (like slug in your case) won't be propagated when you're generating a link to a different action. You need to pass the value you want to use for slug` explicitly:

@Url.RouteUrl("GetFooByName", new { slug = ... })

This change makes attribute routes and conventional routes behave consistently.

@dotnetshadow
Copy link
Author

dotnetshadow commented Aug 2, 2019

@rynowak Thank you for getting back to me much appreciated.
Ok that makes sense if there was a bug in 2.0-2.2.

Is there a section where I can read about this bug?

won't be propagated when you're generating a link to a different action:

I'm a little confused about that statement. To me I'm generating an action from a controller that has an ambient value.

If the values are on the url and I call @Url.RouteUrl("routeName") shouldn't the values be inserted into the correct tokens?

The confusing part for me I guess is, that the slug ambient value is used when generating the pages (i.e. after entering a url in browser) the slug is getting it's values somehow from the url. But if I use any link generation, the ambient values aren't read form anywhere is that correct? Hence I will have to supply them?

In my example GetFooByName won't result in [area]/{slug}/[controller]/[action] by default.
Unless I inject the ambient value myself it won't match?

So in a layout page situation if I want access to the slug I will have to grab it from ViewContext.RouteData.Values["slug"] and pass that in as you suggested?

@rynowak
Copy link
Member

rynowak commented Aug 2, 2019

I'm a little confused about that statement. To me I'm generating an action from a controller that has an ambient value.

Right, so pages (pre-endpoint-routing) have a bug where route values will be reused across different pages when they wouldn't be for controllers.

So if you have /Products.cshtml with route /Products/{id} and /Support/ShowTicket.cshtml with route /Support/Tickets/{id?} then it's possible for the id route value to be reused when you didn't want it to. That's an example of something that changed with endpoint routing.

This is confusing because you will get different link generation behaviour based on how you got to the current page, and what it's route parameter names are.

If the values are on the url and I call @Url.RouteUrl("routeName") shouldn't the values be inserted into the correct tokens?

The decision process about how we decide which route values to re-use (ambient values) is unfortunately one of the most complex features of ASP.NET Core. It's hard to answer a question like that because the details matter quite a lot.

The confusing part for me I guess is, that the slug ambient value is used when generating the pages (i.e. after entering a url in browser) the slug is getting it's values somehow from the url. But if I use any link generation, the ambient values aren't read form anywhere is that correct?

Ambient values are a feature that exist to try and make your life more convenient when you want to reuse values - unfortunately saying when you want to is a pretty imprecise statement. If you're explicit about what values to pass, that will always win.

The easiest way to think about this is that ambient values are reused when you link to the same thing. So if you link from a page back to itself, ambient values are reused. If you link from a page to another page, they will not be reused.

In my example GetFooByName won't result in [area]/{slug}/[controller]/[action] by default.
Unless I inject the ambient value myself it won't match?

Right. The issue is that we won't reuse the value for slug which means that routing is missing a value for a non-optional parameter, which causes link generation to fail.

So in a layout page situation if I want access to the slug I will have to grab it from ViewContext.RouteData.Values["slug"] and pass that in as you suggested?

Yes. That will fix it.

In general it's not a good idea in general to rely on ambient values inside a layout page. Your layout can be used from lots of different places in the app, so it's likely that the ambient value won't work.

@dotnetshadow
Copy link
Author

@rynowak Thank you so much for a thorough explanation. I feel like I understand better now.
I'm wondering if there needs to be some mention of this in the docs?
https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-2.2&tabs=visual-studio

@rynowak
Copy link
Member

rynowak commented Aug 2, 2019

It's discussed here: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-2.2#differences-from-earlier-versions-of-routing

if you think there's something specific we should add, I'm happy to add it.

@dotnetshadow
Copy link
Author

dotnetshadow commented Aug 3, 2019

Thank you so much, I must of missed that section
I'll follow as you said If you're explicit about what values to pass, that will always win.

@shapeh
Copy link

shapeh commented Oct 1, 2019

We just posted this on stackoverflow in case anybody is interested: https://stackoverflow.com/questions/58183201/url-routeurl-is-blank-in-asp-net-core-3-0

From the MSFT documentation, we have not been able to derive how to solve this problem.

@ghost ghost locked as resolved and limited conversation to collaborators Dec 2, 2019
@danroth27 danroth27 changed the title Url.RoutUrl broken after upgrading to asp.net 3.0 endpoint routing Url.RouteUrl broken after upgrading to asp.net 3.0 endpoint routing Nov 17, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates question
Projects
None yet
Development

No branches or pull requests

4 participants