The route parameter with '/' is decoded before it is passed to the controller [duplicate] - asp.net-core-mvc

Question:
I am creating a wiki software, basically a clone of wikipedia/mediawiki, but in ASP.NET MVC (the MVC is the point, so don't recommend me ScrewTurn).
Now I have a question:
I use this route mapping, to route a URL like:
http://en.wikipedia.org/wiki/ASP.NET
routes.MapRoute(
"Wiki", // Routenname
//"{controller}/{action}/{id}", // URL mit Parametern
"wiki/{id}", // URL mit Parametern
new { controller = "Wiki", action = "dbLookup", id = UrlParameter.Optional } // Parameterstandardwerte
);
Now it just occured to me, that there might be titles like 'AS/400':
http://en.wikipedia.org/wiki/AS/400
Incidentially, there is also this one (title 'Slash'):
http://en.wikipedia.org/wiki//
And this one:
http://en.wikipedia.org/wiki//dev/null
Overall, Wikipedia seems to have a list of interesting titles like this:
http://en.wikipedia.org/wiki/Wikipedia:Articles_with_slashes_in_title
How do I make routes like this route correctly ?
Edit:
Something like:
If the URL starts with /Wiki/, and if it doesn't start with /wiki/Edit/
(but not /Wiki/Edit)
then pass all the rest of the URL as Id.
Edit:
Hmm, just another problem:
How can I route this one:
http://en.wikipedia.org/wiki/C&A
Wikipedia can...
Edit:
According to wikipedia, due to clashes with wikitext syntax, only the following characters can never be used in page titles (nor are they supported by DISPLAYTITLE):
# < > [ ] | { }
http://en.wikipedia.org/wiki/Wikipedia:Naming_conventions_(technical_restrictions)#Forbidden_characters
Edit:
To allow * and &, put
<httpRuntime requestPathInvalidCharacters="" />
into section <system.web> in file web.config
(Found here: http://www.christophercrooker.com/use-any-characters-you-want-in-your-urls-with-aspnet-4-and-iis)

You could use a catchall route to capture everything that follows the wiki part of the url into the id token:
routes.MapRoute(
"Wiki",
"wiki/{*id}",
new { controller = "Wiki", action = "DbLookup", id = UrlParameter.Optional }
);
Now if you have the following request: /wiki/AS/400 it will map to the following action on the Wiki controller:
public ActionResult DbLookup(string id)
{
// id will equal AS/400 here
...
}
As far as /wiki// is concerned I believe you will get a 400 Bad Request error from the web server before this request ever reaches the ASP.NET pipeline. You may checkout the following blog post.

in Attribute Routing in mvc i had the same problem having / in string abc/cde in HttpGet
[Route("verifytoken/{*token}")]
[AllowAnonymous]
[HttpGet]
public ActionResult VerifyToken(string token)
{
//logic here
}
so you have to place * because after this it will be considered as parameter

#Darin: Well, that much is obvious, the question is: Why ? controller
+ action + id are given, it's like it's passing all these to routing again... – Quandary Jun 13 '11 at 17:38
Quandry - maybe you have already figured this out since your question is over a year old, but when you call RedirectToAction, you are actually sending an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action. Hence, the infinite loop you are seeing.
See: Controller.RedirectToAction Method

Still as an option write in the file Global.asax:
var uri = Context.Request.Url.ToString();
if (UriHasRedundantSlashes(uri))
{
var correctUri = RemoveRedundantSlashes(uri);
Response.RedirectPermanent(correctUri);
}
}
private string RemoveRedundantSlashes(string uri)
{
const string http = "http://";
const string https = "https://";
string prefix = string.Empty;
if (uri.Contains(http))
{
uri = uri.Replace(http, string.Empty);
prefix = http;
}
else if (uri.Contains(https))
{
uri = uri.Replace(https, string.Empty);
prefix = https;
}
while (uri.Contains("//"))
{
uri = uri.Replace("//", "/");
}
if (!string.IsNullOrEmpty(prefix))
{
return prefix + uri;
}
return uri;
}
private bool UriHasRedundantSlashes(string uri)
{
const string http = "http://";
const string https = "https://";
if (uri.Contains(http))
{
uri = uri.Replace(http, string.Empty);
}
else if (uri.Contains(https))
{
uri = uri.Replace(https, string.Empty);
}
return uri.Contains("//");
}

Related

Allowing parameter name to have "[" in ASP.Net Core WEB API

My action method should look like:
public IActionResult GetAll([FromQuery]string page[Number],[FromQuery]string page[Size])
{
//code
}
HTTP Request is like: "GetAll?page%5Bnumber%5D=0&page%5Bsize%5D=100".
Problem : the parameter name doesn't allow me to have a square bracket.
Besides, that I'd go for simpler names of the parameters, you can solve this by using a dictionary:
public IActionResult GetAll(Dictionary<string, string> page) {
var x = page["Number"];
var y = page["Size"];
}

How to modify ASP NET Web API Route matching to allow parameters with slashes inside?

We're using RavenDB on the backend and so all the DB keys are strings that contain forward slashes e.g. users/1.
AFAIK there's no way to use the default ASP NET Web API route matching engine to match a parameter like this, without using a catch-all which means the key must be the last thing on the URL. I tried adding a regex constraint of users/d+ but it didn't seem to make a difference, the route wasn't matched.
What bits would I have to replace to do just enough custom route matching to allow this, preferably without crippling the rest of the route matching. For example, using url: "{*route}" and a custom constraint that did full regex matching would work but may work unexpectedly with other route configurations.
If your answer comes with some sample code, so much the better.
It seems that it is possible to do this by defining a custom route. In MVC4 (last stable released 4.0.30506.0), it is not possible to do by implementing IHttpRoute as per specification but by defining a custom MVC-level Route and adding it directly to the RouteTable. For details see 1, 2. The RegexRoute implementation below is based on the implementation here with mods from the answer here.
Define RegexRoute:
public class RegexRoute : Route
{
private readonly Regex urlRegex;
public RegexRoute(string urlPattern, string routeTemplate, object defaults, object constraints = null)
: base(routeTemplate, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints), new RouteValueDictionary(), HttpControllerRouteHandler.Instance)
{
urlRegex = new Regex(urlPattern, RegexOptions.Compiled);
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
string requestUrl = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
Match match = urlRegex.Match(requestUrl);
RouteData data = null;
if (match.Success)
{
data = new RouteData(this, RouteHandler);
// add defaults first
if (null != Defaults)
{
foreach (var def in Defaults)
{
data.Values[def.Key] = def.Value;
}
}
// iterate matching groups
for (int i = 1; i < match.Groups.Count; i++)
{
Group group = match.Groups[i];
if (group.Success)
{
string key = urlRegex.GroupNameFromNumber(i);
if (!String.IsNullOrEmpty(key) && !Char.IsNumber(key, 0)) // only consider named groups
{
data.Values[key] = group.Value;
}
}
}
}
return data;
}
}
Add this DelegatingHandler to avoid a NullReference due to some other bug:
public class RouteByPassingHandler : DelegatingHandler
{
protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
HttpMessageInvoker invoker = new HttpMessageInvoker(new HttpControllerDispatcher(request.GetConfiguration()));
return invoker.SendAsync(request, cancellationToken);
}
}
Add handler and route directly to the RouteTable:
RouteTable.Routes.Add(new RegexRoute(#"^api/home/index/(?<id>\d+)$", "test", new { controller = "Home", action = "Index" }));
config.MessageHandlers.Add(new RouteByPassingHandler());
Et voila!
EDIT: This solution has problems when the API is self-hosted (instead of using a WebHost) and requires further work to make it work with both. If this is interesting to anyone, please leave a comment and I'll post my solution.

Url routing in mvc 3.0

I want to have below specific Url routing for my website
http://www.MyWebsite?Region=US&Area=South
based on passed Region and Area query string parameter, i want to redirect to a specific controller action.
Problems:
What should be the Url Routing ?
Whether creating a common controller action which redirects to specific action will work here?
Any help would be greatly appriciated !!
Thanks
If you want to do querystring right off the root, then you need to map routes in global this way. Inside your action you can access context and grab querystring from it. Below is my example to do twitter style usernames, but idea is the same.
routes.MapRoute("UserSettings",
"{username}/settings",
new { controller = "Home", action = "Settings", username = "" },
new { username = new NotNullOrEmptyConstraint() }
);
public class NotNullOrEmptyConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
try
{
return !String.IsNullOrEmpty(values["username"].ToString());
}
catch (Exception)
{
return false;
}
}
}
public ActionResult Settings(string username){
}

