Html.RouteLink href null despite route (seeming) to work - asp.net-mvc-3

I have a route (which works):
routes.MapRoute(
"Details", // Route name
"{controller}/{id}", // URL with parameters
new { controller = "Advisors", action = "Details", id = UrlParameter.Optional }, // Parameter defaults
new { id = new GuidConstraint() } // Constraint
);
And a link:
#Html.RouteLink(#item.FullName, "Details", new { controller = "Advisors", id = item.Id })
However the link is rendering as:
David Wick
Basically my goal here is to have routes that are {controller}/{id} and the links rendered are also in that format. When using Html.ActionLink() links render as {controller}/{action}/{id} and Html.RenderRoute() doesn't seem to work at all, despite the route working.
What's going on here?

I was able to correct this issue just now by changing "item.Id" to "item.Id.ToString()". Probably not the ideal solution, but good enough for me for now.

I had a similar issue (I think) and it was actually the route constraint that was failing because the IRouteConstraint was checking for
routeDirection == RouteDirection.IncomingRequest
whereas the RouteLink HTML helper will actually produce
routeDirection == RouteDirection.UrlGeneration

Related

Ajax Sorting and Filtering Is Not Working in MVC

On each index view page which contain list, I'm using ASP.NET MVC AJAX to sort and filter the list. The list is in the partial view. Everything looks so fine until I have a view with a parameter (reference key/FK)
I don't add any routes, just using the default route:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
so the url is http://localhost:49458/TimeKeeper/New?billingID=7. If the url in that format, the AJAX sort and filter are not working. I tried to add a new route:
routes.MapRoute(
name: "TimeKeeperNew",
url: "TimeKeeper/New/{billingID}",
defaults: new { controller = "TimeKeeper", action = "New", billingID = "" }
);
so the url become: http://localhost:49458/TimeKeeper/New/7.
Now, the ajax sort and filter are working.
Is there anyone can explain me, what's the problem? Did I use the correct way (by adding a new route) or is there any other way?
I don't even understand why you are saying primary key as MVC has no concept of this.
With only (assuming for the duration of this answer until the break):
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home",
action = "Index",
id = UrlParameter.Optional }
);
Any route that does not define id will be appended to the url with the value.
Url.Action("New", "TimeKeeper", new { billingID = 7 })
Always will produce
http://localhost:49458/TimeKeeper/New?billingID=7
Because "billingID" != "id"
So your options are another MapRoute which I would not recommend, or use Id:
Url.Action("New", "TimeKeeper", new { id = 7 })
which always produces:
http://localhost:49458/TimeKeeper/New/7
Optionally:
public class TimerKeeperController
{
public ActionResult New(string id)
{
int billingId;
if (!string.TryParse(id, out billingId)
{
return RedirectToAction("BadBillingId")
}
....
}
}
BREAK
What about if there are 2 parameters, let's say billingID and clientGroupID? I don't quite understand routing in depth, could you help me to explain this in the answer?
Now you need another MapRoute:
routes.MapRoute(
name: "Default2",
url: "{controller}/{action}/{id}/{id2}",
defaults: new { controller = "Home",
action = "Index",
id = UrlParameter.Optional,
{id2} = UrlParameter.Optional }
);
And it is required to be before or after the previous MapRoute because anything that would work for this route will work for the previous route, thus this route will never be called. I can't exactly remember which way it goes at the moment, but if you test it, you'll figure it out quickly.
Then you can have:
http://localhost:49458/TimeKeeper/Copy/7/8
with:
public ActionResult Copy(string id, string id2)
{
....
}
notes
Yes you don't have to use a string and parse the values, you could use constraints on the MapRoute or just use Int and throw errors if someone manually types http://localhost:49458/TimeKeeper/New/Bacon.

Url.Action does not give the expected result. Unwanted route values remain

I have a MVC3 app with several routes. Two of them are defined like this:
routes.MapRoute(
null,
"System/{name}", // URL with parameters
new { controller = "Systems", action = "Index" } // Parameter defaults
);
routes.MapRoute(
null,
"Carrier/{name}", // URL with parameters
new { controller = "Carriers", action = "Index" } // Parameter defaults
);
Now, in my menu I have two links to these routes that are created using Url.Action:
Url.Action("Index","Systems")
Url.Action("Index","Carriers")
Now when i start the application, everything seems fine, and the links in the menu show as /System/ and /Carrier/, and that is the expected value.
However, when i browse to for example /System/MySystem in the web page i would still want the links to point to the same place, but now they point to /System/MySystem and /Carrier/MySystem.
I have tried lots of things to keep the link from using the name from the route value, but to no avail. The strangest case i experienced was when i tried this:
Url.Action("Index","Systems", new{name = (string)null})
Now the link showed up as
/System?name=MySystem
Are there any good way here to make sure that the name value from the route values does not interfer with these links in any way?
As you noticed the Url. helpers reuse the previously given route parameters.
As a workaround (I hope that there is a more elegant solution...) you can remove the name entry from the RouteData.Values in your view:
So before calling you Url.Action in your view:
Url.Action("Index","Systems")
Url.Action("Index","Carriers")
Remove the prefilled name from the RequestContext:
#{
Request.RequestContext.RouteData.Values.Remove("name");
}
It's also an workaround but if you slightly modify your routes with providing a default null value for your name segment:
routes.MapRoute(
null,
"System/{name}", // URL with parameters
new { controller = "Systems", action = "Index", name = (string)null }
);
routes.MapRoute(
null,
"Carrier/{name}", // URL with parameters
new { controller = "Carriers", action = "Index", name = (string)null }
);
Your original solution ("nulling" the name in the Url.Action) will also work :
#Url.Action("Index", "Systems" , new {name = (string)null} )

