MVC - Passing path to action in controller - asp.net-mvc

Fairly new to MVC, I would like the URLs of my article pages to be like this:-
http://www.example.com/article1
http://www.example.com/article2
http://www.example.com/article3
How can I set up the routing such that whenever someone types in the above it calls an action called article in the controller and passes the path to it?
I tried something like this but to no avail: -
routes.MapRoute(
name: "article",
url: "{article}",
defaults: new { controller = "Home", action = "article" }
);

One solution is to add multiple routes.
routes.MapRoute(
name: "article1",
url: "article1",
defaults: new { controller = "<YourControllerName>", action = "article1" }
);
routes.MapRoute(
name: "article2",
url: "article2",
defaults: new { controller = "<YourControllerName>", action = "article2" }
);
Edit:
From OP's comment, it is understood that there would be 'n' number of articles(urls). To deal with that, we can create a custom route handler.
Step 1: Create a new custom route handler inheriting from MvcRouteHandler
public class CustomRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var controller = requestContext.RouteData.Values["controller"].ToString();
requestContext.RouteData.Values["controller"] = "Home";
requestContext.RouteData.Values["action"] = "Index";
requestContext.RouteData.Values["articleId"] = controller;
return base.GetHttpHandler(requestContext);
}
}
Step 2: Register the new route. Make sure to add this route before default route.
routes.Add("Article", new Route("{controller}", new CustomRouteHandler()));
In the given CustomRouteHandler class, the Controller and Action is hard coded with "Home" and "Index" respectively. You can change that to your own controller and action name. Also you would see a "articleId" setting to RouteData.Values. With that setting, you would get the articleId as a parameter in your Action method.
public ActionResult Index(string articleId)
{
return View();
}
After all the changes, for the url http://www.example.com/article1, the Index() method of HomeController gets invoked with articleId set to 'article1'.
Similarly for http://www.example.com/article2, the Index() method gets invoked with parameter articleId set to 'article2'.

Related

Default Route with parameters

I have created a controller and I don't want my default Action or View to be named Index. I created Action in TopicsController as below
[ActionName("Index")]
public ActionResult Topics()
{
var topic = new Topic();
return View("Topics", topic.GetTopics());
}
and it mached to URL xyz.com/Topics.
I tried to apply same philosophy to another controller, named, ModulesController but now I have got parameter.
[ActionName("Index")]
public ActionResult Modules(string id)
{
var topic = new Topic();
return View("Modules", topic.GetTopics());
}
but now it is saying
The resource cannot be found.
what I can do so that this action matches URL like xyz.com/Modules/aaaa?
To access the Url xyz.com/Modules/aaaa change the Action name for the Modules action to aaaa like this:
[ActionName("aaaa")]
public ActionResult Modules(string id)
{
var topic = new Topic();
return View("Modules", topic.GetTopics());
}
FYI - It would be better to avoid naming each action with the ActionName filter. At some point it would become difficult to manage. Instead manage the routes in the RouteConfig like this:
routes.MapRoute(
name: "Modules",
url: "{controller}/{action}/{id}",
defaults: new { controller="Modules", action="Modules", id=UrlParameter.Optional }
);
The following Urls will work for the above route:
xyz.com/Modules/aaaa
xyz.com/Modules/aaaa/123
xyz.com/Modules/aaaa?id=123
Update:
If you want 'aaaa' to be the parameter and want to access the action with xyz.com/Modules/aaaa (where 'aaaa' will be bound as the value to the Id variable) then add the following Route to the route table:
routes.MapRoute(
name: "Modules",
url: "Modules/{id}",
defaults: new { controller="Modules", action="Modules", id=UrlParameter.Optional }
);
Note the value of the Url above.

Why does #Html.ActionLink not use RouteData

