This question already has answers here:
Routing in ASP.NET MVC, showing username in URL
(2 answers)
Closed 4 years ago.
I have an Asp.Net MVC project whereby we allow our users to have public profiles.
I would like to improve the url, so that it is more friendly, and shorter.
The existing code is as follows -
public class ProfileController : Controller
{
private readonly IUserProfileService _userProfileService;
public ProfileController(IUserProfileService userProfileService)
{
this._userProfileService = userProfileService;
}
public ActionResult Index(string id)
{
//Get users profile from the database using the id
var viewModel = _userProfileService.Get(id);
return View(viewModel);
}
}
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//Required for the route prefix attributes to work!
routes.MapMvcAttributeRoutes();
routes.MapRoute(
"ProfileUrlIndexActionRemoval",
"Profile/{id}",
new { controller = "Profile", action = "Index" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
The aforementioned code allows the following url to work (based on the default MVC routing) - www.mydomain.com/profile/john-doe
What routing do I need to implement, in order to allow the following url to work instead - www.mydomain.com/john-doe
Thanks.
This is a little tricky as you want the friendly URL in the root of the site while not conflicting with any other routes.
That would mean that if you have any other routes like About or Contact you would need to make sure that are in the route table before the friendly route to avoid route conflicts.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//Required for the route prefix attributes to work!
routes.MapMvcAttributeRoutes();
routes.MapRoute(
"ProfileUrlIndexActionRemoval",
"Profile/{id}",
new { controller = "Profile", action = "Index" }
);
routes.MapRoute(
name: "Home",
url: "Home/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "About",
url: "About/{action}/{id}",
defaults: new { controller = "About", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Contact",
url: "Contact/{action}/{id}",
defaults: new { controller = "Contact", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"Default_Frieldly",
"{*id}",
new { controller = "Profile", action = "Index" }
);
}
}
And finally because the default route will capture all unmatched routes, you will need to take not found profiles into account.
public class ProfileController : Controller {
//...code removed for brevity
public ActionResult Index(string id) {
//Get users profile from the database using the id
var viewModel = _userProfileService.Get(id);
if(viewModel == null) {
return NotFound();
}
return View(viewModel);
}
}
By having the profile controller prefix in the original URL it made it unique so as to avoid route conflicts, but in wanting the root friendly URL, while not impossible, you see the hoops needed to jump through in order to get the desired behavior.
This is how I would do it. Register a route that matches any string after the root slash.
Note that this severely limits the routes you can use for the application since not everything matching /{id} may actually be a user ID, which is why applications will typically prefix the route with /profile or /p.
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
name: "UserIdRoute",
url: "{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
public ActionResult Index(string id)
{
//Get users profile from the database using the id
var viewModel = _userProfileService.Get(id);
return View();
}
Related
I've a strange behavior of configured route in Route Config in MVC5 app.
So, actually I try to do pretty much easy thing - change default action of a route.
To do that I've changed the defaults of a route, instead of having action = "Index", I've changed it to my required action AddUser (Look at the code below).
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "ManageUser",
url: "{controller}/{action}/{id}",
defaults: new { controller = "ManageUser", action = "AddUser", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
And I've next controller:
[Authorize(Roles ="Admin")]
public class ManageUserController : Controller
{
// GET: ManageUser/AddUser
[HttpGet]
public ActionResult AddUser()
{
return View();
}
}
So, now I expect that whenever user goes by URL: ManageUser/ he'll be redirected to defined default action but it doesn't happen, I just have 404 error. I can fix it simply by adding Index action and then redirect to AddUser, but it doesn't seem right to me. Could somebody help me understand what I've done wrong?
I think, your config should look like this:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"ManageUser",
"ManageUser/{action}/{id}",
new { controller = "ManageUser", action = "AddUser", id = UrlParameter.Optional }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
The first parameter is the name of the route. Second is the URL, which match URLs that start with ManageUser, and allows for other actions in your ManageUser controller. As you can see, it will default to the AddUser action.
And if you want to call AddUser with paramater, you must call it by full url ManageUser/AddUser/1
I want to ask if it's possible to have more than one routing within RouteConfig class. The logic I have is as below, what I want to achieve. I have actionLink("Dashboard", "Account".....) already and want to have a unique one that won't conflict with existing one when page is loaded. Please assist me there is a way.
namespace ContentManagementSystem
{
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 } // I already have this working fine
defaults: new { controller = "Dashbaord", action = "_Index", id = UrlParameter.Optional // I want to have separate, but unique route for this controller for actionResult
);
}
}
}
Yes you can have more routes you can :
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Student",
url: "students/{id}",
defaults: new { controller = "Student", action = "Index"}
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
As shown in the above code, URL pattern for the student route is students/{id}, which specifies that any URL that starts with domainName/students, must be handled by the StudentController.
Notice that we haven't specified {action} in the URL pattern because we want every URL that starts with student should always use Index action of StudentController. We have specified default controller and action to handle any URL request which starts from domainname/students.
You can read more here
Add it to top of default route:
namespace ContentManagementSystem
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("other_route", "other_route/",
defaults: new { controller = "OtherController", action = "OtherAction" });
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } // I already have this working fine
defaults: new { controller = "Dashbaord", action = "_Index", id = UrlParameter.Optional // I want to have seperate, but unique route for this controller for actionResult
);
}
}
}
I would like to create a dynamic routing to a URL like following:
http://localhost:51577/Item/AnyActionName/Id
Please note that the controller name is static and doesn't need to be dynamic. On the other hand, I need to have the action name part dynamic so that whatever is written in that part of URL, I would redirect the user to the Index action inside of Item controller.
What I have tried so far is:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"Items",
"Item/{action}/{id}",
new { controller = "Item", action = "Index", id = UrlParameter.Optional });
}
And when I build my app I get a following error:
The resource cannot be found.
Edit:
Here is my Global.asax file and the routeconfig.cs file:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
And here's the content of the RouteConfig.cs file with the answer that #Nkosi provided:
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: "Items",
url: "Item/{id}/{*slug}",
defaults: new { controller = "Item", action = "Index", slug = UrlParameter.Optional }
);
}
}
What you are referring to in your question is called a slug.
I answered a similar question here for web api
Web api - how to route using slugs?
With the slug at the end the route config would look like this
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Items",
url: "Item/{id}/{*slug}",
defaults: new { controller = "Item", action = "Index", slug = RouteParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
which could match an example controller action...
public class ItemController : Controller {
public ActionResult Index(int id, string slug = null) {
//...
}
}
the example URL...
"Item/31223512/Any-Item-Name"
would then have the parameters matched as follows...
id = 31223512
slug = "Any-Item-Name"
And because the slug is optional, the above URL will still be matched to
"Item/31223512"
I have a blogging system that I'm building and I can't seem to get ASP.NET MVC to understand my route.
the route I need is /blogs/student/firstname-lastname so /blogs/student/john-doe, which routes to a blogs area, student controller's index action, which takes a string name parameter.
Here is my route
routes.MapRoute(
name: "StudentBlogs",
url: "blogs/student/{name}",
defaults: new { controller = "Student", action="Index"}
);
My controller action
public ActionResult Index(string name)
{
string[] nameparts = name.Split(new char[]{'-'});
string firstName = nameparts[0];
string lastName = nameparts[1];
if (nameparts.Length == 2 && name != null)
{
// load students blog from database
}
return RedirectToAction("Index", "Index", new { area = "Blogs" });
}
But it won't seem to resolve...it works fine with /blogs/student/?name=firstname-lastname, but not using the route I want, which is /blogs/student/firstname-lastname. Any advice on how to fix this would be greatly appreciated.
My RouteConfig
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "StudentBlogs",
url: "blogs/student/{name}",
defaults: new { controller = "Student", action = "Index"},
constraints: new { name = #"[a-zA-Z-]+" },
namespaces: new string[] { "IAUCollege.Areas.Blogs.Controllers" }
);
routes.MapRoute(
name: "Sitemap",
url :"sitemap.xml",
defaults: new { controller = "XmlSiteMap", action = "Index", page = 0}
);
//CmsRoute is moved to Gloabal.asax
// campus maps route
routes.MapRoute(
name: "CampusMaps",
url: "locations/campusmaps",
defaults: new { controller = "CampusMaps", action = "Index", id = UrlParameter.Optional }
);
// core route
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
// error routes
routes.MapRoute(
name: "Error",
url: "Error/{status}",
defaults: new { controller = "Error", action = "Error404", status = UrlParameter.Optional }
);
// Add our route registration for MvcSiteMapProvider sitemaps
MvcSiteMapProvider.Web.Mvc.XmlSiteMapController.RegisterRoutes(routes);
}
}
You have to declare custom routes before the default routes. Otherwise it will be mapping to {controller}/{action}/{id}.
Global.asax typically looks like this:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
If you created an Area named Blogs, there is a corresponding BlogsAreaRegistration.cs file that looks like this:
public class BlogsAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Blogs";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Blogs/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
Hyphens are sometimes treated like forward slashes in routes. When you are using the route blogs/students/john-doe, my guess is that it is matching the Area pattern above using blogs/students/john/doe, which would result in a 404. Add your custom route to the BlogsAreaRegistration.cs file above the default routes.
Try adding the parameter to the route:
routes.MapRoute(
name: "StudentBlogs",
url: "blogs/student/{name}",
defaults: new { controller = "Student", action="Index", name = UrlParameter.Optional}
);
Try adding a constraint for the name parameter:
routes.MapRoute(
name: "StudentBlogs",
url: "blogs/student/{name}",
defaults: new { controller = "Student", action="Index" },
constraints: new { name = #"[a-zA-Z-]+" }
);
Dashes are a bit weird in MVC at times.. because they are used to resolve underscores. I will remove this answer if this doesn't work (although.. it should).
This has the added benefit of failing to match the route if a URL such as /blogs/student/12387 is used.
EDIT:
If you have controllers with the same name.. you need to include namespaces in both of your routes in each area. It doesn't matter where the controllers are.. even if in separate areas.
Try adding the appropriate namespace to each of the routes that deal with the Student controller. Somewhat like this:
routes.MapRoute(
name: "StudentBlogs",
url: "blogs/student/{name}",
defaults: new { controller = "Student", action="Index" },
namespaces: new string[] { "Website.Areas.Blogs.Controllers" }
);
..and perhaps Website.Areas.Admin.Controllers for the one in the admin area.
We have a route config like so:
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "About",
url: "{controller}/{action}/{aboutId}",
defaults: new { controller = "Home", action = "About" }
);
routes.MapRoute(
name: "Contact",
url: "{controller}/{action}/{contactId}",
defaults: new { controller = "Home", action = "Contact" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
You will notice that there are two routes that have one mandatory extra parameter. The routes About and Contact.
In our app we have two urls
www.myapp.com/Home/About/2 which works fine.
But when we navigate our browser to www.myapp.com/Home/Contact/5 we get the dreaded routing exception:
The parameters dictionary contains a null entry for parameter 'contactId' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult Contact(Int32)' in 'RoutingTest.Controllers.HomeController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
If we change the sequence of the routing so that it looks like so:
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Contact",
url: "{controller}/{action}/{contactId}",
defaults: new { controller = "Home", action = "Contact" }
);
routes.MapRoute(
name: "About",
url: "{controller}/{action}/{aboutId}",
defaults: new { controller = "Home", action = "About" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Then the Contact url works but the About url does not.
The HomeController looks like this:
public class HomeController : Controller {
public ActionResult Index() {
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
return View();
}
public ActionResult About(int aboutId) {
ViewBag.Message = "Your app description page.";
return View();
}
public ActionResult Contact(int contactId) {
ViewBag.Message = "Your contact page.";
return View();
}
}
What this seems to imply is that two routings cannot have the same number of parameters regardless of the name of the Controller Action. If the two controller actions have a parameter with the same name, then all works fine. I know I can start doing very hacky things to work around this problem such as calling all parameters the same name or giving the actions meaningless parameters to change the number of parameters but I would actually like to know what is happening under the hood.
How do I solve this problem?
The root (no pun intended) of your issue ISN'T that routes can't have the same number of params. They can. The issue is that the routing engine will select the first route that matches the incoming request. Your routes are only different by the defaults, and pattern matching-wise they are exactly the same. So in each and every case you should be hitting the Contact route.
It looks like you are trying to have different routes based on the action. Which I can't actually see why you NEED.
You CAN use the following for that effect.
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Contact",
url: "Home/Contact/{contactId}"
);
routes.MapRoute(
name: "About",
url: "Home/About/{aboutId}"
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
However. I HIGHLY recommend against this approach, as your "default" route would be the Contact route. This means that (under Razor) the #Html.ActionLink() and related methods will be...wrong.
Honestly, it should just work perfectly if you actually just use...
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 }
);
}
Specify controller and/or action in a route explicitly to allow routing to pick correct route:
routes.MapRoute(
name: "About",
url: "{controller}/About/{aboutId}",
defaults: new { controller = "Home", action = "About" }
);
You also can add constraints to parameters to distinguish between routes, but it looks like in your case both actions have the same integer parameter.
Couldn't you just delete your custom routes and just reuse the {id} parameter (turn {contactId} and {aboutId} into just "id" in your action code)?
public class HomeController : Controller {
public ActionResult Index() {
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
return View();
}
public ActionResult About(int id) {
int aboutId = id;
ViewBag.Message = "Your app description page.";
return View();
}
public ActionResult Contact(int id) {
int contactId = id;
ViewBag.Message = "Your contact page.";
return View();
}
}