MVC Routing identifying segments wrong - c#

I have this route:
(Ignore the XML Format)
<route name="note" url="{noteId}-{title}">
<constraints>
<segment name="noteId" value="\d+" />
<segment name="title" value=".+" />
</constraints>
</route>
I want it to match urls like /1234-hello-kitty or /5578-a-b-c-ddd-f-g
MVC Routing Handler seems to be having some troubles with this.
I have been reading a lot about the subject, and I found out some interesting facts:
MVC first identifies the route segments and then it checks the constraints
MVC reads segments from right to left, meaning it first identifies title and then noteId
Taking the first example, I'm guessing MVC is identifying noteId as 1234-hello and title as kitty.
This fails when constraints are checked and therefore the route is not matched.
Is there another way to do this?
Please take in account that I want to keep both my segments noteId and title, and they should be both separated by a hyphen - (This is mandatory)

I can see couple of options for an approach to solving this issue:
URLRewriting
One possibility is to rewrite URLs (similar to mod_rewrite) to convert them from format that is imposed on you into a format that MVC can route natively. There is an IIS Module from Microsoft that does just that, and I believe (though not certain) would have the necessary functionality to accomplish the task in your case. The basic principle here is that if the format cannot be handled by MVC due to route template parsing rules, then convert the URL to something that it can manage before it even reaches the MVC route handling. URL Rewrite is an IIS Module that sits before MVC handler, examines the requests, and is able to rewrite the request from one form into another. Then, this altered form is what is seen by MVC and can be understood and parsed by it. E.g. the URL of /1234-hello-kitty can be rewritten by the module as /1234/hello-kitty and then MVC route template would be a simple {noteId}/{*title}. The downside caveat here is that generating links may not work here since generated links would look like /1234/hello-kitty rather than /1234-hello-kitty. However, mitigation may be to have a route specifically for link generation and not for routing defined as {noteId}-{title}. I believe (should be verified) that this will actually generate a link in form /1234-hello-kitty (albeit not being able to parse it on incoming request).
Custom MVC route handler
This one basically draws on the idea that if MVC doesn't do it for you, override its behavior to do what you wish it would do. The tactical aspect of this is described in SO post on how to provide your own handler. The way you would use it is you can provide your own interpretation of parsing of segments of url to route data, and provide the actual values as you parse them into requestContext.RouteData.Values["nodeId"] = /* your code that gets noteId out of URL. */. The rest of the application works as any other, knowing nothing about this surgical intervention in routing.

I have been reading a lot about the subject, and I found out some
interesting facts:
MVC first identifies the route segments and then it checks the constraints
MVC reads segments from right to left, meaning it first identifies title and then noteId
Taking the first example, I'm guessing MVC is identifying noteId
as 1234-hello and title as kitty.
This fails when constraints are checked and therefore the route is not
matched.
Those facts and the guess are completely correct. This is how ASP.NET routing works, unfortunately.
Why?
ASP.NET routing Simply works in two phases, first parse all routes and second try to match them for every request.
Considering your case, first parses:
Split a routeUrl by "/". Each segment is a path segment. You have just one: "{noteId}-{title}".
For each path segment, split them into sub-segments: parameters and literals. Parameters are enclosed by {} and literals are the rest. You have 3 sub-segments: {noteId}, - and {title}
Then, try to match (when having multiple sub-segments):
Find the last occurrence of last literal (-) and match the text after the literal to the last parameter (title).
Repeats 1st to finish all parameters and literals. If the URL or sub-segments are longer, the match fails.
Possible solutions
So in order to use a literal you have to make sure that your literal won't occur in parameters. Since you are stick with a dash, you may have some possible solutions.
You can use one parameter and no literal with a matching constraint (e.g. ^\d+-[\w-]+$), then try to parse the id inside controller action. This requires no changes in existing URL structure.
You can switch places of title and noteId, like /hello-kitty-1234.
You can try double dashes as literal, like /1234--hello-kitty.

Related

Parameters with + sign within the custom URL