POSTing to my ASP.NET MVC app from 3rd party site

I'm testing a payment provider (SagePay) and as part of a process, their server POSTs to my site and expects a response. I can't get this to work using MVC.
I set up a classic asp test reponse page and added it to my MVC app:
<%
Response.Buffer = True
response.Clear()
response.contenttype="text/plain"
response.write "Status=OK" & vbCRLF
response.write "RedirectURL=http://www.redirectsomewhere.co.uk" & vbCRLF
response.End()
%>
This work fine.
However, when I try to do the same with MVC, it doesn't work:
Controller:
[HttpPost]
public ActionResult TestCallback()
{
return View();
}
View:
#{
Response.Buffer = true;
Response.Clear();
Response.ContentType = "text/plain";
Response.Write("Status=OK" + System.Environment.NewLine);
Response.Write("RedirectURL=http://www.redirectsomewhere.co.uk" + System.Environment.NewLine);
Response.End();
}
The error message is a generic error from the payment provider so is no real help, but I have narrowed the error down to the point at which the page renders.
I can browse to both pages fine (i need remove the HttpPost attribute from the MVC controller method for this), and both pages display identical data.
This is the MVC url that the payment provider is POSTing to:
http://myipaddress/CA_UAT/Token/TestCallback
This is the classic asp URL that works fine:
http://myipaddress/CA_UAT/Token/TestCallback.asp
I created a 'Token' directory for the asp page so the urls would match for testing purposes.
What am I doing wrong?
UPDATE
In response to Hari's comment, I installed a Firefox plugin called 'Header Spy' which gives me this information:
Response HTTP/1.1 200 OK
Source: Response
HttpHeader:Server
Request:User-Agent Cookie
Response:Response Date Set-Cookie
Both pages show the same info.
You don't need to return an action result in order to send just plain text back to the screen. The simplest way of accomplishing this is to return a string value. Replace the code in your controller with what is below.
[HttpPost]
public string TestCallback()
{
string result = "Status=OK";
result += System.Environment.NewLine;
result += "RedirectURL=http://www.redirectsomewhere.co.uk";
result += System.Environment.NewLine;
return result;
}
This will return no other response that what you have in the string. By using an ActionResult and View you are likely returning markup from the master view.
Instead of writing the response in the view, I would write it in the action method like this:
[HttpPost]
public ActionResult TestCallback()
{
Response.Buffer = true;
Response.Clear();
Response.ContentType = "text/plain";
Response.Write("Status=OK" + System.Environment.NewLine);
Response.Write("RedirectURL=http://www.redirectsomewhere.co.uk" + System.Environment.NewLine);
Response.Flush();
return new EmptyResult();
}
When returning EmptyResult you will ensure that MVC doesn't append anything to the response.
Try like this:
[HttpPost]
public ActionResult TestCallback()
{
var sb = new StringBuilder();
sb.AppendLine("Status=OK");
sb.AppendLine("RedirectURL=http://www.redirectsomewhere.co.uk");
return Content(sb.ToString(), "text/plain");
}
or in a more MVCish way:
View model:
public class ResponseViewModel
{
public string Status { get; set; }
public string RedirectUrl { get; set; }
}
and then a custom action result:
public class StatusActionResult : ContentResult
{
private readonly ResponseModel _model;
public StatusActionResult(ResponseModel model)
{
_model = model;
}
public override void ExecuteResult(ControllerContext context)
{
var response = context.HttpContext.Response;
response.ContentType = "text/plain";
response.Write(string.Format("Status={0}{1}", _model.Status, Environment.NewLine));
response.Write(string.Format("RedirectURL={0}", _model.RedirectUrl));
}
}
and finally your controller action:
[HttpPost]
public ActionResult TestCallback()
{
var model = new ResponseModel
{
Status = "OK",
RedirectUrl = "http://www.redirectsomewhere.co.uk"
};
return new StatusActionResult(model);
}
I wonder if sagepay is expecting a file extension..ie doing some kind of URL validation on heir side. Do you know if your Action is being invoked?
Also try adding a route that makes your mvc URL look like "TestCallback.asp".

