I'd like to modify the standard ASP.NET MVC URL routes
http://example.com/Controller/Action[/Id]
to something like:
http://example.com/Controller/Id/Action
Modifying the route scheme is trivial but I'm getting into trouble when handling default values. I would need these URLs
http://example.com/Controller/ -> Maps to Index() -> List of items
http://example.com/Controller/Create
http://example.com/Controller/Id[/Details] -> Maps to Details(id)
http://example.com/Controller/Id/Edit -> Maps to Edit(id)
http://example.com/Controller/Id/Delete -> Maps to Delete(id)
Yikes! This means quite a few routes instead of the nice default one. Or can I use URL parameters with default values in the middle of the URL?
The next step is to use a friendly (yet unique!) object reference (eg customer name. Let's assume it is unique) instead of a DB record identity (id). Such as:
http://example.com/Controller/Name[/Details] -> Maps to Details(id)
So far so good but I'm not sure it's a good idea to use this for the Edit (and possibly delete) page: The edit form lets user modify the object name, leading to possible mismatches in url. Should I stick to the id in Edit URLs?
Did anyone experience with such URLs? Did you push the idea to hierarchical systems? Is this all a good idea or will I dive into more trouble than benefits?
TIA for your thoughts.
You can do this only if the ID parameter is always present in the URL. For obvious reasons optional parameter can only be at the end of the url or it is impossible to disambiguate them.
Try this:
routes.MapRoute(
"Default", // Route name
"{controller}/{id}/{action}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Controller goes to home by default and will fire up the Index action of not said other wise. Id is as well optional. All I actually changed was the order of parameters.
As for the res of the question. If you're users are logged and they edit their profile for example you could get the Id of what to edit from somewhere else - a cookie for example. I do this in my user management. URL /User/Edit is unique for the user that's logged in.
Related
My ASP.NET MVC site allows users to register and give themselves user names, which will be unique and allow others to browse their pages with a clean URL that includes their name, like Twitter, Facebook, LinkedIn etc. do.
For example:
mysite.com/michael.guthrie
mysite.com/john
mysite.com/john/images
mysite.com/john/blog
etc.
The problem is that the first URL segment might be used for other "regular" controllers/actions, like:
mysite.com/about
mysite.com/register
So basically I seek for a routing scheme that says something like: If the first URL segment is a known controller, treat it as a controller (and parse the relevant action and parameters as usual), but if not - treat it as a user name, and pass it to a dedicated controller+action which will parse it and continue accordingly.
I don't want a solution that will enforce me to add routes for every specific controller that I have, such that after the routing module will go over all of them and won't find a match, it will get to the last one which defines a route for this special user name segment. The reason is primarily maintenance (I must remember to add a route every time I code a new controller, for example.)
I assume I can implement my own MvcRouteHandler / IRouteHandler but I feel there must be simpler solution that won't have me tweak MVC's out-of-the-box routing mechanism.
Note: I've read How to achieve nice litle USER page url like facebook or twitter? and it doesn't answer my question, it's just says that there is a URL rewriting module.
Do you know any good, elegant, clean way to achieve that?
You should have your first route be your Usesr route, with a route constraint along the lines of what I described in this answer: MVC routing question.
If your route is in the form {username}/{controller}/{id}, this route should cover all contingencies.
in the global.asax file you can map your routes
in the registerRoutes() method you can do something like this:
routes.MapRoute(
"ToonStudenten", // Route name
"{controller}/{action}/{userID}, // URL with parameters
new { controller = "Docent", action = "ToonStudenten", userID = UrlParameter.Optional} // Parameter defaults
);
I believe you can change the way your views look with this mapRouting, not entirely sure how though.. will try and search it up
You may want to take a look at this post:
MVC 3 keeping short url
You don't need to set a route for each URL. With a little help from route constraints you can do something like this:
routes.MapRoute(
"Home", // Route name
"{action}", // URL with parameters
new { controller = "Home", action = "Index" }, // Parameter defaults
new { action = "TaskA|TaskB|TaskC|etc" } //Route constraints
);
routes.MapRoute(
"Account", // Route name
"{action}", // URL with parameters
new { controller = "Account", action = "Logon" }, // Parameter defaults
new { action = "Logon|Logoff|Profile|FAQs|etc" } //Route constraints
);
fHi,
I need to build an site with some portal like functionality where an param in the request will indentify the portal. like so http:/domain/controller/action/portal
Now my problem is if an portal doesn't exists there must be an redirect to an other site/page and an user can login in to one portal but if the user comes to an other portal the user must be redirected back to the login page for that portal.
I have something working now, but i feel like there must be an central place in the pipeline to handle this. My current solution uses an custom action filter which checks the portal param and sees if the portal exists and checks if the user logged on in that portal (the portal the user logged on for is in the authentication cookie). I make my own IIndentiy and IPrincipal in the application_postauthentication event.
I have 2 problems with my current approach:
1: It's not really enforced, i have to add the attributes to all controllers and/or actions.
2: The isauthenticated on an user isn't really working, i would like that to work. But for that i need to have access to the params of the route when i create my IPrincipal/IIndenty and i can't seem to find an correct place to do that.
Hope someone can give me some pointers,
Richard.
There's a couple different ways you could do this (as always...). If you want to do it in the controller (or via an attribute) but you also want to do it globally, then you could always use a custom base controller class and apply the logic there. The actionfilterattribute is inherited and bob's your uncle.
ON the other hand, this really feels like a routing concern to me. So I'd probably consider creating a custom route to handle what you're doing. If you do that, then once you get it working you'll want to test it out under load to make sure that you have a good caching strategy in place (so that every request isn't a db lookup for the route + another one for whatever happens in the controller).
You can enforce user authorization through an attribute in the controller. You would apply this to each action (both get and post). I think it's reasonable to add some sort of validation to each action within the controller to write secure code, please correct me if I'm wrong here.
For the missing portal redirect, I would handle this in routing. If you have a relatively small number of portals, you can do this by creating a unique route for each of your controllers and then setting a default route for the redirect. Routes are evaluated in the order you create them, so just put the default route at the bottom. Your route registration would look something like this:
routes.MapRoute(
"Portal1",
"{controller}/{action}/FirstPortal",
new {controller = "defaultController", action = "defaultAction",
portal = "FirstPortal"}
);
routes.MapRoute(
"Portal2",
"{controller}/{action}/SecondPortal",
new {controller = "defaultController", action = "defaultAction",
portal = "SecondPortal"}
);
routes.MapRoute(
"Default",
"{controller}/{action}",
new {controller = "defaultController", action = "defaultAction",
portal = "Default"}
);
This way you can use the "portal" route value to select the portal, and any request that does not match will be routed to the controller/action specified in your Default route, which can take care of redirecting the user appropriately.
I created a new ASP.NET MVC project and implemented a site authorization filter.
When I map the routes to the {controller}/{action} pair, I pass a role = "SomeRole" default to the route.
It works perfectly if I go through the full url (http://localhost/somecontroller/someaction) and I specified the full route
MapRoute("SomeAction", "somecontroller/someaction",
new { controller = "SomeController", action = "SomeAction", role = "SomeRole");
The problem is that when somebody visits http://thesiteaddress.com there has to be a default route that invokes /home/index instead of / and if I specify
MapRoute("Default", new { controller="somecontroller",action="action" });
then I lose the role="SomeRole" from the previous MapRoute.
How can I solve this?
Make sure the Default route is at the BOTTOM of your listed route table. Order matters when it comes to ASP.NET MVC Routing tables.
The correct ordering is your 'most specific' route to your least specific route.
Actually, George is right. MVC Routing respect ordering route. Your last route must be generic as possible, and your previous route must be specific as possible.
In your case, both are generic. You should
MapRoute("SomeAction", "Post/{action}", new {controller = "Post", role = "User");
and then
MapRoute("Default", new {controller="Home", action="Index", role = "Anonymous"});
so, you give specificity to both routes.
Phil Haack released a route debugging tool that can be invaluable in gaining an understanding of problems like this.
With this tool you can view how your MVC application parses a URL and matches it to your RouteTable.
When you don't provide the route name or the action is determined through a HTTP request it will look in order from the order they were added. The first time it finds one that matches, it stops. So what's probably happening is it's matching one previous to the one you've added.
What's the best way to handle a visitor constructing their own URL and replacing what we expect to be an ID with anything they like?
For example:
ASP.Net MVC - handling bad URL parameters
But the user could just as easily replace the URL with:
https://stackoverflow.com/questions/foo
I've thought of making every Controller Function parameter a String, and using Integer.TryParse() on them - if that passes then I have an ID and can continue, otherwise I can redirect the user to an Unknown / not-found or index View.
Stack Overflow handles it nicely, and I'd like to too - how do you do it, or what would you suggest?
Here's an example of a route like yours, with a constraint on the number:
routes.MapRoute(
"Question",
"questions/{questionID}",
new { controller = "StackOverflow", action = "Question" },
new { questionID = #"\d+" } //Regex constraint specifying that it must be a number.
);
Here we set the questionID to have at least one number. This will also block out any urls containing anything but an integer, and also prevents the need for a nullable int.
Note: This does not take into account numbers that larger than the range of Int32 (-2147483647 - +2147483647). I leave this as an exercise to the user to resolve. :)
If the user enters the url "questions/foo", they will not hit the Question action, and fall through it, because it fails the parameter constraint. You can handle it further down in a catchall/default route if you want:
routes.MapRoute(
"Catchall",
"{*catchall}", // This is a wildcard routes
new { controller = "Home", action = "Lost" }
);
This will send the user to the Lost action in the Home controller. More information on the wildcard can be found here.
NB: The Catchall should reside as the LAST route. Placing it further up the chain will mean that this will handle all others below it, given the lazy nature of routes in ASP.NET MVC.
Here is some useful infromation that might help.
If you have a action method
public ActionResult Edit(int? id)
{}
then if someone types in
/Home/Edit/23
the parameter id will be 23.
however if someone types in
/Home/Edit/Junk
then id will be null which is pretty cool. I thought it would throw a cast error or something. It means that if id is not a null value then it is a valid integer and can be passed to your services etc. for db interaction.
Hope this provides you with some info that I have found whilst testing.
In ASP.NET MVC, you can define a filter implementing IActionFilter interface. You will be able to decorate your action with this attribute so that it will be executed on, before or after your action.
In your case, you will define it to be executed "before" your action. So that, you will be able to cancel it if there is an error in the passed parameters. The key benefit here that you only write the code which checking the passed paramaters once (i.e you define it in your filter) and use it wherever you want in your controller actions.
Read more about MVC filters here: http://haacked.com/archive/2008/08/14/aspnetmvc-filters.aspx
You can specify constraints as regular expressions or define custom constraints. Have a look at this blog post for more information:
http://weblogs.asp.net/stephenwalther/archive/2008/08/06/asp-net-mvc-tip-30-create-custom-route-constraints.aspx
You will still need to deal with the situation where id 43243 doesn't map to anything which could be dealt with as an IActionFilter or in your controller directly.
The problem with that approach is that they still might pass an integer which doesn't map to a page. Just return a 404 if they do that, just as you would with "foo". It's not something to worry about unless you have clear security implications.
I will take the example of the SO site. To go to the list of questions, the url is www.stackoverflow.com/questions. Behind the scene, this goes to a controller (whose name is unknown) and to one of its actions. Let's say that this is controller=home and action=questions.
How to prevent the user to type www.stackoverflow.com/home/questions which would lead to the same page and would lower the rank of the page as far as SEO is concerned. Does it take a redirect to solve this? Does it take some special routing rules to handle this kind of situation? Something else?
Thanks
I assumed that the controller was questions and the action was index, i.e., the default action as defined by the route handler. Thus there isn't an alternative path to the page.
During Phil Haack's presentation from PDC, Jeff shows some of the source code for Stack Overflow. Among the things he shows is the code for some of the route registrations. He's got these in the controllers, and it's not clear to me that he uses a default route at all. With no default route, you wouldn't need to worry about /home/questions, for example.
As for /questions/index, yes, a permanent redirect is the way to go. You won't get any search engine penalty for a permanent redirect.
Another way to eliminate /home/questions would be to use a route constraint.
You want to use the following route. It is really easy you just create a new route that eliminates the need for the controller to be in the route. You create a template string that just contains the action and you default the controller to the controller you want to use such as "Home".
routes.MapRoute(
"MyRoute",
"{action}",
new { controller = "Home", action = (string)null },
new { action = "[a-zA-z_]+" }
);
Hope this helps.