How to differentiate routes based on two URLs - c#

So I have two URLs that I need separate routes for:
/find-your-new-home/155-detroit/1234-some-value
and
/find-your-new-home/155-detroit/5555g-some-other-flipping-value
The route I have in place to handle #1 is:
routes.MapRoute(
"FindYourNewHomeCommunity",
"find-your-new-home/{market}/{community}.html",
new { controller = "Community", action = "Detail" }
);
I have an action filter on the "Detail" method that splits "155" from "detroit" and also splits "1234" from "some-flipping-value" and passes just the ID's to the action method (the id's are all that matter, the text values are inconsequential).
Now, the second route is almost exactly the same EXCEPT that there is a "g" after the "5555" value. This "g" indicates that it should call another action method ("CommunityGroup") on the Community Controller.
My question is: how do I configure two routes to handle these separately? I tried:
routes.MapRoute(
"FindYourNewHomeCommunityGroup",
"find-your-new-home/{market}/{communityId}g-{community}.html",
new { controller = "Community", action = "CommunityGroup" }
);
That doesn't work however, for two reasons:
1) Both URLs end up matching both routes as proven by Phil Haack's RouteDebugger.
2) Because of greedy matching (and this is why I used the text "flipping-value" in the sample URL), communityId ends up containing "5555-some-other-flippin" since it matches to the last occurrence of "g-" in the URL, which happens to be in the "flipping-value" text.
So my question is, how do I get a different action method to fire for these URLs?
EDIT: Just to clarify, these URLs are pre-defined by some other constraints I'm working in and cannot be changed. They have to follow exactly this format.