I have the simplest setup:
An empty asp.net MVC application with one controller:
public class HomeController : Controller
{
public ActionResult Edit(int id)
{
return View();
}
public ActionResult Commit(int id)
{
return View();
}
}
My Edit.cshtml has a call to ActionLink() like so:
#Html.ActionLink("Commit Data", "Commit")
If I now access the Edit-Action through "/Home/Edit/2" I would expect that the rendered link directs the user to "/Home/Commit/2".
It does not :( ... The link is created to "Home/Commit", completely disregarding the current RouteData entries.
I am using the default routing configuration (have not added any routes).
One way to fix this would be to add an explicit route for both actions:
routes.MapRoute(
name: null,
url: "Home/Edit/{id}",
defaults: new { controller = "Home", action = "Edit" }
);
routes.MapRoute(
name: null,
url: "Home/Commit/{id}",
defaults: new { controller = "Home", action = "Commit" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
This works - but I really dont want to explicitly define every single route in the app - if I am using the "default" pattern...
The second solution would be to just add the routing-values manually like so:
#Html.ActionLink("Commit Data", "Commit", "Home", new {id = Model.Id})
But this also seems not right - ActionLink SHOULD use the current routing information, should it not?
What am I missing?
Ok, in case someone else is wondering the same thing - or just wants to have a solution that works...
I simply created my own #ActionLink() helper method on my custom ViewPage base class:
protected MvcHtmlString ActionLink(string linkText, string actionName)
{
var routeData = ViewContext.RequestContext.RouteData;
var id = routeData.Values["id"];
if (id != null)
return Html.ActionLink(linkText, actionName, new {id = id});
return Html.ActionLink(linkText, actionName);
}
This is exactly what I wanted. Now I can call
#ActionLink("Commit", "Commit")
and when I'm in the context of something with an id, the link will point to the appropriate route for this Id.

Asp.Net MVC 4 routing and link generation

In ASP.NET MVC 4 I wonder about the behavior, how links are generated for me.
Imagine a simple controller with 3 actions, each taking an integer parameter "requestId", for example:
public class HomeController : Controller
{
public ActionResult Index(int requestId)
{
return View();
}
public ActionResult About(int requestId)
{
return View();
}
public ActionResult Contact(int requestId)
{
return View();
}
}
and this registered route (before the default route):
routes.MapRoute(
name: "Testroute",
url: "home/{action}/{requestId}",
defaults: new { controller = "Home", action = "Index" }
);
I call my index-view using http://localhost:123/home/index/8
On this view, I render links for the other two actions:
#Html.ActionLink("LinkText1", "About")
#Html.ActionLink("LinkText2", "Contact")
Now I expect MVC to render this links including the current route-value for "requestId", like this:
http://localhost:123/home/about/8
http://localhost:123/home/contact/8
But i get these links (without the paramter):
http://localhost:123/home/about
http://localhost:123/home/contact
...but not for the index-action if i would specify one:
#Html.ActionLink("LinkText3", "Index")
What I want to avoid is to explicitly specify the parameters in this manner:
#Html.ActionLink("LinkText1", "Contact", new { requestId = ViewContext.RouteData.Values["requestId"] })
When I move the requestId parameter before the action paramter it works like I expect it, but I don't want to move it:
routes.MapRoute(
name: "Testroute",
url: "home/{requestId}/{action}",
defaults: new { controller = "Home", action = "Index" }
);
Can someone explain me this behavior? How can I get this to work without specifying the parameter explicitly?
InController:
Replace the int to nullable int
For Routing:
set requestId as optional in routing
routes.MapRoute(
name: "Testroute",
url: "home/{action}/{requestId}",
defaults: new { controller = "Home", action = "Index" ,requestId=RouteParameter.Optional }
);

Use MVC routing to override controller name when presented with specific action

I'm attempting to specify a route in MVC4 that ignores a controller name if presented with a specific action name. For example.
mysite.com/AnyControllerNameHere/SpecificAction - Would let me specify the controller and action to use while
mysite.com/AnyControllerNameHere/NotSpecificAction - Would take me the the AnyControllerNameHere Controller and NotSpecificAction method like the MVC default.
I've attempted to code something up but it doesn't seem to do what I want. Full route config is just the MVC defaults plus me attempting to do this so...
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"SpecificAction",
"{controller}/SpecificAction",
new { controller = "Home", action = "SpecificAction" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
When you write this:
routes.MapRoute(
"SpecificAction",
"{controller}/SpecificAction",
new { controller = "Home", action = "SpecificAction" });
you intend to override the controller. However, the third argument cannot be used to override parameters. It merely provides the defaults for any parameters that aren't already provided by the URL.
So what you need is a route template which doesn't set the controller parameter, so that the default takes effect:
routes.MapRoute(
name: "SpecificAction",
url: "{ignored}/SpecificAction",
defaults: new { controller = "Home", action = "SpecificAction" });
The easiest way to do this would be to use a Route constraint.
routes.MapRoute(
"SpecificAction",
"{anyController}/{specificAction}",
new {controller="Home", action="SpecificAction"},
new {specificAction = "SpecificAction" }
);
The key here is that you don't using the default {controller} and {action} but rather something different (they could be anything), but use the route constraint to lock it to the SpecificAction action.
Wim's method works as well.
I don't believe that this can be easily achieved with MVC's built-in routing. You could consider writing a custom route handler or, alternatively, do something like the following to have the default route do the job for you:
public class BaseController : Controller
{
public ActionResult SpecificAction()
{
return RedirectToAction("SpecificAction", "Home");
}
}
public class SomeController : BaseController
{
/* ... */
}
public class HomeController : BaseController
{
public override ActionResult SpecificAction()
{
/* Do whatever */
}
}

Why are these two Actions considered ambiguous despite the parameters being different?

The current request for action 'Index' on controller type 'UsersController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult PostUser(Models.SimpleUser) on type Controllers.UsersController
System.Web.Mvc.ActionResult PostUser(Int32, Models.SimpleUser) on type Controllers.UsersController
Is happening when I try to POST website.com/users/ with form values.
When there is NO ID (website.com/users/) I want it to create a new user, when there IS an ID (/users/51 for example) I want it to update that user, so how can I make it tell the difference between these two Actions?
Here are the two actions:
[HttpPost]
[ActionName("Index")]
public ActionResult PostUser(SimpleUser u)
{
return Content("User to be created...");
}
[HttpPost]
[ActionName("Index")]
public ActionResult PostUser(int id, SimpleUser u)
{
return Content("User to be updated...");
}
Here is the MapRoute:
routes.MapRoute(
"Users",
"users/{id}",
new { controller = "Users", action = "Index" }
);
The main problem is there is some ambiguity between your two action methods, as model binding doesn't help decern the routing configuration. Your current routing simply points to index method.
You can still have the same URL, but it would be helpful to name your actions differently and then apply some new routes.
[HttpPost]
public ActionResult Create(SimpleUser u)
{
return Content("User to be created...");
}
[HttpPost]
public ActionResult Edit(int id, SimpleUser u)
{
return Content("User to be updated...");
}
And then in your routing try
routes.MapRoute(
"UserEdit",
"users/{id}",
new { controller = "Users", action = "Edit",
httpMethod = new HttpMethodConstraint("POST") });
routes.MapRoute(
"UserCreate",
"users",
new { controller = "Users", action = "Create",
httpMethod = new HttpMethodConstraint("POST") });
The routes will be constrained to POST events only, which means you can still add some routing for your GET methods on the same route. Such as for a list or something.
You must disambiguate your methods by having two different action names.
What you can do to have a RESTful API is to create your own RouteHandler class which will change the routing, dependent on whether or not the {id} value is present. The RouteHandler class is simple:
using System.Web.Mvc;
using System.Web.Routing;
public class MyRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var routeData = requestContext.RouteData;
if (routeData.Values("id").ToString() == "0" /* our default invalid value */ )
{
var action = routeData.Values("action");
routeData.Values("action") = action + "Add";
}
var handler = new MvcHandler(requestContext);
return handler;
}
}
Next, modify RegisterRoutes() in Global.asax.cs as follows:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
AreaRegistration.RegisterAllAreas();
routes.Add("Default", new Route("{controller}/{action}/{id}",
new RouteValueDictionary(
new { controller = "Home",
action = "Index",
id = "0" /* our default invalid value */ }),
new MyRouteHandler()));
}
Finally, change the ActionName attribute for your Add method to "IndexAdd". When the request comes in without an id, the default invalid value will be supplied, which will alert your RouteHandler to change the method to your "Add" method.
counsellorben
It would be better if you consider using Create and Edit actions to achieve what you want. Generally index can be used to list all the users(in your case).
About the problem you are facing, it is arising because there is no route which does not take any id as parameter.

Resources