ASP.Net MVC: Passing a string parameter to an action using RedirectToAction()

I would like to know how to pass a string parameter using RedirectToAction().
Let's say I have this route:
routes.MapRoute(
"MyRoute",
"SomeController/SomeAction/{id}/{MyString}",
new { controller = "SomeController", action = "SomeAction", id = 0, MyString = UrlParameter.Optional }
);
And in SomeController, I have an action doing a redirection as follows:
return RedirectToAction( "SomeAction", new { id = 23, MyString = someString } );
I tried this redirection with someString = "!##$%?&* 1" and it always fails, no matter if I encode the string. I tried encoding it with HttpUtility.UrlEncode(someString), HttpUtility.UrlPathEncode(someString), and with Uri.EscapeUriString(someString) to no avail.
So I resorted to us TempData to pass someString, but still, I would be curious to know how to make the code above work, just to satisfy my curiosity.
I think the issue might be either in your route order, or in your controller. Here is some code that I got to work.
Route Definitions
routes.MapRoute(
"TestRoute",
"Home/Testing/{id}/{MyString}",
new { controller = "Home", action = "Testing", id = 0, MyString = UrlParameter.Optional }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
// note how the TestRoute comes before the Default route
Controller Action Methods
public ActionResult MoreTesting()
{
return RedirectToAction("Testing", new { id = 23, MyString = "Hello" });
}
public string Testing(int id, string MyString)
{
return id.ToString() + MyString;
}
When I browse to /Home/MoreTesting I get the desired output of "23Hello" to output in my browser. Can you post your routes and your Controller code?
OK, I know this question is a few days old but I wasn't sure if you got this issue sorted or not so I had a look. I played around with this for a while now and this is what the problem is and how you could solve it.
The problem you are having is that the special characters causing issues are one of the many (I think 20) special characters, such as % and ".
In your example the problem is the % character.
As pointed out by Priyank here:
The route values are posted as part of the URL string.
The Url string (not query string parameter) can't handle %(%25), "(%22) and so on.
Further, as pointed out by Lee Gunn in the same post:
http://localhost:1423/Home/Testing/23/!%40%23%24%25%3f%26*%201 - (this will blow up)
One of the ways to fix this is to remove {MyString} from the route mapping. To make your root mapping look like this:
routes.MapRoute(
"TestRoute",
"Home/Testing/{id}",
new { controller = "Home", action = "Testing", id = 0, MyString = UrlParameter.Optional }
);
This will cause the post to generate this:
http://localhost:1423/Home/Testing/23?MyString=!%2540%2523%2524%2525%2B1
Now when you set MyString it will be turned into a query string parameter which works perfectly fine.
I did try that and it did work.
Priyank also mentioned in the SO post I linked above that you maybe could solve this with a custom ValueProvider but you would have to follow his linked article their to check if that is something that would apply to you.

Resources