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);
}
Related
I have the following Get action:
[HttpGet]
[AllowAnonymous, OnlyAnonymous]
public ActionResult VerifyVoucherCode()
{
var model = new VerifyVoucherCodeModel();
model.VoucherCode = Request.GetFirstQueryValue("token", "voucherCode");
if (!string.IsNullOrEmpty(model.VoucherCode))
{
// prepopulates the code for the user already in a form
}
return View(model);
}
And a POST action for when the user submits the form:
[HttpPost, AjaxOnly]
[AllowAnonymous, OnlyAnonymous]
[ValidateAntiForgeryToken]
public ActionResult VerifyVoucherCode(VerifyVoucherCode model)
{
return ExecuteAction(() =>
{
// do something
});
}
I want to change the logic so that if the voucher code is in the request, the form is submitted automatically for the user without them having to submit the form themselves (i.e. it takes them straight to that Post action). I've looked at lots of Stack Overflow questions and answers and it seems I cannot redirect to the Post action. I have also tried creating a second Get action which calls the Post action but that does not work either. Does anyone know the correct way to approach this problem?
Thanks
Assuming the model contains a single string for the voucher, you can do something like this:
[HttpGet]
[AllowAnonymous, OnlyAnonymous]
public ActionResult VerifyVoucherCode(string id)
{
if(string.IsNullOrEmpty(id))
{
MyModel model1 = new MyModel();
...
return View(model1);
}
//otherwise process the voucher here
...
return RedirectToAction("SuccessMsg");
}
I have the following action methods:
[HttpGet]
public ActionResult DBLookupIndex(DBLookupDTO dto)
{
dto.Areas = _ph.GetProfiles();
return View(dto);
}
[HttpGet]
public ActionResult Search(DBLookupDTO dto)
{
dto.Orders = _oh.GetOrders(dto.OrderNumber, dto.ProductNumber, dto.DateRange, dto.SelectDeleted, dto.AreaId);
return RedirectToAction("DBLookupIndex", dto);
}
The user simple enters valid information into one or more of the textboxes on the webpage, and submits it to the controller by pressing submit. This then calls the Search-action.
By debugging, I have determined, that the function works. It does find what it should, but it is not passed on, when it redirects back to the DBLookupIndex-action.
My question is; What am I doing wrong? I have seen code examples similar to the one above provided as solutions for similar issues, but for some reason it does not work for me.
EDIT:
I realised after the first answer came, that I was missing some information. Whenever the page is loaded, it has to update a dropdown that is rendered in the view, in case new profiles/areas have been added. I do that with razor:
<select name="AreaId" asp-for="AreaId" class="form-control">
<option disabled selected value=""> -- Vælg et område -- </option>
#foreach (var a in Model.Areas)
{
<option value="#a.ProfileId">#a.Name</option>
}
That is why I have to used RedirectToAction, instead of having a new action render the same view. Unless there is a better way to do it? :)
Thank in advance for any help!
In addition to Peter B's answer, another option is to store it in the TempData object that exists on your base controller.
[HttpGet]
public ActionResult Search(DBLookupDTO dto)
{
var orders = new List<Order>();
TempData["orders"] = orders;
return RedirectToAction("DBLookupIndex", dto);
}
You can then retrieve the data on the following request like so:
[HttpGet]
public ActionResult DBLookupIndex(DBLookupDTO dto)
{
var yourData = (List<Order>)TempData["orders"];
...
return View(dto);
}
The TempData object exists for a single request and is then cleared up. You can read more about it here.
The object parameter in RedirectToAction is meant to set querystring values for the URL that is generated and then sent to the browser as a "Redirect to other page" result. It is supposed to be an object similar to new { id = 7, otherParam = 5 }, or a Dictionary, but certainly not a recordset or any other kind of business data.
It seems like you want to show the View that belongs to the DBLookupIndex action. That can be done in a very simple way, like this:
[HttpGet]
public ActionResult Search(DBLookupDTO dto)
{
dto.Orders = _oh.GetOrders(dto.OrderNumber, dto.ProductNumber, dto.DateRange, dto.SelectDeleted, dto.AreaId);
return View("DBLookupIndex", dto); // Render the "DBLookupIndex" view and pass it the dto object
}
Update: if you need dto.Areas to be always set, you can create a method that just does that.
Like this (1):
[HttpGet]
public ActionResult DBLookupIndex(DBLookupDTO dto)
{
SetAreas(dto);
return View(dto);
}
[HttpGet]
public ActionResult Search(DBLookupDTO dto)
{
dto.Orders = _oh.GetOrders(dto.OrderNumber, dto.ProductNumber, dto.DateRange, dto.SelectDeleted, dto.AreaId);
SetAreas(dto);
return View("DBLookupIndex", dto);
}
private void SetAreas(DBLookupDTO dto)
{
dto.Areas = _ph.GetProfiles();
}
Or like this (2):
[HttpGet]
public ActionResult DBLookupIndex(DBLookupDTO dto)
{
return SetAreasAndView(dto);
}
[HttpGet]
public ActionResult Search(DBLookupDTO dto)
{
dto.Orders = _oh.GetOrders(dto.OrderNumber, dto.ProductNumber, dto.DateRange, dto.SelectDeleted, dto.AreaId);
return SetAreasAndView(dto);
}
private ActionResult SetAreasAndView(DBLookupDTO dto)
{
dto.Areas = _ph.GetProfiles();
return View("DBLookupIndex", dto);
}
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 attempting to do is passed a viewData from controller to view and display it in drop down list.
I wish to add the selected Id pass into m.movie_type_id.
But I keep on getting ArgumentNullException. Detailed information state that the Value cannot be null.
Below is how I was code and getting error exception.
<%=Html.DropDownListFor(m=> m.movie_type_id,new SelectList((IEnumerable)ViewData["MT"],"Id","Type")) %>
In Controller
public ActionResult AddMovie()
{
ViewData["MT"] = db.MovieType.ToList();
return View();
}
[HttpPost]
public ActionResult AddMovie()
{
return view();
}
Current stage is just for testing purpose. Since the error occur during the data pass back to the controller, I wish to fix it before i proceed.
Where did I done wrong?
Please make sure that viewdata is filled by some collection and passed into your view like
public ActionResult MyAction()
{
ViewData["MT"] =YourIEnumerableCollection() //Code to get the collection
return view(model);
}
EDIT
I think you are getting this in POST scenario, you need to pass the ViewData in POST too like
[HttpPost]
public ActionResult AddMovie()
{
ViewData["SQ"] = db.MovieType.ToList();
return View();
}
I have a problem with "return view()" that doesn't excute code in my controller.
I have a controller with this code:
public class BrokerController : BaseController
{
public ActionResult BestallMaklarBild()
{
return View(new BestallMaklarBildViewModel());
}
[HttpPost]
public ActionResult BestallMaklarBild(FormCollection collection)
{
try
{
//Some code
return View("MaklarBildBestalld",new MaklarBildBestalldViewModel());
}
catch
{
return View(new BestallMaklarBildViewModel());
}
}
public ActionResult MaklarBildBestalld()
{
//Some code
return View(new MaklarBildBestalldViewModel());
}
}
When I post to my controller "BestallMaklarBild" and then try to return the view("MaklarBildBestalld",new MaklarBildBestalldViewModel()), the code in "MaklarbildBestalld" doesn't execute. So when the model is returned to the view it doesn't contain any data, and cause my view leaving me with an error saying that the model.something = null when I try to output some data to the view.
Don't know what Im doing wrong.
That's normal. You need to redirect to this controller action if you want the code to execute:
try
{
//Some code
return RedirectToAction("MaklarBildBestalld", new { id = "some id" });
}
catch
{
return View(new BestallMaklarBildViewModel());
}
and now the action will execute:
public ActionResult MaklarBildBestalld(string id)
{
//Some code
return View(new MaklarBildBestalldViewModel());
}
Here's the correct workflow:
The BestallMaklarBild POST controller action is invoked and passed a view model
This controller action attempts to persist the model and redirects in case of success to the MaklarBildBestalld controller action passing it an unique identifier as query string of this model so that this action is able to retrieve the model back. In case of failure it simply redisplays the form so that the user can fix the errors he did, or see the error message that there was a problem processing his request.
You must ensure that your view has the model in your parameters as a generic type.