I reached a road bump while trying to use some of the cool new feature of MVC 3.
Is it possible to overload controllers using custom JSON binding using MVC3 ?
It doesnt look like it works automatically as of now....
What's the neatest way to do this ?
For example
If I want to implement the following endpoint
[HttpPost]
public ActionResult GetPet(Cat catObject)
{
return Json(catObject.purr());
}
overloaded with this endpoint
[HttpPost]
public ActionResult GetPet(Dog dogObject)
{
return Json(dog.bark());
}
Is there any way i can do this without using thirdparty libraries Or System.Web.Script.Serialization.JavaScriptSerializer
Also Is there any particular reason this is not implemented in mvc3 yet?
Overloading Json objects on controllers seems a distant possibility now rather than a present reality.
This is the closest I could get to overloading the action
[HttpPost]
public ActionResult GetPet()
{
Cat catObj;
Dog dogObg;
if (TryUpdateModel(catObj))
return Json(catObj.purr());
else
{
ModelState.Clear();
if (TryUpdateModel(dogObg))
return Json(dogObj.bark());
else
{
ModelState.Clear();
ModelState.AddModelError("InvalidInput", "The given input does not match with any of the accepted JSON types");
return new HttpBadRequestResult(ModelState);
}
}
}
Related
ActionResult is the base class for the various return types to View in MVC. So your action must return an ActionResult or a class derived from it in order to work.
so we can use
public ContentResult Index()
{
return Content("Hello world");
}
or for example
public ViewResult Index()
{
return View();
}
or ActionResult
public ActionResult Index()
{
if (ViewBag.Hello = "World")
return Json("");
return PartialView();
}
BUT also is possible use string !!!
public string Index()
{
return "Hello World";
}
WHY is than not possible return integer to view? (Or maybe it is?)
public int Index()
{
return 4;
}
and not possible return some entity to view (Or maybe it is?)
public User Index()
{
return new User();
}
My question is : What happening behind scene when we want to render view?
I agree that this is quite a broad question, but I wanted address and answer a few of the points you raised in your question.
You can return an int, string or object from your action method and it will simply return the object's string representation as the result.
Therefore you don't have to return an object of type ActionResult in order for it to work, but the ActionResult enables useful functionality through it's various implementations so that ASP.NET MVC Framework can handle different scenarios straight out of the box.
Such as returning views and handling the ViewModel you want to pass to your view:
return View(); // Default view without view model
return View(viewModelObject); // Default view with a view model
Returning views based on your routing information:
return View("viewName", viewModelObject);
Performing redirects to another page, using your routing information:
return RedirectToAction(actionName, controllerName);
Returning a page with specific status codes:
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
Returning JSON instead of a view:
return JsonResult(myObject);
All of the above examples do different things, return different types of results and handle your objects for you so that you don't have to code the behaviour yourself - they're ready for you to use.
Another handy thing is that you can create your own implementations of ActionResult to create your own behaviour, so it's very flexible in that regard.
I agree with #Daniel J.G. that you should do some more reading on how ASP.NET MVC hangs together and it will become a lot more clear to you.
I am using MVC 4, and I have the following:
[HttpGet]
public ActionResult SomeForm(modelType model = null)
{
if(model != null)
return View(model);
return View(getModelFromSomewhere());
}
[HttpPost]
public ActionResult SomeForm(modelType model)
{
if(isValid())
doSomething();
else
return SomeForm(model) // Line in Question
}
However, obviously, I am getting an ambiguous method error on "Line in Question". I'm wondering if anyone has an elegant solution to be able to specify to return specifically the [Get] method of the same name?
Thank you!
You can't have methods with the same signature as you've pointed out already. In C# it also means you can't distinguish functions by just return type - so you must use different names if parameters are same (again default values are ignored when matching of signatures).
If you want separate GET and POST handler - use different names of methods and ActionNameAttribute to name the action:
[HttpGet]
[AciontName("SomeForm")]
public ActionResult SomeFormGet(modelType model = null) ...
[HttpPost]
[AciontName("SomeForm")]
public ActionResult SomeFormPost(modelType model) ...
make it compile...
[HttpPost]
public ActionResult SomeForm(modelType model, FormCollection fc)
{
if(isValid())
doSomething();
else
return SomeForm(model) // Line in Question
}
If you are using http get method you are waiting that browser will send you serialized model as a string query. For example, you are waiting url like
http://example.com?name=Andrew&type=Worker&field1=param1&field2=param2&....
It is common practice to use only id in your get method, so you can do it like this:
[HttpGet]
public ActionResult SomeForm(int id)
{
var model = FindModelById(id);
if(model != null)
return View(model);
return View(getModelFromSomewhere());
}
If you are looking for an elegant solution, it will be more elegant in architecture
I am used to:
public ActionResult CreateSomething()
{
return View("ViewName", new CreateSomeDto());
}
I just encountered this in some legacy code:
public ActionResult CreateSomething()
{
return RenderView("ViewName", new CreateSomeDto());
}
Could someone please explain the difference?
RenderView() is an antiquated method from MVC 1.0
The ViewEngine was less sophisticated. Controller methods did not return ActionResults. In fact, they returned void. A RenderView call does not return a string, but rather "renders" or writeln's the view into place.
It's functionality is very similar to the way Html.RenderPartial()'s are rendered into a View.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Can you overload controller methods in ASP.Net MVC?
I need to 2 methods that takes different type of argument. so I tried this,
[HttpPost]
public ActionResult ItemUpdate(ORDER ln)
{
// do something
}
[HttpPost]
public ActionResult ItemUpdate(List<ORDER> lns)
{
// Do Something
}
but it does not work.
No error while compiling, but when run, it makes an error.
How I write the code to make that works?
Thanks!
[Edit]
[HttpGet]
public ActionResult ItemUpdate(string name)
{
return Content(name);
}
[HttpGet]
public ActionResult ItemUpdate(int num)
{
return Content(num.ToString());
}
and when I call /Test/ItemUpdate/
it make an error,
Server Error in '/' Application.
The current request for action 'ItemUpdate' on controller type 'OrderController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult ItemUpdate(System.String) on type Ecom.WebUI.Controllers.OrderController
System.Web.Mvc.ActionResult ItemUpdate(Int32) on type Ecom.WebUI.Controllers.OrderController
[EDIT]
It does not match with ORDER even single parameter.
if (lns.GetType() == typeof(ORDER))
{
// always false
}else{
// also I can not cast the object.
ORDER ln = (ORDER)lns; //Unable to cast object of type 'System.Object' to type 'ORDER'
}
Overloaded actions are not supported in MVC. The dispatcher can not tell the difference between the two Actions. You can get around this by giving one of your actions the [HttpGet] attribute and the other one the [HttpPost] attribute.
If that isn't an option (or if you have three or more overloads), you can always dispatch the Action yourself, by using an object parameter and using run time type identification to select the correct function to call. E.g.:
[HttpPost]
public ActionResult ItemUpdate(object arg)
{
if (arg.GetType() == typeof(ORDER))
{
return ItemUpdateOrder((Order)arg);
}
else if (arg.GetType() == typeof(List<ORDER>))
{
return ItemUpdateList((List<Order>)arg);
}
}
public ActionResult ItemUpdateOrder(ORDER ln)
{
//...
}
public ActionResult ItemUpdateList(List<ORDER> lns)
{
//...
}
You can't do that in a controller. You will have to change the second method name.
[HttpPost]
public ActionResult ItemUpdates(List<ORDER> lns)
{
// Do Something
}
public ActionResult ItemUpdates(object myInputValue)
{
if (myInputValue.GetType() == typeof(string)
// Do something
else if (myInputValue.GetType() == typeof(List<ORDER>))
// Do something else
}
You can then cast the object to your type of choice and manipulate normally.
In ASP.NET it's not possible to have overloaded methods without an ActionFilter attribute to distinguish these actions. The reason for this is that the ActionInvoker (a class used inside of the Controller base class to invoke actiosn) cannot determine which method to call since it would need to "ask" a ModelBinder (which are responsible to construct action argument objects) for every overload if the ModelBinder could construct that argument object from the HTTP request passed. For simple scenarios this would work but in more complex scenarios this would fail because the ModelBinder would succeed in binding arguments of multiple overloads. Not to allow overloads in ASP.NET MVC is quite clever design decision.
To solve your problems you can fix ItemUpdate HTTP GET action by just leaving the second action away and having just one action, because a ModelBinder does not mind if a value that is passed as URL parameter for example is a string or an int.
[HttpGet]
public ActionResult ItemUpdate(string name)
{
return Content(name);
}
For the ItemUpdate HTTP POST version I'd recommend to rename one of these actions or to have only one action, the list version because updating a single ORDER is only a specific case of updating multiple ORDER objects.
I am developing a ASP .Net MVC project but i can't make overload of Index() even when i have defined other method with different no. of parameters & have given proper routing for it . But it doesnot seems to work. So i just want to ask can we make overloaded methods in controller or not?
Controller actions with the same name are possible on the same controller if they are called with different HTTP verbs. Example:
public ActionResult Index()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(SomeModel model)
{
return View();
}
When you call GET /Controller/Index the first action will be invoked and when you call POST /Controller/Index the second action will be invoked.
To be more specific, you have to vary it by selection criteria (which might be a verbs change as Darin said, but could also be other selector attributes like NonAction or ActionName). For that matter, you could create your own ActionNameSelectorAttribute derivative to create custom logic indicating when a given method should be used over another.
Update: added code per request.
I am actually creating a sample ActionMethodSelectorAttribute, b/c I couldn't think of a good usecase for just testing on the name that's not already covered by the ActionNameAttribute. Principle is the same either way, though.
public class AllParamsRequiredAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
var paramList = methodInfo.GetParameters().Select(p => p.Name);
foreach (var p in paramList)
if (controllerContext.Controller.ValueProvider.GetValue(controllerContext, p) == null) return false;
return true;
}
}
Basically, this one just gets the names of the params on the action method that it flags, and tests to make sure that the controller's ValueProvider has at least an attempted value of the same name as each. This obviously only works for simple types and doesn't test to make sure the attempted value can cast properly or anyting; it's nowhere close to a production attribute. Just wanted to show that it's easy and really any logic you can return a bool from can be used.
This could be applied, then as follows:
[AllParamsRequired]
public ViewResult Index(int count){/*... your logic ... */}
public ViewResult Index() {/*... more of your logic ... */}
in this example,and default routing, the url mydomain.com/?count=5 will match the first one, and the url mydomain.com/ will match the second one.
Paul