I'm trying to reference an image like this:
<img src="/controller/method/#Model.attribute">
This works until the attribute has a plus sign. I already know that the + sign has a semantic meaning but I'd like to keep it, because some values have the plus sign.
I've tried:
<img src="/controller/method/#HttpUtility.HtmlEncode(#Model.attribute)">
And on the server side:
public method(string param)
{
string p = HttpUtility.HtmlDecode(param);
}
How can I accomplish this using ASP.NET MVC 5?
You need to use UrlEncode:
<img src="/controller/method/#HttpUtility.UrlEncode(Model.attribute)">
And do nothing in the method:
public ActionResult method(string param){
// param should already be decoded
}
Did some testing and got error page while trying to reproduce scenario you described.
Here is related question: double escape sequence inside a url : The request filtering module is configured to deny a request that contains a double escape sequence
In my designs, I'm avoiding any direct use of model fields as part of the URL. It's not only the question of URL-encoding them - which you can always do - but also the question of readability.
What I do instead is to add another field to the model, which is the URL-ready representation of an attribute. That field can be calculated from the original field by only accepting letters and numbers and replacing spaces or any other character with a dash.
For example, if you had the attribute set to someone's pencil + one, the auto-created URL version of this attribute would be someone-s-pencil-one.
You can customize this process, make it recognize some domain-specific words, etc. But that is the general idea I'm always following in my designs.
As a quick solution you can use a regular expression to isolate acceptable words and then separate them with dashes for better readability:
string encoded = string.Join("-",
Regex.Matches(attributeValue, #"[a-zA-z0-9]+")
.Cast<Match>()
.Select(match => match.Value)
.ToArray());
When done this way, you must account for possible duplicates. Part of the information is lost with this encoding.
If you fear that two models could clash with the same URL, then you have to do something to break the clash. Some websites append a GUID to the generated URL to make it unique.
Another possibility is to generate a short random string, like 3-5 letters only, and store it in the database so that you can control its uniqueness. Everything in this solution is subordinated to readability, keep that in mind.

Route parameter with slash "/" in URL

I know you can apply a wildcard in the route attribute to allow / such as date input for example:
[Route("orders/{*orderdate}")]
The problem with wildcard is only applicable to the last paramter in URI. How do I solve the issue if want to have the following URI:
[Route("orders/{orderdate}/customers")]
Update:
I know there are few options to solve the issue by refactoring the code so please do not offer a solution something like:
change the route template to [Route("orders/customers/{orderdate}")]
change the date to a different format (e.g. "dd-mm-yyyy")
#bet.. I think the genericUriParserOptions is no longer applicable to .net 4.5 or later..
Also as suggested by #JotaBe, you might need to correctly decode the url request. In most case the %2F will be automatically translated to a slash '/'. So if you need to escape it you will need to decode the '%' char in the first place.. so your URL: will look something like: www.domain.com/api/orders/23%252F06%252F2015/customers
Notice the characters '%252F' will be translated to the actual '%2F'
EDIT
Ok here is the complete solution (Tried it and working for me):
Assuming you have an API endpoint like so:
[Route("orders/{date}/customers")]
public HttpResponseMessage Get(string date)
{
}
In the web.config you will need to set the requestPathInvalidCharacters to empty which tells the asp.net to allow all request
<system.web>
<httpRuntime targetFramework="4.5" requestPathInvalidCharacters=""/>
</system.web>
<system.webServer>
<security>
<requestFiltering allowDoubleEscaping="true" />
</security>
</system.webServer>
When the client sending the request to the API you will need to make sure to escape the '%' like so:
www.domain.com/api/orders/23%252F06%252F2015/customers
You then need to decode the request
[Route("orders/{date}/customers")]
public HttpResponseMessage Get(string date)
{
DateTime actualDate = DateTime.Parse(System.Net.WebUtility.UrlDecode(date)); // date is 23/06/2015
}
As noted in the comment by #AlexeiLevenkov, this is wrong:
You cannot have a parameter in the URL which accepts forward slashes, because this is a special symbol which separates each URL fragment. So, whenever you include this symbol in your URL, there will be new fragments, and a single parameter can't include several fragments.
If you want more details, read this, but these are the most relevant excerpts:
the URL path finishes in the first ? or # found in the URL. So, the slashes only create fragments in the section of the URL path before the occurrence or one of those symbols.
From section 3.4: The query component is indicated by the first question mark ("?") character and terminated by a number sign ("#") character or by the end of the URI.
So, the query string can include forward slashes, /, if desired, and they will not define path segments at all.
These are some solutions for the question:
include fragments for day, month and year, like this: [Route("orders/{month}/{day}/{year}/customers")] and then create the date on the server side
require the user to use a different separator, like dash or dot, which won't create problems, receive it at string an parse it yourself (or use your own custom binder to support that format)
use the URL Rewrite extension to change the URL before it reaches the routing system, and parse it as explained in the previous solution (this requires hosting in IIS)
receive it as a query string, i.e. something like this: ´?date=02/03/2015´ (you'd better encode it)
NOTE: your original question said "query string", and my comment about encoding referred to the query string, which is the last segment of an URL after the question mark, if present, like &id=27. I corrected your question so that it doesn't mention "query string", which was not the right name for what you need
C# has its own method who skips the rules of escape sequences
the name of method is
Uri.UnescapeDataString(your querystring parameter)
you can use it while getting the parameters value
You can use the following URI [Route("orders/{DD:int}/{MM:int}/{YY:int}}/customers")]
and then use a custom model binder to take DD/MM/YY and turn them into a date that you can bind in your action method.
You can choose how you want to deal with constraints (go stricter with regex's) or use validation and return 400 if it doesn't match.
The simpler approach is, to take the Day/Month/Year and put it together in code.
Here is a link for dealing with modelbinding.

WebAPI routing cuts wildcard parameter on a question mark

I have a route in controller that should match everything in part of url and put it into string parameter.
What I have is:
[Route("api/proxy/{proxyId}/{*parameter}")]
public Task<HttpResponseMessage> Mediate(int proxyId, string parameter)
and for an unknown url, for example:
http://localhost/api/proxy/1/test?a=1&b=2
I would like "parameter" variable to contain:
test?a=1&b=2
Instead, it contains:
test
How can I specify route to not cut everything after question mark?
For this particular case I can extract it from Request.RequestUri object, but it would be.. inelegant.
You cannot do that. By definition the URL segments doesn't include the query string.
However, you can do something really easy: inside your WebApi controller you have the Request property which contains the Query String:
Request.RequestUri.Query
You simply have to concatenate the url param with this to have what you need. This includes the leading question mark:
The Query property contains any query information included in the URI. Query information is separated from the path information by a question mark (?) and continues to the end of the URI. The query information returned includes the leading question mark.
from Uri.Query Property
If you still want to force it to work in a different way, you'd need to include your own custom route provider, implementeing your own IDirectRouteProvider and registering it. See this: get a list of attribute route templates asp.net webapi 2.2
But doing something like this is unnatural. Why do things exactly in a different way as the standard way that all other people aunderstand and use?

How do I use a pattern Url to extract a segment from an actual Url?

If I have a series of "pattern" Urls of the form:
http://{username}.sitename.com/
http://{username}.othersite.net/
http://mysite.com/{username}
and I have an actual Url of the form:
http://joesmith.sitename.com/
Is there any way that I can match a pattern Url and in turn use it to extract the username portion out the actual Url? I've thought of nasty ways to do it, but it just seems like there should be a more intuitive way to accomplish this.
ASP.NET MVC uses a similar approach to extract the various segments of the URL when it is building its routes. Given the example:
{controller}/{action}
So given the Url of the form, Home/Index, it knows that it is the Home controller calling the Index action method.
Not sure I understand this question correctly but you can just use a regular expression to match anything between 'http://' and the first dot.
A very simple regex will do:
':https?://([a-z0-9\.-]*[a-z0-9])\.sitename\.com'
This will allow any subdomain that only contains valid subdomain characters. Example of allowed subdomains:
joesmith.sitename.com
joe.smith.sitename.com
joe-smith.sitename.com
a-very-long-subdomain.sitename.com
As you can see, you might want to complicate the regex slightly. For instance, you could limit it to only allow a certain amount of characters in the subdomain.
It seems the the quickest and easiest solution is going off of Machine's answer.
var givenUri = "http://joesmith.sitename.com/";
var patternUri = "http://{username}.sitename.com/";
patternUri = patternUri.Replace("{username}", #"([a-z0-9\.-]*[a-z0-9]");
var result = Regex.Match(givenUri, patternUri, RegexOptions.IgnoreCase).Groups;
if(!String.IsNullOrEmpty(result[1].Value))
return result[1].Value;
Seems to work great.
Well, this "pattern URL" is a format you've made up, right? You basically you'll just need to process it.
If the format of it is:
anything inside "{ }" is a thing to capture, everything else must be as is
Then you'd just find the start/end index of those brackets, and match everything else. Then when you get to a place where one is, make sure you only look for chars such that they don't match whatever 'token' comes after the next ending '}'.
There are definitely different ways - ultimately though your server must be configured to handle (and possibly route) these different subdomain requests.
What I would do would be to answer all subdomain requests (except maybe some reserved words, like 'www', 'mail', etc.) on sitename.com with a single handler or page (I'm assuming ASP.NET here based on your C# tag).
I'd use the request path, which is easy enough to get, with some simple string parsing/regex routines (remove the 'http://', grab the first token up until '.' or '/' or '\', etc.) and then use that in a session, making sure to observe URL changes.
Alternately, you could map certain virtual paths to request urls ('joesmith.sitename.com' => 'sitename.com/index.aspx?username=joesmith') via IIS but that's kind of nasty too.
Hope this helps!

What's wrong with my url encoding?

In my asp.net mvc application I created the following link:
http://localhost:2689/en/Formula.mvc/351702++LYS+GRONN+5G+9%252f2++fds
I get error 400 (bad request).
I think it blocks at the %25 (forward slash).
What am I doing wrong?
--EDIT 3--
I tried not encoding anything at all but rather rely on the default encoding of Url.RouteUrl().
It seems that this doesn't encode the "/" for some reason.
If I encode it myself first, I end up with the doubel encoded %252f. This gives me a bad request for some reason..
Why?!
--EDIT 2--
I generated the last part of the URI as follows:
Take the id.toString
Take the HttpUtility.UrlEncode(name)
Take the HttpUtility.UrlEncode(code)
String.Format("{0}--{1}--{2}") with the values from the previous parts
Add it as a parameter to Url.RouteUrl()
After that my action gets this parameter again, splits it at -- and HttpUtility.Decode() the values back.
I do it this way because the two last parameters are optional, but functional parameters. IF they are defined in a previous step, they have to be carried along to the other pages.
Less abstract: A color can have multiple names, but if a user selected it by a particular name, it should be kept throughout all the other pages.
--EDIT 1--
It also looks like HttpUtility.UrlEncode() and Url.Encode() return different results :S
If I don't encode the "/", it acts as a separator=>no luck there.
If I encode it with Url.Encode() I end up with %2F => Code 400
If I encode it with HttpUtility.UrlEncode() I end up with %25 => code 400
Because 400 doesn't even let it through to asp.net-mvc, the route debugger is of no use :(
I was there a couple of days ago. If you can accept unreadable route-values in the URL try this:
URL-encoded slash in URL
%25 is actually encoded "%", so %252f is encoded "%2f".
%2f (encoded "/") is not allowed in URL unless you explicitly allow it in webserver's configuration.
Have you run the Routing debugger: http://haacked.com/archive/2008/03/13/url-routing-debugger.aspx
I haven't looked too much at the encoding - but note that if this is to be stored somewhere (or acted upon in some way), then a POST would be more appropriate. If the text on the right is actually representative of the data with id 351702 (a vanity url, much like /665354/whats-wrong-with-my-url-encoding), then you should humanize the text. Much as the spaces have been removed from the above. It is also common to have this as a separate level in the route that is simply discarded.
Generally, MVC urls should be comprehensible.
W3Schools works fine: http://www.w3schools.com/TAGS/html_form_submit.asp?text=hello/world
Here's the URL encoding reference: http://www.w3schools.com/TAGS/ref_urlencode.asp
You can't use a forward slash as a value in the URL. Here is a nice post about creating browser and SEO friendly URLS => http://www.dominicpettifer.co.uk/displayBlog.aspx?id=34
[Edit]
Whenever you create a route you associate it with a URL pattern (The default pattern is {controller}/{action}/{id}). And in this url pattern you are supposed to use the forward slash to separate different tokens. Hope that helps

Categories

Resources