I have two routes in my routeConfig files as follow.
Route with admin prefix which handles request for admin part
default Route without a prefix, for which I have added a datatoken to map routes in candidate Area
routes.MapRoute(
name: "admin",
url: "Admin/{controller}/{action}/{id}",
defaults: new { controller = "Account", action = "Login", id = UrlParameter.Optional },
namespaces: new[] { "abc.namespace1" }
);
routes.MapRoute(
name: "default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Account", action = "Login", id = UrlParameter.Optional },
namespaces: new[] { "abc.namespace2" }
).DataTokens.Add("area", "Candidate");
But the problem is when i type in a url localhost/MyApp/Admin/Home/Index
it is hitting the controller in abc.namespace1 (which is expected) and localhost/MyApp/Home/Index also hitting Home controller inside abc.namespace1 instead of HomeController inside abc.namespace2 in candidate Area.
What i want to do here is handle all routes with Admin prefix with controllers inside abc.namespace1 and all routes without any prefix with controllers inside abc.namespace2 which is my candiate Area.
regards
I believe this might have something to do with the way you have specified your namespaces. The namespace must be for where the controller classes reside.
The pattern is typically <namespace of area>.<area name>.<controller namespace>
For example, in a project with an area named "Admin", the namespace must be:
"MvcMusicStore.Areas.Admin.Controllers"
In my experience, the conventions for how areas are set up are pretty strict. You should not set up the route in an AreaRegistration rather than the root of your project in order to get it to work.
public class CandidateAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Candidate";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Candidate_default",
"{controller}/{action}/{id}",
new { controller = "Account", action = "Login", id = UrlParameter.Optional },
new string[] { "<project name>.Areas.Candidate.Controllers" }
);
}
}
Areas are convention-based. If you deviate too far from the anticipated conventions, they simply don't function.
Related
We're currently making a small CMS module for our application where existing routes and controllers are highly prioritize, while dynamically created pages through the CMS will only be loaded if the provided URL does not exists in the default route.
I already looked at this one: Dynamic Routes from database for ASP.NET MVC CMS
but it prioritizes the dynamically created page before the routes.
This is our default route:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Store", action = "Index", id = UrlParameter.Optional }
);
And aside from the default route, we have other routes that are using prefixes like this one:
routes.MapRoute(
name: "Media",
url: "m/{*url}",
defaults: new { controller = "Content", action = "GetContent", contentlibrary = "Media" },
constraints: new { url = #"(.*?)\.(png|jpg|pdf|mpeg|mp3|mp4)" }
);
UPDATE 1:
I created an IRouteConstraint for validating if the Controller exists for the default route. Here is my code:
public class ControllerValidatorConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
var tempRequestContext = httpContext.Request.RequestContext;
IController controller = null;
try
{
controller = ControllerBuilder.Current.GetControllerFactory().CreateController(httpContext.Request.RequestContext, values["controller"].ToString());
}
catch (Exception ex)
{
}
return controller != null;
}
}
then on routing:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Store", action = "Index", id = UrlParameter.Optional },
constraints: new { url = new ControllerValidatorConstraint() }
);
routes.MapRoute(
name: "Page",
url: "{*url}",
defaults: new { controller = "Content", action = "GetPage" },
constraints: new { url = #"(.*?)(|.cshtml)" },
namespaces: new[] { "AppCore.Controllers" }
);
This already works as what I intend. The only remaining issue is the Match() method. The Match() method is currently creating an instance of the controller, I wrapped it with a Try-Catch block because it throws an error related to the provided path that does not exists as temporary solution but this is still wrong.
I'm almost there, I just have to find a proper way to check if the controller exists. Is reflection a bad choice? Any lighter way to check them? Thanks!
I want to link to a controller with actually specifying the action parameter. I'm pretty sure this is possible. And got the concept from here:
http://www.asp.net/mvc/tutorials/controllers-and-routing/creating-a-route-constraint-cs
These are the two links:
https://localhost/mvcapplication1/customer/order/index/6463 (WORKS)
https://localhost/mvcapplication1/customer/order/6463 (WANT THIS)
I specified a special rout for this controller (Customer_orders).
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Customer_orders",
"Customer/Order/{orderId}",
new { controller = "MvcApplication1.Areas.Customer.Controllers.Order", action = "Index" },
new[] { "MvcApplication1.Areas.Customer.Controllers" } // only map routes to controllers in the specified namespace
);
context.MapRoute(
"Customer_default",
"Customer/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new[] { "MvcApplication1.Areas.Customer.Controllers" } // only map routes to controllers in the specified namespace
);
}
And this is my controller with the namespace:
namespace MvcApplication1.Areas.Customer.Controllers
{
[Authorize]
public partial class OrderController : SupertextController
{
I've also enabled the Route Debugger and according to that my routes are ok. So why do I get a "The resource cannot be found."? Specially since it works if I add "index" to the URL.
In your routes the default value of the controller token should be your controller name without any namespace and without the "Controller" postfix:
So change your route to:
context.MapRoute(
"Customer_orders",
"Customer/Order/{orderId}",
new { controller = "Order", action = "Index" },
new[] { "MvcApplication1.Areas.Customer.Controllers" }
);
My area is below. Only the concerned part is highlighted.
Route Table
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"SubFolder", // Route name
"SubFolder/ChildController",
new { controller = "ChildController", action = "Index" },
new[] { "Practise.Areas.SOProblems.Controllers.SubFolder" });
routes.MapRoute(
"Default", // Route name
"{controller}/{action}", // URL with parameters
new { controller = "Home", action = "Index" } // Parameter defaults
);
}
This only works when the url is like this
localhost:2474/SOProblems/ChildController/index
This does not works when the url is like this
localhost:2474/SOProblems/SubFolder/ChildController/index
Can you please tell me what is missing?
This does not works when the url is like this
localhost:2474/SOProblems/SubFolder/ChildController/index
That's normal. You route pattern looks like this: SubFolder/ChildController and not SubFolder/ChildController/index. In addition to that you defined your route in the WRONG place. You defined it in your main route definitions and not in your area route definitions. So get rid of the custom route definition from your main routes and add it to the SOProblemsAreaRegistration.cs file (which is where your SOProblems routes should be registered):
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"SubFolderRoute",
"SOProblems/SubFolder/ChildController",
new { controller = "ChildController", action = "Index" },
new[] { "Practise.Areas.SOProblems.Controllers.SubFolder" }
);
context.MapRoute(
"SOProblems_default",
"SOProblems/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
Also since your route pattern (SOProblems/SubFolder/ChildController) doesn't have the possibility to specify an action name, you can only have one action on this controller and that would be the default action that you registered (index) in this case.
If you wanted to have more actions on this controller and yet index be the default one you should include that in your route pattern:
context.MapRoute(
"SubFolder",
"SOProblems/SubFolder/ChildController/{action}",
new { controller = "ChildController", action = "Index" },
new[] { "Practise.Areas.SOProblems.Controllers.SubFolder" }
);
In both cases your main route definition could remain with their default values:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}",
new { controller = "Home", action = "Index" }
);
}
Your new route "SubFolder" does not include the possibility of including an action in the route (in your case, "Index").
Your sample URL
localhost:2474/SOProblems/SubFolder/ChildController/index
Wants to try to match a route like:
"SubFolder/ChildController/{action}"
But you don't include the "{action}" in your route, so it won't match your route. It then tries the default route, which obviously fails.
Try adding "{action}" to your route:
routes.MapRoute(
"SubFolder", // Route name
"SubFolder/ChildController/{action}",
new { controller = "ChildController", action = "Index" },
new[] { "Practise.Areas.SOProblems.Controllers.SubFolder" });
or take "index" off your test URL.
For any future users looking to do this; Look into using Areas.
Here's a helpful video.
Organizing an application using Areas
I've got a simple MVC4 web site containing an area (called "User") containing a Controller called "HomeController".
On this controller there are two action methods: Index and Details:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Details(int id)
{
return View();
}
}
The "UserAreaRegistration.cs" class is as follows:
public class UserAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "User";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"User_default",
"User/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "MvcApplication1.Areas.User.Controllers" }
);
context.MapRoute(
"User_default_no_contoller",
"User/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "MvcApplication1.Areas.User.Controllers" }
);
}
}
There's also a Controller that's not in an area called "HomeController":
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
RouteConfig.cs (for non-area routes) is as follows:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "MvcApplication1.Controllers" }
);
}
}
I am able to access the "homepage" via either
/
/Home
/Home/Index
I'm also able to get to the "Index" action in my "User" area
/User
What I cannot do is work out how to get to the other method on my Controller in the Area called "Details":
/User/Details/5 does not work, it returns a 404
I also notice that trying to access the Index method explicitly is also not routed correctly:
/User/Index does not work, it returns a 404
What am I doing wrong?
Is it possible to get to any action method other than "Index" on a controller that's the "default" controller in an area?
There are too many defaults on your first route, so it's swallowing everything up.
the URI /User/Details/5 matches the User_default route (Controller: "Details" and Action: "5").
the URI /User/Index also matches the first route (Controller: "Index" and the default Action: "Index")
The way to approach routes is
put the most selective route first
and set only the minimum default values necessary
use route constraints to ensure that the MVC router doesn't attempt to match parameters incorrectly (ie, give route values intended for int parameters a numeric constraint)
put the most general route (ie, User_default) last (as a catch-all for URIs that didn't match any more specific route)
I'd try setting up the area routes like this.
context.MapRoute(
"User_default_no_contoller",
"User/{action}/{id}",
defaults: new { controller = "Home", id = UrlParameter.Optional },
constraints: new { id = #"\d+" },
namespaces: new[] { "MvcApplication1.Areas.User.Controllers" }
);
context.MapRoute(
"User_default",
"User/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "MvcApplication1.Areas.User.Controllers" }
);
Another suggestion to overcome this "Multi-defaults" problem is to separate using Namespaces.
In your example my suggestion is to have the Class UserAreaRegistration in the namespace "Areas.User" . Subsequently you would have to put your HomeController in the User Area in the Namespace "Areas.User.Controllers"
It works for me and keeps the code clean.
Peter
Thanks for the answer dbaseman. It helped me look at my own routing problem. I am not sure if I was running into the same issue but that is what it seems like.
routes.MapRoute("Tag", "Tag/{tag}", new { controller = "Blog", action = "Tag" });
routes.MapRoute("Category", "Category/{category}", new { controller = "Blog", action = "Category" });
routes.MapRoute("Post", "Archive/{year}/{month}/{day}/{title}", new {controller = "Blog", action = "Post"});
routes.MapRoute("Action", "{action}", new { controller = "Blog", action = "Posts" });
When I had the last route with the "Action" parameters above any of the other routes the ones below "Action" would not be caught. Moving it to the bottom of the RouteConfig allowed all the ones above it to be hit successfully now.
I tried implementing Phil's Areas Demo in my project
http://haacked.com/archive/2008/11/04/areas-in-aspnetmvc.aspx.
I appended the Areas/Blog structure in my existing MVC project and I get the following error in my project.
The controller name Home is ambiguous between the following types:
WebMVC.Controllers.HomeController
WebMVC.Areas.Blogs.Controllers.HomeController
this is how my Global.asax looks.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapAreas("{controller}/{action}/{id}",
"WebMVC.Areas.Blogs",
new[] { "Blogs", "Forums" });
routes.MapRootArea("{controller}/{action}/{id}",
"WebMVC",
new { controller = "Home", action = "Index", id = "" });
//routes.MapRoute(
// "Default", // Route name
// "{controller}/{action}/{id}",// URL with parameters
// new { controller = "Home", action = "Index", id = "" }
// // Parameter defaults
//);
}
protected void Application_Start()
{
String assemblyName = Assembly.GetExecutingAssembly().CodeBase;
String path = new Uri(assemblyName).LocalPath;
Directory.SetCurrentDirectory(Path.GetDirectoryName(path));
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new AreaViewEngine());
RegisterRoutes(RouteTable.Routes);
// RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);
}
If I remove the /Areas/Blogs from routes.MapAreas, it looks at the Index of the root.
In ASP.NET MVC 2.0, you can include the namespace(s) for your parent project controllers when registering routes in the parent area.
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" },
new string[] { "MyProjectName.Controllers" }
);
This restricts the route to searching for controllers only in the namespace you specified.
Instead of WebMVC.Areas.Blogs and WebMVC, use WebMVC.Areas.Blogs and WebMVC.Areas.OtherAreaName. Think of the area name as the namespace root, not an absolute namespace.
You can prioritize between multiple controllers with the same name in routing as follows
E.g., I have one controller named HomeController in Areas/Admin/HomeController
and another in root /controller/HomeController
so I prioritize my root one as follows:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", // Parameter defaults
id = UrlParameter.Optional },
new [] { "MyAppName.Controllers" } // Prioritized namespace which tells the current asp.net mvc pipeline to route for root controller not in areas.
);