Html ActionLink Method Sometimes includes the ID

I have two methods on my controller
Function FollowUp(id As String) As ActionResult
and
Function Details(id As String) As ActionResult
When I write <%:Html.ActionLink("FollowUp", "FollowUp")%> the generated link does not include the id (I get /Project/FollowUp)
But when I write <%:Html.ActionLink("Details", "Details")%> the generated link does include the id (I get /Project/Details/25)
Can anyone explain why this happens?
If you create a link for a routing that takes a parameter, if your current route data includes that variable, then the same value will be included in your URL. For example: if you had just the standard routing:
routes.MapRoute(
null,
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
And were currently at /home/index/25, then if you make a new link #Html.ActionLink("test","testing") then it would include the current id: /home/testing/25.
There are two ways I can think of this default id won't show up:
1) You have a different routing for "FollowUp" which doesn't include the {id} parameter.
routes.MapRoute(
null,
"Project/FollowUp",
new { controller = "Project", action = "FollowUp" }
);
2) You're creating the "FollowUp" link on a page that doesn't have an id currently included in the routing. (like home/index instead of home/index/25)

Routing legacy requests with and without querystring

(Before starting: I am aware of this and this. I'd like to find a more concise solution -if possible- for a slightly more specific problem)
I'm rewriting an old Webforms app in MVC. As usual, no permalinks should be broken.
I'm using standard {controller}/{action}/{id} routes. Legacy paths are usually SomePage.aspx?ID=xxx, and I have one particular case where Foo.aspx is a list of Bar (new URL: /Bar or /Bar/Index) and
Foo.aspx?ID=xxx is the Bar detail (new URL: /Bar/View/xxx)
One possible workaround is adding the following before the Default MapRoute:
routes.MapRoute("Bar View", "Foo.aspx",
new { controller = "Bar", action = "View" });
And then defining the corresponding action in BarController:
public ActionResult View(int? id)
{
if (id == null)
return RedirectToAction("Index");
return View();
}
There are two problems with this:
Now, if I create an ActionLink, it uses the legacy format
I'd like to handle this in the routes; making the id nullable and redirecting in the controller is just wrong
I'm fine with mapping the legacy URLs by hand (I don't need a generic solution and there are only about 8 pages)
This is a new project, so I'm not tied to anything.
I was able to solve this based on Dangerous' idea plus a constraint based on this answer.
My new route table is:
routes.MapRoute("Bar", "Bar/{action}/{id}",
new
{
controller = "Bar",
action = "Index",
id = UrlParameter.Optional
});
routes.MapRoute("Bar View", "Foo.aspx",
new {controller = "Bar", action = "View"},
new {id = new QueryStringConstraint()});
routes.MapRoute("Bar Index", "Foo.aspx",
new { controller = "Bar", action = "Index" });
routes.MapRoute("Default", /*...*/);
And the QueryStringConstraint couldn't be simpler:
public class QueryStringConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route,
string parameterName, RouteValueDictionary values,
RouteDirection routeDirection)
{
return httpContext.Request.QueryString.AllKeys
.Contains(parameterName, StringComparer.InvariantCultureIgnoreCase);
}
}
I believe if you specify the following routes:
routes.MapRoute(
null,
"Bar/{action}/{id}",
new { controller = "Bar", action = "View", id = UrlParameter.Optional },
new { action = "Index|Next" } //contrain route from being used by other action (if required)
);
routes.MapRoute(
null,
"Foo.aspx/{id}",
new { controller = "Bar", action = "View", id = UrlParameter.Optional }
);
//specify other routes here for the other legacy routes you have.
Then this should solve your first problem. If the user specifies Foo.aspx in the url then they will be taken to the View action.
If the action link:
#Html.ActionLink("Click me", "Index", "Bar")
is specified then the first route will be used (as the order matters).
However, I could not figure out how to specify if Foo.aspx?id=... then to go to one route else if Foo.aspx is specified then go to the other route. Therefore, I would check whether id is null in the action. However, if you do find this out I would very much like to know.
Hope this helps.

I can't get the #ActionLink to render the correct url to work with my MVC3 default routing

I'm trying to take advantage of the default routing so I get a URL without a query string parameter.
So, I've currently got this url:
http://www.mysite.Items/Edit?ItemID=19719
And I'm trying to get a URL like this:
http://www.mysite.Items/Edit/19719
The routing works, but I can't get the #Html.ActionLink method to produce the 2nd url.
Here is my razor code:
#Html.ActionLink("Edit", "Edit", new {item.ItemID}, new { id = "edit-" + item.ItemID })
The first argument is my link's text. The 2nd argument is the Action. 3rd is the ID Value and finally the last argument is and HTML attribute I use for some javascript I'm using.
Originally I had my 3rd Argument as
new {itemID = itemID}
This was when my Edit action expected an integer value named itemID as the parameter. I changed it to 'id' so the routing would work.
Ideally I would like a route that would pass the 19719 value to an action with the argument named itemID, but this is beyond the scope of this question.
Thanks in advance.
SOLVED
Thanks Darin Dimitrov for this solution.
I ended up leaving my html code and action arguments the way I had them originally. All that was really required was an update to my routes. I should note that I had to add my new route map before the default. Anyway, here is my route registration now that made this all work.
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("", "Items/{action}/{itemID}", new { controller = "Items", action = "Details", itemID = #"\d+" });
routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional } );
}
The default routes uses id as route token name, so you want:
#Html.ActionLink(
"Edit",
"Edit",
new { id = item.ItemID },
new { id = "edit-" + item.ItemID }
)
Notice new { id = item.ItemID } and not new {itemID = itemID} and not new {item.ItemID}.

Resources