MVC ignores route defaults and renders full “/Home/Index” URLs instead of root - asp.net-mvc-routing

My ASP.NET MVC project seems to ignore route defaults when composing URLs. For instance, Url.Action("Index", "Home") returns /Home/Index/, while it should return just / (which I have seen in all my other MVC websites). It does not have any problem with using URLs, e.g. by visiting http://myserver/, the default controller and action are found correctly.
How can I fix the behavior? (Note that I have found questions asking how to achieve this behavior, but nobody seems to suffer from this opposite problem.)
The project is nothing spectacular, no custom routing handlers etc., and the routing configuration is quite simple:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("LoginHandler");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{*id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
(The * is there because some actions use arbitrary strings as identifiers which might contain embedded slashes, and without the asterisk, the route does not match for such URLs.)

The problem lies indeed in the small asterisk. These catch-all parameters apparently do not match against UrlParameter.Optional defaults, and result in the URL always containing the whole path.
If you want to keep the default behavior, while also allowing arbitrary parameters using the catch-all parameter syntax, you need to use the catch-all parameter in a secondary route:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("LoginHandler");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "CatchAll",
url: "{controller}/{action}/{*id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
With this definition, simple parameters not containing slashes are handled using the first route (as is the URL generation when not specifying the exact route to be used), while complex parameters with slashes fall back to the second route, and are handled correctly there.
(Note that this might be just a bug in the MVC implementation: For catch-all parameters, MVC’s ParsedRoute.IsParameterRequired returns null as the default parameter value, which is then tested against the supplied UrlParameter.Optional from the route’s defaults, and is found to be different. Therefore, you might change the route definition to use id = null default instead of id = UrlParameter.Optional, instead of adding the secondary route, and it also seems to be working, but it might have unwanted side effects, I guess. I am also not sure if this behavior is deliberate, or just a bug.)

Related

Redirect user to from a SEO like url to a controller action in ASP.NET MVC 2

is it possible to redirect a static seo "friendly" url address to a controller/action without loosing the original address?
For example:
if you navigate to http://localhost/find-the-best-employees
under the hood MVC takes you to http://localhost/Search/Employees (SearchEmployee controller, Index action). But the idea is to keep the address http://localhost/find-the-best-employees.
How can I make this work on ASP.NET MVC 2.0?
I was having a look at here and here with no luck during tests.
In Global.asax define a route like:
routes.MapRoute(
"findEmployess", // Route name
"find-the-best-employees", // URL with parameters
new { controller = "SearchEmployee", action = "Index" } // Parameter defaults
);
For this to work you need to use the route-name when generating the url, using this html helper
<%: Html.RouteLink("Search Employees","findEmployess")%>
Also you need to define this route before the default route:
//Your custom routes goes HERE before the default route
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);

Can my MVC2 app specify route constraints on Query String parameters?

