I want to know if this is possible (it seems like it should be)
I would like to have a route such as:
/events/{id}/addcomment
where the {id} is a param to be used to identify the event to add a comment too. I'm aware that I could simple do /events/addcomment/{id} but this is not how I desire to route this action so this is what I've gotten so far by looking at other SO posts
I register a route in my global.asax file -
routes.MapRoute(
"AddComment",
"Events/{id}/AddComment",
new { controller = "Events", action = "AddComment", id = "" }
);
Then my action inside of my events controller -
[ActionName("{id}/AddComment")]
public ActionResult AddComment(int id)
{
Event _event = db.Events.Find(id);
var auth = OAContext.LoginRedirect("Must be logged in to add a comment");
if (auth != null)
return auth;
else if (_event == null)
return HttpNotFound();
else
return View(_event);
}
I've tried this with and without the ActionName annotation, not entirely sure what I'm doing wrong here.
Later I plan to have routes such as /events/{eventId}/comment/{commentId}/{action} that will allow users to edit/delete their comments from an event, but first I need to figure out exactly how to route this.
The reason I'm asking this is I have not seen any other samples of parameters proceeding actions in the url, so perhaps I'm just not able to do this and if so than that'd be good to know.
My question: is this url format possible and if so what is the proper way to code this into the routes and controller?
This is a route that should work:
routes.MapRoute(
"AddComment",
"Events/{id}/AddComment",
new { controller = "Events", action = "AddComment" }
);
You don't need to be using any ActionName attribute on your action for this to work. Just make sure that you have defined this route before your the default route. Also notice that since the {id} route parameter is not the last part of your route definition it cannot be optional anymore. You should always specify a value for it when generating an route.
For example:
#Html.ActionLink(
"click me",
"AddComment",
"Events",
new { id = "123" },
null
)
Related
Suppose I have defined route like that,
context.MapRoute(
"Preview",
"/preview/{id}/{type}",
new { controller = "Preview", action = "Invoice", id = UrlParameter.Optional, type = UrlParameter.Optional }
);
I have controller with action Invoice
public ActionResult(int id, string type)
{
if (type == "someType")
{
// ...
}
else
{
// ..
}
}
I want to get rid of If-Else case inside the action. Is it possible to attribute action somehow, so ASP.MVC would distinguish between both, like:
Just a pseudocode tho show idea?
[HttpGet, ActionName("Action"), ForParameter("type", "someType")]
public ActionResult PreviewSomeType(int id) {}
[HttpGet, ActionName("Action"), ForParameter("type", "someType2")]
public ActionResult PreviewSomeType2(int id) {}
Is something like that possible in MVC2/3 ?
Action method selector
What you need is an Action Method Selector that does exactly what you're describing and are used exactly for this purpose so that's not a kind of a workaround as it would be with a different routing definition or any other way. Custom action method selector attribute is the solution not a workaround.
I've written two blog posts that will get you started with action method selection in Asp.net MVC and these kind of attributes:
Improving Asp.net MVC maintainability and RESTful conformance
this particular post shows an action method selector that removes action method code branches what you'd also like to accomplish;
Custom action method selector attributes in Asp.net MVC
explains action method selection in Asp.net MVC to understand the inner workings of it while also providing a selector that distinguishes normal vs. Ajax action methods for the same request;
I think thats what routing is for.
Just split your single route into two:
context.MapRoute(null, "preview/{id}/SomeType",
new {
controller = "Preview",
action = "Action1",
id = UrlParameter.Optional,
}
);
context.MapRoute(null, "preview/{id}/SomeOtherType",
new { controller = "Preview",
action = "Action2",
id = UrlParameter.Optional,
}
);
Or maybe more general, use the last segment as the action name:
context.MapRoute(null, "preview/{id}/{action}",
new { controller = "Preview",
id = UrlParameter.Optional,
}
);
But i think, optional segments have to appear at the end. So why don't you use the default routing schema
controller/action/id
?
In a default application ASP.NET MVC 3.0 app I have a controller with Index() and Details(string id) methods. These map to /orders/ and orders/details/ORDER_ID_HERE. How do I change the routing so that Details maps to orders/ORDER_ID_HERE, and continue to have Index() serve up a default index page?
If you would add a new route to you route collection like:
routes.MapRoute(
"Detailed", // Route name
"{contoller}/{id}", // URL with parameters
new { controller = "Orders", action = "Details", id = UrlParameter.Optional } // Parameter defaults
);
It would work, but you break a lot of things, a url like [site]/Home/Search for instance would result in a:
System.ArgumentException: The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult Details(Int32)' in 'MvcApplication3.Controllers.OrdersController'.
The reason for this is that /Home/Search matches the route {controller}/{id}.
An other, similar way to solve it would be:
routes.MapRoute(
"Detailed", // Route name
"order/{id}", // URL with parameters
new { controller = "Orders", action = "Details", id = UrlParameter.Optional } // Parameter defaults
);
Make sure that the static part in the route, order, does not match the name of your actual orders controller, otherwise you'll have the same problem, rendering [url]/orders unreachable with an System.ArgumentException instead of calling the Index() method (the default route)
The negative side of this approach is that you'll need to do this for every controller where you would like to have such functionality.
Try adding this route before the default route:
routes.MapRoute("SomeName", "{controller}/{id}", new { controller = "Orders", action = "Details" });
Notice how the {action} has been removed from the URL parameter.
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'd like to have URLs that are even shorter than /{Controller}/{Action}/{Id}.
For example, I'd like {Controller}/{Id}, where {Id} is a string.
This would allow for simple paths, e.g. Users/Username, Pages/Pagename, News/Newsname. I like this better than requiring the /Details action in the URL (Users/Details/Username), which is less elegant for the end-user.
I can easily make this work by setting up custom routes for any controller that I want this level of simplicity for. However, this causes headaches when it comes to implementing other actions, such as {Controller}/{Action}, where {Action} = 'Create', since, in this case the string {Action} conflicts with the string {Id}.
My question: how can I have 'reserved' words, so that if the URL is /News/Create, it is treated as an action, but if the URL is anything else, e.g. /News/A-gorilla-ate-my-thesis, then it is treated as an Id.
I'm hoping I can define this when setting up my routes?
Update:
Using Ben Griswold's answer, I have updated the default ASP.NET MVC routes to be:
routes.MapRoute(
"CreateRoute", // route name
"{controller}/Create", // url with parameters
new { action = "Create" } // parameter defaults
);
routes.MapRoute(
"DetailsRoute", // route name
"{controller}/{id}", // url with parameters
new { action = "Details" } // parameter defaults
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
This works a charm and means, by default, the details pages will use the simplified URL, but I will still be able to target a specific action if I want to (update/delete/details).
Of course, you will need to disallow the reserved "Create" word as an Id, otherwise a user may try to create an article, for example, with the name "Create", which can never be accessed.
This is really nice. If anyone sees that there is something wrong with this approach, chime in, but I like it so far.
I think you're left with creating a route for each reserved word. For example,
routes.MapRoute("CreateRoute",
"{controller}/Create",
new { action = "Create" }
);
would handle /News/Create, /Users/Create, etc. As long as this route is listed before your other custom route, I think you're covered.
I imagine you will need addition routes for various CRUD operations which will follow a similar pattern.
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" });