ASP.NET MVC ActionLink not selecting correct controller - c#

Question background:
I'm trying to pass an variable - in this case an int 'productId' variable' in the url to a controller and action method specified in the ActionLink method.
The issue:
My routes are set as follows:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Login", action = "Login", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "ProductDetailHandler",
url: "{controller}/{action}/{productId}",
defaults: new { controller = "Product", action = "ProductDetail", productId = UrlParameter.Optional }
);
My 'ActionLink' in my 'Products.cshtml' view - which is returned by a controller called 'HomePageController' is set as follows:
#Html.ActionLink("Product", "ProductDetail", new { productId = (ViewBag.data as List<LoginTest.Models.HomePageItem>).First().Id })
The controller that receives the passed 'productId' along with its action method is set as follows:
public class ProductController : Controller
{
public ActionResult ProductDetail(int productId)
{
//logic
return View();
}
}
This is the issue, when looking at the URL it is shown to be redirecting to the 'HomePage' controller:
If someone could tell me why my ActionLink is not going to the Product controller that would be great.
EDIT:
This is the 'Homepage' view that I click a button to redirect me to 'product/productDetail/productId'
My route now just features the 'Default example':
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Login", action = "Login", id = UrlParameter.Optional }
);
The Action Link is now:
#Html.ActionLink("Product", "ProductDetail", "Product", new { id = (ViewBag.data as List<LoginTest.Models.HomePageItem>).First().Id })
The 'Product/ProductDetail' controller and action method now looks like:
public class ProductController : Controller
{
public ActionResult ProductDetail(int id)
{
string hold;
return View();
}
}
This still is giving me the the incorrect URL, as shown, note the 'productId' is now showing as 'length'?

Since the link is on a page rendered by HomePageController the default is to use that controller in the route. You need to use the overload that accepts a controller name
#Html.ActionLink("Your link text", "ProductDetail", "Product", new { id = 1 }, null)
As a side note, your original route table would have created /Product/ProductDetail?productId =1 with this overload because it matches the default route which is the first route in your table (the order of routes is important). In order to have /Product/ProductDetail/1, either reverse the order of the routes or just change the parameter of ProductDetail to int id rather than int productId

Make sure you are using an overload that has controllerName in it, as shown in the following screenshot.
When I remove routeValues: null, it uses a different overlaod which has routeValue as third paramater.

