How do ASP.NET MVC Routes work? - asp.net-mvc

I have the following route's defined:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
// Added custom route here!
routes.MapRoute(
"CatchAll",
"{*catchall},"
new { controller = "Error", action = "NotFound" }
);
}
nothing new - that's the default ASP.NET MVC1 RegisterRoutes method, with one custom route added.
Now, if I goto the following url, i get a 404...
http://whatever/Home/MissingActionMethod
So there's no ActionMethod called MissingActionMethod in the HomeController. So, does this mean, if i goto the 1st route defined, above .. and fail to find an action .. do I then come back and try the second route? rinse-repeat?
Or once i match a route, i then try and execute that route .. and if i fail (ie, find the action is missing) .. then .. bad luck? boomski?
cheers!
EDIT/UPDATE:
Thanks heaps for the replies, but they are not reading my question properly :( I know
1) order of routes are important
b) haack's route debugger
but my question is not about that. I'm asking that .. if the first route is 'handled' .. but fails .. does it then go down the list to the next one?
So, in my example above. The first route called 'Default' is matched against the url/resource requested ... but when the framework tries to find an action, which is missing .. it 404's.
So .. does that mean the framework first matches the "default" route .. tries it .. fails .. goes BACK to the route list .. tries to find the next route that matches .. and finally fails so it then gives up?
Or it only finds the first and only the first route it matches .. and if it fails to find the controller and/or action .. then it just gives up there and then? (This is what i suspect). And if so .. how does it then figure out how to 404?
Update #2:
Phil Haack actually talks about my question, a bit ... but doesn't answer the part I was curious about -> how and where it determines a 404 resource not found.

Routes != Actions.
It goes like this - on incoming request, routing module searches for first route in route table that matches and then tries to call appropriate action.
If action is not found, request fails and returns 404 (it does NOT try to look for next route).
But it should be possible to extend framework in order to achieve this. My first guess - You could write your own RouteHandler.
RouteHandler
Not really specific to ASP.NET MVC, the RouteHandler is the component that decide what to do after the route has been selected. Obviously if you change the RouteHandler you end up handling the request without ASP.NET MVC, but this can be useful if you want to handle a route directly with some specific HttpHanlders or even with a classic WebForm.
Anyway - I wouldn't recommend it though. It's better to keep routing dumb.
After some quick googling - I'm not so optimistic about this anymore. :)

I don't think it will check the second route, because the first one specified is the default. I think if you switch them, it would check CatchAll, see that it doesn't match the route specified in the URL, and then fall back to the default, since you're only providing a controller name, not a route. I think if you wanted CatchAll to do anything at all, you'd have to hit http://whatever/CatchAll/Error/MissingActionMethod, and it would have to come before the default.
See this for more in-depth information.

You should try using the Phil Haack's route debugger from http://haacked.com/archive/2008/03/13/url-routing-debugger.aspx, to see what route is matching and why.

Related

Url.Action returning empty string in some environments

