MVC Attribute Routing RoutePrefix does not work with default routing - c#

I have a work-around, but I'd really like to know why this doesn't appear to work in MVC. (.Net 4.6.1)
I have a controller which I want to use a RoutePrefix:
[RoutePrefix("entry")]
public class DefaultController : Controller
{
[HttpGet]
[Route(), Route("Index")]
public ActionResult Index()
{
// ...
}
}
In the route config:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "entry", action = "index", id = UrlParameter.Optional }
The issue is that with this configuration, running under local IIS to test, navigating to "localhost/testApp/entry" works, "localhost/testApp/entry/index" works, however the default "localhost/testApp/" results in a 404.
This has been doing my head in because on a fresh project with the default controllers and a default set to "home" and "index" the "localhost/testApp" would render Home/Index without an issue.
I narrowed it down to the RoutePrefix being the issue. If I remove the RoutePrefix and change the defaults to: new { controller = "default", action = "index", id = UrlParameter.Optional }
Then "localhost/testApp" works, but obviously this requires using /default for other routes rather than /entry.
Also, if I leave the prefix in (switching the default controller back to "entry") and add Route("~/") to the Index method, then "localhost/testApp" also works as according to the doco that a ~ route overrides the route prefix.
I'd like to know if there is an explanation why RoutePrefix doesn't seem to play nice with default routing? I'm fine with adding a ~/ route for that default action, but it seems I'm missing some understanding on how RoutePrefix is intended to be used.

Action attribute routing has the highest priority. If you use it only route attributes will be working, everything else will be ignored. You can to one action as many routes as you need.
Since you have 2 variants - Route() and Route("Index") it works only for 2 urls -"localhost/testApp/entry" and "localhost/testApp/entry/index".
if you remove Route() it will work only for one url-lcalhost/testApp/entry/index".
If you add 3rd Route("~/") it will work for 3rd url "localhost/testApp"
Sign ~ means that any prefixes should be ignored, it starts from root.
So you can not use default conventional routing on Index action since it is only obeys routing attributes.
Also, you have a controller [RoutePrefix("entry")] attribute routing too and it that next highest priority and because of this it overrides your convention routing in the config file. This is why default routing doesn't work for this controller and it doesn't go to Index automaticaly. To make default route work you need to remove route prefix and fix web config
defaults: new { controller = "default", action = "index", id = UrlParameter.Optional }
Current default controller = "entry" doesn't exist at all.
So you have two choices to have Index as default route action - remove all attribute routing and lost all another extra routes or add one more.

Thanks to Serge for helping point out a bad assumption I had about [RoutePrefix]. The problem here turns out that [RoutePrefix] is not a substitute name for a controller, (though that is how it behaves on the surface) but rather a prefix to each individual action. While the mapping in the URL will be identical:
Example 1:
public class EntryController
{
public ActionResult Index() { ... }
}
Example 2:
[RoutePrefix("Entry")]
public class DefaultController
{
[Route("Index")]
public ActionResult Index() { ... }
}
Both of these examples would resolve "localhost/testApp/entry/index", however only the first mapping would be considered as a match for {controller}/{action} and resolve a "defaults" mapping of "entry/index".
So if an action /w Attribute-based routing needs to be made a root default you need to explicitly declare it as the root using [Route()] if there is no [RoutePrefix], or [Route("~/")] if there is a [RoutePrefix]. since it won't be included in the {controller}/{action} routing. (Verified by removing the Default {controller}/{action} routing entirely).

Related

How do I change the route of an ASP.NET MVC controller?

I have a typical ASP.NET MVC controller, but I just want to change its route. The default route now is:
Blog/{controller}/{action}/{id}
I want to change the route of a specific controller to
Blog/Admin/{controller}/{action}/{id}"
I tried to achieve this by adding the Route, RouteArea and RoutePrefix attributes to the controller but without any success.
How can I achieve this?
Add this route prior to the default
routes.MapRoute(
name: "BlogAdmin",
url: "Blog/Admin/{action}/{id}",
defaults: new { controller = "YourSpecificControllerName", action = "Index or other default action name", id= UrlParameter.Optional });
Since this is for a specific you don't need {controller} part in your url. If you still want to specify it change the url argument to "Blog/Admin/YourSpecificControllerName/{action}/{id}" where YourSpecificControllerName is the name of your controller.
Also since the order of rote registration matters make sure that this route registered prior to the the default one

Asp.net MVC routing using subfolders

