Asp.net MVC 5 MapRoute for multiple routes - asp.net

I have 3 routes in RouteConfig:
routes.MapRoute(
name: "ByGroupName",
url: "catalog/{categoryname}/{groupname}",
defaults: new { controller = "Catalog", action = "Catalog" }
);
routes.MapRoute(
name: "ByCatName",
url: "catalog/{categoryname}",
defaults: new { controller = "Catalog", action = "Catalog" }
);
routes.MapRoute(
name: "ByBrandId",
url: "catalog/brand/{brandId}",
defaults: new { controller = "Catalog", action = "Catalog" }
);
and this is my action controller receiving parameters:
public ActionResult Catalog(
string categoryName = null,
string groupName = null,
int pageNumber = 1,
int orderBy = 5,
int pageSize = 20,
int brandId = 0,
bool bundle = false,
bool outlet = false,
string query_r = null)
{
// ...
}
when I Use in view a link with #Url.RouteUrl("ByBrandId", new {brandId = 5}), I get in Action a parameter "categoryname"="brand" and brandId=0 instead of only brandId=5...
When I Call "http://localhost:3453/catalog/brand/5" with "ByBrandId" routeurl I want to get brandId=5 in actioncontroller..., the equivalent of "http://localhost:3453/catalog/Catalog?brandId=1"
thanks

Your routing is misconfigured. If you pass the URL /Catalog/brand/something it will always match the ByGroupName route instead of the intended ByBrandId route.
First of all, you should correct the order. But also, the first 2 routes are exactly the same except for the optional group name, so you can simplify to:
routes.MapRoute(
name: "ByBrandId",
url: "catalog/brand/{brandId}",
defaults: new { controller = "Catalog", action = "Catalog" }
);
routes.MapRoute(
name: "ByGroupName",
url: "catalog/{categoryname}/{groupname}",
defaults: new { controller = "Catalog", action = "Catalog", groupname = UrlParameter.Optional }
);
Now when you use #Url.RouteUrl("ByBrandId", new {brandId = 5}) it should give you the expected output /catalog/brand/5.
See Why map special routes first before common routes in asp.net mvc for a complete explanation.

Related

How to retrieve parameters from Route to Filter?

I created a filter allowing me to select specific view depending on the value of the parameter lang in the URL. If lang=fr, view_fr.cshtml will be called.
public class LocalizedViewAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
string defaultLang = "en";
var routeData = filterContext.RouteData.Values;
string lang = (string)routeData["lang"];
if (!String.IsNullOrEmpty(lang))
{
defaultLang = lang;
}
var viewResult = filterContext.Result as ViewResultBase;
if (viewResult != null)
{
if (string.IsNullOrWhiteSpace(viewResult.ViewName))
{
viewResult.ViewName = filterContext.RouteData.GetRequiredString("action");
}
var v = ViewEngines.Engines.FindView(
filterContext.Controller.ControllerContext,
viewResult.ViewName + "_" + defaultLang, null
);
if (v.View != null)
viewResult.ViewName += "_" + defaultLang;
}
base.OnResultExecuting(filterContext);
}
}
And the route for the localization
routes.MapRoute(
name: "Localization",
url: "{lang}/{controller}/{action}/{id}", // URL with parameters
new { lang = "", controller = "Static", action = "Index" } // Parameter defaults
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Static", action = "Index", id = UrlParameter.Optional }
);
But the parameter "lang" is always empty. What am I doing wrong ?
Thank you.
Your localization route does not define an optional id parameter and it would therefore only match if all four route parameters are present in the URL. Consequently, you're only hitting the default route, which does not have a lang parameter.

html.ActionLink using wrong route

