MVC Routing picking up Area controllers at root - c#

I'm having difficulties with my controllers that are in an Area answering requests on routes that aren't for the area. So I have a setup like this (Extra stuff cut):
/Areas/Security/Controllers/MembersController.cs
/Areas/Security/SecurityAreaRegistration.cs
/Controllers/HomeController.cs
I have my area for security defined:
namespace MyApp.Web.Areas.Security
{
public class SecurityAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Security";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Security_default",
"Security/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
}
And my global routing rules:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{*robotstxt}", new { robotstxt = #"(.*/)?robots.txt(/.*)?" });
routes.IgnoreRoute("{*favicon}", new { favicon = #"(.*/)?favicon.ico(/.*)?" });
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new string[] { "MyApp.Web.Controllers" }
);
In my global asax I'm doing quite a few things but the relevant part is that I call AreaRegistration.RegisterAllAreas(); then I call the routing function that does the above.
But my problem is that requests for "/Members/" are hitting my Members controller using my "Default" route... even though the controller's not in the namespace I specified. Then when it tries to run it can't find it's Views cause they're defined in the Area and it's trying to find them in the overall Views folders. I tried making the route namespace "Weird.Namespace.With.No.Content" and it STILL hits the Members controller - I can't find any way to make it not use that controller. How do I make it not answer requests that aren't in it's area?

Ended up finding a solution by changing the Route to:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new string[] { "MyApp.Web.Controllers" }
).DataTokens["UseNamespaceFallback"] = false;
For some reason with that unset it seemed to always find my Controllers no matter where they were and totally disregard my namespaces - even from other referenced assemblies. Looking through the ILSpy of DefaultControllerFactory it looks like GetControllerType eventually falls back to searching absolutely every controller if it doesn't find the controller in the namespaces you asked for...
This flag seems to be set automatically on the Routes I made in a specific area, but not on the ones I made globally. When I set it on the global ones they began behaving how I had originally expected. I have no idea why you'd ever want to turn this on...

You should register the namespace on your area as well.
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Security_default",
"Security/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new []{ "MyApp.Web.Areas.Security.Controllers"},
);
}
And then make sure all your controllers are in their appropriate namespaces.

Are you calling register all areas after registering your main route?
This is the default snippet from the MVC template (note how area regisration goes first)
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);

Related

Why does an ASP.NET MVC route have a name?

In an ASP.NET MVC 5 web application, there is a RouteConfig class that registers many routes. In all of the examples I have seen so far, only the "Default" route has a non-empty name. The URL pattern and default route values seem to be sufficient to correctly associate any URL to the controller and action to execute. So, is there any benefit to naming a route? For debugging or logging? Just for self-documenting the code?
For example:
public class RouteConfig
{
public static void RegisterRoutes( RouteCollection routes )
{
routes.IgnoreRoute( "{resource}.axd/{*pathInfo}" );
// Most pages.
routes.MapRoute( name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "home", action = "index", id = UrlParameter.Optional }
// A special page.
// Should the route have a name?
routes.MapRoute( name: "",
url: "special",
defaults: new { controller = "special", action = "index", HttpVerbs = HttpVerbs.Get } );
}
}
The main reason to name a route is so that if you need a link to a route from somewhere in the app, you can reference the name. That way if the actual URL/path changes you don't break everywhere in the code that references that route.

How to use MVC route in ASP.NET WebAPI project

I have a web API and i want to add a few asp.net pages to manage aspects of the API.
In the WebApiConfig.cs file i have a couple of routes, with the following being used as my catch all route for the API.
config.Routes.MapHttpRoute(name: "CatchAll",routeTemplate: "{controller}/{action}/{id}",defaults: new { id = RouteParameter.Optional });
I want to add a new section that is prefixed by /control to push to the asp.net side of things. To do this i have added the following to RouteConfig.cs
routes.MapRoute(
name: "Default",
url: "control/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
I have then added a standard controller that i will use to manage things.
public class ManageController : Controller
{
public ActionResult Index()
{
return Content("Works");
}
}
When i visit /control/manage in the browser i get the following error.
No type was found that matches the controller named 'control'
It looks like the route is being completely bypassed or at best, the catch all from the api route is catching it and giving it priority. IS there a way i can make the request catch this route without having to create a separate project?
The order of registration is matter
You need to register
public class RouteConfig {
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "DefaultMVC",
url: "control/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
//..other routes.
}
}
in the RouteConfig.cs before
config.Routes.MapHttpRoute(name: "CatchAll",routeTemplate: "{controller}/{action}/{id}",defaults: new { id = RouteParameter.Optional });
in the WebApiConfig.cs file.
What happens in your case is that the /control/manage url is handled by CatchAll route thus mapping control to a controller and manage to an action
In order to do that register them in Global.asax in the following order:
RouteConfig.RegisterRoutes(RouteTable.Routes);
GlobalConfiguration.Configure(WebApiConfig.Register);

How to prevent controllers in wrong areas from being used in MVC5