I m trying to use subfolders within the Controllers folder:
The structure looks like this:
Controllers (Folder)
LoginController.cs
WelcomeController.cs
Settings (Folder)
UsersController.cs
I've several problems.
When I perform a return RedirectToAction("Index", "welcome") from my LoginController, the url looks like http://mywebsite.local/settings/welcome
I thought I will get a 404 error..
How to make the redirection launches http://mywebsite.local/welcome and get a 404 error when I launch http://mywebsite.local/settings/welcome
Do I really need to use Areas?
This is my RouteConfig.cs
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "settings",
url: "settings/{controller}/{action}/{id}",
defaults: new { controller = "Users", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Login", action = "Index", id = UrlParameter.Optional }
);
Do I really need to use Areas?
No, but you're trying to re-invent the wheel - creating a structure a bit like Areas. I'd recommend you go with Areas, it will make your life easier.
Area Hype:
I wouldn't recommend applying Areas just because you can.
That would be like "giving a man a hammer and suddenly everything's a nail".
In this case we have a cool feature called "Areas" and (without knowing the underlying architecture)
others recommended it the instant someone asks about sub-folders.
Areas weren't designed for the sole purpose of giving you an extra Route Parameter.
The "Areas" original intent is to give you a clear SoC (Separation of Concerns).
For Example:
Say you have two separate web application experiences and have decided to roll them under one Solution.
Your call-center may need to look up detailed information and enter in data on individual customers, while your managers and executives will peruse higher-level reporting and rarely enter in data.
In this scenario it may make sense to split your business logic into "Reporting" and "CallCenter" Areas.
Sub-Directories:
In MVC, the Sub-Folders you use under "Controllers" are Ignored when it comes to Routing.
SubFolders are perfectly fine with how the Questioner is using them to organize his Controllers.
Adding a SubFolder name to his URL makes for a more human-readable URL too.
He just made a mistake in the exclusivity of his first Mapping Route.
The problem is it was matching on everything.
Just because you have "settings/" in your MapRoute doesn't mean it will apply only to incoming URL's.
MVC will use your MapRoute Logic to figure how you would like to write your URL's too!
The Fix (Option 1 - Use MapRoute):
routes.MapRoute(
name: "Settings",
url: "Settings/{controller}/{action}/{id}",
defaults: new { action = "Index", id = UrlParameter.Optional },//Remove the Default Controller as we want to explicitly require it and constrain it. - 08/26/2018 - MCR.
constraints: new { controller = "Users|Admins" }//If you have other Controllers under a SubFolder (say we also had AdminsController.cs), then simply Pipe-Delimit them. - 08/26/2018 - MCR.
);
This is the way I've chosen to go, but if you want tighter control, you could use the Option below instead.
The Fix (Option 2 - Use Route-Attributes):
First, make sure you enable this feature by adding routes.MapMvcAttributeRoutes():
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();//Add this BEFORE MapRoute for Route-Attributes to work.
Next, Decorate your Controller with the following Attributes:
(You may even include Default/Optional Values, just like in your MapRoute.)
[RoutePrefix("Settings/Users")]//Add your ParentFolder and Include your Controller name too.
[Route("{action=Index}/{id?}")]//You need this if you are using the RoutePrefix Attribute. Without this, you will need to define "[Route]" above every Action-Method. - 08/26/2018 - MCR.
public class UsersController : Controller
Note:
If you use Route-Attributes instead of MapRoute, then you will not be able to hit the
Controller without the "Settings" Prefix.
With the Custom and Default MapRoutes, you could have accessed your controller either way.
By decorating your Controller with these Attributes, you now force it to only use this exact path.
This may be what you want, but if you start IIS Express from Visual Studio on one of your Views,
then it will not find it because Visual Studio does not know to add the RoutePrefix for you.
I say this, so you are not discouraged when you start debugging and think it doesn't work.
See this link for more information about Attribute-Routing:
https://blogs.msdn.microsoft.com/webdev/2013/10/17/attribute-routing-in-asp-net-mvc-5/
The folder structure of your controllers here has very little relevance to what you are seeing. You could maintain that structure and accomplish your goal if the route would work.
However, you've specified two matching routes that can be used to encode the same action, and it is simply giving the first one priority. See, routes don't just work forwards, MVC uses them in reverse, too, to process your ____Action() statements. You could specify a named route (e.g. "settings") in your RedirectToAction("Index", "welcome"), by using RedirectToRoute instead, or manually specify all the routes that have prefixes in your route table. but why start your project off with intentionally conflicting routes like this?
As Joe R said, Areas will make your life easier. Why? Mainly because there is an additional built-in route parameter to do exactly what you want. Really, the question becomes "why avoid Areas?"
Looks like the answer not answering the question, because IMHO, we use Area when we need mini program under our main program,
ie. Forum, (Comprehensive) Blog, Marketplace (under our main site) either forum.mainsite.com or mainsite.com/forum
So you DONT need Area in your case
Solutions :
FYI, routing are something that have nothing to do with your architecture / structure / foldering in your applications.
In Example your ControllerName is SettingsUsersController
routes.MapRoute(
name: "settings",
url: "settings/users/{action}/{id}",
defaults: new { controller = "SettingsUsers", action = "Index", id = UrlParameter.Optional }
);
in your case, you can fix your routing like this (this is for making sure you have pretty url but still simple Controller structure):
routes.MapRoute(
name: "settings",
url: "settings/{controller}/{action}/{id}",
defaults: new { controller = "SettingsUsers", action = "Index", id = UrlParameter.Optional }
);
Your ControllerName would be SettingsUsersController
SettingsUsersController.cs
public class SettingsUsersController : Controller
{
public ActionResult Index()
{
return View("~/Views/SettingsUsers/Index.cshtml", db.YourDBName.ToList());
}
}
And why 404? I think because you are not "routing" correctly your View, you should do make subfolder like this under your Views Folder Views/SettingsUsers/Index.cshtml