My MVC2 app uses a component that makes subsequent AJAX calls back to the same action, which causes all kinds of unnecessary data access and processing on the server. The component vendor suggests I re-route those subsequent requests to a different action. The subsequent requests differ in that they have a particular query string, and I want to know whether I can put constraints on the query string in my route table.
For example, the initial request comes in with a URL like http://localhost/document/display/1. This can be handled by the default route. I want to write a custom route to handle URLs like http://localhost/document/display/1?vendorParam1=blah1&script=blah.js and http://localhost/document/display/1?vendorParam2=blah2&script=blah.js by detecting "vendor" in the URL.
I tried the following, but it throws a System.ArgumentException: The route URL cannot start with a '/' or '~' character and it cannot contain a '?' character.:
routes.MapRoute(
null,
"Document/Display/{id}?{args}",
new { controller = "OtherController", action = "OtherAction" },
new RouteValueDictionary { { "args", "vendor" } });
Can I write a route that takes the query string into account? If not, do you have any other ideas?
Update: Put simply, can I write routing constraints such that http://localhost/document/display/1 is routed to the DocumentController.Display action but http://localhost/document/display/1?vendorParam1=blah1&script=blah.js is routed to the VendorController.Display action? Eventually, I would like any URL whose query string contains "vendor" to be routed to the VendorController.Display action.
I understand the first URL can be handled by the default route, but what about the second? Is it possible to do this at all? After lots of trial and error on my part, it looks like the answer is "No".
QueryString parameters can be used in constraints, although it's not supported by default. Here you can find an article describing how to implement this in ASP.NET MVC 2.
As it is in Dutch, here's the implementation. Add an 'IRouteConstraint' class:
public class QueryStringConstraint : IRouteConstraint
{
private readonly Regex _regex;
public QueryStringConstraint(string regex)
{
_regex = new Regex(regex, RegexOptions.IgnoreCase);
}
public bool Match (HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// check whether the paramname is in the QS collection
if(httpContext.Request.QueryString.AllKeys.Contains(parameterName))
{
// validate on the given regex
return _regex.Match(httpContext.Request.QueryString[parameterName]).Success;
}
// or return false
return false;
}
}
Now you can use this in your routes:
routes.MapRoute("object-contact",
"{aanbod}",
/* ... */,
new { pagina = new QueryStringConstraint("some|constraint") });
You don't need a route for this. It is already handled by the default model binder. Query string parameters will be automatically bound to action arguments:
public ActionResult Foo(string id, string script, string vendorname)
{
// the id parameter will be bound from the default route token
// script and vendorname parameters will be bound from the request string
...
}
UPDATE:
If you don't know the name of the query string parameters that will be passed you could loop through them:
foreach (string key in Request.QueryString.Keys)
{
string value = Request.QueryString[key];
}
This post is old, but couldn't you write a route before your default route
this would only catch routes with "vendor" in the args
routes.MapRoute(
null,
"Document/Display/{id}?{args}",
new { controller = "VendorController", action = "OtherAction" },
new {args=#".*(vendor).*"}//believe this is correct regex to catch "vendor" anywhere in the args
);
And This would catch the rest
routes.MapRoute(
null,
"Document/Display/{id}?{args}",
new { controller = "DisplayController", action = "OtherAction" }
);
Haven't tried this and I am a novice to MVC but I believe this should work?? From what I understand if the constraint doesn't match the route isn't used. So it would test the next route. Since your next route doesn't use any constraint on the args, it should, match the route.
I tried this out and it worked for me.

Return PartialView in MVC3 Area is not searching in area

I am working on an ASP.Net MVC 3 RC project. I have one area named Drivers. I have a LoadPartial() action in a controller in the Drivers area that returns a PartialView(string, object); When this is returned I get an error on my webpage that says "The partial view 'PublicAttendanceCode' was not found." It searched the following locations:
~/Views/AttendanceEvent/PublicAttendanceCode.aspx
~/Views/AttendanceEvent/PublicAttendanceCode.ascx
~/Views/Shared/PublicAttendanceCode.aspx
~/Views/Shared/PublicAttendanceCode.ascx
~/Views/AttendanceEvent/PublicAttendanceCode.cshtml
~/Views/AttendanceEvent/PublicAttendanceCode.vbhtml
~/Views/Shared/PublicAttendanceCode.cshtml
~/Views/Shared/PublicAttendanceCode.vbhtml
Why is it not searching in the Drivers Area?
I have the following pretty basic routes in Global.asax.cs:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home",
action = "Index",
id = UrlParameter.Optional // Parameter defaults
}
);
}
And in DriversAreaRegistration.cs
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Drivers_default",
"Drivers/{controller}/{action}/{id}",
new { action = "RequestLeave", id = UrlParameter.Optional }
);
}
What am I missing that will make it look in the drivers area for the partial?
How are you providing area name to PartialView() method? I think you should be passing it in new { area = "Drivers" } as routeValues parameter.
The way that the MVC view engines know the area that they should look in is based on the route that was used to process the request.
In the case of the controller action that you have, are you certain that the request was processed by the area's route definition, or is it possible that the request was processed by the more general route that you defined in global.asax?
There are only four overloads of the method PartialView and it seems like neither of them accept routeValues as a parameter.
I solved this problem like this:
return PartialView(
VirtualPathUtility.ToAbsolute("~/Areas/MyArea/Views/Shared/MyView.cshtml"));
It works, but looks ugly.
This works too:
return PartialView("~/Areas/Admin/Views/Shared/MyView.cshtml", model);

Inconsistent Routing Results in MVC

Seems I'm still missing something to the MVC routing concept. I have a route that follows nearly the same pattern as another route in an area but for what ever reason I get a 404 every time I attempt to run it. I've tried to use Phil Haack's Route Tester DLL and according to that it hits the correct route (matched route comes out to common/itemhistory/{contentid}). When I try to run it for real, it blows up.
I'm trying to map a call to a JsonResult by passing a Guid. I've had success with other routes working fine (common is an area in my site).
What could I be doing wrong?
context.MapRoute(
"ItemHistory",
"common/itemhistory/{contentid}",
new { controller = "common", action = "GetItemHistory" },
new { contentid = #"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$" }
);
context.MapRoute(
"Common_default",
"common/{action}",
new { controller="common", action = "Index" }
);
You are assigning action attribute to "GetItemHistory" and have defined itemhistory in the regular Route Pattern. Looks like you don't have a method in your controller by name "GetItemHistory"

Action not found in Route table?

Just started my first MVC 2.0 .net application. And I set up some default pages like:
/Loa/Register
/Loa/About
But when I request for /Loa/sdfqsdf (random string) I get the "The resource cannot be found." error, how can I redirect this non-existing action to a default action?
Like an "action not found" default action?
thx!
Using routes
You can define more than one route (which is also quite common in real-life MVC applications), because some routes have particular settings that differ from the default one. And especially if you want to do decent SEO.
routes.MapRoute(
"DefaultRoute",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = string.Empty },
new { action = "Register|Index|About" } // route constraint that limits the actions that can be used with this route
);
routes.MapRoute(
"InvalidRoutes"
"{*dummy}",
new { controller = "Home", action = "Nonexisting" }
);
If you'll add additional routes to our route table, just make sure the InvalidRoutes is defined as the last one.