URLs with slash in parameter? - asp.net-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

asp.net mvc - Rewrite Url after return view

I have a problem like this. In RouteConfig.cs, I set routes
routes.MapRoute(
"NewsDetails",
"news/details-news/{title}-{id}",
new { controller = "News", action = "Details", id = "", title = "" }
);
In my Index.cshtml of NewsController I have a link
#Html.RouteLink(item.Title, "NewsDetails", new {
title = MyWeb.Classes.PrettyUrlHelper.PrettyUrl(item.Title),
id = item.Id
})
In my NewsController:
public ActionResult Details(string title,String id)
{
if (id == null && title == null)
return RedirectToAction("Index");
try
{
int ID = Int32.Parse(id);
var result = NewsConnectionDB.GetInstance().Single<LifeStory>(ID);
return View(result);
}
catch (InvalidOperationException) {
return View("~/Views/Error/Error404.cshtml");
}
catch (FormatException) {
return View("~/Views/Error/Error404.cshtml"); }
}
So if a user click on link in View, that link will route to action Details to process, and the link is Seo Url Friendly (localhost:9224/news/details-news/ten-things-2). But a user types a link instead of clicking to a link in View:
localhost:9224/news/details-news/ten-thingsblahblahblah-2
The url above is correct with id but title is not. So how can I update the url after I return View if a user types the wrong title but right id?
Any help would be appreciated.
P/S: my English is not good, so I hope you understand it.
If title is incorrect then you can send correct url in response headers. If it's ajax call then on completion check for correct url in response header. If correct url exists then change your browser url using window.history.pushState javascript method.
In Details action method use below code to set response header.
HttpContext.Current.Response.AppendHeader("CorrectUrl", "YourUrl");
Use HttpServerUtility.UrlEncode(string);
javascript code can be replace url, I think it will be working :).
C# code
string _entitle = HttpServerUtility.UrlEncode(_strTitle);
string _strCorUrl = "http://example.com/"+ _entitle + "-" + _intID.toString();
script code
top.window.location.replace('CorrectUrl');
or C# code redirect url
Response.Redirect(url);
Update
possible 1 solution with Context.RewritePath
https://msdn.microsoft.com/en-us/library/sa5wkk6d(v=vs.110).aspx
void Application_BeginRequest(Object sender, EventArgs e)
{
string originalPath = HttpContext.Current.Request.Path.ToLower();
if (originalPath.Contains("/page1"))
{
Context.RewritePath(originalPath.Replace("/page1", "/RewritePath.aspx?page=page1"));
}
if (originalPath.Contains("/page2"))
{
Context.RewritePath(originalPath.Replace("/page2", "/RewritePath.aspx"), "pathinfo", "page=page2");
}
}
It code is example, You can use it
I hope it help

Manipulate the url using routing

