ASP.NETMVC routing looping forever - asp.net-mvc

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

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

Routing with parameter conflicts issue

MY route config :
routes.MapRoute(
"LastTwoRoute",
"thong-ke-ket-qua-xo-so-2-so-cuoi/{cityID}/{pnumbers}/{pdays}/{ponlySpecial}",
new { controller = "LastTwo", action = "Index",
cityID = "MB",
pnumbers = "",
pdays = 1000,
ponlySpecial = false
});
The controller :
[HttpGet]
public ActionResult Index(string cityID, string pnumbers, int pdays, bool ponlySpecial)
{
[HttpGet]
public ActionResult Index(string cityID, string pnumbers, int pdays, bool ponlySpecial)
{
LastTwoParameters lastTwoParameters = new LastTwoParameters();
lastTwoParameters.listCities = Common.GetDropDownCitiesList();
lastTwoParameters.Numbers = pnumbers;
lastTwoParameters.Days = pdays;
lastTwoParameters.OnlySpecial = ponlySpecial;
lastTwoParameters.listLastTwoResult = new List<getReport_LastTwo_Result>();
if (TempData["Redirection"] != null || !string.IsNullOrEmpty(pnumbers) )
{
if (!string.IsNullOrEmpty(cityID) && pdays > 0)
{
using (KQXS context = new KQXS())
{
lastTwoParameters.listLastTwoResult = context.getReport_LastTwo(cityID, pnumbers, pdays, ponlySpecial).ToList();
}
}
}
return View(lastTwoParameters);
}
[HttpPost]//Run action method on form submission
public ActionResult Index(List<Cities> c, string cityID, string numbers, int days, bool onlySpecial)
{
TempData["Redirection"] = true;
return RedirectToRoute("LastTwoRoute", new {
cityID = (string.IsNullOrEmpty(cityID) ? "MB" : cityID ),
pnumbers = (string.IsNullOrEmpty(numbers) ? string.Empty : numbers) ,
pdays = (days == 0 ? 1000 : days),
ponlySpecial = onlySpecial});
}
When I frist access the controller :
and hit the submit button without entering/modifying any parameter, there are no errors :
but if I modify the third or the fourth parameter, I will have this error :
No route in the route table matches the supplied values.
I debuged the code, and at the line RedirectToRoute in HttpPost, every parameters are about the same except the parameter that I modified. I can't think of a reason why is this error happening!
If I enter/modified the second parameter (the second text box counting from top to bottom), I have no errors either!
Any help is greatly appreciated!
P/s : If this is not clarify enough for you because of my poor English, I can provide a screen video which records how I get the error!
You have pnumbers = "" in your route but it's not marked as an optional field (and you wouldn't be able to have it as optional if it's in the middle with required fields around it).
Try defaulting it to "0" or something.
Another alternative is to move this option to the end of the required parameters and mark it as optional like:
pnumbers = UrlParameter.Optional
It's worth installing route debugger if you are having routing issues as it adds a nice interface at the bottom of the page which shows which routes will trigger and which wont. It's essential with complex routes IMO.

Asp.net mvc 3.0 tree structure page custom routing

I want to map all CMS pages url to single controller(PageController) and action(Details).
How can I create custom routing to map all these urls?
/teacher
/teacher/kindergarten
/teacher/kindergarten/1
/teacher/primary
/teacher/primary/english
/teacher/primary/language
/teacher/primary/language/chinese
/teacher/primary/math
/teacher/primary/science
/parent
/parent/kindergarten
/parent/primary1-3
/parent/primary4-6
/leader
/leader/kindergarten
/leader/kindergarten/1
If you have these URLs in a database you could map the routes when the application starts up:
var pages = siteDB.Pages.ToList();
string pagePath = "";
foreach (var page in pages)
{
routeVals = new RouteValueDictionary();
constraints = new RouteValueDictionary();
routeVals.Add("controller", "page");
routeVals.Add("action", "details");
constraints.Add("path", "[a-zA-Z0-9\\-]*");
// any child pages? must add these routes before their parent pages.
var childPages = siteDB.Pages.Where(p => p.ParentPageId == page.PageId).ToList();
foreach (var childPage in childPages)
{
pagePath = BuildPath(childPage);
RouteTable.Routes.Add(new Route(pagePath, new MvcRouteHandler())
{
Defaults = routeVals,
Constraints = constraints,
DataTokens =
new RouteValueDictionary {
{ "pageid", childPage.PageId },
{ "path", pagePath }
}
});
// Any further child pages? (Only 3 levels supported)
var childSubPages = siteDB.Pages.Where(p => p.ParentPageId == childPage.PageId).ToList();
foreach (var childSubPage in childSubPages)
{
pagePath = BuildPath(childSubPage);
RouteTable.Routes.Add(new Route(pagePath, new MvcRouteHandler())
{
Defaults = routeVals,
Constraints = constraints,
DataTokens =
new RouteValueDictionary {
{ "pageid", childSubPage.PageId },
{ "path", pagePath }
}
});
}
}
This code takes the pages from a database where they are linked by parent id.
Here's the BuildPath function which generates a full path to each page:
public static string BuildPath(Page page)
{
if (page.ParentPageId == 1)
{
return page.PageKey;
}
else
{
SiteDataEntities siteDB = new SiteDataEntities();
string path = page.PageKey;
Page parent = siteDB.Pages.Find(page.ParentPageId);
while (parent != null)
{
path = parent.PageKey + "/" + path;
parent = siteDB.Pages.Find(parent.ParentPageId);
if (parent.PageKey == "home") break;
}
return path;
}
}
Previous proposed solution is working only for small amount of pages.
Because according to the code:
application generate and register Route for each of site page. In result we have at least same amount of routes as pages in our site. As you probably know RouteModule have to check route by route each of them to find first right one and execute correct handler, controller, action, view...
There are two other way to solve this:
You can create a class that derives from RouteBase and implement the properties and methods that you need: split url to segments, determinate current page fill RouteValueDictionary with pageid, path, parents etc
You can customize UrlRewriteModule with custom rewrite provider. Idea to transform all requests url from tree base structure to mvc default route:
{controller}/{action}/{id}?path=parentlevel1/parent2/parent3/....
90% -same code for both variants could be prepared.
that solution also could be useful when you have different controllers, correct one we could determinate by current page (by page data: type)

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.

ASP.NET MVC - Mapping more than one query string parameter to a pretty url

I am a bit stuck on the design of my seo friendly urls for mvc....Take for example the following url:
http://myapp/venues/resturants.aspx?location=central&orderBy=top-rated
With my mvc app i have mapped it as follows:
http://myapp/venues/list/resturants/central/top-rated
{controller}/{action}/{category}/{location}/{order}
Now the only problem is that location and order are optional...so it should be possible to submit a request like: http://myapp/venues/list/resturants/top-rated . This proves to be a problem when the request hits the controller action, the location parameter has picked up "top-rated", naturally.
Any suggestions? I' am considering using explicit querystrings to handle more than one parameter but this is really my last option as i dont want to sacrifice SEO too much.
Has anyone eles run into such dilemmas? And how did you handle it?
Thanks in advance!
Click on your profile link and look at the URLs for Stats, Recent, Response, etc.
Examples:
https://stackoverflow.com/users/52065?sort=recent#sort-top
https://stackoverflow.com/users/52065?sort=stats#sort-top
with no sort it defaults to stats
https://stackoverflow.com/users/52065
Optional paramters should be query parameters
Assuming that the allowed values for location and order are unique (i.e. when they come in, you can tell them apart, or else if they only supply one, how are you going to know if it's a location or an order?), then you could just take two parameters and work out what they are in the controller.
Route: {controller}/{action}/{param1}/{param2}
Controller action:
public ActionResult MyAction(string param1, string param2)
{
string location;
string order;
if (!ParseLocation(param1, out location))
{ ParseLocation(param2, out location); }
// ...
}
Not particularly elegant, but does let you have the URLs you want.
You will always have this issue if you have multiple optional parameters. Either make one or both of them non-optional (and positioned earlier in the query string than the optional one) or use the querystring parameter notation.
ok guys just posting a solution i've been playing with so far.
I have set up my routes using constraints as follows:
routes.MapRoute(
"VenuesList",
"venues/list/{category}/{location}/{orderBy}",
new { controller = "venues", action = "list", category = "", location = "", orderBy = "" },
new { location = "central|east|west|south", orderBy = "top-rated|price" }
);
routes.MapRoute(
"VenuesListByLocation",
"venues/list/{category}/{location}",
new { controller = "venues", action = "list", category = "", location = "" },
new { location = "central|east|west|south" }
);
routes.MapRoute(
"VenuesListByOrder",
"venues/list/{category}/{orderBy}",
new { controller = "venues", action = "list", category = "", orderBy = "" },
new { orderBy = "top-rated|price" }
);
routes.MapRoute(
"VenuesListDefault",
"venues/list/{category}",
new { controller = "venues", action = "list", category = "" }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
The idea is that if the validation fails it will go to the next route in the list...eventually hitting the default.
Needs some more testing but has worked well so far...
Why don't you create a property in the page for each possible querystring parameter?
This way you can handle it any way you choose with just a few lines of code...

Resources