I have an MVC 5 site with this structure:
example.com
example.com/area1 (index for whole section)
example.com/area2/item5 (specific item for section)
For SEO reasons I'd like to change every URL to this:
example.com/keyword (redirected from example.com)
example.com/keyword/area1
example.com/keyword/area2/item5
The keyword is fixed and always the same, though at some point in the future there may be a site with the same structure but different content with a different keyword in. That is probably at least a few months off though.
What is the quickest / easiest way to implement the above. Being able to get the name of keyword would be an advantage later though isn't essential right now.
I could use attribute routing, though I'd have to update A LOT of ActionMethods to do this - and most currently don't even use attribute routing.
thx.
Update
I've tried adding the below to RouteConfig.cs, but none of the Url's work for some reason:
routes.MapRoute(
name: "Default",
url: "keyword/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
string keyword = "keyword";
// keyword route - it will be used as a route of the first priority
routes.MapRoute(
name: "Defaultkeyword",
url: "{keyword}/{controller}/{action}/{id}",
defaults: new
{
controller = "Home",
action = "Index",
id = UrlParameter.Optional,
keyword = keyword
});
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
also you need to add this Defaultkeyword route with few changes into your Area1AreaRegistration.cs file
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Area1_defaultKeyword",
"{Keyword}/Area1/{controller}/{action}/{id}",
new { Keyword = "Keyword", controller = "HomeArea1", action = "Index", id = UrlParameter.Optional }
);
context.MapRoute(
"Area1_default",
"Area1/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
Related
I am using MVC 4 and need to remove /Home/ folder from address bar...
Eg:
http://localhost:61700/Home/AboutUs
Need to be changed as...
http://localhost:61700/AboutUs
I did that by changing the default controller in "RouteConfig.cs"
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
//url: "{controller}/{action}/{id}",
url: "{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
The above code is working as expected. I do have another folders as
brand, admin etc... here I want to show the url as
http://localhost:61700/brand/productInfo ... But I am getting server
error here as Server Error in '/' Application.
Can somebody suggest me, where am I doing wrong?
Screenshots here for more info:
This is your current RouteConfig.cs configuration:
routes.MapRoute(
name: "Default",
url: "{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
You're telling Asp.net, when a request arrives, assume the first parameter as the action and the second parameter as the id. Right now you're not telling Asp.net to parse any parameter as the controller. Because of this it uses the default value (given as the third parameter of the MapRoute method) which is in this case Home.
In that case when parsing the request http://localhost:61700/AboutUs the values end up being:
controller: Home (it uses the default controller)
action: AboutUs (from the first parameter)
id: null (this doesn't matter right now)
When parsing the request http://localhost:61700/brand/productInfo the values end up being:
controller: Home (it uses the default controller because you haven't specified where to get the controller name from)
action: Brand (from the first parameter)
id: "productInfo"
The error you're getting is because there isn't a Brand action method in HomeController.cs with a parameter of type string named id.
Asp.net processes incoming requests by trying to match with the routes configured and it uses the first route that matches.
There are several ways to achieve what you want, which include but are not limited to:
Manually mapping every action in your HomeController.cs (choosing this method will depend on the amount of actions in your HomeController). This would look like:
routes.MapRoute(
name: "AboutUs",
url: "AboutUs",
defaults: new { controller = "Home", action = "AboutUs" }
);
routes.MapRoute(
name: "ContactUs",
url: "ContactUs",
defaults: new {controller = "Home", action = "ContactUs" }
);
// etc...
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Note how the default route is the last one, this is important because it is less specific than the others and if put before would match the request and want to look for an AboutUsController.
You could use route constraints. This would look like:
route.MapRoute(
name: "HomeControllerRoutes",
url: "{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { action = "AboutUs|ContactUs|etc..." } //Here you would put all your action methods from home controller that you want to accces as /{action}
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
If you want to read more about route constraints, I found this article that explains that the constrains parameter can receive a regular expression (I suggest you modify the regular expression above to make it case insensitive) or an IRouteConstraint.
Update:
I just read your comment about having 160+ actions in your HomeController that would make your regular expression in my second suggestion quite long. In that case the other options you have could be:
Using a regular expression that rejects all other controller names, but that would violate the open/closed principle (OCP) and every time you add another controller you would have to add it to the regular expression.
Create the regular expression from the metadata of you HomeController class. This would look like
string.Join("|", typeof(HomeController).GetMethods().Select(info => info.Name))
Or you could take a look at IRouteConstraint to see if you could figure out a more elegant solution.
I have no experience with IRouteConstraint
Add this in your route.config / glibal.asax and don't change your default routes. Add following above it.
routes.MapRoute(
name: "About",
url: "AboutUs",
defaults: new { controller = "Home", action = "AboutUs" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
I have 160+ views in the home controller
You don't mention how many views you have in the other controllers, nor how complicated they need to be.
Rather than keep the default controller/action and add routes for every view in home, you can add a route for each controller and then have your default route without a controller path.
While this means you do need a route for every controller, it's better than one for every view.
routes.MapRoute(
"AdminRoute",
"Admin/{action}/{id}",
new { controller = "Admin", action = "Index", id = UrlParameter.Optional });
routes.MapRoute(
"BrandRoute",
"Brand/{action}/{id}",
new { controller = "Brand", action = "Index", id = UrlParameter.Optional });
routes.MapRoute(
"HomeRoute",
"{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
routes.MapRoute(
"DefaultRoute",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
(afaicr you don't need the default route as all your views would be covered by the other 3 routes)
Note the path for 'HomeRoute' doesn't have a controller part.
As long as they are in this order any url with /Admin/ or /Brand/ will be picked up first.
I am using the standard MVC template in VS 2013.
With the default set up, http://website/ will be routed to website/Home/Index.
How do I route all "actions" directly under website root url, eg http://website/xxx, to show the same content as http://website/Home/xxx? For example, how do I make http://website/About to execute the About action in the Home controller? If possible, the solution shouldn't be a Http redirect to http://website/Home/About because I don't want to show the "ugly" Home/ in the url.
I couldn't find an answer to this that covered all issues one would face with a public facing website without being a pain to upkeep while still maintaining flexibility.
I ended up coming up with the following. It allows for use of multiple controllers, doesn't require any upkeep, and makes all URLs lowercase.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
//set up constraints automatically using reflection - shouldn't be an issue as it only runs on appstart
var homeConstraints = #"^(?:" + string.Join("|", (typeof(Controllers.HomeController)).GetMethods(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.DeclaredOnly).Select(x => (x.Name == "Index" ? "" : x.Name))) + #")$";
//makes all urls lowercase, which is preferable for a public facing website
routes.LowercaseUrls = true;
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//maps routes with a single action to only those methods specified within the home controller thanks to contraints
routes.MapRoute(
"HomeController",
"{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { action = homeConstraints }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
you can try out like the following one
routes.MapRoute(
name: "MyAppHome",
url: "{action}/{wa}",
defaults: new { controller = "Home", action = "Index", wa = UrlParameter.Optional, area = "Admin" },
namespaces: new string[] { "MyApp.Controllers" }
).DataTokens = new RouteValueDictionary(new { area = "Admin" });
Here, you may notice that the Home controller is hardcoded and is no longer to be supplied in the request. you can also make use of the RouteDebugger to play with routes.
HTH
I study a lesson which describes how to create a site with two or more languages,
and the first step in the lesson - I need to add the following code in my project
context.MapRoute(
name: "lang",
url: "{lang}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints : new { lang = #"ru|en" },
namespaces: new[] { "LessonProject.Areas.Default.Controllers" }
);
context.MapRoute(
name : "default",
url : "{controller}/{action}/{id}",
defaults : new { controller = "Home", action = "Index", id = UrlParameter.Optional, lang = "ru" },
namespaces : new [] { "LessonProject.Areas.Default.Controllers" }
);
in file DefaultAreaRegistration (/Areas/Default/DefaultAreaRegistration.cs)
but I dont have this file in my project.
I dont understand I need to create a new folder Areas and a new file DefaultAreaRegistration.cs or I need to change RouteConfig.cs file which contains
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
?
And in Global.asax there is the following code
AreaRegistration.RegisterAllAreas();
What's part I need to change?
you need to add this code to DefaultAreaRegistration.cs if this area was created before.
It means that the multiculture route cannot be created automatically when area creating. So if you dont have any Areas in your solution you just need the register route in RouteConfig.cs. its alredy done.
And u dont need any Areas for multyculture functionality.
I am extending a model to include a link property. It is based on other properties within it to generate a link:
public partial class MyModelType
{
public string FilterLink
{
get
{
var u = new UrlHelper(HttpContext.Current.Request.RequestContext);
var route = new RouteValueDictionary(u.RequestContext.RouteData.Values);
route.Remove("other");
route.Add("other", null);
route.Add("id", this.Id);
return u.Action("Index", "ControllerName", route);
}
}
}
The link generated is /ControllerName/1?other=2. If I get rid of the route.Remove("other") I get the link based on the mapped route: /ControllerName/1/2. How can I prevent it being used as a query string parameter when it is removed from the route? The UrlHelper seems to be adding it somehow, even though it is not a route value.
Check your routing configuration.
MVCs routing will append any unkown parts of your route in that way, so as long as you define it up front it should work just fine.
You probably just have this:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Try adding the option like this:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}/{option}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional, option = UrlParameter.Optional }
);
The issue is discussed here: http://erraticdev.blogspot.com/2011/03/removing-route-values-from-linksurls-in.html
However, the blogs proposal is convoluted compared with a simple solution in the comments: add "other = null as object" to the defaults values on your DEFAULT route.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional, other = null as object }
);
I have a controller with an index action.
public ActionResult Index(int id = 0)
{
return view();
}
I wish to pass id into the index action, however it doesnt appear to work in the same way as the details action.
e.g. if I want to pass id 4 into the index action, I have to visit url:
http://localhost:8765/ControllerName/?id=4
With the details Action... I can do this.
http://localhost:8765/ControllerName/Details/4
What I want to do with Index is something like...
http://localhost:8765/ControllerName/4
When I visit this url, I get an error:
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /fix/1
Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.17929
Is this possible? How can I get MVC to automatically treat the index action in the same way as the details one?
Thanks
UPDATE - MY CURRENT ROUTES CONFIG
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
UPDATE NEW RouteConfig Class still doesn't work when I visit localhost:1234/Fix/3
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "FixIndexWithParam",
url: "Fix/{id}",
defaults: new { controller = "Fix", action = "Index", id = UrlParameter.Optional });
}
}
Update It's worth pointing out, /ControllerName/Index/4 should work with the default route.
With the default route there, it expects the second parameter to be the controller name.
So with the Default Route /ControllerName/4 is being interpereted as ControllerNameController Action 4, which of course does not exist.
If you add
routes.MapRoute(
name: "IndexWithParam",
url: "{controller}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
before the default one it would allow
/Home/4 to be routed to HomeController action Index with id=4
I have't tested this, it may conflict with the default. You may need to specify the controller explicitly in the route, ie:
routes.MapRoute(
name: "HomeIndexWithParam",
url: "Home/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
(Obviously, replace Home with whatever controller you're actually wanting to route to)