ASP.NET MVC Return Overloaded Get Action - c#

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

Related

Post model to an MVC ActionResult

I have an ASP.net application with a [HttpGet] for a view an ActionResult named 'Create' on a Controller called Students
My 'Student' is then posted to the following controller.
[HttpPost]
public async Task<ActionResult> Create(Student student)
{
Student.Add(student);
if (ModelState.IsValid)
{
var result = db.Students.Add(student);
await db.SaveChangesAsync();
return Details(result);
}
return View(new CreateStudent());
}
return Details(result); is the line I'm interested in.
Previously I had RedirectToAction where I passed in the result. Id property and used a GET request to query a database.
But, I'm not wanting to have a URL like 'students/details/id=123' Or 'students/details/123' I want to Post my model to the controller 'students/details'
var result is a single 'Student'and my 'Details' ActionResult looks like this:
[HttpPost]
public async Task<ActionResult> Details(Student student)
{
//
}
but return Details(result); doesn't work, I receive an error message stating:
'The model item passed into the dictionary is of type 'LinkApp.Models.Student', but this dictionary requires a model item of type 'LinkApp.Models.DTOs.CreateStudent'.'
But I believe this is because my URL never goes to '/Students/Details', my URL still shows '/Students/Create'
Any help is appreciated. And again, if it's not clear, please ask for any clarity
Thanks
EDIT DUE TO ME NOT BEING VERY CLEAR ;)
So I'm a little bit confused with what you're trying to achieve.
Here is what I've gathered: You want to POST the data. Right now you have a GET method. You can think of a GET method like something that retrieves, and a POST as something that gives.
So your GET method should not be trying to POST anything.
Your GET method should looks like it should be a POST since you're trying to "give" the data to something, not get the data.
[HttpPost]
public async Task<ActionResult> Create(Student student)
{
Student.Add(student);
if (ModelState.IsValid)
{
var result = db.Students.Add(student);
await db.SaveChangesAsync();
return View("Details", result);
}
return View(); //You should add a property to the model called ErrorMessage or something like that, then you could do student.ErrorMessage = "Model state was not valid";, then you could do return View(student); and in the view you could do something like #if (Model.ErrorMessage != null) { #Html.DisplayFor(m=>m.ErrorMessage); }
}
This is what your post should look like, you're saving the Student model to the database and returning to a view.
Now your GET should just be something like this:
[HttpGet]
public ActionResult Create()
{
return View();
}
Edit:
If you wanted your post to do the error message I left in the comment above you would just do this right above the second return View();
student.ErrorMessage = "Model state was not valid;" //Or whatever you want to say
return View(student);
Then in your view you would have something like this:
#if (student.ErrorMessage != null)
{
#Html.DisplayFor(m => m.ErrorMessage);
}

What happening behind scene when we want to render view?

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.

Is it possible to have 2 views associated with one action?

I have the following custom route
routes.MapRoute("custom", "Groups/{groupname}/{action}/{id}", new { controller = "Groups", action = "Index", id = UrlParameter.Optional });
This custom route is to achieve the following url schema
Groups/pokerclub/members/ - list all the members of pokerclub group.
Groups/pokerclub/members/bob - list member details of bob.
I tried the following but it doesnt work. it confuses asp .net mvc of which action to select.
public ActionResult Members(string groupName, string id)
{
return View();
}
public ActionResult Members(string groupName)
{
return View();
}
There are two possible solutions that i could think of:
Have different action name. e.g. Memberlist action and member action.
Handle this in the view using simple if statement.
I very much want to maintain my url scheme. thus, as per my original question, how to have two different views associated to one action? Thanks.
RWendi
You can only have one action, but you can perform whatever logic you want inside that action, and you can call views by name:
return View("MyView");
In your example, you would simply return a different view if the id field was null.
where it says return View(); It takes parameters and you can get it to use whatever view you would like
return View("Members", model);
you can use different methods:
[HttpGet]
public ActionResult Members(string groupName, string id)
{
return View();
}
[HttpPost]
public ActionResult Members(string groupName)
{
return View();
}

GET and POST methods with the same Action name in the same Controller [duplicate]