I have a Sitecore site and on 2 of my CD servers, Url.Action returns an empty string. This works locally and on 9 other servers ranging from dev to prod, CD and CM.
Deployment automation ensures that the exact same web.config is deployed to all environments; ditto for all other configs.
My controller inherits from SitecoreController properly. This is not isolated to a certain controller or action, this happens with all controllers and actions.
What would make Url.Action return an empty string in one environment and not others, with identical code?
What would make Url.Action return an empty string sometimes?
Specifically, route values that are derived from the current request.
Explanation
The Url.Action method is driven by the UrlHelper, which in turn is driven by routes. It uses route values to determine which route to use to build the URL. The routing framework attempts to match each route against the route values in the order they are registered until a match is found. If the routing framework reaches the end of the routing table and there is still no match, it returns an empty string (because there is no other reasonable default behavior).
On the other hand, if you call Url.Action and pass a route name, this narrows the possible matches to only 1 specific route (the named one). But the route values still need to match that route or you get the default empty string.
In general, all route values must match, but there are a couple of things that may make the behavior quirky:
Route values can be made optional. This means that the route value doesn't need to be present in order for the route to match.
If a route value is not supplied in the call to Url.Action, it may be supplied automatically if it exists in the current request.
This second quirk means that if Url.Action is put on a shared view and one request contains a route value to make it match a route, and another request doesn't contain that route value, in the latter case the URL may match another route or it may be an empty string.
Example
Say the routing configuration is setup like this:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "AboutWith4RouteValues",
url: "test/home/about/{foo}/{bar}",
defaults: new { controller = "Home", action = "About" });
routes.MapRoute(
name: "ContactWith4RouteValues",
url: "test/home/contact/{foo}/{bar}",
defaults: new { controller = "Home", action = "Contact", bar = UrlParameter.Optional });
routes.MapRoute(
name: "Home",
url: "",
defaults: new { controller = "Home", action = "Index" }
);
}
}
Also, let's say there is a link on the _Layout.cshtml page:
<a href='#Url.Action("About", "Home")'>About</a>
If you go to the home page in the browser (/), the resulting link URL will be:
<a>About</a>
This is because foo and bar are not present in the route values of the request so it doesn't match any of the registered routes.
Actually, Url.Action returns an empty string, but Razor optimizes away the empty href='' attribute.
On the other hand, if you put this URL in the browser:
/test/home/contact/arg1/arg2
The link URL is generated as:
<a href='/test/home/about/arg1/arg2'>About</a>
This is because both {foo} (with value arg1) and {bar} (with value arg2) are available in the current request. Note that the incoming request matches the ContactWith4RouteValues route, but when the link URL is generated it uses the AboutWith4RouteValues route. Since both foo and bar are present in the request, they are carried over to the generation of the URL.
Now, if the URL in the browser is changed to:
/test/home/contact/arg1
It still matches the ContactWith4RouteValues route because the last value is optional. However, the URL that is generated is now:
<a>About</a>
This is because foo has a value in the request, but bar has no value, the Url.Action generation request does not match AboutWith4RouteValues because bar is a required value in order to make it match. And since it also doesn't match the Home route, we have reached the end of the route table and the only logical thing to return is empty string.
Workaround
The simplest workaround to avoid these quirks of the current request is to manually specify the route values when calling Url.Action or other UrlHelper based methods (such as ActionLink, RedirectToRoute, etc).
<a href='#Url.Action("About", "Home", new { foo = Model.Foo, bar = Model.Bar })'>About</a>
This ensures those values are always present when building the URL even if they don't happen to be part of the current request.
I had this exact same issue and for me the issue had to do with Case-Sensitivity in my Routing.
Deploying the same site to two different Applications on the same IIS Web Server.
I used the same web-config and same VS Web Publish Settings.
Yet, my Url.Action was returning blanks (empty-strings) in Prod, but not in Dev.
After reviewing the code, something caught my eye.
I have an Area called WorkBench (upper-case "B").
In a few places (where Url.Action returned blank), I was passing in Workbench (lower-case "b").
I have some fancy logic in my RouteConfig.cs and Area Registration (i.e. WorkBenchAreaRegistration.cs).
In those files, I had Conditional Logic to determine what Environment the Application was running in.
I use the same Project to share code, but only want some Areas accessible in different Environments.
Even though Prod and Dev are different Environments, to debug this issue,
I altered them so they would temporarily appear the same.
The issue was fixed once I capitalized the "b" to "B" everywhere.
I still do not know why it would work on my local machine and in Dev, but not in Prod.
It should have behaved the same way in both Environments.
Again, same server, same publish, same web.config, same iis config, same application code, etc...
Sorry I don't have an explanation for this, but at least this is a possible fix you could try.
Hope this helps someone out there.
Update 02/08/2019: I had this problem again with a different link and for this one I realized I had an option set to access all Actions within an Area when one of my Debug variables was set to true, or when running in Prod. I removed this conditional logic in my AreaRegistration.cs file and it fixed the problem when running in a Staging envrionment.
The Lesson Learned here is that anytime you see a blank Href, you gotta check those Route Configs.

ASP.NET MVC AnchorLink not using route values?

I'm using AnchorLink on a very simple site with just two routes defined, one standard route and another area route for admin/{controller}/{action}/{id}. I'm at a URL like:
/admin/release/push/255
In that view I'm using:
#Html.AnchorLink("Confirm", "Confirm")
AnchorLink is rendering a link without the current request {id} included, ie. /admin/release/confirm! If I inspect the RouteValues I can see {id} is in there. If I explicity pass in the route values from teh current request, as in:
#Html.AnchorLink("Confirm this release", "Confirm", Url.RequestContext.RouteData.Values)
Then it works, I get the URL /admin/release/confirm/255. Why does the explicit version where I pass in the current request route values work, but the implicit one, without the route values argument, which I thought would pick up the current request route values, doesn't? I know the above is a solution, but it's ugly and there's some underlying reason why the AnchorLink without the route values isn't working as I expect?!
MVC is doing exactly the right thing here. It's not to know you require the Id parameter for your anchor link or not -- in other words its not trying anything clever to pre-parse and examine the link. It will only do that when its being rendered. And in your case without the id parameter specified somewhere its going to use the default route.
If you find yourself writing that same code all over the place you could easily extract it out into a static helper class. That can get rid of the ugliness.