In my website I have the following route defined:
routes.MapRoute(
name: "Specific Product",
url: "product/{id}",
defaults: new { controller = "", action = "Index", id = UrlParameter.Optional }
);
In that way I want customers to be able to add the ID of the product and go to the product page.
SEO advisors have said that it would be better if we could add a description of the product on the URL, like product-name or something. So the URL should look something like:
/product/my-cool-product-name/123
or
/product/my-cool-product-name-123
Of course the description is stored in the db and I cannot do that with a url rewrite (or can I?)
Should I add a redirection on my controller (this would seem to do the job, but it just doesn't feel right)
On a few sites I checked they do respond with a 301 Moved Permanently. Is that really the best approach?
UPDATE
As per Stephen Muecke's comment I checked on what is happening on SO.
The suggested url was my own Manipulate the url using routing and i opened the console to see any redirections. Here is a screenshot:
So, first of all very special thanks to #StephenMuecke for giving the hint for slugs and also the url he suggested.
I would like to post my approach which is a mix of that url and several other articles.
My goal was to be able to have the user enter a url like:
/product/123
and when the page loads to show in the address bar something like:
/product/my-awsome-product-name-123
I checked several web sites that have this behaviour and it seems that a 301 Moved Permanently response is used in all i checked. Even SO as shown in my question uses 301 to add the title of the question. I thought that there would be a different approach that would not need the second round trip....
So the total solution i used in this case was:
I created a SlugRouteHandler class which looks like:
public class SlugRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var url = requestContext.HttpContext.Request.Path.TrimStart('/');
if (!string.IsNullOrEmpty(url))
{
var slug = (string)requestContext.RouteData.Values["slug"];
int id;
//i care to transform only the urls that have a plain product id. If anything else is in the url i do not mind, it looks ok....
if (Int32.TryParse(slug, out id))
{
//get the product from the db to get the description
var product = dc.Products.Where(x => x.ID == id).FirstOrDefault();
//if the product exists then proceed with the transformation.
//if it does not exist then we could addd proper handling for 404 response here.
if (product != null)
{
//get the description of the product
//SEOFriendly is an extension i have to remove special characters, replace spaces with dashes, turn capital case to lower and a whole bunch of transformations the SEO audit has requested
var description = String.Concat(product.name, "-", id).SEOFriendly();
//transform the url
var newUrl = String.Concat("/product/",description);
return new RedirectHandler(newUrl);
}
}
}
return base.GetHttpHandler(requestContext);
}
}
From the above i need to also create a RedirectHandler class to handle the redirections. This is actually a direct copy from here
public class RedirectHandler : IHttpHandler
{
private string newUrl;
public RedirectHandler(string newUrl)
{
this.newUrl = newUrl;
}
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext httpContext)
{
httpContext.Response.Status = "301 Moved Permanently";
httpContext.Response.StatusCode = 301;
httpContext.Response.AppendHeader("Location", newUrl);
return;
}
}
With this 2 classes i can transform product ids to SEO friendly urls.
In order to use these i need to modify my route to use the SlugRouteHandler class, which leads to :
Call SlugRouteHandler class from the route
routes.MapRoute(
name: "Specific Product",
url: "product/{slug}",
defaults: new { controller = "Product", action = "Index" }
).RouteHandler = new SlugRouteHandler();
Here comes the use of the link #StephenMuecke mentioned in his comment.
We need to find a way to map the new SEO friendly url to our actual controller. My controller accepts an integer id but the url will provide a string.
We need to create an Action filter to handle the new param passed before calling the controller
public class SlugToIdAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var slug = filterContext.RouteData.Values["slug"] as string;
if (slug != null)
{
//my transformed url will always end in '-1234' so i split the param on '-' and get the last portion of it. That is my id.
//if an id is not supplied, meaning the param is not ending in a number i will just continue and let something else handle the error
int id;
Int32.TryParse(slug.Split('-').Last(), out id);
if (id != 0)
{
//the controller expects an id and here we will provide it
filterContext.ActionParameters["id"] = id;
}
}
base.OnActionExecuting(filterContext);
}
}
Now what happens is that the controller will be able to accept a non numeric id which ends in a number and provide its view without modifying the content of the controller. We will only need to add the filter attribute on the controller as shown in the next step.
I really do not care if the product name is actually the product name. You could try fetching the following urls:
\product\123
\product\product-name-123
\product\another-product-123
\product\john-doe-123
and you would still get the product with id 123, though the urls are different.
Next step is to let the controller know that it has to use a special filer
[SlugToId]
public ActionResult Index(int id)
{
}

ASP.NETMVC routing looping forever

I'm quite new to MVC routing so please bear with me if this is too trivial.
I have created the following route:
routes.MapRoute("ProductSearch", "Category/{CategoryName}/{CategoryID}/{brandName}/{brandID}", new
{
controller = "Search",
action = "Search"
});
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
This is entering the Action
Search
just fine at least the first time round (with the correct parameter values). Then it will keep looping forever and lose the parameters. Any idea what might be happening?
[EDIT]
The issue seems to be coming from the fact that I have 4 placeholders. When I only set 2 placeholders the route worked.
The Action:
public ActionResult Search(string query = null, int CategoryID = 0, int brandID = -1)
{
WebSearch search = null;
try
{
int loyaltyCardID = -1;
if (FocusStoreRemoting.UserInfo != null)
{
loyaltyCardID = FocusStoreRemoting.UserInfo.LoyaltyCardID;
}
if (query != null)
{
search = FocusStoreRemoting.Controller.DoWebSearch(FocusStoreRemoting.ClientSession.SessionID,
FocusStoreRemoting.StoreID, loyaltyCardID, queryString: query);
}
else if (CategoryID >= 0)
{
search = FocusStoreRemoting.Controller.DoWebSearch(FocusStoreRemoting.ClientSession.SessionID,
FocusStoreRemoting.StoreID, loyaltyCardID, groupID: CategoryID, brandID: brandID);
}
}
catch (Exception ex)
{
return RedirectToAction("DisplayError", "Error");
}
Session[SessionStrings.SearchItems] = search.StockItems;
return View(search.RefineCategories);
}
Thanks in advance.
[Edit2]
One thing I have discovered is that it is not looping forever but for each link (and any external file) file I have listed in the <head></head> section of the page
The problem was being caused by links to external files. For example:
I was calling the external JavaScript files like so:
src="../../Content/js/whatever"
So when trying to navigate to the file, the absolute URL would be translated to:
http://localhost/Category/TestCategory/1/TestBrand/1/Content/js/whatever
Which MVC's routing was routing to the Search action mentioned in the Question.
So in reality it wasn't "Looping Forever" but for every image/content file the page contained.
I solved this by removing ../../ changing the relative URL to /Content/js/whatever