You could try creating a route constraint so that the "communityId" part of the route would only match numeric characters, eg:
routes.MapRoute(
"FindYourNewHomeCommunityGroup",
"find-your-new-home/{market}/{communityId}g-{community}.html",
new { controller = "Community", action = "CommunityGroup" },
new { communityId = #"\d+" }
);

If you can generate a list of communities, then you can write a custom regex constraint that will look up the communities at runtime and find them for you. It will then construct the constraint to only catch routes that match what you're looking for for the communities. One potential problem with it is that it only works on Application_Start. It'd be nice to change it dynamically. You could probably do it by updating the RouteTable.Routes at runtime when there are changes, but that is hacky.
Anyway, I ended up answering another Stack Overflow question that covered the same ground, here's the answer I wrote there.

Related

Understand `routes.IgnoreRoute("{resource}.axd/{*pathInfo}")`

I'm learning asp.net MVC and confused by this part.
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
Every article, including What is routes.IgnoreRoute("{resource}.axd/{*pathInfo}"), says the same thing, "this code is to ignore requests to axd files."
If I change the code to
routes.IgnoreRoute("{x}.axd/{*y}");
does it still work?
Do the things in curly brackets matter?
Yes, routes.IgnoreRoute("{x}.axd/{*y}") will still work.
Placeholder - {placeholderName}
The values within curly brackets are known as placeholders. These are simply variables and can be named whatever you want. When evaluating incoming URLs, the names don't matter at all. But when generating URLs or working out action method parameters or model property values, those names must match.
In the case of IgnoreRoute, there are no URLs generated, so those names are basically syntactic surgar.
Catch-All Placeholder - {*placeholderName}
The asterisk * indicates a catch-all placeholder. It is basically saying "match the URL, even if the rest of the segments from here to the end of the URL doesn't match the incoming URL".
Forward Slash - /
When using a catch-all placeholder as in this example, it indicates 1 or more optional segments. Since these segments are optional, so too is the right-most /. This is the same behavior when using the Default route:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
When the right-most segment is optional and it is not supplied in the URL (Home/About), this makes the right-most slash / optional as well. If the next right-most segment is also optional and omitted, the next right-most / is also optional. This explains why the default route matches the home page / instead of requiring // in order to match.
This behavior is special and only applicable to /. If you have placeholders with a different delimiter, such as {foo}-{bar} and bar is marked UrlParameter.Optional, - is still required (in fact, {bar} is required as well). /1-2 matches, /1- and /1 do not match.
Query String - ?key=value&key2=value2
Query string parameters are ignored completely when matching incoming routes. The reason why query string values are provided to MVC's ModelBinder and as action method parameters are because they are processed by value providers later on in the request.
On the other hand, when generating URLs (such as for ActionLink), any left over non-matching route values that are supplied (either in the request or directly) are added to the end of the generated URL as query string parameters.
#Html.ActionLink("Link", "Home", "About", new { key = "value", key2 = "value2" }, null)
Assuming the Default route, this ActionLink will generate the URL
/Home/About?key=value&key2=value2
The 'things' in the curly brackets matter when the route is mapped onto an Action (=method) in a Controller (=class). The names specified between the curly brackets map onto the action's parameters like so:
Account/{action}/{id}/{timestamp}
Will parameterise the following function:
public IActionResult Home(int id, DateTime timestamp) { ...
The asterisk * indicates that zero-or-many (like in RegEx) path segments can occur after that. For example /a/b/c/d/e/....

Route URL such as http://www.example.com/{userId}

I would like my MVC website to be able to route the following URLs:
http://www.example.com/{userId}
http://www.example.com/{userId}/enroll
Note: userId is a string of letters, numbers and/or hyphens.
I realize this is problematic because the URL does not specify a controller name. However, it should be possible in theory. I can use reflection to get a list of all the controllers in my application. If {userId} does not match any of those controllers, then it should be redirected to a particular controller/action.
My first question is how would you specify a map like this? I can specify a string value, I can even specify a regular expression. But how can I filter based on whether or not it matches a list of strings?
Beyond that, just wondered if anyone else has thought of doing things and if they have any other creative ideas on how I might accomplish it.
If I understand your question right:
routes.MapRoute(
name: "Default",
url: "{id}/{action}",
defaults: new { controller = "Custom", action = "Index" },
constraint: new { id = new MyRouteConstraint()});
In custom MyRouteConstraint: IRouteConstraint you can match your parameter with list of strings

Custom ASP.NET Core route with id and title separated by hyphen

In ASP.NET Core, I want to generate some kind of SEO-URLs, that contains the id of an entry and also his title.
Example: example.com/News/ViewNews/123-test-news
In Startup.cs I defined the following Route
routes.MapRoute(
name: "SeoNews",
template: "{controller=News}/{action=ViewNews}/{id:int}-{title}"
);
In the News-Controller, the following Action exists
public IActionResult ViewNews(int id, string title) { }
The breakpoint inside gave me empty values, so id = 0 and title = null.
Calling
Example: example.com/News/ViewNews/123
Gave me id = 123, but empty title cause it's not present in the URL. Whats wrong with my Rewrite-Route?
Other Routes I've defined after my custom route:
routes.MapRoute(
name: "AreaRoute",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
);
routes.MapRoute(
name: "DefaultMVCRoute",
template: "{controller=Dashboard}/{action=Index}/{id?}"
);
EDIT
I tried replacing the hyphen with a slash like SO do, so the routing template is {controller=News}/{action=ViewNews}/{id:int}/{title}. That works perfectly.. Can't understand why. I already tried to escape the hyphen, cause it may conflicts as special char from regular expressions, which doesn't work too.
Using a slash as delimitter is a suiteable workaround for me. But I'm interested to know what's special on this char, that it doesn't work, so I renamed the question title.
I guess you mean slug instead of title? Title would imply that you want to use it as parameter. A slug is only there for the sake of having a more describable URL, but isn't actually used in the real routing.
This case is very well documented in the ASP.NET Core documentation already in the "Routing in ASP.NET Core" article.
You can use the * character as a prefix to a route parameter to bind to the rest of the URI - this is called a catch-all parameter. For example, blog/{*slug} would match any URI that started with /blog and had any value following it (which would be assigned to the slug route value). Catch-all parameters can also match the empty string.
routes.MapRoute(
name: "SeoNews",
template: "{controller=News}/{action=ViewNews}/{id:int}/{*slug}"
);
One reason why {controller=News}/{action=ViewNews}/{id:int}-{title} doesn't work is that title is not optional. Second is it tries to match the whole pattern between the slashes, so you can just have one parameter per segment.
You could also try this, but I doubt it will work
routes.MapRoute(
name: "SeoNews",
template: "{controller=News}/{action=ViewNews}/{id:int}{*slug}"
);

Web Api - How to automatically transform to lowercase url parameters

I have the following configured route:
routes.MapHttpRoute("oneKey",
"{controller}/{id}");
If I go to the endpoint /users/Maticicero, the route will be correctly delegated to the GET method in my UsersController class.
However I am getting the id parameter just as it was typed, like Maticicero.
Since this is the primary key of my database, and the comparision is case sensitive, I need to normalize my ids to lowercase, that is:
id = id.ToLower()
I have to do this in every controller and in every method that takes the id parameter.
Is there a way to tell my Web Api (or route handler) to automatically lower case these parameters (or the whole url)?
Try to use routes.LowercaseUrls = true; when you register your routes. MSDN
RouteCollection.LowercaseUrls:
Gets or sets a value that indicates whether URLs are converted to
lower case when virtual paths are normalized.
But be careful when you use your requests like this http://host/controller?id=Maticicero
If a query string is included in the URL, that part of the URL is not
converted to lower case.
Also take a look at this SO question: How can I have lowercase routes in ASP.NET MVC?
You can trap the URL in the Application_BeginRequest event, parse the URL, and use HttpContext.RewritePath to modify the original URL.
https://msdn.microsoft.com/en-us/library/sa5wkk6d(v=vs.110).aspx

Getting ASP.NET MVC to correctly escape the # (hash/pound) character in routes

I've got a route that looks like this:
routes.MapRoute(
"BlogTags",
"Blog/Tags/{tag}",
new { controller = "Blog", action = "BrowseTag", viewRss = false }
);
And I create a URL using that route like this:
<%= Html.RouteLink(Html.Encode(sortedTags[i].Tag),
new { action = "BrowseTag", tag = sortedTags[i].Tag })%>
However, when a tag with a # character (like "C#") is used, the routing engine doesn't escape it, so I get a URL that looks like this:
C#
What I need is the # escaped so that it looks like this:
C#
I tried doing a Url.Encode on the tag before it went into the route, like this:
<%= Html.RouteLink(Html.Encode(sortedTags[i].Tag),
new { action = "BrowseTag", tag = Url.Encode(sortedTags[i].Tag) })%>
But that makes the routing engine double escape the # (which causes an ASP.NET crash with a bad request error)):
C#
How can I get the routing engine to escape that # character for me correctly?
Thank you for your help in advance.
As a very bald solution, I would manually replace "#" with "%23" in the output of RouteLink. Provided you don't use fragments in your urls, it should work.
You could use regular expression to only apply replace to the last part of your url.
I have a similar SO question relating to "/". While researching that issue I learned that ASP.NET decodes the URL values before they get passed to the MVC framework, and since "#" has special meaning to URLs (just like the "/" I was dealing with) there's a good chance that something in the base routing engine is causing this behavior.
Like Levi mentioned in his comment, one solution is to use ASP.NET 4.0. Another solution would be to write a RouteLink helper that automatically replaces "#" with some marker (like "!MY_HASH_TOKEN!") and then reverse that replacement in your controller (or perhaps via a HttpModule of some sort).
Or, just throw in the towel and pass the tag value as a querystring argument. Not as sexy, but its simple and it works.

Categories