Building my first ASP.NET MVC 3 application and trying to implement the ability to disassociate a given ice cream from a menu. Each has an integer identifier associated with it and on a page I display these mappings and provide a link by them to remove the ice cream from the menu.
I've got an ActionLink that looks like this:
#Html.ActionLink("Remove", "RemoveMenuIceCreamMapping", "IceCream", new { iceCreamId=item.IceCreamId, menuId=item.MenuId}, null)
In my IceCreamController I've got an Action that looks like this:
[HttpPost]
public PartialViewResult RemoveMenuIceCreamMapping(int iceCreamId, int menuId)
{
...
}
Did a little searching and believe I may need to modify the routes in the Global.asax.cs file's RegisterRoutes to handle these two parameters. So I tried this like so:
public static void RegisterRoutes(RoutesCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// I added this route in an attempt to handle the two parameters:
routes.MapRoute(
"RemoveMenuIceCreamMapping", // Route name
"IceCream/RemoveMenuIceCreamMapping/{iceCreamId}/{menuId}", // URLwith parameters
new
{
controller = "IceCream",
action = "RemoveMenuIceCreamMapping",
iceCreamId = UrlParameter.Optional,
menuId = UrlParameter.Optional
}
);
// this was there by default
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new {controller = "Home", action = "Index", id = UrlParameter.Optional }
};
}
But this doesn't work - I get a "The resource cannot be found." error, 404. Requested URL: /IceCream/RemoveMenuIceCreamMapping/1/10
1 is the Id of the IceCream and 10 is the menu's Id.
What I was expecting to happen was that the action RemoveMenuIceCreamMapping would get called, passing those two parameters, but I'm obviously not doing something right here and may just misunderstand how to accomplish what I want and be going about this the wrong way. Any guidance would be most appreciated.
Update
So, one more thing I've learned, after reading this SO question, my ActionLink isn't triggering a POST so removing the [HttpPost] from the action seemed like the right thing to do. And, in fact, as soon as I did that, the route was found and the action executed.
I think you problem is that the ActionLink uses an HTTP GET and you are only accepting HTTP POST.
You will probably need to change your view to issue an HTTP POST (e.g. with a regular HTML button inside a form) so that the verb that the browser sends matches with what you accept on the controller.
Related
I'm working on my first ASP.NET MVC 3 application and I've got a couple of actions defined to handle adding/removing an ice cream from a menu. They look like so:
[HttpPost]
public PartialViewResult AddMenuIceCreamMapping(int iceCreamId, int menuId)
[HttpPost]
public PartialViewResult RemoveMenuIceCreamMapping(int iceCreamId, int menuId)
In order for these actions to not result in a 404 error, I went into the Global.asax.cs file and added the following:
routes.MapRoute(
"AddMenuIceCreamMapping", // Route name
"IceCream/AddMenuIceCreamMapping/{iceCreamId}/{menuId}", // URLwith parameters
new
{
controller = "IceCream",
action = "AddMenuIceCreamMapping",
iceCreamId = UrlParameter.Optional,
menuId = UrlParameter.Optional
}
);
routes.MapRoute(
"RemoveMenuIceCreamMapping", // Route name
"IceCream/RemoveMenuIceCreamMapping/{iceCreamId}/{menuId}", // URLwith parameters
new
{
controller = "IceCream",
action = "RemoveMenuIceCreamMapping",
iceCreamId = UrlParameter.Optional,
menuId = UrlParameter.Optional
}
);
and those work, meaning I can click an Add or Remove button on my page and add or remove an ice cream/menu mapping. Great. But I'm expecting that there will be more situations like this and I can see this routes container having more entries such as these. And to my novice web programmer eye, it seems a bit clunky and I begin to think that perhaps I'm going about this the wrong way. Am I? Is there a better way to approach this so that I don't end up doing the "go create some action that takes N parameters and then go add a route" thing. Any suggestions here?
You can use placeholders in your mappings to deal with this... Instead of what you've got above, you could use this (note that I don't specify a default):
routes.MapRoute(
"TwoParameterRoute", // Route name
"{controller}/{action}/{id1}/{id2}", // URL with parameters
);
And if you have to add a route that takes three parameters, you extend the idea:
routes.MapRoute(
"ThreeParameterRoute", // Route name
"{controller}/{action}/{id1}/{id2}/{id3}" // URL with parameters
);
In a controller for the two parameter route, the code would look like this (for a contrived sample):
public ActionResult Index(int id1, int id2)
{
ViewData["id1"] = id1;
ViewData["id2"] = id2;
return View();
}
One final thing to note is that you can change the naming to match a pattern you might have. In the first example, instead of id1 and id2 you might have entityId and relatedId or something similar.
You know that you can also use querystring parameters even with POST requests? Using just the default route (that comes with new ASP.NET MVC projects), you can reach your actions with /IceCream/AddMenuIceCreamMapping/?iceCreamId=1&menuId=1 and /IceCream/RemoveMenuIceCreamMapping/?iceCreamId=1&menuId=1.
Using querystringparameters also show that this url is a parameterized method call, and not a reference to a resource, as the original URLs would indicate.
If your set of CRUD actions will be repeating with every custom Entity that you introduce in your application, then you can have the controller as a parameter too. As it is now, you have controller fixed. Also I would avoid such long action names. I would also avoid spelling Icecream as IceCream. Just coz of its looks.
Probably you should have a controller Menu, which handles Icecreams. It kinda doesn't make sense that Icecream handles its own things because it is a part of something else.
Such as
routes.MapRoute(
"RemoveIcecreamFromMenu", // Route name
"menu/remove-icecream/{iceCreamId}/{menuId}", // URLwith parameters
new
{
controller = "Menu",
action = "RemoveIcecream",
iceCreamId = UrlParameter.Optional, // i don't think those two should be optional
menuId = UrlParameter.Optional
}
);
But it makes sense for an Icecream controller to handle its own CRUD:
routes.MapRoute(
"CreateIcecream", // Route name
"icecream/create", // URLwith parameters
new
{
controller = "Icecream",
action = "Create"
}
);
routes.MapRoute(
"DeleteIcecream", // Route name
"icecream/delete/{id}", // URLwith parameters
new
{
controller = "Icecream",
action = "Delete",
id = #"\d+" // this tells the routing engine only integer are accepted
}
);
I would go with something that had descriptive urls and no default route:
routes.MapRoute(
"IceCreamEdit", // Route name
"{controller}/{action}/icecream/{iceCreamId}/menu/{menuId}" // URLwith parameters
);
I have to admit in advance that I'm quite new to MVC, I've been going through the resources at www.asp.net/mvc, but I was wondering if you guys could help me with something.
I have been asked to create a ASP.NET version of an existing PHP website, this website has a huge number of existing links to it in a particular format, which I have to replicate in due to the amount of work to change all the existing links would be far too much.
The format of the existing links is;
/([A-Za-z0-9]{14})/([A-Za-z0-9_-]*)
My attempt at creating a custom route doesn't appear to be working. What I have done is change the RegisterRoutes method in Global.asax.cs file to be;
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"ExistingLink",
"{LinkId}/{Title}",
new {controller="ExistingLinkController", action="Index"},
new {LinkId = #"([A-Za-z0-9]{14})", Title = #"([A-Za-z0-9_-]*)"});
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
I have also created the 'ExistingLinkController' with an 'Index' action of;
public ActionResult Index(string LinkId, string Title)
{
ViewData["LinkId"] = LinkId;
ViewData["Title"] = Title;
return View();
}
And a view which contains the code;
<h2>LinkId: <%: ViewData["LinkId"] %>
</h2>Title: <%: ViewData["Title"] %></h2>
But when I try to go to;
/55a3ef90c4b709/This-is-just-a-test_0-9
I get the following error;
Description: HTTP 404. The resource
you are looking for (or one of its
dependencies) could have been removed,
had its name changed, or is
temporarily unavailable. Please
review the following URL and make sure
that it is spelled correctly.
Requested URL:
/55a3ef90c4b709/This-is-just-a-test_0-9
I was wondering whether anyone can see what I am doing wrong and possibly point me in the right direction, perhaps pointing at the bit of code that's wrong if its a simple problem or pointing me to a article that will help me get a better understanding if I've got the wrong end of the stick with this routing stuff.
Thanks for any help in advance
Satal :D
I think this:
new {controller="ExistingLinkController", action="Index"},
Should just be this:
new {controller="ExistingLink", action="Index"},
MVC adds the Controller part of the name itself - In the second route the controller is also called HomeController, but you only enter "Home" as the default for the controller argument.
when i get stuck with my routes i use the route debugger from Phil Hack
http://haacked.com/archive/2008/03/13/url-routing-debugger.aspx
its just epic
I am still very new to the MVC framework, but I managed to create a controller that reads from a database and writes JSON to an url;
host.com/Controllername?minValue=something&maxValue=something
However when I move the site to a subfolder;
host.com/mvc/
it doesn't seem to be able to call the controller from there when I do it like this;
host.com/mvc/Controllername?minValue=something&maxValue=something
Did I forget to do something somewhere to make this url call valid from that subfolder?
Any help here would be greatly appreciated.
In the first case you are specifying the controller name while in the second case you are not. You could setup a default route:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new
{
controller = "Controllername",
action = "ActionName",
id = UrlParameter.Optional
}
);
Once this default route points to the controller and action both urls should work:
host.com/?minValue=something&maxValue=something
host.com/mvc/?minValue=something&maxValue=something
I tried to use the solution explained at http://weblogs.asp.net/paulomorgado/archive/2010/01/31/web-site-globalization-with-asp-net-routing.aspx to localize my application using the language parameter in my routes.
Here's the code I have in my Global.asax:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add("en", new Route("en/{*path}", new GlobalizationRouteHandler(CultureInfo.GetCultureInfo("en-US"))));
routes.Add("fa", new Route("fa/{*path}", new GlobalizationRouteHandler(CultureInfo.GetCultureInfo("fa-IR"))));
routes.MapRoute(
"AdminHome",
"{language}/admin",
new { controller = "Admin", action = "Index" }
);
}
But when I point my browser to /en/admin or /fa/admin I receive a 404 error message.
I tried this one too:
routes.MapRoute(
"AdminHome",
"admin",
new { controller = "Admin", action = "Index" }
);
But still a 404 error for /en/admin - (in this case "/admin" works.)
Any idea?
I have a very similar route pattern in my own MVC site.
routes.MapRoute(
"BlogSpecific", // Route name
"{blogSubFolder}/{controller}/{action}", // URL with parameters
new { blogSubFolder = "", controller = "", action = "Index" } // Parameter defaults
);
The two main differences that I can see are that I specify the {action} in my route, and I also call out the first route param as a parameter in my object ("blogSubFolder = "",").
Now I just did some testing, and I found the same behavior that you are seeing, I take out the {action} out of my route and I get a 404. But if I specify the action everything works out.
Ok, so I created a new project, with the default route, and I don't have to specify the action, it defaults to Index just like I'd expect it to. I then add a new route where I specify the controller {language}/Foo/{action}, and I continue to get errors if I don't include the index in my url. Long story short, As near as I can tell if your route has a variable that precedes the controller you have to specify the action in your url.
UPDATE - 1/21/09:
The only way I've been able to get this working moderately well is to create routes that have additional path info... in other words instead of http://company.com/myDepartment/myTeam/action/id, the routes need to be built to handle paths
like http://company.com/department/myDepartment/team/myTeam and so on.
END UPDATE - 1/21/09:
I have two possible URL conventions that I need to be able to route to the same controllers/views
http://team.department.company.com/Action/id
http://department.company.com/Action/id
http://company.com/Action/id
The above has been pretty easy to handle, since the department/team aliases are in the hostname of the Uri.
However, when I want to implement the following, I've been unable to get it working satisfactorily:
http://company.com/department/team/Action/id
http://company.com/department/Action/id
http://company.com/Action/id
Here are the routes I've defined:
routes.MapRoute("departmentAndTeam1", "{departmentId}/{teamId}/{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "" });
routes.MapRoute("departmentAndTeam2", "{departmentId}/{teamId}/{controller}/{action}", new { controller = "Home", action = "Index" });
routes.MapRoute("departmentAndTeam3", "{departmentId}/{teamId}/{controller}", new { controller = "Home", action = "Index" });
routes.MapRoute("department", "{department}/{controller}/{action}/{id}", new {controller = "Home", action = "Index", id = ""});
routes.MapRoute("departmentAndTeam4", "{departmentId}/{teamId}/", new { controller = "Home", action = "Index"});
routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "" });
The problem I'm having is now every action method in my controllers require a departmentId & teamId parameter. I would really prefer just to be able to "pre-process" the department/team id values in the base controller class that I've got in the OnActionExecuting event handler. Am I barking up the wrong tree? Do I need to implement my own Mvc Route handler?
Note: This is replacing a legacy app and the URL formats need to be kept/maintained.
EDIT: First off, thanks for the responses so far. Based on the responses I've received I think I've not been clear enough on one point. This is (loosely) what my controller looks like:
public class ControllerBase : Controller
{
DepartmentDTO department;
TeamDTO team;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
PopulateDept(filterContext.ActionParameters("departmentId");
PopulateTeam(filterContext.ActionParameters("teamId");
}
}
However, with this scheme, all controllers that inherit from this class (and are routed appropriately with the routes listed above) expect (what I perceive to be redundant/uneeded) parameters in their action handlers. I want to avoid having each handler require these "extra" parameters and am looking for suggestions.
Thanks!
-Giorgio
First off, you don't need departmentAndTeam2 or departmentAndTeam3. departmentAndTeam1 can handle it because it has the defaults for controller, action and id.
Another thing I notice is that none of your example URLs have the controller specified in them. In that case, leave it out of the route and just leave it as the default. eg
for the URL
http://company.com/department/team/Action/id
use the route
routes.MapRoute("departmentAndTeam1", "{departmentId}/{teamId}/{action}/{id}", new { controller = "Home", action = "Index", id = "" });
Notice how "controller" will always be "Home" because it's never mentioned in the URL.
For the actions and URLs that you don't want to specify the department and team IDs, they should just be passing by all the other routes and just using the Default route. The fact that they aren't means that they are getting caught by one of the earlier routes. I think if you fix the earlier route's problems then it should fix the problems of needing the extra IDs.
Finally, if you are still haveing trouble, try using an MVC route debugger like this one from Phil Haacked http://haacked.com/archive/2008/03/13/url-routing-debugger.aspx. I've used it to work out the problems with some of my routes and it is really helpful to see what it is actually doing and runtime.
How about this (I haven't tested because I'm not sure what requirements you have other than those 3 particular urls mapping)?
routes.MapRoute("1", "{department}/{team}/{action}/{id}", new { controller = "Home" });
routes.MapRoute("2", "{department}/{action}/{id}", new { controller = "Home" });
routes.MapRoute("3", "{action}/{id}", new { controller = "Home" });