Drop Route Values on Redirect - c#

So, I've been trying for a few hours now to workaround something that should theoretically be very simple. Let's take this sample url:
http://sample.com/products/in/texas/dallas
This maps onto a specific route:
routes.MapRoute(
"products",
"products/in/{state}/{city}",
new { controller = "Products", action = "List", state = UrlParameter.Optional, city = UrlParameter.Optional });
In my action method, I can do lookups to make sure "texas" and "dallas" exist, and that "dallas" exists within Texas. That's all fine and dandy. However, in the situation where the city doesnt exist (either because the geo is incorrect, or mispelled), I want it to back up the state level. Example:
http://sample.com/products/in/texas/dallax
That should issue a redirect to
http://sample.com/products/in/texas
The "easy" way to do this was to simply issue a Redirect call like so:
return Redirect("/products/in/" + stateName);
However, I'm trying to decouple this from the URL structure; for example, if we ever decided to change how the route looks (say, change the pattern to products/around/{state}/{city}), then I would have to know that I need to make updates to this controller to fix the URL redirect.
If I can make a solution that just inspects the route values and can figure things out, then I don't have to worry if I change the route pattern, because the route values could still be figured out.
Ideally, I would have liked to do something like this:
return RedirectToRoute(new { controller = "Products", action = "List", state = state });
(Note, that is is a simplified example; the "required" route pieces like controller name and action method name would be determined by Generic argument and Expression inspection respectively).
That actually performs the redirect, HOWEVER, the route values from the current request get appended onto the redirect and thus you get in a redirect loop (note that I didn't include the city route value in the route object). How do I stop the "city" route value from being included in this redirect?
I've tried the following things to get rid of the route value:
Compose my own RouteValueDictionary / anonymous route data object and pass that to the overload of RedirectToRoute.
Create my own action result to inspect RouteTable.Routes and find the route myself, and do the replacement of the tokens myself. This seems the most "kludgy", and would seem to be re-inventing the wheel.
Make a method like RedirectWithout that takes a key value and calls RedirectToRouteResult.RouteValues.Remove(key) - which also didnt work.
I've also attempted to add a null value for the key I don't want to add; however, this alters the route to something that isnt correct - ie new { controller = "Products", action = "List", state = stateName, city = (string)null } issues a redirect to /Products/List?state=Texas which is not the right URL.
It all seems to stem from the RedirectToRoute taking the current request context to construct the virtual path data. Is there a workaround?

If you were using T4MVC, you should be able to do something like this:
return RedirectToAction(MVC.Products.List(state, null));
Have you tried this?
return RedirectToRoute(new
{
controller = "Products",
action = "List",
state = state,
city = null,
});
Reply to comments
Maybe MVC is confused because your optional parameter is not at the end. The below should work with the above RedirectToRoute that specifies city = null:
routes.MapRoute(
"products",
"products/in/{state}/{city}",
new
{
controller = "Products",
action = "List",
// state = UrlParameter.Optional, // only let the last parameter be optional
city = UrlParameter.Optional
});
You can then add another route to handle URL's where state is optional:
routes.MapRoute(null, // I never name my routes
"products/in/{state}",
new
{
controller = "Products",
action = "List",
state = UrlParameter.Optional
});

Related

Is it possible to have a parameter named action in MVC4?

PLEASE MARK AS DUPLICATE - Its already answered here
Is it possible to have a parameter named action in MVC4?
Trying to do this results in the parameter returning me the name of the controller action rather than the parameter value.
/Somecontroller/Someaction?action=value
When I try to access the parameter action, I get "Someaction" as the result rather than "value".
Trying to bind the parameter to a different name doesn't help either.
public ActionResult Someaction([Bind(Prefix = "action")] String id)
Edit: I have not found 'Action'/'action' in reserved MVC keywords either.
I haven't tested this, but I would assume you could try changing the url of your default route to something other than action
routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
to
routes.MapRoute("Default", "{controller}/{cAction}/{id}",
new { controller = "Home", cAction = "Index", id = UrlParameter.Optional });
this should then allow you to use action as a parameter in your Action. I'm not certain why you'd want to do this, I would probably build a route url that accepted my action and built it as part of the url similar to this:
/SomeController/SomeAction/value where value is your "action" parameter.
EDIT based on comments:
I successfully created a route that goes to an aspx url'd route
routes.MapRoute(null, "third-party.aspx", new { controller = "Home", action = "Index" });
Obviously you can add whatever controller/action you want here and where you want to handle it, then you access that route via /third-party.aspx?action=value and it seemed to work for me

Custom Routing without changing the URL

I have a controller and a view which returns book information when I pass the ID, e.g.:
/Content/Index?id=1
Now I want to make this as a friendly URL to end user. For eg:
Books/BookName (Name of the book the Id 1 is mapped to)
So I added a route values in global.asax as :
route.maprRoute(name:"custom", url:"Books/{bookname}",
defaults: new {controller = "bookMap", action ="index"}
in "BookMap" controller I get the bookname and convert that to the ID (which is 1)
and do a redirectionToAction to Content/Index by passing the ID as a parameter.
This works fine. But the problem is I want to keep the friendly name after redirecting to the view. Now it changes to Content/Index?id=1. But I want to keep the friendly URL which is Books/BookName. How do I achieve this pls.
You can use HttpContext.RewritePath(url) to do a redirect "internally" which keeps the external URL. Use this in place of the RedirectToAction. Note however that it's not properly supported by the MVC framework at this time so it will be a little "hacky" to implement.
You should change your route for this:
routes.MapRoute(
"Books",
"book/{bookname}/{bookid}",
new { controller = "book", action = "Index", bookname = UrlParameter.Optional },
new { id = #"\d+" }
);

Url.Action doesnt work with non standard MVC Route

I am having trouble making #Url.Action work with Area's that have a non standard route structure.
For instance if I register this route in my Dashboard area:
context.MapRoute(
"Dashboard_default",
"Dashboard/{controller}/{action}/{id}",
new { controller = "View", action = "Display", id = UrlParameter.Optional }
);
and then in my layout view I call:
#Url.Action("Select", "View", new { area = "Dashboard" })
I get a proper url: /Dashboard/View/Select
However, if I change the route to include an optional secondary id like this:
context.MapRoute(
"Dashboard_default",
"Dashboard/{controller}/{action}/{id}/{secondaryid}",
new { controller = "View", action = "Display", id = UrlParameter.Optional, secondaryid = UrlParameter.Optional }
);
Then the same call to #Url.Action(...) doesn't return any url. If I specify those optional parameters with real values like so:
#Url.Action("Select", "View", new { area = "Dashboard", id = 1, secondaryid = 2 })
I do get a god return value of: /Dashboard/View/Select/1/2
The problem is that for some of my actions in this area don't need the id or secondary id and I want the url to be generated without them. If i set them to (int?)null it still doesn't work.
Am I doing something wrong? Shouldn't Url.Action(...) return the URL without the id and secondaryid tokens if I dont specify them in the routeValues parameter?
Having multiple optional parameters does funky things to your routes. Basically, the route engine cannot (has trouble) matching one or no optional parameters where there is a group of them. For more information, check out this blog post on the same issue.
Since you don't always need id or secondary id, just make a couple of routes to handle those cases.
context.MapRoute(
"Dashboard_IdAndSecondaryId",
"Dashboard/{controller}/{action}/{id}/{secondaryid}",
new { controller = "View", action = "Display"}
);
context.MapRoute(
"Dashboard_default_WithSecondaryId",
"Dashboard/{controller}/{action}/{secondaryid}",
new { controller = "View", action = "Display"}
);
context.MapRoute(
"Dashboard_default",
"Dashboard/{controller}/{action}/{id}/",
new { controller = "View", action = "Display", id = UrlParameter.Optional}
);
Now, when you send just an Id, just a secondaryId or both, you will have routes that will match. We can remove the optional parameter declarations in the first two routes, because in order to match that route, you would have to send the required parameters. Without sending the required parameters, you would want only the last route to match.
The last route is your default route when only Id or none is sent in the action link. I believe that this order works as well, keeping in mind you want your routes to go from most specific to least specific given that they are processed in order.

MVC URL not rewriting

I'm trying to change the URL displayed in the user's browser from Happy/Balloons to happy-times/balloon-pops. There are many links in the project to the action "Balloons", so rather than change those links I'd like to change the global.asax so that a different URL appears for the same action. The original MVC Route looks like:
routes.MapRoute(
"Happy.Balloons",
"Happy/Balloons/{groupId}/{paymentType}/{mortgageValue}/{province}",
new { controller = "Happy", action = "Balloons" },
new { groupId = "\\d+", paymentType = "\\d+", mortgageValue = "\\d+", province = "\\d+" }
);
I've changed the code to
routes.MapRoute(
"Happy.Balloons",
"happy-times/balloon-pops/{groupId}/{paymentType}/{mortgageValue}/{province}",
new { controller = "Happy", action = "Balloons" },
new { groupId = "\\d+", paymentType = "\\d+", mortgageValue = "\\d+", province = "\\d+" }
);
I thought this second parameter was the URL displayed, but I'm getting a: "The resource you are looking for has been removed, had its name changed, or is temporarily unavailable." error. Is there a simple way to do this by modifying parameters in MapRoute? If so, how?
Question Follow-up: Change URL of action in mvc
this works, (replacing urlParameter.Optional to default values)
routes.MapRoute( _
"Happy.Balloons", _
"happy-times/ballons-pops/{groupId}/{paymentType}/{mortgageValue}/{province}", _
New With {.controller = "happy", .action = "Balloons", .groupId = UrlParameter.Optional, .paymentType = UrlParameter.Optional, .mortgageValue = UrlParameter.Optional, .province = UrlParameter.Optional} _
)
but... it's a bad practice (really bad one)
you can learn about route maps here
You're doing it wrong.
Changing the route mapping will not change the name of the controller. Change the controller and action name, and map your route as {controller}/{action} like it is by default. Then you can set your default controller and action as you have before.
Then your controller will have to be renamed to happy-times and your action renamed to balloon-pops
I should note that it will look more like this:
{controller}/{action}/{groupId}/{paymentType}/{mortgageValue}/{province}
Here's a good resource on the topic.
http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/asp-net-mvc-routing-overview-cs
as brought to you by this SO post https://stackoverflow.com/a/2375293/1178921
**Looks like you might be trying to see more ways to do this.
This post talks about attributes you can use to route URLS differently.
ASP.NET MVC Routing Via Method Attributes
Additionally, your way of changing the url in the mapping CAN work, but isn't what I thought your intent was. Zach dev had this one dead on in that case. Your optional parameters need to be marked as such with UrlParameter.Optional

Problems understand Routing in MVC

I thought I had at least a bases for understanding routing in MVC after all the documentation I have read, only to fail at it when attempting to utilize it.
I have the following two routes declared in my Global.aspx
routes.MapRoute(
"", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Admin", action = "List", id = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
I have a AdminController that I have a few methods, one that is "List" method that renders a list of products to a "List" view.
I thought I understood how RedirectToAction works, and I added an "Add" method (see below) that adds a new Product and returns a RedirectToAction which I understood would be the proper way to redirect to the List action on the same "AdminController"
[HttpPost]
public ActionResult Add(Product product) {
if (_prodRepo.Add(product)) {
return RedirectToAction("List", "Admin");
}
return View("Add", product);
}
However, on the return of the "Add" it always attempts to route to the path website.com/Account/Login/ReturnUrl=%2f.
However, if I go to website.com/Admin it does render the list as I expect. But when using the RedirectToAction as in the example above, it attempts to go to the /Account/Login (Controller/action).
It was my understanding that the RedirectToAction("List", "Admin") would route to the "List" method on the AdminController controller and that I was using it as expected.
Can someone please help me understand the reasoning behind this. But also, could someone post some recommended articles for understanding the whole MVC routing including how the web.config works with routing.
Finally, it was also my understanding that route discovery by the framework is done in the order they are specified in your routes.MapRoute() declaration and stops at the first one that matches. Therefore, if the first one is listed as Controller = "Admin", Action = "List", I would expect by convention that this is the correct route it would first match and return.
Your routes need to be different (the url parameter) since the first route with a matching url will be used.
This will therefore work for you:
routes.MapRoute("Admin",
"admin/{action}/{id}",
new { controller = "Admin", action = "List", id = UrlParameter.Optional });
The defaults (third parameter in the method) are used if the parameters isn't found/specified in the uri.
As for your question regarding /Account/Login/ReturnUrl=%2f. The login redirections are handled by the MembershipProvider and not by the standard routing mechanism.

Categories

Resources