I have actions
public virtual ActionResult Show(string userId)
and
public virtual ActionResult Show(int groupId)
In Global.asax I have
routes.MapRoute(
"Group_Default",
"{controller}/{action}/{groupId}",
MVC.Groups.Show()
);
routes.MapRoute(
"UserProfile_Default",
"{controller}/{action}/{userId}",
MVC.Profile.Show()
);
Now when I request for group/show/... it works fine. But when I call Profile/Show/... parameter is null. But if I remove UserProfile_Default then both works but profile URL contains question mark for parameter (and I want it clean like .../profile/show/5678)
It seams that somehow one route block another.
Try these instead:
routes.MapRoute(
"Group_Default",
"Group/{action}/{groupId}",
new { controller = "Group" }
);
routes.MapRoute(
"UserProfile_Default",
"Profile/{action}/{userId}",
new { controller = "Profile" }
);
For future reference, the route debugger is a really good tool to see exactly what's going on with your routing and which URLs are hitting what actions: http://haacked.com/archive/2008/03/13/url-routing-debugger.aspx
Related
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
Is there a way to use Convention and Attribute Routing together?
I want to call an action method with the real name of method and controller when I defined the attribute routing.
The mapping method is calling at startup:
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 }
);
}
Here is the controller:
[RoutePrefix("d")]
[Route("{action=index}")]
public class DefaultController : Controller
{
[Route]
public ActionResult Index()
{
return View();
}
[Route("f")]
public ActionResult Foo()
{
return View();
}
}
I can reach to Foo action method with /d/f url. But when I try this url: /Default/Foo, the 404 error occurs. Actually it throws the action not found exception which it says like A public action method 'Foo' was not found on controller 'Namespace...DefaultController'.
I checked the source code of asp.net mvc and I saw these lines:
if (controllerContext.RouteData.HasDirectRouteMatch())
{
////////
}
else
{
ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
return actionDescriptor;
}
It checks if there is a direct route or not, which the /Default/Foo route is not a direct route so it should act as convention routing which is registered at startup as {controller}/{action}/{id}. But it doesn't find the action with controllerDescriptor.FindAction method and it throws the exception.
Is this a bug or cant I use both routing methods together? Or are there any workaround to use both?
Edit
I debugged into mvc source code, and I saw these lines:
namespace System.Web.Mvc
{
// Common base class for Async and Sync action selectors
internal abstract class ActionMethodSelectorBase
{
private StandardRouteActionMethodCache _standardRouteCache;
protected void Initialize(Type controllerType)
{
ControllerType = controllerType;
var allMethods = ControllerType.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public);
ActionMethods = Array.FindAll(allMethods, IsValidActionMethod);
// The attribute routing mapper will remove methods from this set as they are mapped.
// The lookup tables are initialized lazily to ensure that direct routing's changes are respected.
StandardRouteMethods = new HashSet<MethodInfo>(ActionMethods);
}
The last comments about attribute routing explains why this problem happens. The Attribute routing removes StandardRouteMethods when you call MapMvcAttributeRoutes.
I'm still seeking a workaround.
I want to call an action method with the real name of method and controller when I defined the attribute routing.
It seems like you are going pretty far in the wrong direction if all you want is to call the application knowing the controller and action names. If you have the name of the controller and action (area and other route values) you can use them to get the URL to use quite easily.
var url = Url.Action("Index", "Default");
// returns /d/f
If this works for your usage, then you don't have to have a duplicate set of route mappings at all.
NOTE: Creating 2 URLs to the same page is not SEO friendly (unless you use the canonical tag). In fact, many question here on SO are about removing the duplicate routes from the route table. It seems like they did us all a favor by removing the duplicate routes so we don't have to ignore them all manually.
Alternatives
One possible workaround is to add 2 route attributes to the action method (I don't believe it can be done without removing the RoutePrefix from the controller).
[Route("d/f")]
[Route("Default/Foo")]
public ActionResult Foo()
{
return View();
}
Another possible workaround - don't use attribute routing for all or part of your application. Attribute routing only supports a subset of convention-based routing features, but is useful in some specific scenarios. This does not appear to be one of those scenarios.
I have a controller called News in my WebAPI project, I have two default actions called Get that handled the following URL's:
/api/News <- this returns a list of news
/api/News/123 <- this returns a specific news item by id.
All straightforward so far and obviously the default route handles these scenarios. I next want to have a URL that looks like this:
/api/News/123/Artists <- will return all artists related to the specified news item.
Now I am fairly news to ASP.Net MVC and WebAPI so this is the first time I have had to deal with routing. I have modified my default route to look like this:
routes.MapRoute(
name: "Default",
url: "{controller}/{id}/{action}",
defaults: new { controller = "News", action = "Get", id = UrlParameter.Optional }
So here I have moved the {action} to the end of the URL and I have added a Artists method to my News controller. This still works with the first two scenarios but returns a 404 for the third scenario.
Obviously the routing isn't working for /api/News/123/Artists but I have no idea why.
I can't seem to find any examples of people using WebAPI like this which makes me think I am doing something fundamentally wrong.
Any help would be appreciated.
The issue is, that you are trying to acces Web API but mapping the ASP.NET MVC
this is a mapping you need:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{action}",
defaults: new {controller = "News", action = "Get", id = RouteParameter.Optional}
);
And it should be done in the App_Start \ WebApiConfig (if using the default template settings)
Example of the methods (in your news API controller):
// GET api/values/5
public string Get(int id)
{
return "value " + id;
}
// GET api/values/5
[HttpGet]
public string Artist(int id)
{
return "artist " + id;
}
The AttributeRouting should be a good solution. It can be installed by Nuget, and the document is here.
Some examples
public class SampleController : Controller
{
[GET("Sample")]
public ActionResult Index() { /* ... */ }
[POST("Sample")]
public ActionResult Create() { /* ... */ }
[PUT("Sample/{id}")]
public ActionResult Update(int id) { /* ... */ }
[DELETE("Sample/{id}")]
public string Destroy(int id) { /* ... */ }
[Route("Sample/Any-Method-Will-Do")]
public string Wildman() { /* ... */ }
[GET("", ActionPrecedence = 1)]
[GET("Posts")]
[GET("Posts/Index")]
public ActionResult Index() { /* ... */ }
[GET("Demographics/{state=MT}/{city=Missoula}")]
public ActionResult Index(string state, string city) { /* ... */ }
}
It works very well about custom routing.
Update
In asp.net WebApi 2, AttributeRouting is included inside by native. It has some history, the first version, asp.net WebApi 1, is weak about routing annotations.
And then, asp.net WebApi 2 is released, the AttributeRouting is included by native. So, that open project is not maintained anymore, said in GitHub page.
In microsoft blog, the section Independent Developer Profile – Tim McCall – Attribute Routing in MVC and Web API 2 said about the history too.
In routing Action is the action name on the method that you want to route to .That action name should be in the attribute used on the method.
[HttpGet]
[ActionName("CustomAction")]
public HttpResponseMessage MyNewsFeed(Some params)
{ return Request.CreateResponse(); }
Now your route should look like this
routes.MapRoute(
name: "Default",
url: "{controller}/{id}/{action}",
defaults: new { controller = "News", action = "CustomAction", id = UrlParameter.Optional
Let me know if this helps.
If an action exists on a controller, does asp.net-mvc route to that action before attempting to process any custom mapped routes?
Example.
Say I have the following controller
public class ShopController : Controller
{
public ActionResult Shop(Category category)
{
// returns some result
}
public ActionResult CartItemCount()
{
// returns some result
}
}
And I have registered the following route in my route collection:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"Shop",
"Shop/{category}",
new { controller = "Shop", action = "Shop", category = UrlParameter.Optional } // Parameter defaults
);
}
What would happen if I had the following URL?
http://www.example.com/Shop/CartItemCount
Edit
I confused myself by thinking that the name of the mapped route was related to how it was processed. That is not the case, when it comes to url matching the name of the route does not matter.
As it turns out I had another route defined just above the one I gave in the example. This route, though named differently, was getting matched. I didn't even think to check it because, as I said, I thought the name given to a route somehow impacted the matching.
The routes decide which action to use. If there are no routes defined, you wont hit an action even if it exists. Install RouteDebugger and fire off your url. It will tell you ALL routes that it matches and which one it has actually used.
I am working on an ASP.NET MVC application. For some reason, everytime I think I understand routing, something pops up that I don't understand. Currently, I have two routes that I can't seem to figure out. My directory structure looks like the following
- Views
- Internal
- Profile
- Index.cshtml
- Input
- Page1.cshtml
In my global.asax.cs file, I have added the following mappings:
routes.MapRoute(
"UserProfileInfo",
"{controller}/profile",
new { controller = "Internal", action = "UserProfileInfo" }
);
routes.MapRoute(
"Page1",
"{controller}/input/page1",
new { controller = "Internal", action = "Page1" }
);
In MyController, I have the following:
public ActionResult UserProfileInfo()
{
return View("~/Views/internal/profile/Index.cshtml");
}
public ActionResult Page1()
{
return View("~/Views/internal/input/Page1.cshtml");
}
I want to store my actions in a single controller. I thought I had everything setup properly. But I continue to get a 404. What am I doing wrong?
Remove the "Controller" suffix from the controller name in your calls to MapRoute to create a mapping to a class called InternalController. The Controller suffix is appended by the framework when looking for a matching implementation. e.g.:
routes.MapRoute(
"UserProfileInfo",
"{controller}/profile",
new { controller = "Internal", action = "UserProfileInfo" }
);