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
?
Related
While reading about mcv3 I came across an attribute name called [ActionName]. It actually gives a new name to the action method. I tested a scenario which made me think; how are the internals working. When I have the following two action methods in my controller class
[ActionName("Test")]
public ActionResult Index()
{
return View();
}
[ActionName("Index")]
public ActionResult Test()
{
return View();
}
I thought this will end up in some kind of infinite loop or will give some ambiguity exception. But the same works fine and the second method is called when i give this url http://mysite:1234/mycontroller
What made MVC engine to choose the second method and not the first?
Any idea why this happens?
Phil Haack has a post on this matter: How a method becomes an action
In short: the ControllerActionInvoker uses reflection to find a method matches the action name.
The ActionNameAttribute redefines the name of the method.
Also be aware that the name of your View matches the ActionName, not the MethodName: the method Index will search for a view with the name "Test"
This is the magic of the Routing engine. Somewhere within the global.asax.cs file there would be routing patterns defined, mostly which defaults to
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
This is a routing pattern defined for your application. The action name attribute maps to the 'action' parameter within the parameter collection (3rd parameter for MapRoute).
In your case if you map the action 'Index' to method 'Test'. It should call Test() method. I am not sure whether it is still calling Index() for you. In fact the routing engine does not care about the method name if it finds the ActionName attribute over your public method.
ActionNameAttribute it represents an attribute that is used for the name of an action. If it is not present the name of the method is used.
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've got a website running on ASP.NET MVC 2 - on one action I have some code running and then returning ANOTHER view than the action's name. i.e - action1 will return view "view2".
Somehow, action1 runs one time, then calls
return View("view2",model)
and runs again, for the second time.
Why is this so? and can it be fixed?
EDIT: added some code
Action:
public ActionResult View1(int id, int id2) {
// some code ...
return View("View2", u);
}
where as View2 has nothing to do with View1 or the action (just needed for display).
Route:
routes.MapRoute(
"Default", // Route name
"{action}/{id}", // URL with parameters
new { controller = "Main", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
"View1", // Route name
"View1/{id}/{id2}", // URL with parameters
new { controller = "Main", action = "View1" } // Parameter defaults
);
Link:
http://<some server>/View1/15/fb
Thanks.
Put your default route at the bottom. Routes are evaluated top to bottom so ...
http://<some server>/View1/15/fb
... gets evaluated by the default route as Controller = Main, Action = View1
I have been having the same problem like you.
There are some possible reasons they are producing this double action execution. There you are some useful links:
MVC controller is being called twice
ASP.NET MVC Action is Called Twice
In my case was an extension for chrome called "HTML Validator 1.3.3". This extension was calling the action again. (I guess to do the validation)
We need to present the same site, with a different theme (and different data) depending on the "route" into the site.
www.example.com/Trade
www.example.com/Public
NB: These are not dynamic themes which the user can pick. Certain customers will be coming via a third party link which will always point them to one of the urls.
The value trade/public also needs to be used in queries from the UI to the database (to pull back varying data depending on the route into the site).
So what are my options?
Create custom view engine which uses the querystring (mvc route param) value to load the relevant master page.
In each controller action, grab the parameter (trade/public etc) and pass it off to the database queries.
public ActionResult List(string siteType){
products.ListFor(siteType);
}
The problem here is having to alter every controller action to pass the querystring value through.
This also presents an issue with any route defined in global.asax having to accept the parameter.
I'm wondering if there's another way, perhaps some combination of a custom controller base and host name e.g. trade.example.com, public.example.com?
First define routing:
routes.MapRoute(
"Default", // Route name
"{siteType}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
and then use it in controller:
RouteData.Values["siteType"]
How about this:
Create a base controller that all your controllers inherit from
Add a property: public string SiteType { get; protected set; }
Add an OnActionExecuting method to set it before any action is called, e.g.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//use filterContext to set SiteType somehow, e.g. you can look at the URL or the route data
}
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" });