Try this:
routes.MapRoute(
name: "ProductDetailHandler",
url: "Product/{action}/{productId}",
defaults: new { controller = "Product", action = "ProductDetail", productId = UrlParameter.Optional }
);
routes.MapRoute
(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Not sure what you are trying to do with the Login controller. Maybe you can put log-in on your Home page or redirect the Home/Index to Login.
Also you can specify the default namespace if it doesn't find your controller:
routes.MapRoute(
name: "ProductDetailHandler",
url: "Product/{action}/{productId}",
defaults: new { controller = "Product", action = "ProductDetail", productId = UrlParameter.Optional },
new string[] { "MyProject.Controllers" }
);
routes.MapRoute
(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new string[] { "MyProject.Controllers" }
);

Related

MVC Uri Parameter is null

When attempting to pass a parameter to a simple controller I receive the parameter as always being null.
Controller
public class GetOrgController : Controller
{
private DirectoryEntities de;
public getOrgController()
{
de = new DirectoryEntities();
}
// GET: getOrg
public ActionResult Index(string district)
{
getorg_Result org = de.getorg(district).FirstOrDefault();
return View(org);
}
}
When I try to navigate to that url with a parameter localhost:660366/GetOrg/Index/D123 the district variable is always null.
I thought maybe it had to do something with the default RouteConfig. When I put a new value ahead of the default route config it worked! However now, whenever I try to launch the application it goes to the GetOrgController first. What happens when I want a new controller with different parameters? I have to do this every time? Here is my new RouteConfig with the new entry.
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "GetOrg",
url: "{controller}/{action}/{district}",
defaults: new { controller = "GetOrg", action = "Index", district = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
I actually started doing Rest initially in Web Api 2, but the view-model attracted me to MVC. It is a lot easier specifiying above the method the routes/parameters I wanted like below. Is this possible in MVC?
WebApi2
[Route("Index/{district}")]
public ActionResult Index(string district)
{
getorg_Result orgs = de.getorg(district).FirstOrDefault();
return View(orgs);
}
The above method seems so much cleaner than having to rely on the order of controllers being correct in the RouteConfig
These two routes literally make no sense. The first one says, for ANY controller with ANY method default to GetOrgController. That isn't what you want because now id won't work for all the other controllers and the second MapRoute is close to a duplicate.
routes.MapRoute(
name: "GetOrg",
url: "{controller}/{action}/{district}",
defaults: new { controller = "GetOrg", action = "Index", district = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
What you really should be doing is saying for any call starting with GetOrg then call GetOrgController for any method....
routes.MapRoute(
name: "GetOrg",
url: "GetOrg/{action}/{district}",
defaults: new { controller = "GetOrg", action = "Index", district = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
thats because both routs are generic thats why it hits the first route by default so you can remove UrlParameter.Optional so that the route should be a little specific
routes.MapRoute(
name: "GetOrg",
url: "{controller}/{action}/{district}",
defaults: new { controller = "GetOrg", action = "Index"}
);
but still you can use the default route for that purpose no need of additional route unless you want to use a custom url like random/234

How to remove Home from HomeController in Mvc

I have a HomeController and it has many Actions in it. I would like users to visit my actions without typing Home. Here is the Route below
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
I would like users not to enter controller name, which is Home in this case. How can I do that? Or is it mandatory?
You can add custom route before defult route like this:
routes.MapRoute(
"OnlyAction",
"{action}",
new { controller = "Home", action = "Index" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Solution 01 (attribute routing)
Add below line on the top of the other routes in RouteConfig
routes.MapMvcAttributeRoutes();
Then add an attribute route on top of each action as you want. (actions in the home controller in this case)
eg. Below code sample will remove "/Home" from http://site/Home/About and be available on http://site/About
[Route("About")]
public ActionResult About()
{
Solution 02 (using a Route constraint) [ Source ]
Add a new route mapping to RouteConfig as below. (Remember to add these specific routes before the default (generic) routes.
routes.MapRoute(
"Root",
"{action}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { isMethodInHomeController = new RootRouteConstraint<HomeController>() }
);
This will remove "Home" from all the actions (routes) of the Home controller
RootRouteConstraint class
public class RootRouteConstraint<T> : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
var rootMethodNames = typeof(T).GetMethods().Select(x => x.Name.ToLower());
return rootMethodNames.Contains(values["action"].ToString().ToLower());
}
}
Optional Info: This line (constraint) will make sure to apply this routing only for the HomeController
new { isMethodInHomeController = new RootRouteConstraint<HomeController>

ActionLink is showing query string instead of URL parameters

My question is very similar other questions. When using an ActionLink in MVC .Net 4.5, I am getting a query string for one parameter, instead of just a URL path. I tried the solution HERE, but it did not work.
CODE-
Inside RouteConfig.cs -
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "MyControllerRoute",
url: "{controller}/{action}/{id}/{description}",
defaults: new { controller = "MyController", action = "MyAction", id = UrlParameter.Optional, description = UrlParameter.Optional }
);
Inside HomeController.cs -
public ActionResult Index(){
--do stuff--
return View();
}
Inside MyController.cs -
public ActionResult Vote(int id, string description){
--do stuff--
return View();
}
Inside Index.cshtml
#Html.ActionLink(
"This is stuff",
"MyAction",
"MyController",
new { id = 123, description = "This-is-stuff" },
null)
GETTING THIS RESULT - (NOT WHAT I WANT)
This is stuff
DESIRED RESULT - (HOW CAN I GET THIS?)
This is stuff
You need to swap the order of the routes. I would also recommend that you use the controller name (and optionally the action name) in the url definition to prevent possible conflicts with other routes. In addition, only the last parameter can be marked as UrlParameter.Optional (otherwise if only one of the parameters were provided, the route would be ignored and the url would revert to using query string values). Your definitions should be (in order)
routes.MapRoute(
name: "MyControllerRoute",
url: "MyController/MyAction/{id}/{description}",
defaults: new { controller = "MyController", action = "MyAction" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

I cannot figure out RouteConfig.cs file

I am very new to MVC and trying to build my first website. I couldnt set my RouteConfig file properly. I have 2 rules that apply to different ActionResults. But, only one of them works properly. if GetProducts is above the GetProductByCode, then GetProducts works. If GetProductByCode is above the GetProducts, then GetProductByCode works. What am I doing wrong?
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "GetProducts",
url: "{controller}/{action}/{PageNo}",
defaults: new { controller = "Home", action = "GetProducts", PageNo = UrlParameter.Optional }
);
routes.MapRoute(
name: "GetProductByCode",
url: "{controller}/{action}/{ProductCode}",
defaults: new { controller = "Home", action = "GetProductByCode", ProductCode = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
My Solution is like below
routes.MapRoute(
name: "GetProducts",
url: "{controller}/GetProducts/{PageNo}",
defaults: new { controller = "Home", action = "GetProducts", PageNo = UrlParameter.Optional }
);
routes.MapRoute(
name: "GetProductByCode",
url: "{controller}/GetProductByCode/{ProductCode}",
defaults: new { controller = "Home", action = "GetProductByCode", ProductCode = UrlParameter.Optional }
);
If you look at the default route:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Then think of the portion after url: as a format or pattern:
{controller}
{action}
{id}
Your 3 URLs Home/GetProducts, Home/GetProductsByCode and Home/Index all match this pattern.
With the {action} portion being GetProducts, GetProductsByCode and Index respectively.
You would need to leverage routing if you wanted to map the parameter to a variables called PageNo or ProductCode in the Action, but in general you don't need routes for each possible combination. If your parameters in these actions are id then it will just work without you needing to create routes for each.
E.g.
public ActionResult GetProducts(int id)
{
// stuff
}
public ActionResult GetProductsByCode(string id)
{
// stuff
}
To have the parameter names, specify the controller and action explicitly:
routes.MapRoute(
name: "GetProducts",
url: "Home/GetProducts/{PageNo}",
defaults: new { controller = "Home", action = "GetProducts", PageNo = UrlParameter.Optional }
);
routes.MapRoute(
name: "GetProductByCode",
url: "Home/GetProductsByCode/{ProductCode}",
defaults: new { controller = "Home", action = "GetProductByCode", ProductCode = UrlParameter.Optional }
);
And
public ActionResult GetProducts(int PageNo)
{
// stuff
}
public ActionResult GetProductsByCode(string ProductCode)
{
// stuff
}
But in general, only define custom routes that differ from the normal {controller}/{action}/{id} pattern.
The default section of MapRoute means that if it can't find a controller and action that exists in your code base use these instead. It's a fallback, not the functionality driver.
All 3 routes are identical in that they contain 3 segments (the controller name, action name and an optional parameter) and which ever of the 3 are placed first will always be hit.
If you want GetProducts to be hit the you could modify the definition to
routes.MapRoute(
name: "GetProducts",
url: "Home/GetProducts/{PageNo}",
defaults: new { controller = "Home", action = "GetProducts", PageNo = UrlParameter.Optional }
);
although there seems no real point. If you just changed the name of the parameter in the GetProducts() and GetProductByCode() to id then the only route definition you require is the Default
There is not way for asp.net to understand if the last parameter is /{ProductCode}", or {ProductCode}", since the action is the same - so your URL looks the same,
Therefor only the first match is taken, a solution would be using a full query string since the /{id} is just a shorthand for id=5 in the query string

Routing to index with id in ASP.NET MVC 4

I'd like to maintain ASP.NET MVC 4's existing controller/action/id routing with default controller = Home and default action = Index, but also enable controller/id to route to the controller's index method as long as the second item is not a known action.
For example, given a controller Home with actions Index and Send:
/Home/Send -> controller's Send method
/Home -> controller's Index method
/Home/Send/xyz -> controller's Send method with id = xyz
/Home/abc -> controller's Index method with id = abc
However, if I define either route first, it hides the other one. How would I do this?
Do the specific one first before the default generic one. The order matters.
routes.MapRoute(name: "single", url: "{controller}/{id}",
defaults: new { controller = "Home", action = "Index" },
constraints: new { id = #"^[0-9]+$" });
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home",
action = "Index",
id = UrlParameter.Optional }
);
In case, that the list of your actions (e.g. Send) is well known, and their (action) names cannot be the same as some ID value, we can use our custom ConstraintImplementation:
public class MyRouteConstraint : IRouteConstraint
{
public readonly IList<string> KnownActions = new List<string>
{ "Send", "Find", ... }; // explicit action names
public bool Match(System.Web.HttpContextBase httpContext, Route route
, string parameterName, RouteValueDictionary values
, RouteDirection routeDirection)
{
// for now skip the Url generation
if (routeDirection.Equals(RouteDirection.UrlGeneration))
{
return false; // leave it on default
}
// try to find out our parameters
string action = values["action"].ToString();
string id = values["id"].ToString();
// id and action were provided?
var bothProvided = !(string.IsNullOrEmpty(action) || string.IsNullOrEmpty(id));
if (bothProvided)
{
return false; // leave it on default
}
var isKnownAction = KnownActions.Contains(action
, StringComparer.InvariantCultureIgnoreCase);
// action is known
if (isKnownAction)
{
return false; // leave it on default
}
// action is not known, id was found
values["action"] = "Index"; // change action
values["id"] = action; // use the id
return true;
}
And the route map (before the default one - both must be provided), should look like this:
routes.MapRoute(
name: "DefaultMap",
url: "{controller}/{action}/{id}",
defaults: new { controller = string.Empty, action = "Index", id = string.Empty },
constraints: new { lang = new MyRouteConstraint() }
);
Summary: In this case, we are evaluating the value of the "action" parameter.
if both 1) action and 2) id are provided, we won't handle it here.
nor if this is known action (in the list, or reflected...).
only if the action name is unknown, let's change the route values: set action to "Index" and action value to ID.
NOTE: action names and id values need to be unique... then this will work
The easiest way is to simply create two Action methods in your Controller. One for Index and one for Send and place your string id parameter on both. Since you cannot have duplicate or overloaded action methods, that solves that problem. Your Index method will now handle both index or blank paths where the id is present or not (null) and process your views that way. Your Send method will do the exact same as Index. You can then route, process, or redirect how you like, depending on if id is null. This should work with no changes to RouteConfig.cs:
public ActionResult Index(string id) {if (id == null) Do A else Do B}
public ActionResult Send(string id) {if (id == null) Do A else Do B}
I had this struggle for a long time, and this was the simplest solution.
I don't think there is a solution for your requirements, as you have two competing routes. Maybe you can define something specific (in case you don't have any other controllers).
routes.MapRoute(
name: "Send",
url: "{controller}/Send/{id}",
defaults: new { controller = "Home", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Home",
url: "Home",
defaults: new { controller = "Home", action = "Index" }
routes.MapRoute(
name: "Index",
url: "{controller}/{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 }
);
This what worked for me:
1) In RouteConfig, I put this to be the first line:
routes.MapRoute(name: "single", url: "{controller}/{lastName}",
defaults: new { controller = "LeaveRequests", action = "Index" });
2) In my controller:
public ViewResult Index(string lastName)
{
if (lastName == null)
{
return //return somthing
}
else
{
return //return something
}
}
3) when you call the controller
http://localhost:34333/leaveRequests/Simpsons
it will give you all requests for Simpsons.
if you call http://localhost:34333/leaveRequests/
it will give all requests
this is working for me
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default1",
url: "{id}",
defaults: new { controller ="Home", action ="Index" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller ="Home", action ="Index", id = UrlParameter.Optional }
);

Categories