I have following methods in my MVC controller:
[HttpGet]
public ActionResult Add(Guid b)
{
ViewBag.Title="Add Location";
//init some info
return View("Edit",<Model>);
}
[HttpGet]
public ActionResult Edit(Guid l)
{
ViewBag.Title="Edit Location";
//get object from db
return View("Edit",<Model>);
}
Following is the route registration:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Now when I try to access following two routes:
1. http://localhost:60732/Location/Add?b=512f770f-51c3-4791-8eba-61fe753e2a83
2. http://localhost:60732/Location/Edit?l=512f770f-51c3-4791-8eba-61fe753e2a83
First one works but 2nd gives 404.
I have also tried AttributeRouting with following routes:
[HttpGet]
[Route("Location/AddLocation/{g}")]
public ActionResult Add(Guid g)
[HttpGet]
[Route("Location/EditLocation/{l}")]
public ActionResult Edit(Guid l)
Again,
http://localhost:60732/Location/AddLocation/512f770f-51c3-4791-8eba-61fe753e2a83
works but
http://localhost:60732/Location/EditLocation/512f770f-51c3-4791-8eba-61fe753e2a83
does not.
What am I doing wrong?
Strangely, if I pass wrong Guid like:
http://localhost:60732/Location/EditLocation/512f770f
It gives following error:
The parameters dictionary contains a null entry for parameter 'l' of non-nullable type
'System.Guid' for method 'System.Web.Mvc.ActionResult Edit(System.Guid)' in
'AppointmentScheduler.Controllers.LocationController'. An optional parameter must be a
reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters
There is nothing wrong with your routing here. However, the code in your Edit method must itself be throwing a 404 error by throwing HttpNotFound.
I just hope this is last WTF of 2014. I pass the id (Guid) and try to fetch object from db. Actually, it is not able to find it and I'm returning: HttpNotFound (I think copied from some API method). So I am always getting 404 for this route.
Related
This is the controller class. I am showing only method signatures.
[Authorize]
[RoutePrefix("specification")]
[Route("{action=index}")]
public class SpecificationController : BaseController
{
[HttpGet]
[Route("~/specifications/{subcategoryID?}")]
public ActionResult Index(int? subcategoryID);
[HttpPost]
[Route("get/{subcategoryID?}")]
public JsonResult Get(int? subcategoryID);
[HttpGet]
[Route("~/specifications/reorder/{subcategoryID}")]
public ActionResult Reorder(int subcategoryID);
[HttpGet]
[Route("new/{id?}")]
public ActionResult New(int? id);
[HttpGet]
[Route("edit/{id?}")]
public ActionResult Edit(int id);
[HttpPost]
[ValidateAntiForgeryToken]
[Route("edit")]
public JsonResult Edit(SpecificationJson specification);
[HttpPost]
[Route("moveup")]
public JsonResult MoveUp(int specificationID);
[HttpPost]
[Route("movedown")]
public JsonResult MoveDown(int specificationID);
[HttpDelete]
[Route]
public ActionResult Delete(int id);
}
The problem is that calling
#Url.Action("index", "specifications", new RouteValueDictionary() { { "subcategoryID", #subcategory.SubcategoryID } })
returns
/specifications?subcategoryID=15
instead of
/specifications/15
Why is this happening? I do not have any similar methods on that route expect this one!
Your call to generate the URL is incorrect. To match the controller name, it should be "specification" not "specifications".
#Url.Action("index", "specification", new { subcategoryID=subcategory.SubcategoryID })
Keep in mind, the URL specified in the [Route] attribute is only cosmetic. Your route values must match the controller name and action method name for it to utilize that route to generate the URL.
To make this more clear for those maintaining the code (and slightly faster), it might be better to make the parameter values Pascal case just like the controller and action names.
#Url.Action("Index", "Specification", new { subcategoryID=subcategory.SubcategoryID })
Why is this happening?
-------------------------------------------------------------
| Route Key | Route Value | Your Action Request |
|--------------------|---------------|----------------------|
| Controller | Specification | Specifications | No Match
| Action | Index | Index | Match
| subcategoryID | ? | XXX | Match (Always)
-------------------------------------------------------------
To get a route match, all parameters of #Url.Action must match the route value dictionary. The problem is that Controller=Specifications is not defined in the route value dictionary because your actual controller's name is SpecificationController. Therefore, the route value name is Specification regardless of what you put in the [Route] attribute. The URL ~/specifications/{subcategoryID?} has nothing at all to do with an outgoing (URL generation) match - it only matches incoming URLs and determines what the URL will look like when it is generated.
If you want to use Specifications instead of Specification for the route value, you need to move the action method to a new controller named SpecificationsController. That said, I don't see what difference it makes, since the end user won't see the route value name anyway.
You have to use this in order to generate follow url: /specifications/15
#Url.Action("index", "specifications", new { subcategoryID=subcategory.SubcategoryID })
[Route("~/specifications/{subcategoryID?}")]
public ActionResult Index(int? subcategoryID);
What did I do wrong and how can I revert to using subcategoryID as
parameter name ?
You have to add another route (before DEFAULT ROUTE) in order to have another optional parameter:
Something like this:
routes.MapRoute(
"SecondRoute",
"{controller}/{action}/{subcategoryID}",
defaults: new { controller = "Home", action = "Index", subcategoryID = UrlParameter.Optional }
);
I'm sending multiple requests to the same action method in a controller all these requests have some common querystring attributes and some specific to that request.
request1 : http://localhost/home/test?a=a1&b=b1&c=c1&d=d1....around 25 parameters
request2 : http://localhost/home/test?a=a1&b=b1&j=j1&k=k1...around 20 parameters
similarly request 3 , request4,etc...
My action method in mvc in homecontroller is as below..
public string test(string a, string b, string c, string d, ...around 50 parameters)
This is working perfectly..
But when I take this code and move it to web api, it no longer works..
Moreover, if I try with just two parameters, it works and I can get the two parameters..
public string test(string a, string b)
I have no control on the requests that I receive in my application as it is coming from a 3rd party host application, so the method name and parameters can not change ...
The route configured in mvc in route.config is standard..
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
I have configured a separate route for webapi in webapiconfig on similar lines..
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Any ideas how to solve this..
Thanks
Arnab
The reason is that Web API does action overloading and all these parameters are required, if they are not provided you are ending up with 404. The simple answer to your question is to make them optional by giving them a default value, so your signature will look like this:
public IHttpActionResult Get(string a = null, string b = null, ...)
However this code seems very elaborate for what you are doing, it's probably also not the most efficient and you end up with a lot of if statements.
Consider alternatively just parsing the query string yourself and get a more convenient to use data set.
public class ValuesController : ApiController
{
public IHttpActionResult Get()
{
var collection = Request.RequestUri.ParseQueryString();
foreach (var key in collection.Keys)
{
var value = collection[(string)key];
// do something with key & value
}
return Ok();
}
}
and as another option is to build a model including all the parameters, something like:
public class Settings
{
public string A { get; set; }
public string B { get; set; }
...
}
and bind to the model using the FromUri:
public IHttpActionResult Get([FromUri]Settings settings)
{
...
}
Here is a link from Mike Stall's blog - http://blogs.msdn.com/b/jmstall/archive/2012/04/16/how-webapi-does-parameter-binding.aspx
I want to have links http://localhost:2409/Account/Confirmation/16 and that link http://localhost:2409/Account/Confirmation/ (without parametr). But with this action methods, it isn't working. Why?
public ActionResult Confirmation(int id, string hash)
{
Some code..
return View();
}
second, I just want to return View, if parametr is empty.
public ActionResult Confirmation()
{
return View();
}
Error (translated):
The current request for action on a controller Confirmation
AccountController is ambiguous between the following methods of
action: System.Web.Mvc.ActionResult Confirmation (Int32,
System.String) for type TC.Controllers.AccountController
System.Web.Mvc.ActionResult Confirmation () for type
TC.Controllers.AccountController
You cannot have multiple actions with the same name using the same HTTP verb (in your case GET.) You can name your actions differently but this means the link will change or you can use different VERB but this can also leads to other problems like you cannot just enter the link in your browser.
What you should do is to change your id to be optional with int? and merge your two actions into one:
public ActionResult Confirmation(int? id, string hash)
{
if(id.HasValue)
{
//Some code.. using id.Value
return View();
}
//There was no Id given
return View();
}
You may also need to allow in your route that the id is optional. If you are using the default routes this should be the default setting:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
There is no need to make 2-methods for it. Your HTTP request get confused that which ActionMethod should be called on both cases;
http://localhost:2409/Account/Confirmation/16
http://localhost:2409/Account/Confirmation/
Instead of all this, just create a single method. Make its parameter optional or assign some default value to the parameters. Here are 2-examples to understand it.
// 1. Default value to paramter
public ActionResult Confirmation(int id = 0, string hash = null)
{
//Some code..
return View();
}
// 2. Make id optional
public ActionResult Confirmation(int? id, string hash)
{
//Some code..
return View();
}
You can adopt any one approach from them.
Is it possible to overload the action methods based on number of parameters in request?
Eg:
1.
domain.com/List/Filter/ByName
invokes -> public ActionResult Filter(string criteria1)
2.
domain.com/List/Filter/ByName/ByRanking
invokes -> public ActionResult Filter(string criteria1, string criteria2)
I'm using asp.net mvc2.
Action methods cannot be overloaded based on parameters because there would be no reasonable way to disambiguate a URL into multiple overloaded methods.
What you can do, though is either this:
public ActionResult Filter(string criteria1, string criteria2)
and then check whether criteria2 is null to filter only by name.
Alternatively, you can use ActionNameAttribute to decorate your action methods
[ActionName("FilterByName")]
public ActionResult Filter(string criteria1)
[ActionName("FilterByNameAndRanking")]
public ActionResult Filter(string criteria1, string criteria2)
and then use that name in route registration. This approach, however, can lead to much confusion.
If I'm not mistaken the best way to do this would be to add two different controller methods and map them to two different Urls.
public ActionResult Filter1(string criteria1);
public ActionResult Filter2(string criteria1, criteria2);
Then you have two route definitions:
This will map this URL List/Filter/xxCriteria/ to the first controller
routes.MapRoute(
"Filter", // Route name
"{controller}/Filter/{criteria1}", // URL with parameters
new { controller = "List", action = "Filter1", criteria="" } // Parameter defaults
);
This will map this URL List/Filter/xxCriteriaName/xxxCriteriaRank to the second controller. Without this route you could still map a url to the second method, but it would look like : List/Filter/?criteria1=xx&criteria2=xx
routes.MapRoute(
"Filter2", // Route name
"{controller}/Filter/{criteria1}/{criteria2}", // URL with parameters
new { controller = "List", action = "Filter2", criteria1 = "", criteria2 = "" } // Parameter defaults
);
Hope it helped.
I've got a very basic ASP.Net MVC project where I'd like to use a parameter name of id on one of my controller actions. From everything I've read that shouldn't be a problem but for some reason using a parameter name of id fails to get the value extracted from the query string but if I change it to any other different name it will work.
I only have a single route in my global.asx
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
My controller method is:
public ActionResult Confirm(string id)
{
....
}
A URL of http://mysite/customer/confirm/abcd works. A URL of http://mysite/customer/confirm?id=abcd fails.
If I change the controller method to:
public ActionResult Confirm(string customerID)
{
....
}
then a URL of http://mysite/customer/confirm?customerID=abcd works.
Is there something special about using "id" as a parameter in an ASP.Net MVC query string?
Update: Changed id from 1234 to abcd, my id's are actually strings.
If you do not apply an id parameter (either querystring or POST), the system just ignores it, and you can remove the "id" parameter in your controller:
public ActionResult Confirm()
In your case, you would just stick with the id parameter. Why make an ugly customerID parameter, when id is "mapped" automatically?
This is an easy and simple example of the use of id parameter.
public ActionResult Confirm(int? id)
{
if (id.HasValue && id.Value > 0) // check the id is actually a valid int
_customerServer.GetById(id.Value);
// do something with the customer
return View();
}
This works too, for me. We're doing it in our application right now with a standard route:
public ActionResult Confirm(string id)
{
if (!string.IsNullOrEmpty(id)) // check the id is actually a valid string
_customerServer.GetByStringId(id);
// do something with the customer
return View();
}
If you need to have id in query string, then don't create route with 'id' parameter.
In case you have route "{controller}/{action}" then you can use public ActionResult Confirm(string id) as your controller method.
Routes don't care about query strings.