ASP.NET MVC 2 RC 2 returns Area-specific controller when no area specified - asp.net-mvc-2

I have a basic MVC 2 (RC2) site with one base-level controller ("Home"), and one area ("Admin") with one controller ("Abstract"). When i call http://website/Abstract - the Abstract controller in the Admin area gets called even though i haven't specified the Area in the URL. To make matters worse - it doesn't seem to know it's under Admin because it can't find the associated view and just returns:
The view 'Index' or its master was not found. The following locations were searched:
~/Views/Abstract/Index.aspx
~/Views/Abstract/Index.ascx
~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx
Am i doing something wrong? Is this a bug? A feature?

My friend and I were experiencing the same issue with Areas in ASP.NET MVC 2. We found a "hack" that, so far, seems to be working. For the tl;dr version, see the bottom of this answer.
You've probably got something similar to the following in your "Admin" area's "AdminAreaRegistration.cs" class:
// Web/Areas/Admin/AdminAreaRegistration.cs
public override void RegisterArea(AreaRegistrationContext context) {
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
Thus, it should make sense that when you make a request for "http://website/Abstract", the "Admin_default" route does not match the request. So, by design, the MVC framework attempts to match the request against any other defined routes. If you used the MVC tooling within Visual Studio to create your web project, you'll have a "Default" route defined in your "Global.asax" file (at the root of your web project). It should look similar to this:
// Web/Global.asax.cs
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new {controller = "Home", action = "Index", id = UrlParameter.Optional}
);
}
The "Default" route succeeds in matching the request for "http://website/Abstract", with "controller" = "Abstract", "action" = "Index" (default value), and "id" = UrlParameter.Optional (default value). This is the correct, and intended, behavior... so far.
Now, the MVC framework will attempt to load the "Abstract" Controller. By design, MVC will search for a class called "AbstractController" that extends "Controller" anywhere within the web project's file/namespace hierarchy. It is important to note that a Controller's file location and namespace do not affect MVC's ability to find it; in other words, just because you've placed the "AbstractController" within a folder called "Areas\Admin\Controllers" and changed the namespace to be "Web.Areas.Admin.Controllers" instead of, say, "Web.Controllers", doesn't mean that MVC won't use it.
When MVC executes the "Index" action in "AbstractController" which, most likely, just returns "View()", then MVC gets confused because it doesn't know where to find the "Index" view. Because MVC has matched a non-area route (the "Default" route in Global.asax), it thinks the matching view should be located in non-area view folders. Thus you get the familiar error message:
The view 'Index' or its master was not found. The following locations were searched:
~/Views/Abstract/Index.aspx
~/Views/Abstract/Index.ascx
~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx
We, just as you, didn't want requests for "http://website/Abstract" to resolve to "Admin" area's "AbstractController"; only "http://website/Admin/Abstract" should work. I can't think of why anyone would want this behavior.
A simple solution is to delete the "Default" route in Global.asax, but this will break any regular non-area Controllers/Views. This is probably not an option for most people...
So, we thought we could restrict the set of Controllers that MVC would use for requests matched by the "Default" route in Global.asax:
// Web/Global.asax.cs
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new {controller = "Home", action = "Index", id = UrlParameter.Optional},
new[] {"Web.Controllers"} // Added this line
);
}
Nope. A request for "http://website/Abstract" will still use "AbstractController" within the "Admin" area, even though the "AbstractController"'s namespace is "Web.Areas.Admin.Controllers" and (clearly) not "Web.Controllers". This is thoroughly confusing; it seems like this white-list has no dicernable affect on MVC's Controller resolution.
- tl;dr answer starts here -
After some hacking, we figured out how to force MVC to only use Controllers within the white-listed namespace(s).
// Web/Global.asax.cs
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new {controller = "Home", action = "Index", id = UrlParameter.Optional},
new[] {"Web.Controllers"}
).DataTokens["UseNamespaceFallback"] = false; // Added this line
}
Set the "UseNamespaceFallback" key of the DataTokens dictionary on the "Default" route to false. Now, when we make a request for "http://website/Abstract", the "Default" route will still be matched (this is valid behavior!) but MVC will not use any Controller that is not within the defined namespace(s); in this case, only Controllers within the "Web.Controllers" namespace are valid. Finally, this is the functionality we were looking for! We can't figure out why this isn't the default behavior. Weird, huh?
Hope this helps.

Did you setup your routing correctly? When you use areas you have to manually change your routing code so that MVC looks in the right namespaces.
http://haacked.com/archive/2010/01/12/ambiguous-controller-names.aspx

Related

Hiding Get-Action in Web API (mvc)

