Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Using "page" route parameter in convention routing fails with Asp.Net Core Mvc 2.0 #6660

Closed
eat-sleep-code opened this issue Aug 15, 2017 · 40 comments
Assignees

Comments

@eat-sleep-code
Copy link

eat-sleep-code commented Aug 15, 2017

I have a website that was working perfectly fine in .NET core 1.1.

Today -- with the release of .NET Core 2.0 -- I decided to update my application. I changed my Target Framework to .NET Core 2.0, and switched over to using the Microsoft.AspNetCore.All package as outlined here: https://blogs.msdn.microsoft.com/webdev/2017/08/14/announcing-asp-net-core-2-0/

I installed the .NET Core 2.0 Runtime on my server. I restarted my server.

The application builds fine, but when I deploy, it seems my routes -- which worked perfectly in .NET Core 1.1 -- no longer work.

When I try using https://example.com/history (desired URL, which the route SHOULD direct to the "History" action of the "Pages" controller) it complains of a missing View.

If I use https://example.com/pages/history (not desired URL) it loads the home page, not the history page.

Again, this worked fine in .NET Core 1.1

This is my /Program.cs:

using Microsoft.AspNetCore.Hosting;
using System.IO;

namespace Framework
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .Build();

            host.Run();
        }
    }
}

This is the portion of my /Startup.cs with the routes defined at the end:


            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "404",
                    template: "404/{id}",
                    defaults: new { controller = "Pages", action = "Error404"}
                );

                routes.MapRoute(
                    name: "news",
                    template: "news/{page}",
                    defaults: new { controller = "Pages", action = "News", page = 1 }
                );

                routes.MapRoute(
                    name: "history",
                    template: "history/{page=1}/{sortFilter?}/{sortOrder?}",
                    defaults: new { controller = "Pages", action = "History"}
                );

                routes.MapRoute(
                    name: "restoration-resources",
                    template: "restoration-resources/{page=1}/{sortFilter?}/{sortOrder?}",
                    defaults: new { controller = "Pages", action = "RestorationResources"}
                );

                routes.MapRoute(
                    name: "vehicles",
                    template: "vehicles/{vehicle?}/{page=1}/{sortFilter?}/{sortOrder?}",
                    defaults: new { controller = "Pages", action = "Vehicles" }
                );

                routes.MapRoute(
                    name: "gallery",
                    template: "gallery/{gallery?}",
                    defaults: new { controller = "Pages", action = "Gallery" }
                );

                routes.MapRoute(
                    name: "rss",
                    template: "rss/{source?}/{post?}",
                    defaults: new { controller = "Pages", action = "RSS"}
                );

                routes.MapRoute(
                    name: "pages",
                    template: "{action}/{id?}",
                    defaults: new { controller = "Pages", action = "News"}
                );

                routes.MapRoute(
                    name: "default",
                    template: "{controller}/{id?}",
                    defaults: new { controller = "Pages", action = "News"}
                );
            });
        }
    }
}
@davidfowl
Copy link
Member

davidfowl commented Aug 15, 2017

Can you show the exception?

PS: There are a bunch of things that can be cleaned up with your code in general but I'll discuss those after we figure out why your views can't be found.

@davidfowl
Copy link
Member

One more thing, I'd suggest turning up the logging verbosity. That should show you exactly what's happening.

@eat-sleep-code
Copy link
Author

eat-sleep-code commented Aug 15, 2017

@davidfowl: Here ya go:

