I am currently using MVC Routing and MvcSiteMapProvider. I have just noticed something which I am unsure about:
[Route("mypath/{param1}/{param2}/{param3:int}/", Name = "myaction")]
[MvcSiteMapNode(Title = "My Thing", ParentKey = "myparent", Key = "myaction", PreservedRouteParameters = "param1, param2, param3")]
public ActionResult myaction(string param1, string param2, int param3)
{
mymodel model = gd.getmydata(param3);
var node = SiteMaps.Current.CurrentNode;
node.Title = model.name;
node.ParentNode.Title = location;
node.ParentNode.RouteValues["param"] = location;
return View(model);
}
When I test the url is how I designed it:
http://localhost:12345/mypath/param1/param2/param3
However if I bastardise the URL with drivel in param2 or param3 the view still resolves with no error. Only by changing mypath or param3 does it 404:
http://localhost:12345/mypath/drivel/param2/param3
http://localhost:12345/mypath/param1/drivel/param3
Am I missing something? Should I have parameter validation in the controller?
The tokens in the URL parameter of the route act like variables. The way you have your route configured, it will always match any URL starting with mypath/ and containing 3 additional segments. The route does not care what values are put into those segments, it will make them into route keys named param1, param2, and param3 containing whatever values are in the actual URL.
If you want the route only to match the URL /mypath/param1/param2/param3, you will need to use literal route segments.
[Route("mypath/param1/param2/param3/", Name = "myaction")]
But then, since your param3 segment is an integer value and your URL contains a string, it is difficult to tell what you are trying to achieve. If you declare it an integer, you must place an integer in the URL or you will get an error.
/mypath/param1/param2/123
Another thing to note if you are using literal segments in your URL is that they are not converted into route values, so there is no need to use PreservedRouteParamters in that case.
So, if you declare your route with variables, it should not be a surprise that any value works. If you want an explicit route, declare it with literal segments. If you want variables but want to restrict the route to a certain range of possible values, then you need to use route constraints.
Related
I am looking at an example of MVC Route and it looks like following:
routes.MapRoute("Cuisine", "cuisine/{name}", new
{ controller = "Cuisine", action = "Search", name = ""})
I am just trying to dissect what each element stands for here:
"cuisine/{name}"
Does this part say that if a request comes with URL starting from word cuisine and there is a second string parameter and route it to this particular Route and consider second part of URL as parameter name?
{ controller = "Cuisine", action = "Search", name = ""})
If nothing is passed into the parameter then please use empty string as default and if some value is passed for name parameter then use that value?
"cuisine/{name}"
. Does this part say that if a request comes with URL starting from word cuisine and there is a second string paramter and route it to this particular Route and consider second part of URL as parameter name?
Yes, that is correct.
{ controller = "Cuisine", action = "Search", name = ""})
If nothing is passed into the parameter then please use empty string as default and if some value is passed for name parameter then use that value?
Yes, that is also correct. However, it is usually more sensible to specify an optional parameter as UrlParameter.Optional.
Basically, there are 2 (important) parts to a route configuration.
There is a match specification which always consists of the URL parameter and may also include constraints, optional, and required values. A required value is one where you don't specify any default for a route key and also include it as a route segment. If a route doesn't fit the criteria of the match, the framework will attempt the next route in the configuration until it finds a match (or not). It is helpful to think of it as a switch case statement.
Then there is a route value specification. This is a combination of the default values and any incoming URL segments that may override or add to them. Note that constant parts of the URL are not included in the route values. The only reason why "Cuisine" is inserted into the route values in your example is because it is defined as a default route value.
Some MVC sites have querystring params appended to the route Url (of which I noticed StackOverflow does), such as:
https://stackoverflow.com/questions/tagged/java?page=9802&sort=newest&pagesize=15
What are the advantages of having the parameters as more conventional ?querystring params, rather than /param/values/ ?
Also, how are these params appended to routes that have been set up? I'm familiar with setting up mvc routes with params like "users/details/{id}" etc. but don't know how to configure routes for use with 1 or more ?params as per the example url above?
Query string parameters are useful when you have multiple optional parameters and don't want to include default values for non-specified parameters just to satisfy a path.
And you don't have to do anything special to include these parameters in a rendered URL.
Take the following route for example:
routes.MapRoute
(
"QuestionsTagged",
"questions/tagged/{tag}",
new { controller = "Questions", action = "Tagged" }
);
If you render a link to that route using:
Url.RouteUrl
(
"QuestionsTagged",
new
{
tag = "java",
page = 9802,
sort = "newest",
pagesize = 15
}
)
...then the routing engine is smart enough to see that the route contains a parameter named tag and that the passed route values object also has something named tag so it uses that value in the route.
Any provided route values that don't have corresponding parameters in the route (page, sort and pagesize in this case) get tacked on as query string parameters. So the Url.RouteUrl call above would return /questions/tagged/java?page=9802&sort=newest&pagesize=15.
And your action method can explicitly list these parameters in its signature (promotes readability and maintainability) or you can access them via Request.QueryString.
public class QuestionsController : Controller
{
// I can explicitly list the parameters in my signature and let routing do
// its magic, like this...
public ViewResult Tagged(string tag, int? page, int? pagesize)
{
// ...or I can grab parameters like this:
string sort = Request.QueryString["sort"];
return View();
}
}
Note that the parameters to the action method do not have to match the parameters specified in the route. (In the route, I only specified tag, but the action method's signature lists tag, page, and pagesize.) However, any parameter of the action method that is not also a parameter of the route must be a reference or nullable type.
I've normally seen paging and filtering data be passed as querystring parameters since it gives information to the user in the URI. It is also normally harmless if a user alters this data since it will just filter the data you see on the page. Any sensitive data is normally posted so as it is not as easily seen or modified, but I would argue to keep your URI's clean and use quesrystrings as little as possible.
You don't need to do anything special when specifying routes to be able to handle quesrystrings. They will just be extra data that is passed to your action. On your action you will need to do some work to handle the data though. Using your querystring above you will have to specify the querystring names as the parameter names and then whatever datatype you are expecting.
public ActionResult Index (int page, string sort, int pagesize)
In this example, page will be the value of 9802, sort will be "newest" and pagesize will be 15.
If I have this route:
routes.MapRoute(
"BlogRoute", // Route name
"blog/{action}", // URL with parameters
new { controller = "Blog", action = "Index", id="abc" } // Parameter defaults
);
... and have this Index method in the controller:
public ActionResult Index(string id)
{
return View((object)id);
}
Is it possible for someone to change that id parameter from "abc" to something else? For example, by appending ?id=somethingElse to the URL? I tried that but it didn't change it. So is it guaranteed that I'll always get "abc" in the Index method?
Basically I need to send a hardcoded string when one route is chosen and I don't want the user to be able to change this string via the URL or any other mechanism. It's like "abc" is a password (it's not but just assume it is). Only the developer is allowed to set this string by editing Global.asax.cs.
Is it possible?
You can add a constraint for the id parameter using the regular expression /abc/
I am workingon an MVC route that will take an unknown number of parameters on the end of the URL. Something like this:
domain.com/category/keyword1/keyword2/.../keywordN
Those keywords are values for filters we have to match.
The only approach I can think of so far is UGLY... just make an ActionResult that has more parameters than I am ever likely to need:
ActionResult CategoryPage(string urlValue1, string urlValue2, string urlValue3, etc...)
{
}
This just doesn't feel right. I suppose I could cram them into a querystring, but then I lose my sexy MVC URLs, right? Is there a better way to declare the handler method so that it handles a uknown number of optional parameters?
The routes will have to be wired up on Application Start, which shouldn't be that hard. The max number of keywords can easily be determined from the database, so no biggie there.
Thanks!
You could use a catch-all parameter like this:
routes.MapRoute("Category", "category/{*keywords}", new { controller = "Category", action = "Search", keywords = "" });
Then you will have one parameter in your Search action method:
public ActionResult Search(string keywords)
{
// Now you have to split the keywords parameter with '/' as delimiter.
}
Here is a list of possible URL's with the value of the keywords parameter:
http://www.example.com/category (keywords: "")
http://www.example.com/category/foo (keywords: "foo")
http://www.example.com/category/foo/bar (keywords: "foo/bar")
http://www.example.com/category/foo/bar/zap (keywords: "foo/bar/zap")
You could make keywords parts of the same route parameter and concatenate them with dashes (-).
Your search route would look like this
routes.MapRoute("Category", "category/{searchstring}", new { controller = "Category", action = "Search", searchstring = "" }, null));
and you would construct your URLs to look like this:
www.domain.com/category/cars-furniture-houses-apparel
You would split it up in your controller action.
Try to avoid huge number of params at all cost.
The new ASP.NET routing is great for simple path style URL's but if you want to use a url such as:
http://example.com/items/search.xhtml?term=Text+to+find&page=2
Do you have to use a catch all parameter with a validation?
You can match querystring parameters with routes as well, if you want to just capture everything you need to add a parameter like so:
{*contentUrl}
Which will populate the rest of the url into that variable.
Any view data items that are not listed in the route are automatically mapped to the querystring, so if you map "items/search.xhtml" to an action:
Search(string term, int page)
Then you should get the results you are looking for.
I was also having trouble passing an encoded URL to a route as a route parameter.
You can't use url encoded chars in a URL, but you can in a query string.
Therefore I needed my route to also have a query string element to it.
Say I have a route:
MapPageRoute("myroute", "myroute/{x}", "~/routehander.aspx")
But I want it in the form of:
http://mywebsite.com/myroute/{x}?url=myurl
We can do this:
Dim x as integer = 12
Dim rvd As New Routing.RouteValueDictionary
rvd.Add("x", x)
rvd.Add("url", Server.UrlEncode("/default.aspx"))
HttpContext.Current.ApplicationInstance.Response.RedirectToRoutePermanent("myroute", rvd)
This would redirect us to the following url:
http://mywebsite.com/myroute/12?url=%252fdefault.aspx
You can still use Request.QueryString["some_value"];