I am working on an ASP.NET MVC application. For some reason, everytime I think I understand routing, something pops up that I don't understand. Currently, I have two routes that I can't seem to figure out. My directory structure looks like the following
- Views
- Internal
- Profile
- Index.cshtml
- Input
- Page1.cshtml
In my global.asax.cs file, I have added the following mappings:
routes.MapRoute(
"UserProfileInfo",
"{controller}/profile",
new { controller = "Internal", action = "UserProfileInfo" }
);
routes.MapRoute(
"Page1",
"{controller}/input/page1",
new { controller = "Internal", action = "Page1" }
);
In MyController, I have the following:
public ActionResult UserProfileInfo()
{
return View("~/Views/internal/profile/Index.cshtml");
}
public ActionResult Page1()
{
return View("~/Views/internal/input/Page1.cshtml");
}
I want to store my actions in a single controller. I thought I had everything setup properly. But I continue to get a 404. What am I doing wrong?
Remove the "Controller" suffix from the controller name in your calls to MapRoute to create a mapping to a class called InternalController. The Controller suffix is appended by the framework when looking for a matching implementation. e.g.:
routes.MapRoute(
"UserProfileInfo",
"{controller}/profile",
new { controller = "Internal", action = "UserProfileInfo" }
);
Related
Is there a way to use Convention and Attribute Routing together?
I want to call an action method with the real name of method and controller when I defined the attribute routing.
The mapping method is calling at startup:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();///
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Here is the controller:
[RoutePrefix("d")]
[Route("{action=index}")]
public class DefaultController : Controller
{
[Route]
public ActionResult Index()
{
return View();
}
[Route("f")]
public ActionResult Foo()
{
return View();
}
}
I can reach to Foo action method with /d/f url. But when I try this url: /Default/Foo, the 404 error occurs. Actually it throws the action not found exception which it says like A public action method 'Foo' was not found on controller 'Namespace...DefaultController'.
I checked the source code of asp.net mvc and I saw these lines:
if (controllerContext.RouteData.HasDirectRouteMatch())
{
////////
}
else
{
ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
return actionDescriptor;
}
It checks if there is a direct route or not, which the /Default/Foo route is not a direct route so it should act as convention routing which is registered at startup as {controller}/{action}/{id}. But it doesn't find the action with controllerDescriptor.FindAction method and it throws the exception.
Is this a bug or cant I use both routing methods together? Or are there any workaround to use both?
Edit
I debugged into mvc source code, and I saw these lines:
namespace System.Web.Mvc
{
// Common base class for Async and Sync action selectors
internal abstract class ActionMethodSelectorBase
{
private StandardRouteActionMethodCache _standardRouteCache;
protected void Initialize(Type controllerType)
{
ControllerType = controllerType;
var allMethods = ControllerType.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public);
ActionMethods = Array.FindAll(allMethods, IsValidActionMethod);
// The attribute routing mapper will remove methods from this set as they are mapped.
// The lookup tables are initialized lazily to ensure that direct routing's changes are respected.
StandardRouteMethods = new HashSet<MethodInfo>(ActionMethods);
}
The last comments about attribute routing explains why this problem happens. The Attribute routing removes StandardRouteMethods when you call MapMvcAttributeRoutes.
I'm still seeking a workaround.
I want to call an action method with the real name of method and controller when I defined the attribute routing.
It seems like you are going pretty far in the wrong direction if all you want is to call the application knowing the controller and action names. If you have the name of the controller and action (area and other route values) you can use them to get the URL to use quite easily.
var url = Url.Action("Index", "Default");
// returns /d/f
If this works for your usage, then you don't have to have a duplicate set of route mappings at all.
NOTE: Creating 2 URLs to the same page is not SEO friendly (unless you use the canonical tag). In fact, many question here on SO are about removing the duplicate routes from the route table. It seems like they did us all a favor by removing the duplicate routes so we don't have to ignore them all manually.
Alternatives
One possible workaround is to add 2 route attributes to the action method (I don't believe it can be done without removing the RoutePrefix from the controller).
[Route("d/f")]
[Route("Default/Foo")]
public ActionResult Foo()
{
return View();
}
Another possible workaround - don't use attribute routing for all or part of your application. Attribute routing only supports a subset of convention-based routing features, but is useful in some specific scenarios. This does not appear to be one of those scenarios.
Recently I had to update my mvc webapplication so that a basic entity of the system is displayed in the UI with a different literal.
Lets say
Previously I had: "Vessels"
Now I am asked to make it: "Ships"
The urls where mapped as by convention: mysite/{controller}/{action}/{id}
So I had urls like :
mysite/Vessels/Record/1023
mysite/Vessels/CreateVessel
I did all the renaming in the User Interface so that the titles and labels are changed from Vessel to Ship and now I a m asked to take care of the urls as well.
Now, I do not want to rename the Controller names or the ActionResult method names, because it is some heavy refactoring and because it is VERY likely, that the literals will soon be required to change again... ;-)
Is there any quick solution for me by editing just the RouteConfig, or something like that, that could do the work with a couple of lines coding?
Yeah, just register the route that will map your VesselsController actions:
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Vessels", // Route name
"Ship/{action}Ship/{id}", // URL with parameters
new { controller = "Vessel", id = "" } // Parameter defaults
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
}
Also make sure to register your route before default one. Because, in other case, default route will be executed first and you will get an exception because no ShipController is defined in your application.
If an action exists on a controller, does asp.net-mvc route to that action before attempting to process any custom mapped routes?
Example.
Say I have the following controller
public class ShopController : Controller
{
public ActionResult Shop(Category category)
{
// returns some result
}
public ActionResult CartItemCount()
{
// returns some result
}
}
And I have registered the following route in my route collection:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"Shop",
"Shop/{category}",
new { controller = "Shop", action = "Shop", category = UrlParameter.Optional } // Parameter defaults
);
}
What would happen if I had the following URL?
http://www.example.com/Shop/CartItemCount
Edit
I confused myself by thinking that the name of the mapped route was related to how it was processed. That is not the case, when it comes to url matching the name of the route does not matter.
As it turns out I had another route defined just above the one I gave in the example. This route, though named differently, was getting matched. I didn't even think to check it because, as I said, I thought the name given to a route somehow impacted the matching.
The routes decide which action to use. If there are no routes defined, you wont hit an action even if it exists. Install RouteDebugger and fire off your url. It will tell you ALL routes that it matches and which one it has actually used.
I have actions
public virtual ActionResult Show(string userId)
and
public virtual ActionResult Show(int groupId)
In Global.asax I have
routes.MapRoute(
"Group_Default",
"{controller}/{action}/{groupId}",
MVC.Groups.Show()
);
routes.MapRoute(
"UserProfile_Default",
"{controller}/{action}/{userId}",
MVC.Profile.Show()
);
Now when I request for group/show/... it works fine. But when I call Profile/Show/... parameter is null. But if I remove UserProfile_Default then both works but profile URL contains question mark for parameter (and I want it clean like .../profile/show/5678)
It seams that somehow one route block another.
Try these instead:
routes.MapRoute(
"Group_Default",
"Group/{action}/{groupId}",
new { controller = "Group" }
);
routes.MapRoute(
"UserProfile_Default",
"Profile/{action}/{userId}",
new { controller = "Profile" }
);
For future reference, the route debugger is a really good tool to see exactly what's going on with your routing and which URLs are hitting what actions: http://haacked.com/archive/2008/03/13/url-routing-debugger.aspx
I have multiple controllers with different actions (no "Index" actions). The actions I consider "default" actions, are named differently. I want to create default routes for their names and have the first available action (from my list of default actions) executed if only the controller name is provided in the route.
So, for example, I have the following actions which I want to consider default and want checked for their existence in a controller in this order:
List()
Draw()
ViewSingle()
The routing should somehow search for /{controller} and then take the first available action from the list above as default action, e.g.:
/ControllerA -> ControllerA.List()
/ControllerB -> ControllerB.Draw()
/ControllerC -> ControllerC.ViewSingle()
/ControllerD -> ControllerD.Draw()
/ControllerE -> ControllerE.List()
Is this possible? I tried creating additional Default actions like this but couldn't get it to work:
routes.MapRoute("Default1", "{controller}/{action}",
new { controller = UrlParameter.Optional, action = "List" }
routes.MapRoute("Default2", "{controller}/{action}",
new { controller = UrlParameter.Optional, action = "Draw" }
routes.MapRoute("Default3", "{controller}/{action}",
new { controller = UrlParameter.Optional, action = "ViewSingle" }
Help?
I think you got something wrong about the default route. Those controller and action parameters are there for convention. If URL has a controller name in it, it will route to suitable controller. Same thing is true for Index methods. It just provides a default value for that.They are already optional and that's why. I think you don't need routes here: You could try to place your default functions in Index methods and it would work.Just to be clear:
public class ControllerA:Controller
{
public ActionResult Index()
{
return List();
}
public ActionResult List()
{
//List function
}
}
public class ControllerB:Controller
{
public ActionResult Index()
{
return Draw();
}
public ActionResult Draw()
{
//Draw function
}
}
I think it would work. But if you want to go on with creating routes, you create each route like this:
routes.MapRoute("Default1", "ControllerA",
new { controller = ControllerA, action = "List" }
Notice that ControllerA is not in curly braces,it's a static text. So you create routes for each controller. Keep controller naming convention in mind.
I agree with Narsil. You should write a constant to url. -like controller name- 'cause MVC cannot resolve this routes and cannot figure out what you want to show.
eg.
routes.MapRoute(null, "ControllerA/{action}",
new { controller = "ControllerA", action = "List" }
routes.MapRoute(null, "ControllerB/{action}",
new { controller = "ControllerB", action = "Draw" }
routes.MapRoute(null, "ControllerC/{action}",
new { controller = "ControllerC", action = "ViewSingle" }