System.InvalidOperationException: The view 'History' was not found. The following locations were searched:
/Views/Shared/History.en-US.cshtml
/Views/Shared/History.en.cshtml
/Views/Shared/History.cshtml
at Microsoft.AspNetCore.Mvc.ViewEngines.ViewEngineResult.EnsureSuccessful(IEnumerable`1 originalLocations)
at Microsoft.AspNetCore.Mvc.ViewResult.d__26.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__19.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__24.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResultExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__22.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__17.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__15.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Builder.RouterMiddleware.d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.d__7.MoveNext()

@davidfowl
Copy link
Member

@eat-sleep-code See my other suggesting about turning up the logging verbosity.

@eat-sleep-code
Copy link
Author

@davidfowl Can that be done through the config? If it requires a rebuild, I will have to wait until later as I am in the office now and the code for this site is at home.

@davidfowl
Copy link
Member

@eat-sleep-code It can be done through configuration in 2.0.0 but you didn't update your code to use any of the new APIs.

I'm assuming you also have views in the right folder? Can you show the layout on disk? Are you running behind IIS?

@eat-sleep-code
Copy link
Author

eat-sleep-code commented Aug 15, 2017

@davidfowl Do you have an outline of what code changes need to made to use the new APIs?

Yes it is behind IIS. (Windows Server 2016 Datacenter)

When I switched the DLL references in my project, I suddenly get a Framework.PrecompiledViews.dll file instead of the /Views folder.

image

This route, should (and used to) make it work.

routes.MapRoute(
  name: "history",
  template: "history/{page=1}/{sortFilter?}/{sortOrder?}",
  defaults: new { controller = "Pages", action = "History"}
);

@davidfowl
Copy link
Member

@davidfowl Do you have an outline of what code changes need to made to use the new APIs?

@eat-sleep-code https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/

Just to be clear, I'm not saying this is the reason for any failures. It's more about enabling logging by default (and the ability to change it via configuration).

When I switched the DLL references in my project, I suddenly get a Framework.PrecompiledViews.dll file instead of the /Views folder.

Yes, this is one of the new defaults. Any clues if anything here needs to be changed @pranavkm ?

@eat-sleep-code
Copy link
Author

@davidfowl Thanks for that migration reference. I will have to tackle that stuff tonight.

But you are right, nothing there seems to explain why the silly routes are not working.

@pranavkm
Copy link
Contributor

@eat-sleep-code could you include a file list tree /f from your app root?

@pranavkm
Copy link
Contributor

@eat-sleep-code - sorry, I was specifically looking for the apps Views directory which is missing here.

@eat-sleep-code
Copy link
Author

@pranavkm Yeah, doesn't get created. Instead I have the Framework.PrecompiledViews.dll.

@pranavkm
Copy link
Contributor

I was looking for the views directory in your application root (not the output). Alternatively if you could share your application that would also work.

@eat-sleep-code
Copy link
Author

@pranavkm I will provide a repo tonight when I get home.

@eat-sleep-code
Copy link
Author

@pranavkm Invited you to the private repo I created with all the entire project.

@pranavkm
Copy link
Contributor

Thanks. I missed this when I was reading your initial issue. The route template you are using template: "history/{page=1}/{sortFilter?}/{sortOrder?}"adds a token named page to RouteValueDictionary which causes the view engine to incorrectly infer the route to be targeted at Razor Pages.

  1. Remove the PageViewLocationExpander that messes up the view lookup. As part of your Startup:
services.AddMvc().AddViewLocalization().AddDataAnnotationsLocalization().AddRazorOptions(options =>
{
    options.ViewLocationExpanders.Remove(options.ViewLocationExpanders.First(f => f is Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageViewLocationExpander));
});
  1. Modify the name of your route parameter. For instance, change the template from template: "history/{page=1}/{sortFilter?}/{sortOrder?}" to template: "history/{pageId=1}/{sortFilter?}/{sortOrder?}". You'd have to make corresponding changes to your action to either rename the action argument (page -> pageId) or use FromRoute { Name = "page" } to keep the existing name.

@rynowak, am I missing something a more obvious solution? Would we need to do anything additional to specifically address this breaking behavior?

@eat-sleep-code
Copy link
Author

@pranavkm Just confirming, I would need to complete both steps to fix this correct?

@pranavkm
Copy link
Contributor

@eat-sleep-code doing either one of these should fix your issue.

@eat-sleep-code
Copy link
Author

eat-sleep-code commented Aug 16, 2017

@pranavkm Thanks!

This appears to have fixed this issue. This probably should be at least documented somewhere to help out other folks who might encounter this scenario.

PS: I took option 1 since I am not using RazorPages. Option 2 seemed to wreak some havoc on my paging module as it seems to expect "page" not "pageId" (but otherwise Option 2 would have worked fine at fixing my issue).

@davidfowl
Copy link
Member

@pranavkm isn't this a breaking change?

@jeffputz
Copy link

I'm having a similar issue, but not using precompiled views. My generic area routes like {area:exists}/{controller=Home}/{action=Index}/{id?} are working fine, but specific routes like:

"Forums/Recent/{page?}", new { controller = "Forum", action = "Recent", page = 1, Area = "Forums" }

are not. The crazy thing is that the actions are being executed, it just can't find the view, and appears to only look in shared locations:

InvalidOperationException: The view 'Recent' was not found. The following locations were searched:
/Areas/Forums/Views/Shared/Recent.cshtml
/Views/Shared/Recent.cshtml

These functioned as expected in v1.1. In the above example, the view is absolutely in /Areas/Forums/Views/Forum/Recent.cshtml and the controller is in the Forums area called ForumController and action Recent(page = 1). The controllers are in a separate project, but I don't imagine that matters.

@pranavkm pranavkm added bug and removed investigate labels Aug 17, 2017
@pranavkm pranavkm added this to the 2.0.1 milestone Aug 17, 2017
@eat-sleep-code
Copy link
Author

@jeffputz It appears that having an attribute of "page" in a route causes the issue. It looks as this has now been logged as a bug, but in the meantime -- if you aren't using the new Razor Pages, try using the "Remove the PageViewLocationExpander" option / option 1 that @pranavkm suggested above.

@jeffputz
Copy link

That did it. That's an unfortunate bug. Thank you for your help, everyone!

@fredplus-develop
Copy link

Good job I found this thread:)
I thought I was losing it....
Especially after I had read that upgrading to NetCore 2 was "easy"

@M-S-M-A-S
Copy link

I have the same bug but your solution doesn't fix it

routes.MapRoute(
                    name: null,
                    template: "Community/Page{pageID}",
                    defaults: new { Controller = "Forum", action = "Home" });
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Forum}/{action=Home}/{id?}");
                routes.MapRoute(
                    name: null,
                    template: "",
                    defaults: new { controller = "Forum", action = "Home", pageID = 1 });
                routes.MapRoute(name: null, template: "{controller}/{action}/{id?}");

this issue happen because i'm using tag helper

    [HtmlAttributeName(DictionaryAttributePrefix = "page-url-")]
      public Dictionary<string, object> pageUrlValues { set; get; }
      public override void Process(TagHelperContext context, TagHelperOutput output)
      {
        pageUrlValues["**page**"] = i;

if i changed page to pageid the url Community/Page3 display page1
and if i used it as page, now when i navigate between pages they update but i get page1?page=3
note that since it show ?page=3 now my mapping is useless

@pranavkm pranavkm changed the title .NET Core 2.0 route configuration being ignored Using "page" route parameter in convention routing fails with Asp.Net Core Mvc 2.0 Sep 11, 2017
AndreiMaz added a commit to nopSolutions/nopCommerce that referenced this issue Oct 4, 2017
@BingZL1983
Copy link

Hi I have the same issue with routing. The following code worked, but the result looks like this: http://localhost:51409/?page=2, which is not ideal
routes.MapRoute(
name: null,
template: "",
defaults: new { controller = "Product", action = "List" }
);

but when I add {page} variable into Template as following: then the view List is not found.
routes.MapRoute(
name: null,
template: "Page{page:int}",
defaults: new { controller = "Product", action = "List" }
);

Can anyone help me out? Thank you!

@davidfowl
Copy link
Member

Change route name “page” to something else

@BingZL1983
Copy link

BingZL1983 commented Nov 14, 2017

Hi Davidfowl

Thank you for your response. I have change variable "page" to "pg" as following.

public ViewResult List(string category, int pg = 1)
=> View(new ProductsListViewModel{
Products = repository.Products
.Where(p => category == null || p.Category == category)
.OrderBy(p => p.ProductID)
.Skip((pg - 1) * PageSize)
.Take(PageSize),
PagingInfo = new PagingInfo
{
CurrentPage = pg,
ItemsPerPage = PageSize,
TotalItems = category == null ?
repository.Products.Count() :
repository.Products.Where(e => e.Category == category).Count()
},
CurrentCategory = category
});

routes.MapRoute(
name: null,
template: "{pg}",
defaults: new { controller = "Product", action = "List" }
);

it still didn't work. Do you think it is a bug in ASP Net Core 2.0?

FYI, I started a web app project using empty template. Thank you!

@Temoxa
Copy link

Temoxa commented Nov 30, 2017

Hi, I have the same problem BIngZL1983...((

use ASP Net Core 2.0,

but i resolved problem change parametr "page" in routes.... =)

@pranavkm
Copy link
Contributor

@Temoxa, the issue was fixed as part of the 2.0.3 release. Upgrading your package reference - Microsoft.AspNetCore.All to 2.0.3 in CoreCLR projects or Microsoft.AspNetCore.Mvc to 2.0.1 in desktop projects should address this without requiring code changes.

@Temoxa
Copy link

Temoxa commented Nov 30, 2017

Today upgrade to 2.0.3 , but problem saves....(

@serpent5
Copy link

serpent5 commented Dec 16, 2017

I see a similar issue, but related to using page as a query-string parameter inside of a Razor Pages model. e.g.:

public void OnGet(string page)

Using this, page is set to the path for the current page, which is unexpected. I'm seeing this when using Microsoft.AspNetCore.All 2.0.3 too.

The problem first showed up when using int page, which always comes out as 0 due to the difference in data type when binding.

As suggested, changing this to something like pageNumber is a solution, but that's going to be a pain when using a convention of e.g. int page, int pageSize for pagination.

@pranavkm
Copy link
Contributor

@Temoxa could you file a new issue that details the issue you're running in to?
@serpent5 the term page like the terms controller, action or area is reserved by Mvc for routing and it's attempting to bind the route value. Unfortunately, there isn't a very good solution to this outside of using a different name.

@Temoxa
Copy link

Temoxa commented Dec 19, 2017

My problem is resolved in version 2.0.3

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

10 participants