Url.Action based on the current route

I'd like to generate a new URL based on the existing route, but will add a new parameter 'page'
Here are a few examples:
old: ~/localhost/something?what=2
new: ~/localhost/something?what=2&page=5
old: ~/localhost/Shoes
new: ~/localhost/Shoes/5
I can not just append &page=5 to existing url because routes may be different.
Some use the query string and some do not.
I had a similar issue, and took the approach of extending the UrlHelper. The code in the View looks like:
Page 2
The UrlHelper extension looks like:
using System.Web.Mvc;
using System.Web.Routing;
using System.Collections.Specialized;
public static class UrlHelperExtension
{
public static string AddPage(this UrlHelper helper, int page)
{
var routeValueDict = new RouteValueDictionary
{
{ "controller", helper.RequestContext.RouteData.Values["controller"] },
{ "action" , helper.RequestContext.RouteData.Values["action"]}
};
if (helper.RequestContext.RouteData.Values["id"] != null)
{
routeValueDict.Add("id", helper.RequestContext.RouteData.Values["id"]);
}
foreach (string name in helper.RequestContext.HttpContext.Request.QueryString)
{
routeValueDict.Add(name, helper.RequestContext.HttpContext.Request.QueryString[name]);
}
routeValueDict.Add("page", page);
return helper.RouteUrl(routeValueDict);
}
}
A couple of notes: I check for the ID, since I don't use it in all my routes. I add the Page route value at the end, so it is the last url parameter (otherwise you could add it in the initial constructor).
This seems like a good approach:
// Clone Current RouteData
var rdata = new RouteValueDictionary(Url.RequestContext.RouteData.Values);
// Get QueryString NameValueCollection
var qstring = Url.RequestContext.HttpContext.Request.QueryString;
// Pull in QueryString Values
foreach (var key in qstring.AllKeys) {
if (rdata.ContainsKey(key)) { continue; }
rdata[key] = qstring[key];
}
// Update RouteData
rdata["pageNo"] = "10";
// Build Url
var url = Url.RouteUrl(rdata);
and it avoids collisions such as ?controller=example&action=problem etc.
You could reconstruct a url by pulling out the parts of the existing route by way of the RouteData object. For instance, the following would render a url with the controller and action of the current route:
<%=Url.RouteUrl(new { controller = ViewContext.RouteData.Values["controller"],
action = ViewContext.RouteData.Values["action"] }) %>
To get you started, you could go with something like a custom extension method that generates the url with an additional "page" parameter. Adjust as necessary:
public static string UrlWithPage(this UrlHelper urlHelper, string name, int page)
{
string url = urlHelper.RouteUrl(
new {
controller = urlHelper.RequestContext.RouteData.Values["controller"],
action = urlHelper.RequestContext.RouteData.Values["action"],
id = urlHelper.RequestContext.RouteData.Values["id"],
page = page
}
);
return "" + name + "";
}
This will construct a properly formatted link based on the routing configuration, whether page is real segment in the url or just appended as a querystring.

ASP.NET MVC view locations and routing

I have a base controller that I use to return basic views like this.
public ActionResult Index(string pageName)
{
return View(pageName);
}
public ActionResult LanguageSpecific(string ul, string pageName)
{
var result = View("sv/" + pageName);
return View(result.ViewName);
}
The controller's name is home is there a way that for it not to look for the sv content in /home but just in /sv
"EnglishRoute", // Route name
"{pageName}.aspx", // URL with parameters
new { controller = "Home", action = "Index", pageName = "" } // Parameter defaults
);
routes.MapRoute(
"SwedishRoute", // Route name
"{ul}/{pageName}.aspx", // URL with parameters
new { controller = "Home", action = "LanguageSpecific", ul = "",pageName = "" } // Parameter defaults
);
It looks in these locations:
~/Views/Home/sv/index.aspx
~/Views/Home/sv/index.ascx
When you call the View method you can pass in an app-relative path that starts with "~/" and then ASP.NET MVC will use the exact path you specify:
return View("~/UseExactlyThisFile.aspx");
That way it won't do its search in the various paths and locations that are pre-configured.
Please keep in mind that this doesn't have very much to do with routing (though it does a little bit).
If you try to localize your pages, why don't you use resources? With the pattern above you don't really take the advantages of mvc. Or do i misunderstand you? A simple solution would be to use an action filter which picks up the language identifier from the route and sets the UICulture. The Views then may use resources to localize their content.

Resources