SUMMARY:
I'm reviewing the route mapping for a site I've recently begun work on. I've encountered a route that I'm not familiar with:
RouteTable.Routes.MapRoute(NamedRoutes.ROUTE_NAME, "urlSegment1/urlSegment2", new { });
Notice that the "default" parameter for MapRoute is an empty object. This is normally where I would specify my controller, action, and any parameters. I've been googling around and am finding that I'm not asking the right questions to produce the answer I'm looking for.
QUESTION:
How does MVC routing behave when the "defaults" parameter of MapRoute is an empty object? An answer would be great. Supporting docs would be even better.
EDIT:
The actual route being used is:
routes.MapRoute(NamedRoutes.BROWSE_MEN, "browse/Mens", new { } });
And the problem is occurring when generating the URL using:
Html.BeginForm("Add", "Signup", FormMethod.Post, new { id = "signup", enctype = "multipart/form-data" })
They are mapped as stings and in this case matches everything. The defaults are only there if one of the controller/action parameters are not supplied by the url. In this case no defaults are meaningless. It is actually catching everything string/string.
The route you have there is will match a request that contains exactly the two segments in the url ex. http://localhost/urlSegment1/urlSegment2 and returns a 404. I guess you have this route defined to avoid the following routes handle this request.
When you don't specify a controller, either as URL parameter (token) or default value, you should get an InvalidOperationException:
The matched route does not include a 'controller' route value, which
is required.
Same for action. That is for incoming request. For URL generation there's no requirement for controller or action.
You don't have to specify defaults.
So it works as if there were no default object specified
Related
In my ASP.NET Core 2.1 project, I've noticed that my Html.ActionLinks and Html.Actions, that do not have an id set, are automatically including the id from the current URL.
For example, given the following Html.ActionLink in a view:
<li>#Html.ActionLink("Apply Online", "Apply-Online", "Careers")</li>
If the URL of the current page is localhost:1234/careers/apply-online/53, then when I inspect the href of the above Html.ActionLink in my navigation, it includes the "53" in the generated URL (even though it wasn't set in my view).
I can prevent this from happening if I explicitly set the id to nothing, like this:
<li>#Html.ActionLink("Apply Online", "Apply-Online", "Careers", new { id = "" })</li>
However, is there a way to stop this across the board without having to update all my action links?
I'm getting weird behavior, where if two controllers have the same action name (such as Index) and the URL for one has an id, it adds the same id to all of the other actions with a matching name on the page.
Here's my routing in Startup.cs:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
UPDATE
According to the documentation, it appears to be AmbientValues that is causing this behavior:
The second parameter to the VirtualPathContext constructor is a
collection of ambient values. Ambient values provide convenience by
limiting the number of values a developer must specify within a
certain request context. The current route values of the current
request are considered ambient values for link generation. In an
ASP.NET Core MVC app if you are in the About action of the
HomeController, you don't need to specify the controller route value
to link to the Index action—the ambient value of Home is used.
Ambient values that don't match a parameter are ignored, and ambient
values are also ignored when an explicitly provided value overrides
it, going from left to right in the URL.
So this seems to explain why, I can prevent the id from being automatically set if I explicitly set it to { id = "" } in my Html.ActionLink.
My question is, is there a way to set id to not use ambient values by default, perhaps in my MapRoute? And only use the id if it is explicitly set to a value in an Action or ActionLink?
Have you tried TagHelpers ?
TagHelpers remplace HtmlHelpers, they are more readable and easy to use.
For example, with HtmlHelpers the links were written like that:
#Html.ActionLink("Apply Online", "Apply-Online", "Careers")
It's remplaced by...
<a asp-area="" asp-controller="Careers" asp-action="Apply-Online"
asp-route-id="53"
asp-route-toto="foo">
Apply Online
</a>
<!-- expected: /Careers/Apply-Online/53?toto=foo -->
Obviously, if you remove asp-route-id, "53" is removed and if you remove asp-route-toto, the query string is removed too.
I am currently on the page /Customer/Edit/13244.
When I use #Url.Action("Edit", "Customer") on the same page it returns me /Customer/Edit/13244, but I want it to return /Customer/Edit/.
Kindly tell me how to fix this issue.
This is a "feature" of MVC that many people find unnatural and was previously reported as a bug.
Microsoft's official response:
Ultimately if you want the most control over what gets generated for a URL there are a few options to consider:
Use named routes to ensure that only the route you want will get used to generate the URL (this is often a good practice, though it won't help in this particular scenario)
Specify all route parameters explicitly - even the values that you want to be empty. That is one way to solve this particular problem.
Instead of using Routing to generate the URLs, you can use Razor's ~/ syntax or call Url.Content("~/someurl") to ensure that no extra (or unexpected) processing will happen to the URL you're trying to generate.
Actually, this bug only rears its ugly head when you try to re-purpose an action method name. If you use a different action method name other than Edit in the case where it is not followed by id, this problem will magically disappear.
You will need to use (assuming your using the default route with id = UrlParameter.Optional
#Url.Action("Edit", "Customer", new { id = "" })
I've been having some problems getting redirects after login to work how I want. So I came up with the idea to store the current page in the viewbag and use that to redirect, so if the page is mydomain.com/debate/1 I end up with "/debate/1" stored in the viewbad but when I try to redirect its giving me this complaint
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult DebateDetails(Int32)' in 'PoliticalDebate.Controllers.DebateController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters
However If I manually type in mydomain.com/Debate/1 it works as expected.
Is there some way to get Redirect to work how I want ?
Since I don't see any code, I can't comment on the way you are trying to do it (which isn't working). On future posts, please post your code. This is one way how you can redirect if you are simply redirecting to the default action on the controller:
return this.RedirectToAction("Index", new { id = debateDetailsID } );
Although it's very hard to tell what you are truly trying to do because you mention debate/1 yet the method being called is DebateDetails which doesn't match (unless you've changed the default routes, again I don't know, there's no code).
Update
According to your comment, you have an error in your MapRoute. Your MapRoute should look like:
routes.MapRoute("Debate Details",
"debate/{id}",
new { controller = "Debate",
action = "DebateDetails",
// this id value is missing
// so it's not being passed to the controller
id = UrlParameter.Optional } );
the answer is there in the complaint, in this particular case you're sending a parameter so, checkout if this is specified
your code must look like redirectToAction("nameOfAction", new {id = yourIdOnViewBag}
This has probably been asked already - if so sorry! I couldn't find it.
I am unsure as to how asp is able to decide when to use a query string and "normal looking paths" (Embedded values)
Take this for example:
routes.MapRoute(
"SomePage",
"Net/Fix/{value}",
new { controller = "Net", action = "Index" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
I don't know how to explain - I will try.. If I am wrong please explain it
Question 1.
Is the first argument in mapRoute so that we can specify which routing we want to take place when using hyperlinks?
Question 2.
What does the second argument do?
It appears as if the second argument gives you the option of specifying how the routing should occur as below: "Net/Fix/hello" or by specifying placeholders in the form of {controller}/{action}/{somevar}
Question 3:
I assume if nothing is used in question 2 scenario - this specifies default routing that should take place?
Question 4:
How does ASP.NET infer whether to use a query string or an embedded value..
Because for example when I decide to call my page
http:/localhost:portno/Net/Fix/hello
It dutifully prints hello.. but when I do this
http:/localhost:portno/Net/Index/hello
It doesn't work.. unless I do
http:/localhost:portno/Net/Index?value=hello..
Question is... why?!!!
I hope questions were clear.. I will reply to answers (if any later).
The first argument is a route name. Each route should have a unique name, and they can be used for creating links, to assure a link is based on a certain route. It's not important in your case of matching a route.
The second argument is a matching pattern. Literal values are shown in clear, and parameterized values inside curly braces. {}. The parameterized values are not just for specifying the location of a parameter, but also the name of it.
I'm not sure offhand why you would define a route without any matching pattern. Does such an overload of MapRoute() exist?
The reason you get the behavior you do with this url: http:/localhost:portno/Net/Index?value=hello It matches the second (the default) route, not the first.
However, look at the second route pattern:
"{controller}/{action}/{id}"
The controller is the first parameter, action is the second. So with your URL, that request is routed to the Net controller, Index action. the same as your first example.
Because the query string contains a value parameter, that still gets passed to the action method. And it just so happens your action method has a string parameter named value, so it works.
I'm curious whether this is possible, or as I suspect, by design not.
In an ASP.NET MVC project I have multiple routes like this:
new Route(
url, // This can be arbitrary
new RouteValueDictionary {
{"area", "MyArea"},
{"controller", "MyController"},
{"action", "Index"}
},
new RouteValueDictionary(),
new RouteValueDictionary {
{"area", "MyArea"},
},
new MvcRouteHandler()))
I'd like to generate urls (or links) in the (Razor) views used by the actions of MyController. These urls should point to another action of MyController.
Now the problem is, there are multiple routes like above registered under different urls, so simply calling Html.ActionLink() or Url.Action() with the current route values yields a link that points to the url that's route first matches it. That's not necessarily the url the action is currently invoked from.
So basically what I'd like is take the current route and substitute the action with another one. I couldn't find any way to do that.
The urls can be arbitrary, but if necessary, constraints can be applied, e.g. so that the url must contain an action token. Actually all of them currently do, so urls have the following structure:
/AnotherArea/SubSegment/{action} // Routes point from other areas to MyArea/MyController
These urls are there in Html.ViewContext.RouteData.Route.Url of the view, so that action token should be changed somehow when generating a new url. (Well, one could do that with string replacement, but I guess if there is a solution, it should be better than that.)
Thank you for your time!
Now I found a solution, pretty simple:
#Url.RouteUrl("RouteName", new { Action = "OtherAction" })
However this implies the knowledge of the currently used route's name. Since this isn't stored in the Route object itself I opted with the kind of hackish solution of storing the name in the route's DataTokens dictionary. That seemingly doesn't harm and since routes are filled through a service this convention of using DataTokens doesn't need to be kept in mind.
I'm wondering if there's a better solution, though.