MVC 3 routing help with dynamic route - asp.net-mvc

Im trying to do something like this:
routes.MapRoute("Product", "{product}/{id}",
new
{
action = "Product",
controller = "Home",
product = UrlParameter.Optional,
id = UrlParameter.Optional
});
It gives me error when im trying to load page 404 i think,
Im trying to make the url look like this: www.tables.com/productName/ID .
How can i do it without adding a strong type word like this:
routes.MapRoute("Product", "Products/{product}/{id}", ... )
rest of the routes:
routes.MapRoute("Product", "{product}/{id}",
new
{
action = "Product",
controller = "Home",
product = UrlParameter.Optional,
id = UrlParameter.Optional
});
routes.MapRoute("Category", "Category/{category}/{template}",
new
{
action = "Index",
controller = "Category",
category = UrlParameter.Optional,
template = UrlParameter.Optional
});
routes.MapRoute("Profile", "Profile/{fullName}",
new
{
action = "Index",
controller = "Profile",
fullName = UrlParameter.Optional
});
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
thanks.

Your problem is that the Product route will match everything not starting with Category or Profile.
I would place the product route just before the default route and use a IRouteConstraint such that it doesn't match non products.
Code sample:
routes.MapRoute("Category", "Category/{category}/{template}",
new
{
action = "Index",
controller = "Category",
category = UrlParameter.Optional,
template = UrlParameter.Optional
});
routes.MapRoute("Profile", "Profile/{fullName}",
new
{
action = "Index",
controller = "Profile",
fullName = UrlParameter.Optional
});
routes.MapRoute("Product", "{product}/{id}",
new
{
action = "Product",
controller = "Home",
product = UrlParameter.Optional,
id = UrlParameter.Optional
},
new { product = new ProductRouteConstraint() });
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
And the route constraint:
public class ProductRouteConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if (routeDirection == RouteDirection.IncomingRequest &&
parameterName.ToLowerInvariant() == "product")
{
var productName = values[parameterName] as string;
if (productName == null)
return false;
var productId = values["id"] as string;
if (productId == null)
returns false;
return ProductCatalogue.HasProductById(productId);
}
return false;
}
}
The ProductCatalogue should obviously be replaced with however you lookup products in your system.

Related

ActionLink/RouteLink not using specified actionName

On the Index.cshtml view of EuroController i have an ActionLink that i want to use the "IndexByYear" action of Euro controller:
#Html.ActionLink("Year 2006", "IndexByYear","Euro", new { id = "", year = 2006 }, null)
But the problem is that it goes to the Index() method, even though it's everything set on RouteConfig:
routes.MapRoute(
name: "Default",
url: "{controller}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Euro",
url: "{controller}/{year}/{id}",
defaults: new { controller = "Euro", action = "IndexByYear", year = DateTime.Now.Year, id = UrlParameter.Optional }
);
Here's the EuroController:
public ActionResult Index(int? id)
{
...
}
public ActionResult IndexByYear(int? id, int year)
{
...
}
Also this don't work, as it also goes to Index() method:
#Html.RouteLink("Ano 2006","Euro",new { id = "", year = 2006 },null)
If i manually navigate to domain/Euro/2016/1, then it uses the correct route. Seems like with no parameters it goes through the Default route.
My question is, why does the ActionLink don't use the IndexByYear as specified, or the RouteLink use the specified default (Euro) route?
If you want just the EuroController to have a "special" routing, you would want to do something like:
routes.MapRoute(
name: "Euro",
url: "euro/{year}/{id}",
defaults: new { controller = "Euro", action = "IndexByYear", year = DateTime.Now.Year, id = UrlParameter.Optional }
);
routes.MapRoute(
name: "DefaultNoController",
url: "{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { isMethodInHomeController = new RootRouteConstraint<Controllers.HomeController>() }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Where the constraint is defined like so:
public class RootRouteConstraint<T> : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
var rootMethodNames = typeof(T).GetMethods().Select(x => x.Name.ToLower());
return rootMethodNames.Contains(values["action"].ToString().ToLower());
}
}
This will match
domain/euro/1 to the Index action on EuroController
domain/euro/2008/1 to the IndexByYear action on EuroController
domain/ to the Index action on HomeController
domain/about to the About action on HomeController
domain/someothercontroller to the Index action on whatever other controllers you define
You may want to read https://msdn.microsoft.com/en-us/library/cc668201.aspx and https://www.asp.net/mvc/overview/older-versions-1/controllers-and-routing/creating-a-custom-route-constraint-cs

UrlHelper.Action not generating pretty url