This question already has answers here:
MVC [HttpPost/HttpGet] for Action
(4 answers)
Closed 2 years ago.
Why is this incorrect?
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
Some Code--Some Code---Some Code
return View();
}
[HttpPost]
public ActionResult Index()
{
Some Code--Some Code---Some Code
return View();
}
}
How can I have a controlller thas answer one thing when is "getted" and one when is "posted"?
Since you cannot have two methods with the same name and signature you have to use the ActionName attribute:
[HttpGet]
public ActionResult Index()
{
// your code
return View();
}
[HttpPost]
[ActionName("Index")]
public ActionResult IndexPost()
{
// your code
return View();
}
Also see "How a Method Becomes An Action"
While ASP.NET MVC will allow you to have two actions with the same name, .NET won't allow you to have two methods with the same signature - i.e. the same name and parameters.
You will need to name the methods differently use the ActionName attribute to tell ASP.NET MVC that they're actually the same action.
That said, if you're talking about a GET and a POST, this problem will likely go away, as the POST action will take more parameters than the GET and therefore be distinguishable.
So, you need either:
[HttpGet]
public ActionResult ActionName() {...}
[HttpPost, ActionName("ActionName")]
public ActionResult ActionNamePost() {...}
Or,
[HttpGet]
public ActionResult ActionName() {...}
[HttpPost]
public ActionResult ActionName(string aParameter) {...}
I like to accept a form post for my POST actions, even if I don't need it. For me it just feels like the right thing to do as you're supposedly posting something.
public class HomeController : Controller
{
public ActionResult Index()
{
//Code...
return View();
}
[HttpPost]
public ActionResult Index(FormCollection form)
{
//Code...
return View();
}
}
To answer your specific question, you cannot have two methods with the same name and the same arguments in a single class; using the HttpGet and HttpPost attributes doesn't distinguish the methods.
To address this, I'd typically include the view model for the form you're posting:
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
Some Code--Some Code---Some Code
return View();
}
[HttpPost]
public ActionResult Index(formViewModel model)
{
do work on model --
return View();
}
}
You received the good answer to this question, but I want to add my two cents. You could use one method and process requests according to request type:
public ActionResult Index()
{
if("GET"==this.HttpContext.Request.RequestType)
{
Some Code--Some Code---Some Code for GET
}
else if("POST"==this.HttpContext.Request.RequestType)
{
Some Code--Some Code---Some Code for POST
}
else
{
//exception
}
return View();
}
Can not multi action same name and same parameter
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(int id)
{
return View();
}
althought int id is not used
You can't have multiple actions with the same name. You could add a parameter to one method and that would be valid. For example:
public ActionResult Index(int i)
{
Some Code--Some Code---Some Code
return View();
}
There are a few ways to do to have actions that differ only by request verb. My favorite and, I think, the easiest to implement is to use the AttributeRouting package. Once installed simply add an attribute to your method as follows:
[GET("Resources")]
public ActionResult Index()
{
return View();
}
[POST("Resources")]
public ActionResult Create()
{
return RedirectToAction("Index");
}
In the above example the methods have different names but the action name in both cases is "Resources". The only difference is the request verb.
The package can be installed using NuGet like this:
PM> Install-Package AttributeRouting
If you don't want the dependency on the AttributeRouting packages you could do this by writing a custom action selector attribute.
Today I was checking some resources about the same question and I got an example very interesting.
It is possible to call the same method by GET and POST protocol, but you need to overload the parameters like that:
#using (Ajax.BeginForm("Index", "MyController", ajaxOptions, new { #id = "form-consulta" }))
{
//code
}
The action:
[ActionName("Index")]
public async Task<ActionResult> IndexAsync(MyModel model)
{
//code
}
By default a method without explicit protocol is GET, but in that case there is a declared parameter which allows the method works like a POST.
When GET is executed the parameter does not matter, but when POST is executed the parameter is required on your request.

Is it possible to make a non ActionResult Method to return an ActionResult... Or best/neatest workaround?

I have an object from a database that is used in a lot of places in my application.
The actual precise object is a bit complicated to build, and, especially during development, I have changed it several times. For this reason, I extracted the method out of the Controller and built a method that has a return type of the object.
However, it was possible that this object did not exist, and if it did not, my code would create it and return it.
For example:
public ActionResult Index()
{
var model = GetTheObject();
return View(model);
}
public MyComplicatedObject GetTheObject()
{
MyComplicatedObject passback = ...database query....
if(passback==null)
create the object here....
return passback;
}
However, I no longer want to create a default object. If it does not exist, I want the user to be sent to a view to create a new one.
I don't know if it is because I am coding at almost 4AM, or if I am just not that good, but, a neat way of doing this is escaping me.
I know the following won't work, but ideally this is what I want:
public MyComplicatedObject GetTheObject()
{
MyComplicatedObject passback = ...database query....
if(passback==null)
return View("CreateObject");
return passback;
}
Obviously though, this will not work.
The best solution I can think of is to basically return either null or an exception, then have if(passback==null)&return View("CreateObject"); (in case of a null) on the ActionResult.
However, as I want to repeat this in a few places, it makes more sense to be able to just have GetTheObject() in one line/call from the ActionResult and nothing else.
Is there any way to achieve this?
I have a similar scenario where I want to return a "NotFound" view in case that my repository returns a null object. I implemented a ViewForModel helper method to avoid repeating myself:
public ActionResult Details(int id)
{
var model = _repository.Retrieve(id);
return ViewForModel("Details", model);
}
public ActionResult Edit(int id)
{
var model = _repository.Retrieve(id);
return ViewForModel("Edit", model);
}
private ActionResult ViewForModel(string viewName, object model)
{
return model == null
? View("NotFound")
: View(viewName);
}
Just return null from your method, and have your action method return the create view when it gets a null.

Categories