I have set up the following routes so that I can use duplicate controller names (in different namespaces). This works fine but when I use html.actionlink from any controller it always includes the “CRUD” subfolder to the link.
var route1 = routes.MapRoute(
"CRUD",
"CRUD/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
route1.DataTokens["Namespaces"] = new string[] { "College.Controllers.CRUD" };
route1.DataTokens["UseNamespaceFallback"] = false;
var route2 = routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "College.Controllers" }
);
route2.DataTokens["Namespaces"] = new string[] { "College.Controllers" };
route2.DataTokens["UseNamespaceFallback"] = false;
So an html.actionlink in http://localhost/students/index looks like this
http://localhost/CRUD/students/Edit/1
What I want is this
http://localhost/students/Edit/1
I know I could fix this by specifying the route in the actionlink but I don’t want to do this because I want to re-scaffold in future and my changes would be overwritten.
The issue here is that your 2 routes are ambiguous when building the URL. There are basically 3 ways to fix this:
Add another route value to match that is not part of the URL.
Use RouteLink to specify the route by name (along with the other route value criteria to make it match).
Create a custom route to handle "constraining" it to a specific namespace or make a custom route constraint.
Since you explicitly stated the second option is unacceptable, here is an example of the first:
var route1 = routes.MapRoute(
"CRUD",
"CRUD/{controller}/{action}/{id}",
new { crud = "crud", action = "Index", id = UrlParameter.Optional }
);
route1.DataTokens["Namespaces"] = new string[] { "College.Controllers.CRUD" };
route1.DataTokens["UseNamespaceFallback"] = false;
var route2 = routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
route1.DataTokens["Namespaces"] = new string[] { "College.Controllers" };
route1.DataTokens["UseNamespaceFallback"] = false;
Now when you call #Html.ActionLink("Students", "Index", "Students", new { crud = (string)null }, null) it will not match the CRUD route, it will match the Default route.
To make it match the CRUD route, you have to explicitly add the route value to the ActionLink: #Html.ActionLink("Students", "Index", "Students", new { crud = "crud" }, null) or leave it out entirely: #Html.ActionLink("Students", "Index", "Students")
Constraining the Route
Here is an example of the 3rd option.
Unfortunately, we can't use a regular route constraint because Microsoft decided not to make the RequestContext object available in the IRouteConstraint interface. This means the namespace information about what controller the request is bound for is not available. So, we need to drop to a lower level and make a custom RouteBase class that implements the decorator pattern to wrap our existing Route class configuration.
This class simply checks to see if the namespace from the request matches a specific namespace before generating the URL.
public class NamespaceConstrainedRoute : RouteBase
{
private readonly string namespaceToMatch;
private readonly RouteBase innerRoute;
public NamespaceConstrainedRoute(string namespaceToMatch, RouteBase innerRoute)
{
if (string.IsNullOrEmpty(namespaceToMatch))
throw new ArgumentNullException("namespaceToMatch");
if (innerRoute == null)
throw new ArgumentNullException("innerRoute");
this.namespaceToMatch = namespaceToMatch;
this.innerRoute = innerRoute;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
return innerRoute.GetRouteData(httpContext);
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
object namespaces;
if (requestContext.RouteData.DataTokens.TryGetValue("Namespaces", out namespaces)
&& namespaces is IList<string>
&& ((IList<string>)namespaces).Contains(namespaceToMatch))
{
return innerRoute.GetVirtualPath(requestContext, values);
}
// null indicates to try to match the next route in the route table
return null;
}
}
Usage
var route1 = new Route(
url: "CRUD/{controller}/{action}/{id}",
defaults: new RouteValueDictionary(new { action = "Index", id = UrlParameter.Optional }),
routeHandler: new MvcRouteHandler()
)
{
DataTokens = new RouteValueDictionary
{
{ "Namespaces", new string[] { "College.Controllers.CRUD" }},
{ "UseNamespaceFallback", false }
}
};
var route2 = new Route(
url: "{controller}/{action}/{id}",
defaults: new RouteValueDictionary(new { controller = "Home", action = "Index", id = UrlParameter.Optional }),
routeHandler: new MvcRouteHandler()
)
{
DataTokens = new RouteValueDictionary
{
{ "Namespaces", new string[] { "College.Controllers" }},
{ "UseNamespaceFallback", false }
}
};
routes.Add(
name: "CRUD",
item: new NamespaceConstrainedRoute(
namespaceToMatch: "College.Controllers.CRUD",
innerRoute: route1));
routes.Add(
name: "Default",
item: new NamespaceConstrainedRoute(
namespaceToMatch: "College.Controllers",
innerRoute: route2));
From this point, you could build your own MapRoute extension methods if you so choose to make the above configuration look cleaner.