I'm generating SEO-friendly "Pretty URLs" using this custom route, inspired by posts here on stackoverflow:
// Route used for Details view
routes.Add("CarDetailsRoute", new SeoFriendlyRoute(
url: "car/{state}/{community}/{make}/{model}/{slug}-{id}",
valuesToSeo: new string[] { "state", "community", "make", "model", "slug" },
defaults: new RouteValueDictionary(new { controller = "Vehicle", action = "Details", slug = UrlParameter.Optional, state = UrlParameter.Optional, community = UrlParameter.Optional, make = UrlParameter.Optional, model = UrlParameter.Optional }),
constraints: new RouteValueDictionary(new { id = #"\d+" })
));
/// The interesting route
routes.Add("CarIndexRoute", new SeoFriendlyRoute(
url: "car/{state}/{community}/{make}/{model}/",
valuesToSeo: new string[] { "state", "community", "make", "model" },
defaults: new RouteValueDictionary(new { controller = "Vehicle", action = "Index", state = UrlParameter.Optional, community = UrlParameter.Optional, make = UrlParameter.Optional, model = UrlParameter.Optional })
));
// Unrelated routes
// Fallback default route
routes.Add("Default", new SeoFriendlyRoute(
url: "{controller}/{action}/{id}",
valuesToSeo: new string[] { "action", "controller" },
defaults: new RouteValueDictionary(new { controller = "Home", action = "Index", id = UrlParameter.Optional }))
);
However, when I'm generating the breadcrumbs from my Details view to generate generic searches on parts of the details information, somehow the custom route fails and the default route kicks in.
// RequestContext here being a full qualified with Make, Model, Community
// and State. These are inserted in to all .Action()s by default, so i have
// to "remove them" where I don't want them.
var url = new UrlHelper(Request.RequestContext);
var breadcrumbs = new List<IBreadcrumbLink>() {
new BreadcrumbLink() {
Title = vehicle.Dealer.State,
Url = url.Action("Index", new { model = string.Empty, make = string.Empty, community = string.Empty })
// Doesn't work, generates ~/vehicle/?model=aaa&make=bbb&community=ccc&state=ddd
// Expected ~/car/state/
},
new BreadcrumbLink() {
Title = vehicle.Dealer.Community,
Url = url.Action("Index", new { model = string.Empty, make = string.Empty })
// Works, generates ~/car/state/community/
},
new BreadcrumbLink(){
Title = vehicle.Make,
Url = url.Action("Index", new { model = string.Empty })
// Works, generates ~/car/state/community/make/
},
new BreadcrumbLink(){
Title = vehicle.Model,
Url = url.Action("Index")
// Works, generates ~/car/state/community/make/model/
}
};
What would cause this behavior? Visiting the expected url of ~/car/state/ works like a charm, but as stated I cannot generate this url?

MVC Response.RedirectToRoute(RouteData.Values) redirects to the Area controller

I use RedirectToRoute method to force culture in the url. All was working until I create an Admin area in my project. Now the method redirects to the Area controller.
Example with the main HomeController : /Home/Contact
public ActionResult Contact()
{
Response.RedirectToRoute(RouteData.Values);
return View();
}
The method redirects to /Admin/Home/Contact, the values of RouteData.Values before the redirection are :
[0] "Controller" Home
[1] " Action" Contact
My Main Route :
routes.MapRoute("Default_culture", "{culture}/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional , new { culture = "([a-z]{2,3})(-[a-zA-Z]{2})?" }, new[] { "Project.Controllers" });
routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional }, new[] { "Project.Controllers" });
Route in RegisterArea method :
context.MapRoute(null, "{culture}/Admin/{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional }, new { culture = "([a-z]{2,3})(-[a-zA-Z]{2})?" }, new[] { "Project.Areas.Admin.Controllers" });
context.MapRoute(null, "Admin/{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional }, new[] { "Project.Areas.Admin.Controllers" });
I don't understand this behaviour. What am I doing wrong?
I just fixed this problem in my site.
Change Response.RedirectToRoute(RouteData.Values);
To Response.RedirectToRoute("Default", RouteData.Values);

Route segment parameter issues

I am running into a problem with my routes in MVC4.
I have a few actions that live outside of a specific product and many more that live within the user chosen product. In order to accommodate the actions I have mapped two routes
context.MapRoute(
"CMS_product",
"CMS/{productId}/{controller}/{action}/{id}",
new { controller = MVC.CMS.Home.Name, action = MVC.CMS.Home.ActionNames.Index, productId = default(Guid).ToString(), id = UrlParameter.Optional },
new string[] { "Areas.CMS.Controllers" }
);
context.MapRoute(
"CMS_default",
"CMS/{controller}/{action}/{id}",
new { controller = MVC.CMS.Home.Name, action = MVC.CMS.Home.ActionNames.Index, id = UrlParameter.Optional },
new string[] { "Areas.CMS.Controllers" }
);
So while this works in a generic since, none of my routes will match the default route any longer and instead of getting a URL like
~/CMS/Product/List
When operating outside of a product I get urls like this.
~/CMS/00000000-0000-0000-0000-000000000000/Product/List
Another note: I have tried to hard code the Prodcut/List in as a route, and have placed it before CMS_product in the hopes that it would match prior to the other url. I feel like I must be overlooking something simple.
For completeness, should anyone else run into a similar issue here is the solution.
// used to match ~/CMS/00000000-0000-0000-0000-000000000000/Product/List
// prevents the GUID.Empty from showing when there is no product value
// in the segment
context.MapRoute(
name: "CMS_nullproduct",
url: "CMS/{controller}/{action}/{id}",
defaults: new { controller = MVC.CMS.Home.Name, action = MVC.CMS.Home.ActionNames.Index, id = UrlParameter.Optional },
constraints: new { productId = Guid.Empty.ToString() },
namespaces: new string[] { "Areas.CMS.Controllers" }
);
// matches any route with a productId segment value of anything aside from
// GUID.Empty
context.MapRoute(
name: "CMS_product",
url: "CMS/{productId}/{controller}/{action}/{id}",
defaults: new { controller = MVC.CMS.Home.Name, action = MVC.CMS.Home.ActionNames.Index, id = UrlParameter.Optional },
constraints: new { productId = #"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$" },
namespaces: new string[] { "Areas.CMS.Controllers" }
);
context.MapRoute(
name: "CMS_default",
url: "CMS/{controller}/{action}/{id}",
defaults: new { controller = MVC.CMS.Home.Name, action = MVC.CMS.Home.ActionNames.Index, id = UrlParameter.Optional },
namespaces: new string[] { "Areas.CMS.Controllers" }
);
In my opinion you should remove default value for productId.
context.MapRoute(
"CMS_product",
"CMS/{productId}/{controller}/{action}/{id}",
new { controller = MVC.CMS.Home.Name, action = MVC.CMS.Home.ActionNames.Index, id = UrlParameter.Optional },
new string[] { "Areas.CMS.Controllers" }
);
If you not provide productId you routing engine should match second route and generate ~/CMS/Product/List but if you provide productId it match first rule.
Additionaly you can write custom IRouteConstraint or use regex to limit productId values.
context.MapRoute(
"CMS_product",
"CMS/{productId}/{controller}/{action}/{id}",
new { controller = MVC.CMS.Home.Name, action = MVC.CMS.Home.ActionNames.Index, id = UrlParameter.Optional },
new { productId = #"^\d{8}\-\d{4}\-\d{4}\-\d{4}\-\d{12}$" }
new string[] { "Areas.CMS.Controllers" }
);

How do I access the id parameter from the ActionLink?

The issue is that id = 5 is already in the URL as http://localhost:4032/Category/5. But to pass that value to the ActionLink it seems I have to do this:
<td> #Html.ActionLink(p.ProdNo, "Main", "Home", new { id = 5, prodno = p.ProdNo }, null) </td>
Which results in the correct URL: http://localhost:4032/Main/5/1097
But having to do that doesn't seem very right. I know there must be some clever way to handle this. Unfortunately, it's late in the day and I'm all out of clever.
I tried this:
<td> #Html.ActionLink(p.ProdNo, "Main", "Home", new {prodno = p.ProdNo }, null) </td>
But ended up with http://localhost:4032/Main?prodno=1097.
I tried adding the parameters to the corresponding method in the codebehind but that didn't seem to work either.
So in short, when using an ActionLink how do I get the routevalue already in the URL AND pass in another/new routevalue?
Global.asax
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Sections", // Route name
"{controller}/{action}/{id}/{prodno}/{instid}/{section}", // URL with parameters
new { controller = "TestEdit", action = "Index", id = UrlParameter.Optional, prodno = UrlParameter.Optional, instid = UrlParameter.Optional, section = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
"Pumps", // Route name
"{controller}/{action}/{id}/{prodno}", // URL with parameters
new { controller = "Home", action = "Main", id = UrlParameter.Optional, prodno = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
"Jobs", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Jobs", id = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
I think you need to set up the route for your prodno-parameter. Looks like this thread could contain a solution:
Routing with Multiple Parameters using ASP.NET MVC
You could fetch it from the RouteData:
#Html.ActionLink(
p.ProdNo,
"Main",
"Home",
new { id = ViewContext.RouteData["id"], prodno = p.ProdNo },
null
)

Resources