Is there a way to make the route mapping based on specific path

I code lots of ASP.NET but I'm kind of new with .net MVC, I've a default route registered like this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
And I want to add another Administrator area on the site and all the URL would be something like "http://localhost/Administrator/controller1", "http://localhost/Administrator/controller2", etc. I've lot of controllers in the Administrator namespace and I'm trying to register those controller with only one MapRoute, I did something like this:
routes.MapRoute("Administrator_default", "Administrator/{controller}/{action}/{id}", new { controller = "Administrator", action = "Index", id = "" });
it works with those controller but one problem is that in some other controller while I try to do a redirect like:
return RedirectToAction("Index", "Forum");
Then I'll always be redirect to http://localhost/Administrator/Forum instead of http://localhost/Forum, it's not a big issue but make the URL looks strange, I tried to restrict to certain namespace but it's not working. It looks just as I'm trying to register two default route and .Net just match the first one, I'm wondering is there a way to make it two default route and map on only specific path only?
This exact issue is why Areas were added to MVC 2. http://www.asp.net/whitepapers/what-is-new-in-aspnet-mvc#_TOC3_2
Agree with Zach's answer.
Not ideal, but you do have the option to have controllers in the controller root folder (e.g. /controllers/HomeController.cs) of your project as well as the controllers in Areas (maybe high level root pages that display menus for areas).
Secondly a quick tip on using the RedirectToAction method. You can specify the area you would like to redirect too using the route parameters e.g:
RedirectToAction("Index","Form", new { area = "MyOtherArea" });

Why doesn't this route to the default action?

So I have this route mapped:
routes.MapRoute(
"Solutions",
"{lang}/Solutions/{controller}/{action}",
new { lang="en-US", controller = "WhatWeDo", action = "Index"}
);
When I go to"
/en-GB/Solutions/SolutionA/Index/
It routes just fine, but
/en-GB/Solutions/SolutionA/
Doesn't route at all. However, if I take out the lang parameter, so the route looks like
routes.MapRoute(
"Solutions",
"Solutions/{controller}/{action}",
new { controller = "WhatWeDo", action = "Index"}
);
and I go to
/Solutions/SolutionA/
It routes just fine. Any ideas? I'd like to not have to specify the default action all the time for this route. Thanks.
What order are you defining your routes in global.asax, this could have something to with the issue but my initial guess is that it's matching the default route as such
controller: en-GB
action: Solutions
id : SolutionA
What you might be able to do is setup some sort of regular expression matching on your routes to exclude your language from the "default" route? Try this link for more information about using regex for route filtering http://www.iridescence.no/post/Defining-Routes-using-Regular-Expressions-in-ASPNET-MVC.aspx

URL routing in asp.net mvc

i have a ApplicationController class with an action called Admin
so my url is www.mysite.com/Application/Admin
is there anyway i can have the routing be www.mysite.com/Admin and go to this method.
i know i can do this by creating an AdminController but for this one function i thought it was easier to just put in another controller
Put this above your default route:
routes.MapRoute(
"ShortRoute",
"{action}",
new { controller = "Application", action = "Index"}
);
You can set the Application controller and the Admin method as the default controller and action, using parameter defaults:
routes.MapRoute(
"Default", // Route name
"{action}", // URL with parameters
new { controller = "Application", action = "Admin" }
);
If this is your last route, it will match any request that does not have a controller name and an action name in it. In this particular example, even a request without an action will execute your Admin action, since it's the default action.
Note that routes with parameter defaults can create strange behavior in your existing routes, if you have any. You can always use the ASP.NET MVC Routing Debugger to test which routes match a given URL.

Categories

Resources