I am learning ASP.NET MVC and URL routing seems to be a very tough chapter in whole MVC. I read many blog post and many other SO questions but none of them helped the way to understand every aspect of routing.
I would like to have a URL likes www.sitename.com/controller/action/username/userid. How can I do it with MVC routing? A detailed answer to cover every aspect of it would be very helpful.
1. Using Traditional convention-based routing
Update your route registrations to include this new url pattern. Make sure you do this before registering the default route registration.
So in your RouteConfig file
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{userName}/{userId}",
defaults: new { controller = "Home", action = "Details" }
);
routes.MapRoute(
name: "Default2",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
This will route all the requests matching that url pattern to the corresponding controller's action method (with the 2 parameters). This means, If you have 2 controllers, both having an action method with userName and userId params, based on your controller & action method name in the request url, the corresponding method will be returned.
2. Attribute Routing
public class HomeController : Controller
{
[Route("Home/Details/{userName}/{userId}")]
public ActionResult Details(string userName,int userId)
{
return Content("Details : "+userName + userId);
}
}
Here we are registering the new route which says when the request url is Home/Details/{userName}/{userId}, return the response from Details action method of Home controller. ( This is very specific as we define specific controller name and action name)
This is an official article on ASP.NET MVC Attribute Routing which for most scenarios is sufficient. Sometimes in advanced scenarios you need to configure routes in the Global.asax/Bootstrapper.
Basically it would look like:
[RoutePrefix("Controller")]
public class Controller : Controller
{
[Route("action/{username}/{userid:int}")]
public ActionResult Action(string username, int userid){}
}
I did that from memory so it might not be exactly right, please refer to the documentation in that link and what's on MSDN.
Related
Controller
[HttpGet]
[Route("find-a-doctor")]
public ActionResult FindADoctor()
{
ViewData["sList"] = specialities ;
return View();
}
Route.Config
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}",
defaults: new { controller = "UserPanelFinADoctor", action = "FindADoctor" },
namespaces: new string[] { "xyz.Controllers" }
);
}
This is the result when I debug code. It says "The resource cannot be found"
I resarched about it , and found this on microsot docs.
Actions are either conventionally routed or attribute routed. Placing
a route on the controller or the action makes it attribute routed.
Actions that define attribute routes cannot be reached through the
conventional routes and vice-versa. Any route attribute on the
controller makes all actions in the controller attribute routed.
So in your example .Since you have registered the route to your action under attribute based routing. Convention routing wont work.
Either of the one should be used, not both.
Link to article - https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-5.0#mixed-routing-attribute-routing-vs-conventional-routing
I know about routing in MVC. I added a new MapRoute under RegisterRoute method in RouteConfig.cs class and successfully called my function with the URL http://localhost:53363/package/PackageDetail/mypackage/5.
However, my question is do i have to add different Map Routes for every method or is there any better way ? Like in PackageController class you can see i have two methods one methods takes PackageId and PackageName and the other takes only PackageId. So do i have to register different Map Routes or not ?
RouteConfig
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Package",
url: "Package/PackageDetail/{packageName}/{packageId}",
defaults: new { controller = "Package", action = "PackageDetail", packageName = UrlParameter.Optional, packageId = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
PackageController.cs :
[HttpGet]
public ActionResult PackageListing(int packageId = 0)
{
return View();
}
[HttpGet]
public ActionResult PackageDetail(string packageName = "", int packageId = 0)
{
return View();
}
Despite the fact that Muhammed's answer will work, it is very repetitive, especially if you're using the same style of routes for multiple types.
There are a few things to consider before deciding upon a single approach to routing. The main one is why have both the name and ID in the route? If you want a more SEO friendly URL structure, don't bother with the ID at all.
If you have multiple products within the same type that have identical names, then there's no point in including the name as part of the URL since that won't get a user where they want to go by itself. In that event, just leave the original route.
However, if you have several different controllers (or actions) with a similar name/id structure for the routes, you'll be far better served with making your custom route more generic.
routes.MapRoute(
name: "NameAndId",
url: "{controller}/{action}/{name}/{id:int}",
defaults: new
{
controller = "Package",
action = "PackageDetail",
name = UrlParameter.Optional,
id = UrlParameter.Optional
});
Keep this above the default route, and this will redirect not just
/Package/PackageDetail/Deluxe/5
but also allow you to have stuff like this:
/Meals/Menu/Dinner/3
That may not necessarily be applicable for you in this project, but since you're learning MVC, this is a good skill to pick up. The more generic you're able to maintain your route definitions, the less you'll need to repeat it. Of course, if this is a one-time special route, there's nothing wrong with using the attributes.
Also to answer your final question, you do not need to create another custom route, because your PackageListing method will be routed through the default route that was provided when you created your project.
If you want to override default route url and generate custom url then you need to register route in route config file.
You can pass Package name and package Id as below.
http://sitename/Package/PackageListing?packageId=1
http://sitename/Package/PackageDetail?packageName=packagename&packageId=1
but if you want to generate URL as below than you need to add route in route.config file.
http://sitename/Package/PackageListing/1
http://sitename/Package/PackageDetail/packageName/1
I am trying to configure routing with both Conventional & Attribute based.
If I just use the default Conventional route included with MVC everything works. but if I add this Route attribute, I get a 404.
Here is the GET request URL: http://localhost:52386/Home/SimpleSearch?searchTerms=test&dateRange=0
Here is my RouteAttributes in Code:
[RoutePrefix("Home")]
public class HomeController : Controller
{
[Route("SimpleSearch/{searchTerms}/{dateRange}/{page?}")]
[HttpGet]
public ActionResult SimpleSearch(string searchTerms, DateRangeEnum dateRange, int page = 1)
{
//Code here
}
}
Also the Route Config looks like this:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
//Default
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
I don't see what is wrong with this RouteAttribute, but even if something is wrong with it, why doesnt it fall back onto the default Conventional Route and work?
With the attribute route definition, you explicitly specified the route pattern to be
Home/SimpleSearch/{searchTerms}/{dateRange}/{page?}
So you should try to access your action method with same url pattern.
This should work.
http://localhost:52386/Home/SimpleSearch/test/0
and Model binder will be able to map "test" to searchTerms parameter and 0 to dateRange parameter.
Your conventional (explicitly using querystring) will not work when you have an attribute route with a different pattern
Reddit links are normally like this:
https://www.reddit.com/r/<subreddit>/<topic>
meaning that, the subreddit can be anything depending on how the user created it.
Usually on ASP MVC, We can do it like this:
local/controller/action?subreddit=subname&topic=topicname
but what if I want it to be something like this:
local/controller/action/subname/topicname ?
The keyword for this feature is attribute routing in ASP.NET MVC. There is a lot of information availbale in blogs etc.
With the Route-Annotation you can decorate your action and define a mapping between URL parts and parameters for the action call.
public class ExampleController : Controller
{
[Route("r/{subreddit}/{topic}")]
public ActionResult Topic(string subreddit, string topic)
{
//Logic goes here
}
}
Furthermore the attribute routing has to be activted in the RouteConfig.cs with routes.MapMvcAttributeRoutes(); like
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
Then you can call the Topic-Action of the ExampleController by http://localhost:PORT/r/reddit/topic.
I would like to suppress the controller from the route, and it worked fine using this:
routes.MapRoute(
name: "HomePages",
url: "{action}",
defaults: new { controller = "Home", action = "Index" }
);
The problem is when doing the same for a different controller like "Account", the first option only will take effect :
routes.MapRoute(
name: "LoginRoute",
url: "{action}",
defaults: new { controller = "Account", action = "Login" }
);
My objective is to hide the controller from the route, so i can directly access mysite.com/login and mysite.com/index, how to achieve that when login is under Account controller and index is under Home controller?
How to specify the second option for the Account actions, and keep the first for the Home actions?
Have you thought about using the Routing attributes e.g:
[Route("{getId:int}")]
public ActionResult Show(int getId) { ... }
you can use this in conjunction with the old way of routing. You do need to explicitly set this functionality in your config:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
}
}
I have found the routing Attributes makes it very easy to set good routes on my controller methods. This also bleeds into web api and RESTFul stuff as well.