MVC URL not rewriting - c#

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

Related

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+" }
);

MVC4: same url base for different actions

I would like to have 2 routes similar to these:
routes.MapRoute("Detail", "guide/{urlname}", new { controller = "Application", action = "Detail" });
routes.MapRoute("Search", "guide/{keyword1}/{keyword2}", new { controller = "Guide", action = "Index", keyword1 = UrlParameter.Optional, keyword2 = UrlParameter.Optional });
So one route is a detail page that looks up an object in the database based on its url name, and the other route is a search results page based on application-generated keywords, both of which share the same url root (/guide). The two actions are in different controllers. Possible urls are:
/guide/evernote --> should route to the application detail page
/guide --> should route to search results without filter
/guide/iphone --> should route to iphone apps search results
/guide/iphone/medical --> should route to medical iphone apps search results
Obviously, like this, the second route will never be matched for a url like /guide/iphone because the first route will already match the same url.
I don't want to do a redirect in the first action if the controller can't find the object in the database. So what other alternatives are there? Do I need to create a custom RouteHandler or UrlRoutingModule for this or is there a simpler way?
If {urlname} is a url like it implies, you can add a constraint to test if the url matches a regex:
http://www.asp.net/mvc/tutorials/controllers-and-routing/creating-a-route-constraint-cs
Change the order and it will work:
routes.MapRoute("Search", "guide/{keyword1}/{keyword2}", new { controller = "Guide", action = "Index" });
routes.MapRoute("Detail", "guide/{urlname}", new { controller = "Application", action = "Detail" });

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.

Drop Route Values on Redirect

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
});

Dynamic ASP.net MVC Routing

I have a need for some dynamic routing. So my routes would look like this:
{UserName}
{UserName}/Edit/{id}
{UserName}/Delete/{id}
Where the users would be routed to the user controller. But I still want to maintain routes to controls like:
{Controller}/Edit/{id}
{Controller}/Delete/{id}
So basically I want it to direct to the physical controller say called OrdersController for edit delete but if someone navigates to /jdoe/ it sends it to the user controller.
How do I do this in my routes?
You need to create multiple routes, and keep them in the appropriate order
// one route for Users
routes.MapRoute("Users",
"{username}/{action}/{id}",
new { controller = "Users", action = "Index", username = string.Empty, id = UrlParameter.Optional },
new { id = #"\d+" }
);
// one route for everything else
routes.MapRoute("Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional},
new { id = #"\d+" }
);
It's not "really" as easy as above, but that's the jist of it. You would need too add a RouteConstraint to validate usernames.
Lastly, if you're using the username parameter, then why do you need the id? Just a thought.
Aside:
If you look at the user section here on StackOverflow, you'll see the routing look more like this.
users/{id}/{username}
users/{id}/edit
users/{id}/delete
I would personally say that this is a lot less work to achieve... but hey, that's just me.

Categories

Resources