My MVC application is set up with controllers at the root/global level. These have no explicit Area. I also have a single "Admin" area. URLs that begin with /admin/ are routed to the matching controller in the Admin area. Other URLs are routed to the matching global controller. This is working fine for the most part.
The issue I'm having is that in the case when a URL matches a controller in the Admin area when one of the same name doesn't exist on the global area, the request is incorrectly routed to the controller in Admin area. I know this is happening because I put a breakpoint on the matching action in the relevant controller.
For example, I have a controller called CalendarController in the Admin area. When I visit /admin/calendar, it works because it finds the action and the corresponding view in Areas/Admin/Views/Calendar/Index.cshtml. The problem occurs when I visit /calendar. I do not have a controller named Calendar at the root level, but for some reason it routes the request to the Admin area's CalendarController, which I don't want. I want it to return a 404 because no CalendarController exists at the root level. Instead, I get an error because it's searching for the view at the root level (at /Views/Calendar/Index.cshtml) even though the matching controller was in the Admin area.
How can I prevent the Admin area from being searched for matching controllers except when the URL has /admin in it?
Here's the relevant route code, which is basically stock except for the namespaces addition. The issue still happens without the namespace. There are more routes in the actual application, but I'm getting the same behavior in a brand new MVC project.
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Admin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new[] { "AreaProblem.Areas.Admin.Controllers" }
);
}
}
public class RouteConfig
{
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 }
);
}
}
What I mean by a controller at the "root level" is Controllers/HomeController in this screenshot. I want URLs that don't start with /admin to only look at those controllers. The problem is that it's also searching in Areas/Admin/Controllers.
So the MVC routing engine will look in different namespaces to try and find a matching controller. You can solve this by specifying a namespace (like you did for the admin area). You can also specify that a route not search other namespaces using DataTokens.
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new [] { "AreaProblem.Controllers" }
).DataTokens["UseNamespaceFallback"] = false;

MVC route matching routes that aren't valid

Problem
For the default route MVC is returning a "Multiple types were found that match the controller named 'admin'." error instead of a 404 not found. There is no admin controller in that namespace.
We're using MVC 5.2.2.
Background
We're using MVC areas. Two areas contain an "admin" controller. When you use the full path as defined in their respective routes, both areas' admin controllers are accessible and work correctly.
The problem arises when you try to access "admin" from the default route. Admin does not exist in that context, so we'd expect a 404 not found, however instead we receive:
Multiple types were found that match the controller named 'admin'. This can happen
if the route that services this request ('{controller}/{action}/{id}') does not
specify namespaces to search for a controller that matches the request. If this
is the case, register this route by calling an overload of the 'MapRoute' method
that takes a 'namespaces' parameter.
The request for 'admin' has found the following matching controllers:
DMP.Stock.Web.Controllers.AdminController
DMP.Inventory.Web.Controllers.AdminController
Here is our default route and two area routes:
public static void RegisterRoutes(RouteCollection routes)
{
// Default Route
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new string[] { "DMP.Core.Web.Controllers" }
);
}
public override void RegisterArea(AreaRegistrationContext context)
{
// Area 1: Stock
context.MapRoute(
name: "Stock_default",
url: "Stock/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new string[] { "DMP.Stock.Web.Controllers" }
);
}
public override void RegisterArea(AreaRegistrationContext context)
{
// Area 2: Inventory
context.MapRoute(
"Inventory_default",
"Inventory/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new string[] { "DMP.Inventory.Web.Controllers" }
);
}
/Stock/Admin/Index works correctly.
/Inventory/Admin/Index works correctly.
/Admin/ does not work correctly (expect: 404 not found, receive "multiple controllers" error).
The errors suggests we add namespaces to our routes, but as you can see above both the default and the two areas already have a namespace definition. The default route points at a namespace without any "admin" controllers in it.
I think MVC is trying to be "helpful" by searching for possible controllers that could match the requested URL. Is there any way I can turn that off?
I've been able to resolve this issue myself. Here is the solution I found:
// Default Route
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new string[] { "DMP.Core.Web.Controllers" }
).DataTokens["UseNamespaceFallback"] = false;
Note the addition of .DataTokens["UseNamespaceFallback"] = false; this is what resolves the issue. There isn't very much (any?) documentation on this functionality, however I found it while reading the MVC source code, specifically within DefaultControllerfactory (the source of this issue).
After you know to google for "UseNamespaceFallback" you can find a few blog posts and questions where people have had a similar issue and resolved it the same way. However I can find no MSDN documentation on this DataToken.

MVC controllers with same name with areas but finds "Multiple types"

I have myself a little problem with areas and controllers.
I have a controller called "HomeController" this is the default controller, I then added a new area called "Supplier", I then copied over my HomeController and removed all the methods I don't want to edit and just edited my Index method.
Now when I build it works fine but when I go to my home controller as a supplier it comes up with this error
Multiple types were found that match the controller named 'Home'. This can
happen if the route that services this request ('{controller}/{action}/{id}')
does not specify namespaces to search for a controller that matches the request.
If this is the case, register this route by calling an overload of the
'MapRoute' method that takes a 'namespaces' parameter.
The request for 'Home' has found the following matching controllers:
TestProject.Controllers.Home
TestProject.Areas.Supplier.Controllers.Home
I have updated my areas like so
This is my default area
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "TestProject.Controllers" }
);
}
}
And here is my area route file
public class SupplierAreaRegistration: AreaRegistration
{
public override string AreaName
{
get
{
return "Supplier";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"SupplierHomeIndex",
"Home/Index/{id}",
defaults: new { area = "Supplier", controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "TestProject.Areas.Supplier.Controllers" },
constraints: new { permalink = new AreaConstraint(AreaName) }
);
context.MapRoute(
"SupplierDefault",
"{controller}/{action}/{id}",
defaults: new { area = "Supplier", action = "index", id = UrlParameter.Optional },
namespaces: new[] { "TestProject.Controllers"},
constraints: new { permalink = new AreaConstraint(AreaName) }
);
}
}
Can anyone sign some light on this? I have looked at many topics and answers for this via Google and Stackoverflow however nothing seems to work for me.
You've customized the area's routes and removed the Supplier URL prefix. When the routing framework spins up it merely collects all controllers from your application, regardless of where they are, and then looks for a match based on the URL. In your case, you now have two controllers that are both bound to the URL /Home/*. Typically, the area's URL would be prefixed with the area's name to avoid the confusion, i.e. /Supplier/Home.

Categories

Resources