Optional static value in attribute routing - asp.net-mvc

I'm using attribute routing for a current project and in a few of the routes, I'm using some optional parameters. So for a URL like...
/detail/H40466/wood-to-wood-foundation-and-boxspring-frame-assembly
With its route definition like...
[Route("detail/{productName}/{applicationSlug?}")]
The wood-to-wood... is an optional parameter. What I'm wanting to do (if possible) is to have a static value only show up if the second parameter is present. Something like...
/detail/H40466/for/wood-to-wood-foundation-and-boxspring-frame-assembly
Where the word for is only part of the url when the last optional parameter is present. Is there any mechanism available to accomplish this beyond setting up another action that maps to that route?

You can define 2 different routes for the same action method. In that case, the "optional" parameter should be required for one route and not present on the other.
[Route("detail/{productName}/for/{applicationSlug}", Order = 1)]
[Route("detail/{productName}", Order = 2)]

Related

I want to take into account double slashes (//) or more in url parameter value

I would like that if my URL is domain/page?param=//, $_GET[‘param’] == ‘//’
Actualy, $_GET[‘param’] == ‘/’
To put some context, my URL domain/page is in fact rewritten in domain/index.php?action=page where the action parameter holds my route and parameters.
Lets’s say my action parameter value is ‘route/subroute/param1/param2’. Each part of the string is removed one after another starting by the end. If a route is find (ie route/subroute exists in my route array), then I call the controller etc… and the deduced parameters are param1 and param2.
Now I would like to take double or more following slashes into account for 2 reasons :
let’s say I have optional parameters a, b, c, and they are like this in URL : ‘a//c’. Then it is interpreted as ‘a/c’ and so b=’c’ instead of b=’’.
If I have route//subroute, I want it to be interpreted as an error (‘route//subroute’ doesn’t exists nor ‘route/’) and display an error view instead of it becoming valid (‘route/subroute’) to avoid duplicate content SEO wise.
Some sites manage to keep the URL intact and display an error view when typing domain//somepage or domain/somepage//subpage, so I guess it is possible.
Thanks a lot!

Is there a better way to distinguish between string id parameters and action names in my routes?

Problem
I have a TagsController, with the standard CRUD Actions. But for the Details result I would rather not include the action name in the url. I want URLs like this:
tags
tags/my-tag-url //no action name specified
tags/create
tags/edit/123
... and as normal ...
But obviously, there is no way for the routing to distinguish between an action name and the my-tag-url parameter.
Possible Solution 1
Create 2 routes. One for the tags/{my-tag-url} route, and another for the tags/{action}/{id} routes.
Downside: The requirement to adorn one of the routes with a constraint - either checking for the existince of my-tag-url in the DB, or checking against a list of the action names on the controller.
(BTW - is either of these prefereable over the other?)
Possible Solution 2
Change tags/my-tag-url to tag/my-tag-url (notice singular) and map this to a different controller.
Downside: The urls are no longer 'hackable'. I can't just remove the my-tag-url to get to the index page listing all tags.
But obviously, there is no way for the routing to distinguish between
an action name and the my-tag-url parameter.
Constraints are the means to achieve this.
The requirement to adorn one of the routes with a constraint - either
checking for the existince of my-tag-url in the DB, or checking
against a list of the action names on the controller.
You've given yourself an either-or situation but you can also (preferably) use the regex option so no lookup is required:
routes.MapRoute("blog", "{year}/{month}/{day}"
, new {controller="blog", action="index"}
, new {year=#"\d{4}", month=#"\d{2}", day=#"\d{2}"});
In your case:
routes.MapRoute("tagurl", "{tagurl}"
, new {controller="tags", action="TagRedirector"}
, new {tagurl=#"#"^htt\w*"});
public ActionResult TagRedirector(string tagurl)
{
..
}
This will likely run into the dangerous request territory however, so if your tags have no pattern (www.)? that you can filter for then you could just as easily use ! operator in your regex pattern to knockout the names of action methods on your controller that should be filtered out i.e. only use this route if {tagsurl} is not "index,other, whatever"

Understanding MVC routing conventions

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.

Appending ?param= to mvc routes

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.

How Can I Stop ASP.Net MVC Html.ActionLink From Using Existing Route Values?

The website I'm working on has some fairly complicated routing structures and we're experiencing some difficulties working with the routing engine to build URLs the way we need them to be built.
We have a search results page that uses RegEx based pattern matching to group several variables into a single route segment (i.e. "www.host.com/{structuralParameters}" can be the following: "www.host.com/variableA-variableB-variableC" - where variables A through C are all optional). This is working for us fine after a bit of work.
The problem we are experiencing resolves around an annoying feature of the ActionLink method: if you point to the same controller/action it will retain the existing route values whether you want them or not. We prefer to have control over what our links look like and, in some cases, cannot have the existing parameters retained. An example would be where our site's main navigation leads to a search results page with no parameters set - a default search page, if you like. I say this is an annoying feature because it is a rare instance of the ASP.Net MVC Framework seemingly dictating implementation without an obvious extension point - we would prefer not to create custom ActionLink code to write a simple navigation link in our master page!
I've seen some say that you need to explicitly set such parameters to be empty strings but when we try this it just changes the parameters from route values into query string parameters. It doesn't seem right to me that we should be required to explicitly exclude values we aren't explicitly passing as parameters to the ActionLink method but if this is our only option we will use it. However at present if it is displaying in the query string then it is as useless to us as putting the parameters directly into the route.
I'm aware that our routing structure exasperates this problem - we probably wouldn't have any issue if we used a simpler approach (i.e. www.host.com/variableA/variableB/variableC) but our URL structure is not negotiable - it was designed to meet very specific needs relating to usability, SEO, and link/content sharing.
How can we use Html.ActionLink to generate links to pages without falling back on the current route data (or, if possible, needing to explicitly excluding route segments) even if those links lead to the same action methods?
If we do need to explicitly exclude route segments, how can we prevent the method from rendering the routes as query string parameters?
This seemingly small problem is causing us a surprising amount of grief and I will be thankful for any help in resolving it.
EDIT: As requested by LukLed, here's a sample ActionLink call:
// I've made it generic, but this should call the Search action of the
// ItemController, the text and title attribute should say "Link Text" but there
// should be no parameters - or maybe just the defaults, depending on the route.
//
// Assume that this can be called from *any* page but should not be influenced by
// the current route - some routes will be called from other sections with the same
// structure/parameters.
Html.ActionLink(
"Link Text",
"Search",
"Item",
new { },
new { title = "Link Text" }
);
Setting route values to be null or empty string when calling Html.ActionLink or Html.RouteLink (or any URL generation method) will clear out the "ambient" route values.
For example, with the standard MVC controller/action/id route suppose you're on "Home/Index/123". If you call Html.RouteLink(new { id = 456 }) then MVC will notice the "ambient" route values of controller="Home" and action="Index". It will also notice the ambient route value of id="123" but that will get overwritten by the explicit "456". This will cause the generated URL to be "Home/Index/456".
The ordering of the parameters matters as well. For example, say you called Html.RouteLink(new { action = "About" }). The "About" action would overwrite the current "Index" action, and the "id" parameter would get cleared out entirely! But why, you ask? Because once you invalidate a parameter segment then all parameter segments after it will get invalidated. In this case, "action" was invalidated by a new explicit value so the "id", which comes after it, and has no explicit value, also gets invalidated. Thus, the generated URL would be just "Home/About" (without an ID).
In this same scenario if you called Html.RouteLink(new { action = "" }) then the generated URL would be just "Home" because you invalidated the "action" with an empty string, and then that caused the "id" to be invalidated as well because it came after the invalidated "action".
Solution at the root of the problem
It seems that the optimal solution (that doesn't smell like a workaround) is the one that solves the problem where it has roots and that's in routing.
I've written a custom Route class called RouteWithExclusions that is able to define route value names that should be excluded/removed when generating URLs. The problem is when routing falls through routes table and subsequent routes don't have the same route value names...
The whole problem is detailed and explained in my blog post and all the code is provided there as well. Check it out, it may help you solve this routing problem. I've also written two additional MapRoute extension methods that take an additional parameter.
If you want total control of the link, just build the link yourself:
Click Here
Substitute whatever you need inside the href attribute.

Resources