I have a web api with the following routes:
routes.MapHttpRoute(
name: "DefaultGet",
routeTemplate: "api/{controller}/{id}",
defaults: new { controller = "Home", id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = RouteParameter.Optional }
);
These are two Actions inside a Controller:
public class UserController {
public HttpResponseMessage Get(int id) {}
public HttpRespnseMesseage GetDetails(int id) {}
}
The first route allows me to access the Get()-Method by "/api/User/4711" The second route allows me to access the GetDetails()-Method by "/api/User/GetDetails/4711"
This works.
But in addition, the Get()-Method can also be called by "/api/User/Get/4711" (and is listed in the automaticly generated documentation, too)
How can I make sure, that I can access the Get-Method by "/api/User/4711", but not by "/api/User/Get/4711"?
Note: I want to keep the default routes and do not want a solution that can only be achieved by removing my default routes and using route attributes instead
You're doing something quite strange: you're mixing up deafault RESTful style routing, with action based routing.
With your current configuraton, your second route will match any ULR like /api/User/XXX/4711 where XXX is anything. No matter if XXX is Get or anything else.
The only thing that you can do to avoid the second route to accept anything is to use route constraints. But, to do so, you must have a fixed set of rules. For example, if you only want to exclude Get you can do that with a regex constraint. But if you don't know the rules, of course, you cannot implement it.
Recommendation
As the OP finally decided himself, if you're using a RESTful API routing style it's much better to use routing attributes, available since Wep API v2. (In fact there was a Nuget package to support it on previous versions).

ASP.NET 4 MVC Web API: Documentation for complex routing

The web api seems to be only suited for the standard use-cases.
But I want to do more complex routing but can't find documentation for complex routing.
If I have more controller, the routings gets more and more complicated.
Can i define several optional parameters with dependencies?
Like this:
/api/document/{par1}/{par2}
par1 & par2 should be optional but par2 should be only matched if par1 is present.
And are recursive parameters possible?
/api/documents/characteristics/{round/green/red-dots}
/api/documents/characteristics/{square/yellow}
/api/documents/characteristics/{square/yellow/flat}
/api/documents/characteristics/{square/yellow/flat/...}
Is there a detailed documentation for the web api routing? The microsoft tutorial is too basic...
I need more information about the routing.
I have two controllers and some trouble because two routings are quite similar, so the wrong route is taken. I can use [Action]-Attribute as a workaround, but this feels not right... I also have to consider the order of the routes. This is logical but is nowhere mentioned.
Is the web api only for simple rest api's?
Edit:
I tried this:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{mandant}/documents/{id}",
defaults: new { controller = "documents", id = RouteParameter.Optional }
);
//routes.MapHttpRoute(
// name: "DefaultApiWithAction",
// routeTemplate: "api/{mandant}/documents/{id}/{action}",
// defaults: new { controller = "documents" }
// );
I have two methods:
[AcceptVerbs("get")]
public HttpResponseMessage Get(int id)
[ActionName("file")]
[AcceptVerbs("get")]
public HttpResponseMessage filedownload(int id)
Now I have the problem, the file-action is triggered even if I comment out the second route and the normal get specific document method is not triggered because multiple actions... I tried the [NoAction] attribute but this is not working...
But why will the file-method be triggered if there is no action in the route-template? (Or if the second route is active, why will the normal get-document method not be triggered if there is no action in the url....)
I my current workaround is to set a default-action for all other methods, but this is not a good solution.
You can use routing constraints to set conditions on the routing in global.asax like:
routes.MapRoute(
"ApiRoute",
"api/document/{par1}/{par2}",
new {controller="Document", action="SomeMethod"},
new {par1 = #"\d+" }
);
In the last parameter you can specify regular expression that has to be matched for specified parameter for the route to be used. In the example above par1 is used for digits only, but you can use any regular expression, like:
routes.MapRoute(
"ApiRoute",
"api/document/{par1}/{par2}",
new {controller="Document", action="SomeMethod"},
new {par1 = #"(value1|value2|value3)" }
);

asp.net map route with parameter inside controller and view

I need to create a url scheme like this
friend/{userid}/wishlist
where friend is the controller, wishlist is the view, and userid is the id of hte friend whose wishlist you would like to see.
I have setup a route like this
routes.MapRoute(
"FriendWishlist",
"friend/{userid}/wishlist",
new { controller = "WishList", action="FriendWishlist", userid = 123}
);
when i try to browse to /friend/123/wishlist i get the following error
A public action method '123' was not
found on controller
'GiffrWeb.Areas.Api.Controllers.FriendController'.
Routes in MVC are evaluated in the order they are declared. It sounds very much like you have declared your route below the default one:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"FriendWishlist",
"friend/{userid}/wishlist",
new { controller = "WishList", action="FriendWishlist", userid = 123}
);
So the MVC framework is trying to match your URL /friend/123/wishlist first to the default route. Because it's all variables and everything has a default or is optional, it's guaranteed to match. It doesn't check if the controllers and actions exist and take the relevant arguments. You have a FriendController class - check. 123 action - it goes bang.
Simplest fix - declare the route above the default one (ie just swap these two statements) and it should work OK.
I might just add that it seems a little weird to have a URL that starts with /friend/ going to a WishList controller when you obviously have a Friend controller (your error message says so).
Finally, I can't recommend highly enough that if you introduce custom routing that you also test those routes thoroughly - as you have seen, the routing engine often might not do what you think it does. I recommend either the route testing stuff in MvcContrib or Brad Wilson's blog post.

How would I configure the global.config to allow for root path to actionMethod in MVC2?

specifically, the default directory naming structure is [Controller]/[ActionMethod] resulting in a rendered url like www.mysite.com/home/actionMethodName.
What if I want to simply imply the Controller (in this example, 'home') so that I can get a url that looks like this: www.mysite.com/actionMethodName.
I haven't seen many requests for this kind of configuration. I can see how it breaks convention, but I would imagine that there are lots of people who need root pathing.
Because you are planning to remove the {controller} element of the url, you may need to get a bit more specific with your other urls, e.g.:
routes.MapRoute("MyOtherControllerRoute", "Account/{action}", new { controller = "Account", action = "Index" });
routes.MapRoute("MyDefaultRoute", "{action}", new { controller = "Home", action = "Index" });
When the route table is interrogated, if the url such as www.mysite.com/Account is used, it will match the first route, because we have been specific about the pattern used to match the url. If we then do something like www.mysite.com/DoSomething it will use the default route we've selected last, trying to invoke the DoSomething action on the HomeController type.
Something which I've noticed is that a lot of MVC developers seems to assume that the url is strictly {something}/{something}/{something}, whereas it can essentially be anything you like, e.g, I can have a route that does: www.mysite.com/my-weird-and-wonderful-url which I could map specifically:
routes.MapRoute("Somewhere", "my-weird-and-wonderful-url", new { controller = "Whatever", action = "Whenever" });
Hope that helps.
Easy as apple pie - you just specify a route of your own! =)
An example:
routes.MapRoute(
"RootPathing",
"{action}",
new { controller = "Default", action = "Index" });
This will register a route that catches all paths, and try to map them to the DefaultController with an action name corresponding to the path. However, note that if you place this route above the included default route, you will not be able to reach any other controller than the DefaultController - hence, place this route below the default route in the chain. It will then be matched by all paths that don't match a controller name.
When debugging routes, Phil Haack's Routing Debugger is really worth taking a look at.

