Where is Url.Action getting these values from? - c#

I'm trying to create a link to the following GET action with Url.Action:
public class FooController : Controller
{
[HttpGet]
[Route("Foo/Apply/BarDetails/Part/{id}")]
public IActionResult BarDetails(int id)
{
}
}
And in my view I'm creating a URL with
Url.Action("BarDetails", "Foo", new {id = 1})
However this generates the URL Foo/Apply/BarDetails/Part/10?id=1 which is definitely not what I want! Removing the values parameter simply removes the query string part of the URL. The only route I have registered at startup is
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
I've had a look at the ASP.NET Core source and I suspect that the "10" part of the URL is coming from a "1" + "0" operation somewhere as the routing code is split across 2+ repos it's hard to follow. Does anyone know how the last part of this URL is being generated and how I can fix it to match the id provided in the route values?

It turns out that routing has changed between MVC5 (where this controller was written) and ASP.NET Core 2. It's also related to the way I've structured my controller. Basically at some point the list of possible routes gets sorted by the URL so Foo/Apply/BarDetails/Part/10 gets sorted to the top of the list and as this matches the requested URL, it gets returned. For the full details, see this GitHub issue.

You can set maximum and minimum range of optional route parameter in attribute based routing. Follow the code below
enter code here [Route("Licence/Apply/Details/Part/{id:int:max(1000):min(10)}}")]

Related

Url.Action routes to the wrong controller

I have two controllers, one Web API controller and the other a MVC controller. These two controllers have the same name and the same action name, though the one in API controller is a post while the one in MVC is a get, as shown here.
This is the API controller:
[Route("api/[controller]")]
public class SameNameController : ControllerBase
{
[HttpPost]
public IEnumerable<*className*> SameNameAction([FromBody] *typeName*[] data)
{
// Detail implementation
}
}
And this is the MVC controller:
public class SameNameController: Controller
{
public ActionResult SameNameAction(int? page, int? pageSize)
{
//Detail implementation
}
}
I'm using X.PagedList to generate pagination. When I click any of pages, I receive an error
HTTP 405 - this page isn't working
Upon further inspection, I realized that the URL generated is the issue.
Here is the problematic part of the view.
#Html.PagedListPager((IPagedList)Model, page => Url.Action("SameNameAction", "SameNameController", new { page, pageSize= 5}))
Here is the routing definition in program.cs. As you can see, it's just basic stuff.
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
I expect that Url.Action would generate an url to the get action of the MVC controller. Instead, it generates an url to the post action of the Web API controller.
This is what I got:
2
This is what I expect:
2
I've never experienced Url.Action generating routing to an API controller. I'm not sure this is due to pagelist combined with url.action or url.action alone.
I can change the action name of the API controller, and the URL is generated as expected. However, this is not the issue. I would like to know why it map to the API controller and not the MVC controller to begin with.
I expect that Url.Action would generate an url to the get action of
the MVC controller. Instead, it generates an url to the post action of
the Web API controller.
Based on your scenario, I have checked your code between the line. As you know \[Route("")\] Attribute always has the higher precedence in routing therefore, this output/api/SameNameController?page=2&pageSize=5is obvious. However, using Url.RouteUrl HTML helper you can overcome your issue.
Solution:
#Html.PagedListPager((IPagedList)Model, page => Url.RouteUrl("default", new{httproute = "", controller="SameName",action="SameNameAction",page=1, pageSize= 5}))
Note: Please fit the code as per your environment.The exact code should be as folloing:
#Url.RouteUrl("default", new{httproute = "", controller="SameName",action="SameNameAction",page=1, pageSize= 5})
Output:
Hope it will guide you accordingly.

Generate URL Without Index Using Tag Helpers

I have an action link that looks like this
<a asp-controller="Complaint" asp-action="Index" asp-route-id="#complaint.Id">
This will then generate the link /Complaints/Index/be8cd27e-937f-4b7d-9004-e6894d1eebea
I'd like the link to not have Index. I've tried to add a new route to Startup but with no success.
routes.MapRoute(
"defaultNoAction",
"{controller=Complaint}/{action=Index}/{id}");
I can't find any documentation to see if it's possible to generate the URL with tag helpers without Index. Perhaps someone here knows how?
Decorate your controller's action with a route attribute that would have the route you need.
[Route("Complaints/{id}")]
public IActionResult Index(string id)
{
return View();
}
This way your controller's action becomes the default one. Please make sure other methods have different signature.
UPDATE
To set up routing in Startup class it's important to define the order of routing rules correctly. Per ASP.NET Routing Documentation:
The route collection is processed in order. Requests look for a match in the
route collection by URL matching. Responses use routing to generate
URLs.
This means your specific rules should go first, and the default route template should go afterwards like a catch-all rule. In your case the following should do the trick.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "complaints",
template: "complaints/{id}",
defaults: new { controller = "Complaints", action = "Index" });
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

How routing works in ASP.NET MVC4

