Route Config for dynamic urls [duplicate] - asp.net

This question already has an answer here:
Multiple levels in MVC custom routing
(1 answer)
Closed 5 years ago.
So Im working on an MVC 4 application. My problem is for a certain controller the Url can be http://domain/category/clothing/1256 or it can be http://domain/category/clothing/shirts/formal/128'. As you can see the depth of the url changes but all must route to the category controller.
Since the max dept of the url is not known, I cant User Routeconfig as I dont know when the parameters will come.
routes.MapRoute(
name: "Category",
url: "Category/{ignore}/{id}/{SubCatId}/{SubSubCatId}",
defaults: new { controller = "Category", action = "Index", CatId = UrlParameter.Optional, SubCatId = UrlParameter.Optional, SubSubCatId = UrlParameter.Optional },
namespaces: new[] { "CSPL.B2C.Web.Controllers" },
constraints: new { id = #"\d+" }
);
The above code will not work as it accounts for only one level. Any ideas on how to achieve this
The Index method of category controller
public ActionResult Index(string id, string SubCatId, string SubSubCatId)
{
return view();
}

Your only option is a catch-all param. However, there's two caveats with that:
The catch-all param must be the last part of the route
The catch-all param will swallow everything, including /, so you'll have to manually parse out individual bits you need with a regular expression or something.
Essentially, you'll just change the route to:
routes.MapRoute(
name: "Category",
url: "Category/{*path}",
defaults: new { controller = "Category", action = "Index" },
namespaces: new[] { "CSPL.B2C.Web.Controllers" }
);
Then, your action would need to be changed to accept this new param:
public ActionResult Index(string path)
{
// parse path to get the data you need:
return view();
}

Related

Routing url with several params

I'm trying to build a REST API with ASP mvc and i'm having some trouble with the routing.
I would like to match the following urls in a nice and convenient way:
https://foo.com/collections/
https://foo.com/collections.json/
https://foo.com/collections.xml/
https://foo.com/collections/collectionID/
https://foo.com/collections/collectionID.json/
https://foo.com/collections/collectionID.xml/
And in future more items on the same pattern:
https://foo.com/persons/
https://foo.com/persons.json/
https://foo.com/persons.xml/
https://foo.com/persons/personID/
https://foo.com/persons/personID.json/
https://foo.com/persons/personID.xml/
My best attempt so far is:
routes.MapRoute(
name: "RESTCollections",
url: "{controller}s/",
defaults: new { controller = "Collection", action = "Index" }
);
routes.MapRoute(
name: "RESTCollections",
url: "{controller}s/{format}/",
defaults: new { controller="Collection", action="Index", format = UrlParameter.Optional },
constraints: new { format = "json|xml|html" }
);
It manages to match:
https://foo.com/collections/
https://foo.com/collections/json
But I'm stuck there trying to replace the "/" between the controller and format gives 404. Simply removing the "/" also gives 404.
To answer your question you could try
`context.MapRoute(
"Api_default",
"{controller}/{action}.{format}",
new { controller = "Home", action = "Index", format = UrlParameter.Optional });`
As stated here stackoverflow.com/.../customizing-asp-net-mvc-routing-to-service-json-xml-style-urls
On the other hand I don't see why you would like to state which format you want to accept to be sent to your API.
You could use the [FromBody] string content which deserialises the sent value to the method.
Or you could define the type of the parameter with the Route attribute
[Route("Post/{collection:string}")
public void Post(string collection)
Where string is a predefined constraint, you can create your own custom constraints.

Html.Routelink, Html.BeginRouteForm, Ajax.RouteLink and Ajax.BeginRouteForm not working

RouteLink and BeginRouteForm gives empty href. If I try to remove any of the custom routes defined in my code, it gives a compilation error. I tried looking for answers in similar questions but none of them works.
My routes contain optional parameters like this:
routes.MapRoute("CustomRoute", "Double/Steps/Add/{id}", defaults: new { controller = "DoubleDodge", action = "Step" });
This is how I am calling the route:
<div>
#Ajax.RouteLink("Dodge Me", "CustomRoute", new { id = ViewBag.Id }, new AjaxOptions { AllowCache = false, UpdateTargetId = "main-content-div", InsertionMode = InsertionMode.Replace })
</div>
Looking forward for your response. Thanks :)
Using Routelink, you will get a empty href if MVC cannot resolve your route as specified.
I'm betting that your CustomRoute is below your Default route config. Make sure that any specialized routes are at the top so MVC matches those first, as it will return the 1st match. And MVC is probably looking for a "Double" controller, "Steps" action, and an "Add" id value.
If that's not the case, then the same issue could happen if your ViewBag.Id doesn't contain a value, especially since your "id" isn't optional in your route. You could make that optional by adding UrlParameter.Optional like so:
routes.MapRoute(
name: "CustomRoute",
url: "Double/Steps/Add/{id}",
defaults: new { controller = "DoubleDodge", action = "Step", id = UrlParameter.Optional }
);
And I assume your Controller/Action looks like this:
public class DoubleDodge : Controller
{
public JsonResult Step(string id)
{
// create result
MyViewModel vm = new MyViewModel();
return Json(vm);
}
}

MVC3 Routes with Different Parameter Types

I am struggling to get my head around routing in MVC3.
Previously I have generally just avoided the whole area and stuck with ugly old ?id=1&foo=bar type urls. Not nice.
I have 4 routes defined thusly
routes.MapRoute("Blog", "{controller}/{action}/{PageNumber}/{PostsPerPage}", new { controller = "blog", action = "list", PageNumber = UrlParameter.Optional, PostsPerPage = UrlParameter.Optional });
routes.MapRoute("Code", "{controller}/{action}/{title}", new { });
routes.MapRoute("Id", "{controller}/{action}/{id}", new { });
routes.MapRoute("Default", "{controller}/{action}", new { controller = "home", action = "index" });
I have tried to order them from most specific to least.
The first 'blog' route works fine and I can use a URL like /blog/list/2/5 and it maps correctly to my controller.
The default route at the bottom is also working as I would expect.
However if I have action methods like this:
public ActionResult BarX(int id)
{
//some stuff
}
public ActionResult BarY(string title)
{
//some stuff
}
I would expect it to use the third route and produce a URL like /foo/barX/3.
Yet if I use
#Html.ActionLink("TEST1", "barX", "foo", new { id = 3 }, null)
the URL generated is
/foo/barx?id=3
Similarly the URL generated for
#Html.ActionLink("TEST2", "barY", "foo", new { title = "test" }, null)
is
/foo/bary?title=test
So I guess my question is: why are they producing URLs with the old ?id= syntax and not /foo/barx/3?
All of your routes are basically the same. They are
{controller}/{action}/{param}/{param}
I say the same because the routing engine would not understand the difference between {controller}/{action}/{id} and {controller}/{action}/{title}
and the route engine would really just see
{controller}/{action}/{PageNumber}/{PostsPerPage}
and confuse all the routes with the first one.
Once the route engine sees your almost generic {optional}/{optional}/{optional}/{optional}/ route at the top, anything with 4 or fewer elements can fit it so it goes no further.
If, on the other hand, your routes had distinctive beginnings, rather than generic {Controller}:
routes.MapRoute("Blog", "Blog/{PageNumber}/{PostsPerPage}", new { controller = "blog", action = "list", PageNumber = UrlParameter.Optional, PostsPerPage = UrlParameter.Optional });
routes.MapRoute("Code", "Code/{title}", new { controller = "code", action = "list", title = UrlParameter.Optional });
routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "home", action = "index", id = UrlParameter.Optional });
Now, every time the routing engine sees Blog/... it understands that only 1 route can match. With all others, it will move on looking for a match. Whenever it sees Code/..., again only 1 route is a match. And Default will handle anything like {Controller}/{Action}/{id} that doesn't have Blog or Code as a first route param.

