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?
Related
How would I create a route mapping to the following url:
http://localhost/SiteName/AdminCP/Topics/EditTopic/28
AdminCP is an area -- I can get to the Topics controller and show a list of topics to the user, then the user clicks a link to edit the topic which should
take them to the url above. EditTopic is a controller in AdminCP which returns a single Index action.
This is my AdminCP registration code which is not working.
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"AdminCP_default",
"AdminCP/{controller}/{action}/{id}",
new { controller= "Home", action = "Index", id = UrlParameter.Optional }
);
context.MapRoute(
"AdminCP_Topics",
"AdminCP/Topics/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
I have been able to reproduce this URL:
http://localhost/SiteName/AdminCP/EditTopic/Index/26
using this code snippet
#Html.ActionLink("Edit Options", "", "EditTopic", new { id = item.CategoryId }, new { #class = "popup-link" })
...but that's not exactly what I want.
Try this (Add before the AdminCP Default Route)
context.MapRoute(
"AdminCP_Topics",
url: "AdminCP/Topics/{controller}/{id}",
defaults: new { action = "Index", id = UrlParameter.Optional }
);
And
#Html.ActionLink("Edit Options","Index","EditTopic", new {id = 1, area = "AdminCP" },null)
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" }
);
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.
I could have SWORN I had this working, but somewhere along the line I apparently broke it. Maybe it was during my migration from ASP.NET MVC in VS2008 to ASP.NET MVC2 in VS2010.
My ActionLink:
Html.ActionLink(segment.TitleWithChapterNumber, "Index", "Read", new { bookCode = Model.Product.Id, segmentCode = segment.Index }, null)
The route I expect it to match:
routes.MapRoute(
"Read",
"Read/{bookCode}/{segmentCode}/{sectionCode}/{renderStrategy}",
new { controller = "Read", action = "Index", bookCode = "", segmentCode = "", sectionCode = "", renderStrategy = "" }
);
This renders a link that looks like: http://localhost/Obr/Read?bookCode=14&segmentCode=0
But I want it to look like http://localhost/Obr/Read/14/0
Clicking the link that it renders does take me to the right controller and the response is accurate. If I paste in the link I WANT it to look like, it does work. I guess it's just not matching?
Am I missing something obvious? I've stared at it so long I don't even know what I am looking for anymore.
For reference, here are ALL of my routes:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"ReadImage",
"Read/Image/{bookId}/{imageName}",
new { controller = "Read", action = "Image" }
);
routes.MapRoute(
"Read",
"Read/{bookCode}/{segmentCode}/{sectionCode}/{renderStrategy}",
new { controller = "Read", action = "Index", bookCode = "", segmentCode = "", sectionCode = "", renderStrategy = "" }
);
routes.MapRoute(
"BookReport",
"BookReport/{action}/{folder}",
new { controller = "BookReport", action = "Details", folder = "" }
);
routes.MapRoute(
"Reference",
"Reference/Details/{referenceType}/{searchText}",
new { controller = "Reference", action = "Details", referenceType = "", searchText = "" }
);
routes.MapRoute(
"PaginatedAudits", // Route name
"Audit/Page/{pageNumber}", // URL with parameters
new { controller = "Audit", action = "Index" } // Parameter defaults
);
routes.MapRoute(
"PaginatedReadLog", // Route name
"ReadLog/Page/{pageNumber}", // URL with parameters
new { controller = "ReadLog", action = "Index" } // Parameter defaults
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
The action signature looks like this:
[Authorize]
public ActionResult Index(string bookCode, string segmentCode, string sectionCode, string renderStrategy)
{
// code
}
Try a route link by explicitly giving the name of the route:
Html.RouteLink(
segment.TitleWithChapterNumber, // linkText
"Read", // routeName
new {
bookCode = Model.Product.Id,
segmentCode = segment.Index
}, // routeValues
null // htmlAttributes
)
Your route definitions should have UrlParameter.Optional instead of empty strings.
routes.MapRoute(
"Read",
"Read/{bookCode}/{segmentCode}/{sectionCode}/{renderStrategy}",
new { controller = "Read", action = "Index", bookCode = UrlParameter.Optional, segmentCode = UrlParameter.Optional, sectionCode = UrlParameter.Optional, renderStrategy = UrlParameter.Optional }
);
This will also help with redirect in controllers and form urls created through MVC extensions.
I want a Route with two optional args; I thought the following would work:
routes.MapRoute(
"ProductForm",
"products/{action}/{vendor_id}_{category_id}",
new { controller = "Products", action = "Index", vendor_id = "", category_id = "" },
new { action = #"Create|Edit" }
);
But it only works when both vendor_id and category_id are provided; using RouteDebug I see that /products/create/_3 doesn't trigger my route, so I added other two routes:
routes.MapRoute(
"ProductForm1",
"{controller}/{action}/_{category_id}",
new { controller = "Home", action = "Index", category_id = "" },
new { controller = "Products", action = #"Create|Edit" }
);
routes.MapRoute(
"ProductForm2",
"{controller}/{action}/{vendor_id}_",
new { controller = "Home", action = "Index", vendor_id = "" },
new { controller = "Products", action = #"Create|Edit" }
);
So, the questions:
Is using three routes the only way to make a route with optional args?
Are these URLs ok or not, that is, would you suggest a better way to do this?
Why don't you try to give vendor_id a default value (if it is not specified i.e 0) that would help you get away with one route
routes.MapRoute("ProductForm","products/{action}/{vendor_id}_{category_id}",
new { controller = "Products", action = "Index", vendor_id = "0", category_id = "" },
new { action = #"Create|Edit" });
looks good to me but i would do it a little different:
routes.MapRoute(
"ProductForm1",
"product/category/{category_id}",
new { controller = "Home", action = "Index", category_id = "" },
new { controller = "Products", action = #"Create|Edit" }
);
and then
routes.MapRoute(
"ProductForm1",
"product/details/{product_id}",
new { controller = "Home", action = "Index", product_id = "" },
new { controller = "Products", action = #"Create|Edit" }
);
then your class can be as follows:
ActionResults Index(){}
ActionResults Index(int category_id){// get categories}
ActionResults Index(int product_id){ // get products}
but thats just me
you could try it like this:
routes.MapRoute(
"ProductForm",
"products/{action}/{arg1}/{arg1_id}/{arg2}/{arg2_id}",
new { controller = "Products", action = "Index", arg1 = "", arg2 = "", arg1_id = "", arg2_id = "" },
new { action = #"Create|Edit" });
Then you would create some logic in your actionresult method to check arg1 and arg2 and identify wich argument has been passed in.
your actionlink urls would look like this:
/products/create/vendor/10
/products/create/category/20
/products/create/vendor/10/category/20
/products/create/category/20/vendor/10
Personally i dont like this as the route doesn't seem very clean but should give you what i think your looking to achieve?