URL rewriting using similar MapRoutes

I have 2 similar maproute requests but I'm trying to target different routes.
Basically I'm creating a picture project using ASP.NET MVC.
What I want is to have the URL as:
website.com/pictures/username
and
website.com/pictures/pictureid
I'm using this as the map routes atm. Hoped that the different signatures would be enough to distinguish which action i would need.
The pictures controller has the action methods as
ActionResult Index (string username) { ... }
ActionResult Index (long id) { ... }
routes.MapRoute(
"UsersPicturesRoute",
"Pictures/{username}",
new { controller = "Pictures", action = "Index", username = UrlParameter.Optional }
);
routes.MapRoute(
"SinglePictureRoute",
"Pictures/{id}",
new { controller = "Pictures", action = "Index", id = UrlParameter.Optional }
);
Is there a way to have this desired outcome?
You can change your RegisterRoutes in below sequence then you will get your required output
routes.MapRoute(
"SinglePictureRoute",
"Pictures/{id}",
new { controller = "Home", action = "abcd", id = UrlParameter.Optional },
new { id = #"\d+" } // Parameter constraints
);
routes.MapRoute(
"UsersPicturesRoute",
"Pictures/{username}",
new { controller = "Home", action = "abcTest", username = UrlParameter.Optional }
);

For Loop not working in route.config in MVC4

I am working on MVC 4 Application and Mapping the URL in route.config..
I want to make the route with 50 different route names for which i want to run a for loop in route.config something like this..
for (int i = 1; i <= 50; i++)
{
string routeChildLink = "URLRoute" + i.ToString();
string pathChildLink = menuSubChild.pageid.ToString() + "/" + menu.title.Replace(" ", "_") + "/" + menuChild.title.Replace(" ", "_") + "/" + menuSubChild.title.Replace(" ", "_") + "/" + i;
routes.MapRoute(routeSubChildLink, pathSubChildLink, new { controller = "home", action = "index" });
}
But when I run the Site it through an error stating "A route named 'URLRoute1' is already in the route collection. Route names must be unique."
The For loop is not working.
Please Help.
Thanks
Use
Routing debugger to see what routes are being created and called
http://haacked.com/archive/2008/03/13/url-routing-debugger.aspx
It looks like that loop is being called second time.
MapRoute table have URLRoute1 in it already.
The Framework always tries to match the URL of the request to a route in the order of the Routes added to the RouteCollection.
So you should put the custom routes before the default route,
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
for (int i = 1; i <= 50; i++)
{
string routeChildLink = "URLRoute" + i.ToString();
//Custom route
routes.MapRoute(
name: "Route name",
url: "URL with parameters",
defaults: new { controller = "Home", action = "MethodName" }
);
}
//default route
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}

Map a route to the same controller action

I want only one controller action to handle all GETs. How can I map a route to do this?
routes.MapRoute("AllGETs",
"{*any}",
new { Controller = "YourController", Action = "YourAction" },
new { HttpMethod = new HttpMethodConstraint("GET") }
);
I actually ended up doing this, seemed to do what I needed:
routes.MapRoute(
// Route name
"Default",
// URL with parameters
"{controller}/{id}",
// Parameter defaults
new {controller = "Home", action = "GenericPostHandler", id = "" }
);

Resources