How should I configure the following non area routes?
/foo/{controller}/{action}/{id}
maps to controllers in namespace myapp.foo.
/{controller}/{action}/{id}
maps to controllers in namespace myapp.
I also have 2 areas, bar and baz, they are registered with registeraAllAreas.
My current setup
This is my current setup. It gives the problem below when I use the url /Home/Index.
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("myapp/elmah.axd/{*pathInfo}");
AreaRegistration.RegisterAllAreas();
routes.MapRoute(
"foo", // Route name
"foo/{controller}/{action}/{id}", // URL with parameters
new { action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new string[] { "myapp.Controllers.foo" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new string[] { "myapp.Controllers" }
);
Multiple types were found that match the controller named 'Menu'. This
can happen if the route that services this request
('foo/{controller}/{action}/{id}') does not specify namespaces to
search for a controller that matches the request.
The request for 'Menu' has found the following matching controllers:
myapp.Controllers.MenuController
myapp.Areas.bar.Controllers.MenuController
myapp.Areas.baz.Controllers.MenuController
Clearly there's something I'm doing the wrong way.
Update
I also get the wrong adress generated when I use:
<% using (Ajax.BeginForm("SaveSomething", "Home", ...
It renders <form target="/foo/Home/SaveSomething"
I'm guessing that one cannot reliably use {controller} in two routes in the same area.
Update 2
It seems to work much better when I put the /foo route registration at the bottom.
This raises the question, what is considered a/the default route? (As the default route is reccomended to be put at the very end.)
You have two controllers that has the name MenuController so MVC doesn't know which one to use if you don't give it more information. In you areas you probably have a files named something like <YourAreaName>AreaRegistration. Open those files and update the RegisterArea method so you route the request to the right controller.
From your error message it seems like the route is getting mapped to foo/{controller}/{action}/{id}, which doesn't have a MenuController. My guess is that you have a action link on a page under foo something something. That will generate an incorrect link if you don't specify the area for the link.
Try this to use the default route with ActionLink:
#Html.ActionLink("Some text", "action", "controller", new { area = "" }, null)
If you want the request to go to a specific area just write it down in the call.
UPDATE: The problem is that when you write something like Ajax.BeginForm("SaveSomething", "Home",...) it will match the first route. You can't solve this by putting the area in the BeginForm statement as I suggested before since the foo route is not an area. You have two options, 1: move the foo part to an area, 2: put the foo route after the default route. If you put the default route before the foo route you will get a hard time rendering urls as long as you have foo in the same area as the default route (the default area), since the route engine will always find the default one first. However, you will be able to catch request to the foo route. So my best suggestion is to put the foo route in an area.
Related
I'm a bit baffled by how MVC is figuring out my routing details. Let me see if I can explain this right.
So ... given that I have the default route ...
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "CMS", action = "GetPage", id = UrlParameter.Optional }
);
And my app is a content management system so I want to create nice urls from the site structure so i'll map a wildcard url that lets me determine if i need to render a 404 based on what's in my database ...
routes.MapRoute(
"CMS",
"{*path}",
new { controller = "CMS", action = "GetPage", path = string.Empty }
);
Herein lies the problem.
MVC will basically match everything to the default route because technically no params are required assuming "GetPage" on the "CMS" controller requires no params, which is not what i want.
What i'm trying to say to it is something like "given 2 or 3 url parts, look for a controller and action match with an optional id parameter but for all other urls including ones that you can't match to this route fall down in to the CMS route".
The only "easy" way I found to do this is to change the first route to something like this ...
routes.MapRoute(
"Default",
"Get/{controller}/{action}/{id}",
new { controller = "CMS", action = "GetPage", id = UrlParameter.Optional }
);
Then any url that starts "Get/" will match that route and all other routes automatically fall down in to the second route, but that doesn't sit right somewhere in my head and I can't figure out quite why yet (i think it's because it doesn't really solve the problem it simply moves it).
My problem is that I don't really want a route that says "given no values match this route anyway" so I changed it to this ...
routes.MapRoute(
"Default",
"{controller}/{action}/{id}"
);
Now for some odd reason literally every request is hitting the catch all (not quite what i want but close).
So any ideas guys?
EDIT:
I came a touch closer with this ...
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { id = UrlParameter.Optional}
);
... but that now matches all urls with 2 parts "foo/bar" rather than dropping through like it should to the other route because there is no "foo" controller.
Ok I have a solution, it works for me because 99% of requests need to map to the CMS route but in your case it may not if you have a lot of controllers you need to map to.
I was hoping to find an ideal solution for all but this is merely an ideal in my scenario ...
So assuming you have (like me) only a cms controller and a an accounts controller you can do this:
routes.MapRoute("Account", "Account/{action}", new { controller = "Account" });
routes.MapRoute(
"Default",
"{*path}",
new { controller = "CMS", action = "GetPage", path = string.Empty }
);
That way only urls starting with "Account" get caught by the first rule, and everything else falls through to the default route and gets handled by the cms controller.
I plan to simply add more routes as I add more controllers. It's not an ideal solution because it could mean i end up with a lot of route mappings in the long term but its a good enough solution to meet my needs.
Hope it helps someone else out there.
Maybe I don't understand your problem fully but why not have this route:
routes.MapRoute(
"CMS",
"CMS/{action}/{path}",
new { action = "GetPage", path = string.Empty }
);
and add it into the route collection before your default path...
I'm attempting to do a custom route so I can prefix a url in my application with a chosen string and the do some processing based on that. The problem I'm running into is, that the action links that are generated are not contextualized based on the url that it exists on.
Routes:
routes.MapRoute(
"TestRoute",
"TEST/{controller}/{action}/{id}",
new { controller = "Space", action = "Index", id = UrlParameter.Optional });
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Space", action = "Index", id = UrlParameter.Optional });
Navigating to TEST/Space/Index works, as well as Space/Index, but the odd issue I need fixed is that the links generated via ActionLink do not obey the context in which they are loaded, at least for the {controller}/{action}/{id} default route. Pages that are loaded under TEST/Space/Index list links properly, but when /Space/Index is loaded, they are all referencing the TEST/ route that the calling url does not. Is this the default behavior? Is there a way to get these links to generate in the proper context?
Edit:
The first place I saw this was in the Html.BeginForm without the TEST/
Html.BeginForm("ToTheMoon", "Space", FormMethod.Post)
which renders the link as TEST/Space/ToTheMoon
but it also shows up in links:
#Html.ActionLink("Take Me To The Space Port", "SpacePort", "Space")
which renders TEST/Space/SpacePort
I found a bit of a way around this so that the context wouldn't be lost. Here's what I did to get this to work.
TestRoute changes to this:
routes.MapRoute(
"TestRoute",
"{path}/{controller}/{action}/{id}",
new { controller = "Space", action = "Index", id = UrlParameter.Optional },
new { path = #"TEST" },
new string[] { "The.Namespace" });
Setting the constraint on path and removing it from the route makes this routing work. Now I can hit the /TEST/Space/Index and all my links generated from ActionLink behave as intended. Also on a related issue, I ended up adding the namespace specification in the map route, as the development environment required that be in there to properly route things to the TEST path.
Some of the info I found was on this page.
If you do:
#Html.ActionLink("Text", "Index", "Space")
That is going to match the first route in your collection (TestRoute). This is the default behavior.
If you want to choose a specific route then use #Html.RouteLink instead.
If you want to target a specific route, you could use RouteLink extension, it allows you to specify which exact route should be used to generate the link.
#Html.RouteLink("with Test", "TestRoute")
#Html.RouteLink("with Test", "TestRoute", new {controller="Space", action="Foo"})
#Html.RouteLink("without Test", "Default", new {controller="Space", action="Foo"})
If I am registering the route to an area (say it's called Admin) in the global.asax file rather than the area's AdminAreaRegistration.cs file, is there any reason why I can't delete the AdminAreaRegistration.cs file? Is there any other code in the framework that may call into it at some stage which might throw an exception if it is missing?
As asked in comments, here is the code to register an Area in global.asax
routes.MapRoute(
"AdminAreaRoute",
"Admin/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new string[] { "Payntbrush.Presentation.Demo.MVC3.Areas.Admin.Controllers" }
).DataTokens.Add("Area", "Admin");
routes.MapRoute(
"SiteAreaRoute",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { controller = "/|Home|Account" }, // Constraint's on the URL (second param above) {controller} segment
new string[] { "Payntbrush.Presentation.Demo.MVC3.Areas.Site.Controllers" }
).DataTokens.Add("Area", "Site");
The key is in using the DataTokens collection, as highlighted in this excellent post by Phillip Haydon. You can add as many as you want, but make sure you put the root site area after the explicitly named areas so that {controller}/{action}/{id} doesn't catch all the requests before the other Map Routes can.
Could you post the code that you are using to register your area in the global.asax?
The reason that I ask, is I don't think that it is technically possible to get an area working correctly without the xxxAreaRegistration.cs class implemented somewhere (even if in another project.
It might seem like it is working without it but in reality I think you might simply telling .net that your route is pointing to a sub directory.
I would expect you to get an error at some point.
Have a look at msdn AreaRegistration Class documentation it says
Provides a way to register one or more areas in an ASP.NET MVC application.
while it doesn't say that its the only way, I think that might be the case.
I've deleted these files and haven't had any problems so far. I'm going to assume that it is OK until I run into any issues.
What are the best practices for setting up projects with multiple interfaces in asp.net MVC2? I'm starting a project where we need a typical arrangement like:
example.com/ - end-user interface
example.com/Admin/ - site management
The site will be involved enough that simply having all the user logic in HomeController and all the admin logic in AdminController isn't really an option. I've read about using multiple Route definitions, but the following doesn't seem to work properly:
routes.MapRoute(
"Admin", // Route name
"Admin/{controller}/{action}/{id}", // URL with parameters
new { controller = "Admin", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
It causes all the links on the homepage to point to example.com/Admin/local-part instead of example.com/local-part.
I'd like to avoid specifying a different MapRoute for each Admin controller (omitting {controller} from the mapping each time).
I've also read about setting up different Areas within a project, but that seems like it would be too involved for the scope of this project.
Finally, I've also read that you can put constraints on MapRoutes, but the documentation on that part seems confusing to me. Is there something obvious I'm missing? Failing that, are there any best practices for asp.net structure that I should keep in mind?
It sounds like Area's is ready made for what you want to do. Setting up an area isn't really all that involved, basically you just have to register it. By default the area routing will match the default in the global.asax, the exception being the extra "\area" slug in the url. I'm fairly certain it only took me a few minutes when I set this up on a project a few months ago.
If your Admin controller is complicated enough to exceed the scope of a single controller then that indicates that an area may be warranted.
I have these 2 routes mapped out:
routes.MapRoute(
"Admin",
"admin/{controller}/{action}/{id}",
new { controller = "Admin", action = "index", id = "" }
);
and then I have:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
So the 2 routes are identical, except the first one has /admin prefixed in the URLS.
This is what is happening, I have no idea how to explain this:
When I go to:
www.example.com/user/verify
it redirects to
www.example.com/admin/user/complete
instead of
www.example.com/user/complete
The action Verify simply redirects to Complete like this:
return RedirectToAction("complete", "user");
And all the complete action does is populate the ViewModel, and then calls the view.
How can it be redirecting and adding the prefix /admin/ to the URL?
I believe it is redirecting to the Admin route because the Admin route is the first with all the matching parameters (controller and action in the case provided). If you want to use something like this you will need to either look into using areas (MVC2) or using a named route redirect.
admin is your controller, you dont need an admin/controller/action the default route works just fine
all you need is an admin controller and the default route will find it for you
ie {controller}/{action}/{id}
will send /admin/addproduct to a controller named admin and an action called addproduct
you only need to add routes if you want something custom for example
/products/televisions/hdtv/2
where products would be a controller and the last 3 are category,subcategory and pagenumber
on the controller you point it to within your route.
hope that makes sense
Not sure exactly how your controllers are structured, but you can add a constraint to the first MapRoute to limit it to the specific controllers you want the route to apply to:
routes.MapRoute(
"Admin",
"admin/{controller}/{action}/{id}",
new { controller = "Admin", action = "index", id = "" } ,
new { controller = "[Some regex Expression - e.g. Admin]" }
);
Which will make the route only applicable for those controllers related routes. You can also use this tool to debug your routes. Depends how you have things structured, but like #NickLarson said - sounds like your using area functionality of MVC 2.
mvc goes from top to bottom while matching router, that' why you are dealing with this problem