ASP.NET MVC 2: Application areas and routes

So my application is growing and I'd like to split out my View-based controllers from my JSON-based controllers. Basically, I have a bunch of controllers that all they do is return JSON results for AJAX calls.
What I would like to do would be to set up the routes (and the folder structure under my Controllers folder, for that matter) such that my JSON controllers live under /RPC/ and my regular controllers live under / -- i.e. my JavaScript would call /RPC/SomeController/SomeAction and my regular pages would live under /SomeOtherController/SomeOtherAction.
So what I did was I set up my Controllers folder as such:
Controllers (folder)
RPC (folder)
JsonController1
JsonController2
ViewController1
ViewContoller2
ViewController3
I wasn't able to just go to /RPC/JsonController1/Index and have that work so then I set up my routes as follows:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
// Register new annotations.
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(UniqueUsernameAttribute), typeof(UniqueUsernameValidator));
}
public static void RegisterRoutes(RouteCollection routes)
{
// Add the combres routes, too
routes.AddCombresRoute("Combres");
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("RPC",
"RPC/{controller}/{action}/{id}",
new { controller = "None", action = "Index", id = UrlParameter.Optional },
new[] { "Backplane.Web.Controllers.RPC" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new[] { "Backplane.Web.Controllers" }
);
}
This works just great! My controllers are accessible under both /RPC/ and /! Unforunately I can validly access /RPC/ViewController1/and I can also validly access /JsonController1/.
It seems that I have a fundamental misunderstanding about routing and how it's related to the physical path on the file system. Will I need a custom route scheme here? I'd like to keep all of this in the same project as I'd like to keep all the controllers in the same DLL.
The routing is not related to the physical path on the file system (either on the build machine or on the server). The routing is performed at run-time, at which point there are no artifacts that would suggest the MVC routing handler where on the file system particular class was located. As such, the routing is dependent only on the order of the routes in the routing table (which depends on the order of them in the Global.asax.cs) and how the URL path parts map onto the routes and the controller classes names and actions.
The easiest solution to make your scenario work is to remove the Default rule and list explicit rules for any controller under the root.
Another approach would be to move all your RPC controllers into a separate area (Areas/RPC/Controllers) in your solution. The way the areas work is that any /rpc/* path is mapped to the RPC area. There are certain caveats with that approach as well, but in general it should work, as the area routes are registered after the main routes and the MVC routing does an attempt to avoid using controllers that are part of an area with URLs that don't map to that area.