Switching to {controller}/{id}/{action} breaks RedirectToAction

I am trying to use proper REST urls with MVC. To do that I switched default Routing from:
{controller}/{action}/{id}
to
{controller}/{id}/{action}
so instead of:
/Customer/Approve/23
there is now
/Customer/23/Approve
ActionLink seems to work ok, but the following code in CustomerController:
[CustomAuthorize]
[HttpGet]
public ActionResult Approve(int id)
{
_customerService.Approve(id);
return RedirectToAction("Search"); //Goes to bad url
}
ends up on url /Customer/23/Search. While it should be going to /Customer/Search. Somehow it remembers 23 (id).
Here is my routing code in global.cs
routes.MapRoute(
"AdminRoute", // Route name
"{controller}/{id}/{action}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { id = new IsIntegerConstraint() }
);
routes.MapRoute(
"Default",
"{controller}/{action}",
new { controller = "Home", action = "Index" });
If I switch the two functions, RedirectToAction starts working, but using:
Html.ActionLink("Approve", "Approve", new { Id = 23})
Now generates /Customer/Approve?id=23, instead of /Customer/23/Approve.
I could specify direct urls like ~/Customer/23/Approve, instead of using ActionLink and RedirectToAction, but would rather stick to functions provided by MVC.
When you use RedirectToAction(), internally, MVC will take the existing route data (including the Id value) to build the url. Even if you pass a null RouteValueDictionary, the existing route data will be merged with the new empty route value data.
The only way around this I can see is to use RedirectToRoute(), as follows:
return RedirectToRoute("Default", new { controller = "Customer", action = "Search"});
counsellorben
Try passing in new (empty) RouteValueDictionary in your controller
return RedirectToAction("Search", new System.Web.Routing.RouteValueDictionary{});
And here:
Html.ActionLink("Approve", "Approve", new { Id = 23})
I don't even know how can it pick up the Customer controller, since you are not specifying it anywhere. Try providing both controller and action to ActionLink helper.
Try passing the current route data to methon in your controller action:
return RedirectToAction("Search", this.RouteData.Values);
Remove this part:
id = UrlParameter.Optional
may be resolve the problem; when you define "id" as an optional parameter, and you have the "Default" map, the "Default" and the "AdminRoute" are same together!
regards.
I was having a similar problem. Route values that were passed to my controller action were being reused when I tried to redirect the user with RedirectToAction, even if I didn't specify them in the new RouteValueDictionary. The solution that I came up with (after reading
counsellorben's post) with was to clear out the RouteData for the current request. That way, I could stop MVC from merging route values that I didn't specify.
So, in your situation maybe you could do something like this:
[CustomAuthorize]
[HttpGet]
public ActionResult Approve(int id)
{
_customerService.Approve(id);
this.RouteData.Values.Clear(); //clear out current route values
return RedirectToAction("Search"); //Goes to bad url
}
I had a similar problem and was able to solve it by adding the id to the default route as well.
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
If there is truly no id in your default route then you could also try:
routes.MapRoute(
"Default",
"{controller}/{action}",
new { controller = "Home", action = "Index", id = string.Empty });

