ActionLink not working but RouteUrl does - asp.net-mvc

I have a route
// Sample URL: /Fixtures/Team/id
routes.MapRoute(
"Fixtures-by-TeamID",
"Fixtures/Team/{teamId}",
new { controller = "Fixtures", action = "GetByTeamID", },
new { teamId = #"\d{1,3}" }
);
and I am trying to use ActionLink in ASP.net MVC p5.
<%= Html.ActionLink(g.HomeTeam.TeamName, "Team", new { teamId = g.HomeTeam.TeamID })%>
However it is not working and giving me
Team A
If I use Url.RouteUrl i get the correct link.
<%=g.HomeTeam.TeamName%>
Team A
Any help would be great? Will this change in ASP.net MVC beta?
Thanks
Donald

In my experience, the only time action routes really work properly is when you only have the single default route. As soon as you start adding custom routes like yours above, action routes become... I guess "finicky" is the right word. The matching system doesn't work exactly how you'd expect it to.
In this case, you said the action was "Team", but this route doesn't match an action of "Team", it only matches "GetTeamByID". So the routing system keeps going and ends up giving you a route based on the default route. The teamId isn't explicitly part of your default route, so it ends up as a query parameter tacked on the end.
MVC Beta has already shipped, and this behavior is unchanged.
Also, don't you find the named route to be clearer anyway? Personally, I do.
I even go one step further and actually create route helpers for all my custom routes, which might look like this in your current example:
<%= g.HomeTeam.TeamName %>
Or even:
<%= Html.LinkToFixturesByTeam(g.HomeTeam) %>
where you can pull the values for name and ID directly from the model.

Try this:
// Sample URL: /Fixtures/Team/id
routes.MapRoute(
"Fixtures-by-TeamID",
"Fixtures/Team/{teamId}",
new { controller = "Fixtures", action = "Team", teamId = -1 }
);
your controller should look like:
public class FixturesController : BaseController // or whatever
{
/*...*/
public ActionResult Team(int teamId)
{
return View("Detail", Team.GetTeamById(teamId)) // or whatever
}
/*...*/
}
And your link would look like
<%= Html.ActionLink("Click here for the team details", "Team", "Fixtures", new { teamId = ViewModel.Data.Id /*orwhateverlol*/ }) %>
(I don't have MVC on this machine so this is all from memory; may have a syntax error or some arguments reversed).
Note your route map's path matches your 1)controller, 2) action 3) argument name. I've found the default action (third argument in MapRoute) works, whereas your overload of that method I've never seen before (may be a holdover from a previous release).
Also observe how your FixturesController matches the path (Fixtures) and the action name matches (Team), and the argument matches as well (teamId).
Lastly, your ActionLink's last argument must match your controller's arguments in name (teamId) and type.
Its a bit too "magical" at this point (there's LOTS of string comparisons going on in the background!). Hopefully this will improve over time. The old Expression style was MUCH MUCH better. You essentially called the method you wished to run, with the values you wished to pass it. I hope they bring that expression style back into the framework. Haack?

Have you tried this yet?
Html.ActionLink<FixturesController>(c => c.GetByTeamID(g.HomeTeam.TeamID), "Team")
Also
You might want to add action = "GetByTeamID" to your constraints.

When a parameter ("action" in this case) is defined only in defaults and not in the route url, it has to be an exact match (unless you force it to go against a particular route as in the RouteUrl case).
To make everything work as is right now, you could add another route to the list just below the above route:
routes.MapRoute(
"Fixtures-by-TeamID1",
"Fixtures/Team/{teamId}",
new { controller = "Fixtures", action = "Team", },
new { teamId = #"\d{1,3}" }
);
OR you could add the action parameter to the route url,
OR you could use the named route as you did.

Related

routing with just one of two parameters of controller

I have an actionresult with two parameter:
public ActionResult Index(int a,string b)
{
//some code
return View(b);
}
it creates this url automatically:
mysite.com/a=1&b=http://site.com/b=1
I just need to show first parameter "a" in my url:
mysite.com/a=1
I use the default route of MVC that creates in global.ascx:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
what should i do?
Thanks...
If you are seeing the "b" parameter bleed through from the current request, you can set the "b" parameter explicitly to empty string to avoid this behavior:
#Html.ActionLink("Home", "Index", "Home", new { a = 1, b = "" }, null)
I reported this "feature" as a bug, but the developers at Microsoft seem to think this behavior is supposed to make your URLs easier to configure, and they don't intend to fix it.
What you're seeing here is a feature of routing where "ambient" values (i.e. values that were detected in the incoming request's URL) are used to simplify (sometimes...) the generation of routes to other pages.
You can check our my answer (under the name "Eilon") in this StackOverflow post, where I explain the behavior in a bit more detail:
How Can I Stop ASP.Net MVC Html.ActionLink From Using Existing Route Values?
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.
Thanks,
Eilon

MVC .NET 4 MapRoute + ActionLink or RouteLink issue

Okay, so here's the deal. I've got controller called "Hotel" with view called "Index", where I'm trying to produce code allowing me to generate links in form of:
../Hotel?id=1
with ID passed as argument. To do so, I've tried using MapRoute:
#Html.RouteCollection.MapRoute("Hotel", "../{controller}/{id}", new { controller = "hotel" });
together with ActionLink:
#Html.ActionLink("More >>>", "", "Hotel", new { id = item.HotelId }, null)
But the outcome link goes like this:
Hotel/Index/1
Which leads to correct location, but burns visual consistency of all links at my website. I've tried RouteLink as well, but with no success.
Thanks in advance!
Do you want to make all links in the application use the standard querystring format for parameters named "id"? If so, removing the {id} from url and defaults object in the "Default" route should do that for you just fine.
If you want to limit it to the "Hotel" controller, you are on the right track w/ the custom route. First, make sure the custom route comes before the default route definition, and second, define it with nothing beyond the controller/action like:
routes.MapRoute(
"HotelRoute", // Route name
"Hotel/{action}/", // URL with parameters
new { controller="Hotel", action = "Index" } // Parameter defaults
);
Any params you pass in should then be appended to the query string as a name/value pair.

Setting up ASP.Net MVC 4 Routing with custom segment variables

I just began working on an application with a couple areas (basic grid master / details type system..) I'm looking into taking advantage of the nice routing features in MVC (4 specifically) and I'm "just not getting it" I presume.
Currently the only route defined is the basic one:
routes.MapRoute("Default",
"{controller}/{action}/{id}",
new { controller = "Account", action = "Index", id = UrlParameter.Optional }
);
which is fine, it works with the areas we have defined, so I'm assuming it must know where the user is and route to the appropriate controller based contextually on the location / area that the user is in.. so far nice..
Now, i'm trying to set up a new route that can handle
/someController/someAction/{statusName}
and specifically something like:
/OrderManager/List/New
AND
/OrderManager/List/Viewed
where "New" is the "New" status and have the Action Signature look like:
public ActionResult List(string statusName)
i was assuming I could just add the new route below the default one identifying "statusName" instead of Id, but of course, how the H would the routing mechanism know the difference between :
/controller1/action1/15
/controller2/action2/new
I did try adding a "static" route in the form of
routes.MapRoute("Default",
"ControllerName/ControllerAction/{statusName}",
new { statusName = UrlParameter.Optional }
);
I thought I could "hiJack" just that one route and do something special with it, but to know avail, the router stops at first match?? I'm assuming that was the wrong way to address this issue anyhow..
so now I'm going through the idea of getting to something like:
/somecustomroutename/somesortValue
ex.
/OrderManagerList/viewNew
where these routes would basically be "aliases". I had thought that adding the following route would do the trick:
routes.MapRoute("Default_List",
"OrderManagerList/{statusName}",
new {controller="OrderManager", action="List", statusName= UrlParameter.Optional }
);
with the associated action on the OrderManager controller:
public ActionResult List(string statusName)
no matter what I try, the argument is null, or the "resource cannot be found"
I know the controllers need to have a corresponding View file.. but that's not the issue here, the issue is my attempt at understanding the routing..
SO my questions.. fundamentally, what am I missing about the routing in MVC (4)? even some good articles for a simpleton like myself to understand?
my understanding; define a route, and map it's "endpoint".. however, i think i'm not understanding the assumptions that the machine is making..
anyhow, let me know if further explain / edit is required..
thanks in advance.
The basic principle of routes is that they are evaluated from the top down, and the routing systems uses the first match, not the best match.
The implication of this is that you must order your routes in order of specificity, with the most specific route first and the most general route last.
With this principle in mind, let’s look at your situation. Initially, you have only the default route defined:
routes.MapRoute("Default",
"{controller}/{action}/{id}",
new { controller = "Account", action = "Index", id = UrlParameter.Optional }
);
The URL pattern is "{controller}/{action}/{id}". By itself, this would match any three-segment URL, and would not match any URL that had less than three segments. However, the third input parameter is the default parameter, which defines defaults for the first and second segment and indicates that the third segment is optional. The next effect of this is to make the route match URL having 0,1, 2 or 3 segments.
Now you want to add a route where the third URL segment is mapped to a “statusName” parameter, and can handle URLs like:
OrderManager/List/New
OrderManager/List/Viewed
There are two basic approaches you can take here. You can 1) create a very specific route that will handle these two URLs only, or 2) you can try and create a more general route to handle the general case. Let’s look at the first case first. You can create a route as follows:
routes.MapRoute("", "OrderManager/List/{statusName}",
new { Controller = "OrderManager", Action = "List" });
Note that because this route is more specific than the default route, you must put this route before the default route.
If you want to have a more general route, you need to decide how this route will differ from the default route, since they both will match URLs having three segments. Let’s say you decide that the new route will accept anything that only contains letters in the third segment, leaving the default route to handle anything containing numbers. You can do this using route constraints. For example you could write a route as follows:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { id = #"^\d+$" });
routes.MapRoute(
name: "NewRoute",
url: "{controller}/{action}/{statusName}",
defaults: new { Controller = "OrderManager", Action = "List" },
constraints: new { statusName = "^[A-Za-z]+$" });
With these two routes, any three-segment URL having only letters in the third segment will put the third segment into a variable called “statusName”, whereas any URL with an integer in the third segment will put the third segment into a variable called “id”.
In any applications of real complexity, routes can get complicated, and it is very advantageous to write unit tests for your routes, to insure that you don’t screw things up when you add or modify a route.
For good references on routing, see Scott Sanderson's book or see the MSDN documentation

What do the curly braces mean in a route in MVC?

I'm setting up my routes with an MVC project but im a little confused about the curly braces...
If I have...
routes.MapRoute( "Music", "Music/{name}", new { } );
What is the purpose of the curly braces around name, does this get passed to something? Or does this map to something if I pass a default object in?
They are parameter names that are used in routing requests. For example the default route defines three of them:
{controller}/{action}/{id}
controller and action parameters are for finding your controller action. id parameter can be used as an input in those actions.
When you define a custom route you have to provide controller and action parameters. If they are not defined in your URL, you should provide default values so MVC knows what action to run when a request matches that route.
routes.MapRoute("Music",
"Music/{name}",
new { controller="Music", action="SomeAction" });
Other parameters like id or name like you defined can be used to provide input to actions. In your example, name parameter is passed to matching action like this:
public ActionResult SomeAction(string name)
{
//do something
}
The curlybraces indicate a kind of named wildcard.
The "Music/Index" route will only match the URL Music/Index and nothing else
The "Music/{Name}" route will match any URLs starting with Music, and having anything after the slash. It will match both the URLs Music/metallica and Music/madonna.
With the curly brace, you'll be able to pick up "metallica" or "madonna" from the above URLS as routevalues.
As a final example: With ASP.NET MVC, there's always a standard route. {controller}/{action}/{id}. This route will catch URLs like Music/genre/rock or Product/edit/5.
The resulting routevalues for these two will be:
controller=music, action=genre and id=rock for the first one
controller=product, action=edit and id=5 for the last one.
I'll try to provide a less contrived example.
Routes in ASP.NET MVC are placed into a dictionary, and when there's an incoming request, the MVC pipeline looks at the request and tries to determine what Controller and Action to route it to.
So let's say I have the following controllers: Home, Forum, and Article
And while we're at it, let's say I have the following actions: View, Edit, Create on both the Forum and Article controllers.
Those braces allow me to create one route for both:
routes.MapRoute("Viewing",
{controller}/{action}/{id},
new {controller = "Article", action="" }, //The article controller has precedence
new { controller = "Article|Forum" } //contrived for this example
);
Those braces mean that whatever controller they put in (as long as it's Article or Forum based on the Constraints), the same route works. This keeps me from having to have a route for each and every action in the Forum and Article controller.
I could have just as easily made two routes:
routes.MapRoute("Articles",
article/{action}/{id},
new {controller = "Article" } //The article controller has precedence
);
routes.MapRoute("Forums",
forum/{action}/{id},
new { controller = "forum" }
);
But there's duplication there that doesn't need to be there.
Routes are also pretty tricky things, in that order matters. The top route will be evaluated before the bottom route. If it matches the top route's structure, it will go to that action, even if that's not the right action.
Phil Haack has a Route Debugger that helps with this. And I've also taken his source code and modified it so that you can make it a control and put it on all your pages as a partial (and hopefully you will also put code on there that would only allow internal folks to see it).

ASP.Net MVC routing action name conflicts

We have a website that deals with artists and venues and we're developing it in ASP.net MVC.
We have our artist views in a folder (Views/Artists/..), an ArtistsController, ArtistsRepository and adhere to the REST action names such as Show, New, Delete etc.
When we first mocked up the site, everything worked well in our test environment as our test URLs were /artists/Show/1209
but we need to change this so the website appears as /artists/Madonna and /artists/Foo-Fighters etc
However, how can we distinguish between valid artist names and the names of the actions for that controller?! For example, artists/PostComment or artists/DeleteComment? I need to allow the routing to handle this. Our default Show route is:
routes.MapRoute(
"ArtistDefault",
"artists/{artistName}",
new { controller = "Artists", action = "Show", artistName = ""}
One way around this is for our website to visibly run on /artists, but have our controller renamed to the singular - ArtistController - as opposed to ArtistsController. That would go against the naming conventions we went with when we started (but hey!).
Do you have any other recommendations? If possible we could also route depending on the verbs (so PostComment would be a POST so we could perhaps route to that action), but I'm not sure if that is advisable let alone possible.
Thanks
The 4th parameter to MapRoute allows you to specify restrictions for values. You can add a route before this one that is for "artists/{action}/{id}" with a restriction on the valid values for action; failing to match one of your actions, it'll fall through to the next route which will match for artist name.
You would actually define multiple routes... the defined actions in your controller would go first with the default being at the bottom. I like to think of route definitions as a "big 'ole switch statement" where first rule satisfied wins..
routes.MapRoute(
"ArtistPostComment",
"artists/PostComment/{id}",
new { controller = "Artists", action = "PostComment", id = "" }
);
routes.MapRoute(
"ArtistDeleteComment",
"artists/DeleteComment/{id}",
new { controller = "Artists", action = "DeleteComment", id = "" }
);
routes.MapRoute(
"ArtistDefault",
"artists/{artistName}",
new { controller = "Artists", action = "Show", artistName = "" }
);

Resources