Verify that a URL maps to an actual route in ASP.Net MVC

To support legacy URLs in my application, I use a regex to convert URLs of the form /Repo/{ixRepo}/{sSlug}/{sAction} to the new form /Repo/{sName}/{sAction}, using the ixRepo to get the correct sName. This works well, and I can redirect the user to the new URL with a RedirectResult.
However, I'd like to catch legacy URLs with an invalid action before I redirect the user. How can I verify if a URL string will map to a registered route? MVC clearly does this internally to map a request to the correct action, but I'd like to do it by hand.
So far, I've come up with this:
var rd = Url.RouteCollection.GetRouteData(new HttpContextWrapper(new HttpContext(
new HttpRequest("", newPath, ""),
new HttpResponse(null))));
which appears to always return a System.Web.Routing.RouteData, even for bad routes. I can't find a way to check if the route was accepted as a catch all, or if actually mapping to a route that's registered on the controller.
How can I use MVC's routing system to check if a URL maps to a valid controller/action via a registered route?
(I've seen ASP.NET MVC - Verify the Existence of a Route, but that's really inelegant. MVC has a routing system built in, and I'd like to use that.)
Wrong question. Anything can be a route, whether or not it actually maps to an action.
I think you're asking, "Will this execute OK, or will it 404?" That's a different question.
For that, you need to do what MVC does. Look in the MVC source at MvcHandler.ProcessRequestInit and then ControllerActionInvoker.InvokeAction to see how MVC looks up the controller and action, respectively.
If you know the controller and ask for valid actions, just do some reflection stuff as done in here.
If the redirected url goes to your application, then you can check if the url goes to a valid route. Some code on haacked.com http://haacked.com/archive/2007/12/17/testing-routes-in-asp.net-mvc.aspx does route testing as a unit test. After this you have controller and action as routedata and you have to do, what Craig said "do the same as mvc does".
The routing system maps request uris to route handler. The mvc route handler (class) throws an exception if it fails. There is no checking.
You can add constraints to your routes. If you constrain the action property. Then checking if the url goes to a valid route my be what you want.

Asp.NET MVC Redirect not working

The following examples are redirects wich we're trying.
The first one, for user login, works, after that no redirect works.
I have no idea about what may be the cause, tried redirecttoroute too, it is like if the server wasn't sending the headers or anything.
I have no sniffing software to check this out.
UrlHelper uH = new UrlHelper(new RequestContext(this.HttpContext, this.RouteData), RouteTable.Routes);
Response.Redirect(uH.Action("TrabalheExperiencia", "Contact"), true);
return this.RedirectToAction("TrabalheExperiencia");
return RedirectToAction("TrabalheExperiencia");
Edit: All those lines were tried, and none worked. Even using in the FIRST line of the action, the redirect command works, but the redirect it self fails.
Is the action on the same controller? If not, you need to specify the controller as well. Note that the last two are exactly the same; you're just explicitly using this on the penultimate one, whereas on the last one it is merely implied.
return this.RedirectToAction( "TrabalheExperiencia", "Contact" );
Your question is not very clear. Normally you'd use the syntax in the last line to redirect from an action in MVC, which is the same as the third line. The first two lines should not be used - you should stick to ActionResults.

Why is ASP.NET MVC ignoring my trailing slash?

Consider the following route:
routes.MapRoute(
"Service", // Route name
"service/", // URL with parameters
new {controller = "CustomerService", action = "Index"} // Parameter defaults
);
Using Url.Action("Service", "CustomerService") produces an url of /service instead of the expected /service/
Is there any way to get this to work, or do I have to resort to implementing my own routing deriving from RouteBase?
Legenden - there is no immediate solution to the problem. You may have run across Jason Young's blog post about the issue, which is very informative. Scott Hanselmann posted a reply to it here, basically stating that he didn't think it was a big deal, and if it is, you can leverage the new IIS7 rewrite module to solve it.
Ultimately though, you might want to look at a solution that was posted by murad on a similar question on StackOverflow: Trailing slash on an ASP.NET MVC route
In your page load event add:
Dim rawUrl As String = HttpContext.Current.ApplicationInstance.Request.RawUrl
If Not rawUrl.EndsWith("/") Then
HttpContext.Current.ApplicationInstance.Response.RedirectPermanent(String.Format("~{0}/", rawUrl))
End If

Resources