ASP.NET MVC twitter/myspace style routing

This is my first post after being a long-time lurker - so please be gentle :-)
I have a website similar to twitter, in that people can sign up and choose a 'friendly url', so on my site they would have something like:
mydomain.com/benjones
I also have root level static pages such as:
mydomain.com/about
and of course my homepage:
mydomain.com/
I'm new to ASP.NET MVC 2 (in fact I just started today) and I've set up the following routes to try and achieve the above.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("content/{*pathInfo}");
routes.IgnoreRoute("images/{*pathInfo}");
routes.MapRoute("About", "about",
new { controller = "Common", action = "About" }
);
// User profile sits at root level so check for this before displaying the homepage
routes.MapRoute("UserProfile", "{url}",
new { controller = "User", action = "Profile", url = "" }
);
routes.MapRoute("Home", "",
new { controller = "Home", action = "Index", id = "" }
);
}
For the most part this works fine, however, my homepage is not being triggered! Essentially, when you browser to mydomain.com, it seems to trigger the User Profile route with an empty {url} parameter and so the homepage is never reached! Any ideas on how I can show the homepage?
Know this question was asked a while back but I was just looking to do the same sort of thing and couldn't find any answer that quite solved it for me so I figured I'd add my 2 cents for others that may also look to do the same thing in future.
The problem with the proposed solution above (as mentioned in Astrofaes' comment) is that you would need to create static routes for every controller in your assembly. So in the end I ended up using a custom route constraint to check whether or not a controller exists in the executing assembly that could handle the request. If there is then return a false on the match so that the request will be handled by another route.
public class NotControllerConstraint : IRouteConstraint
{
private static readonly IEnumerable<Type> Controllers = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.BaseType == typeof(Controller));
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return Controllers.Where(c => c.Name == values["id"] + "Controller").Count() == 0;
}
}
Routes can then be set up as follows:
routes.MapRoute("User", "{id}", new { controller = "User", action = "Index" }, new { notController = new NotControllerConstraint() });
routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional });
Can you not swap the bottom two routes?
The reason that swapping the routes works, is because the {url} route doesn't have a constraint on it against empty strings (which is what your last route is). As a result, it will match the empty string first as it's higher in the route table.
With that in mind, you can either add constraints or add your specifically named routes higher in the routes table, or use the default catch all routes that mvc gives you to start with.
If you want to know which routes are matching at any given moment, then you can use the Route Debugger from Phil Haack.
I was looking to implement same style of url for my MVC 1.0 application.
I have implemented that with information from this post and blog post by Guy Burstein.
Thanks for sharing :)
I have a simlar setup as below:
routes.MapRoute(
"Common",
"common/{action}/{id}",
new { controller = "common", action = "Index", id = "" }
);
routes.MapRoute(
"Home",
"",
new { controller = "Home", action = "Index", id = "" }
);
routes.MapRoute(
"Dynamic",
"{id}",
new { controller = "dynamic", action = "Index", id = "" }
);
This allows me to be flexible and have the routes
mysite.com/
mysite.com/common/contact/
mysite.com/common/about/
mysite.com/common/{wildcard}/
mysite.com/{anything}

Resources