I'm developing an ASP.NET MVC4 application and I'm new. I have two MapRoutes for routing.
routes.MapRoute("Char",
"ABC/Alpha/{number1}",
defaults: new { Controller = "ABC", action = "Alpha" });
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id=UrlParameter.Optional }
);
I didn't assign any default value to the number1 in the MapRoutes. But I know it is mandatory because it is in the URL path.
If I didn't provide any parameter in the url (when I run the app), I should get an error but I didn't.
Eg.:
http://localhost:32066/ABC/alpha/value---- getting desired output.
http://localhost:32066/abc/alpha/ ---- expected an error but there is no error.
Can anyone help me?
It doesn't match the first route, but it matches the second route. So it gets the controller/action = ABC/Alpha, and Id is optional.
Kyle is right, the second route (which is the global default) matches the url which is valid hence you don't see an error. I am guessing you are following one of the official Microsoft training videos that simulates an error in the request due to bad url that does not honor the route path.
To simulate the error, remove the default route, compile the project and run. Only this time specify the second url you have listed you should see the error.
Hope this helps.
The Route path is checked from top to bottom,
so in the first case
{Controller}/{Action}/{ID}
is passed by you (mention the id is must here) so the First routepath works
but Magic you was expecting the second case to throw an error, and it should have because if
your Action is like
public ActionResult ActionName(int id)
{
//some Code
}
then for sure it would have thrown error like Action
"Cannot convert null to 'int' because it is a non-nullable value type"
and i'm Pretty sure your Action will be having either
public ActionResult(int? id)
{//Some Code here}
or
Public ActionResult()
{
//Some Code here
}

ASP.NET MVC 3 Url.Action matching

I have the following two routes defined:
routes.MapRoute(null, "" // ~/
,new { controller="Products", action="List", page=1 });
routes.MapRoute(null, "{category}/Page{page}" // ~/CategoryName/Page21
, new { controller = "Products", action = "List", page = 1 }
, new { page = #"\d+" } //page must be numerical
);
I am generating a URL using this code in the view being used by ProductsController: Url.Action("List", new {page=p, category=Model.CurrentCategory})
With the current configuration, I get this URL: /Riding/Page2
However, if I omit the default page parameter from the first route, I get this URL instead: /?category=Riding&page=2
It seems to me that Url.Action() can match both routes and is making a decision to use the second route if I have the default page parameter specified in the first route but is choosing to use the first route if that parameter is omitted.
Given that I am supplying a value for page parameter, why would eliminating it from the route's defaults make a difference in the URL I get back?
Thanks!
Try installing the NuGet package Glimpse. It has excellent route debugging, it should be able to help you.
Here's a blog post by Scott Hanselan on how to use it: NuGet Package of the Week #5

How to define this route in ASP.NET MVC?

I have a controller named Movie, with an action named ByYear, which takes the year as a parameter :
public ActionResult ByYear(int year)
{
ViewData["Title"] = string.Format("Movies released in {0}", year);
var repository = MvcApplication.GetRepository();
var movies = repository.Medias
.OfType<Movie>()
.Where(m => m.Year == year);
return View("Index", movies);
}
I'd like to access this action with the following URL : /Movie/ByYear/{year}, but the only valid route for this action is this : /Movie/ByYear?year={year}.
I tried to add new routes in my application's RegisterRoutes method, but I can't find a way to get the desired result...
Could anyone tell me how to achieve that ?
Note: this is actually very similar to this question, but no answer was accepted, and the highest voted answer makes no sense to me as I'm completely new to MVC...
Change the name of your parameter year to id and this will match the default route that MVC adds to your project.
So for further clarification, let's take a look at the default route added by ASP.NET MVC:
routes.MapRoute(
"default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
In this route you can see three tokens that are named specifically for controller, action, and the third token which is passed to the action is id. When a request comes into your application, ASP.NET MVC will analyze the routes that are currently mapped and try to find a method signature that matches them by using reflection against your controllers.
When it looks at your Movie controller, it sees an action called ByYear, however that method takes an integer called year, not id. This is why you end up with something like /Movie/ByYear?year={year} when you create an ActionLink for that particular Action. So to fix this, you have two options:
The first and most simple method to fix this is to just change the method signature for your Action to accept a parameter named id which is what I recommended above. This will work fine, but I can see where it might cause a little bit of confusion when you go back to that source later and wonder why you called that parameter id.
The second method is to add another route that matches that method signature. To do this, you should open your Global.asax and just add the following (untested, but should work):
routes.MapRoute(
"MoviesByYear",
"Movies/ByYear/{year}",
new { controller = "Movie", action = "ByYear" }
);
This route is hard-coded, yes, but it won't break the other routes in your system, and it will allow you to call the method parameter year.
EDIT 2: Another thing to note is that the routing engine will stop on the first route it finds that matches your request, so any custom routes like this should be added before the default route so you are sure they will be found.
OK, I just found out how to do it. I just had to create the new route before the default route... I didn't think the order had any significance
routes.MapRoute(
"MovieByYear", // Route name
"Movie/ByYear/{year}", // URL with parameters
new { controller = "Movie", action = "ByYear" } // Parameter defaults
);
EDIT: Isn't there a simpler way ? (not involving renaming the parameters). I'd like to be able to do something like that :
[Route("/Movie/ByYear/{year}")]
public ActionResult ByYear(int year)
{
...
Design considerations aside, if you did not want to rename the parameter, you could add something like the route below, which enforces having the year parameter
routes.MapRoute(
"MovieByYear", // Route name
"Movie/ByYear/{year}", // URL with parameters
new { controller = "Movie", action = "ByYear" },
new { year = #"\d+" } // Parameter defaults
);

Categories