This question already has an answer here:
How to pass IEnumerable list to controller in MVC including checkbox state?
(1 answer)
Closed 6 years ago.
All, please clear up my confusion on how the model binding works with IEnumerables and Editor Templates.
I have a view, Approve.cshtml
#model IEnumerable<MvcWebsite.Models.Approve>
<table>
<tr>
<th>
Name
</th>
</tr>
#Html.EditorForModel()
</table>
A model, Approve.cs
public class Approve
{
public string Name { get;set;}
public string Role { get; set; }
}
And an editor template
#model MvcWebsite.Models.Approve
#using (Html.BeginForm("Approve", "Registration", FormMethod.Post))
{
<tr>
<td>
#Html.HiddenFor(m => m.Name)
#Html.EditorFor(m => m.Role)
</td>
<td>
<input type="submit" value="Approve" class="submit-button" />
</td>
</tr>
}
This is all fine and good. It renders the following output.
<input name="[0].Name" type="hidden" value="" />
....
However, in my Controller, I can't seem to receive values back for the Model (binding).
[HttpPost]
public ActionResult Approve(Approve approveModel)
{
.... approveModel has all default values
}
Can someone shed light on what I am doing wrong here? I abbreviated the code, I am using the Editor Template with other EditorFor and HiddenFor fields from my model...
Edited: I basically have a table layout, each with the User's Name, a textbox where I can enter their role (User or Admin) and then an Approve button which submits to my controller. Hence the reason I want to only return a single Approve object. I can return the entire IEnumerable to my Controller, but if I do that, how can I tell which of the items was the one I clicked the Approve button (submit) for?
EDIT:
So I have modified the code so that I have a single form surrounding my entire View Approve.cshtml
#model IEnumerable<MvcWebsite.Models.Approve>
#using (Html.BeginForm("Approve", "Program", FormMethod.Post))
{
<table>
<tr>
<th>
Name
</th>
</tr>
#Html.EditorForModel()
</table>
}
And then changed the controller to
[HttpPost]
public ActionResult Approve(IEnumerable<Approve> approvals)
{
// ???????????????????????
}
Now I'm still not clear on how to know which row I clicked Approve for. I know there are other ways to accomplish this task (create a checkbox for approve, and approve anything checked, etc.) However, I need the ability to click a button and only save 1 row back to the database, regardless if the user entered information into the other rows. Is it better practice to wrap my IEnumerable inside of it's own model (i.e. AllApprovals) and then add helper properties to that parent model (SelectedIndex, etc.)? If that is the approach to take, then how do I set the SelectedIndex after clicking an Approve button? Is that still jquery magic or is there a correct MVC way to accomplish this? Jquery magic seems very hackish to me?
EDIT: Based on Brian's response, here is my final. Still doesn't feel quite right, but it works!
View
#model IEnumerable<MvcWebsite.Models.Approve>
<table>
<tr>
<th>
Name
</th>
</tr>
#Html.EditorForModel()
</table>
Editor Template
#using (Html.BeginForm("Approve", "Registration", FormMethod.Post))
{
<tr>
<td>
#Html.HiddenFor(m => m.Name)
#Html.EditorFor(m => m.Role)
</td>
<td>
<input type="submit" value="Approve" class="submit-button" />
</td>
</tr>
}
Controller
[HttpPost]
public ActionResult Approve([Bind(Prefix="approval")]Approve approval) {
// WORKS!
}
Since you are only changing one at a time, I think the following is easier than trying to figure out at the controller which values changed, or adding a changed property and setting it via javascript.
Change Approve.cshtml to
#model IEnumerable<MvcWebsite.Models.Approve>
<table>
<tr>
<th colspan=2>
Name
</th>
</tr>
#foreach(var user in Model){
#using (Html.BeginForm("Approve", "Registration", FormMethod.Post)) {
<tr>
<td>
#Html.EditorFor(m => user)
</td>
<td>
<input type="submit" value="Approve" class="submit-button" />
</td>
</tr>
}
}
</table>
Change the Approve Editor Template to
#Html.HiddenFor(m => m.Name)
#Html.EditorFor(m => m.Role)
You're binding to the single Approve class, you should bind to IEnumerable<Approve>
Martin is correct I just want to add some more information. Your rendered HTML with the [0] is special syntax the model binder looks at and assumes you are working with a list if objects. Since your action method has a single Approve class and not a kist, you are experiencing this problem.
Related
I have an anchor <a> element within a cell of a <table>, leading to an action within my app. When I click the link in the browser, the event app goes to the route that I expect it to (Admin/ManageCustomers/ViewPayments/1), but the action doesn't get called (I've put a breakpoint on the expected method, but it doesn't get hit). I've double- and triple-checked the names of the controller and action, and compared it to other places in my app where <a> elements inside of <table>s that do work, and I can't figure out why this one is behaving differently.
If it matters at all, both the view and the controller are inside the "Admin" Area, but I think that would still work (I know that the asp-area tag for the anchor probably isn't necessary, since both the view and the model are in the same Area, but I was just trying everything I could think of at that point).
Controller:
namespace Shop.Areas.Admin.Controllers
{
[Authorize(Roles =Constants.ROLE_ADMIN)]
[Area(Constants.ROLE_ADMIN)]
public class ManageCustomers : Controller
{
...
// other stuff
...
[HttpPost]
public IActionResult ViewPayments(int id) // This is the method I want to call
{
....
}
}
}
View:
...
#* stuff *#
...
<div class="container-fluid">
<main>
<table class="table table-bordered table">
<tr>
<th>Name</th>
<th>Balance</th>
<th></th>
<th></th>
<th></th>
</tr>
#foreach (Customer customer in Model)
{
...
#* stuff *#
...
<tr>
<td>
#* This is the element that isn't working right *#
<a asp-area="Admin"
asp-controller="ManageCustomers"
asp-action="ViewPayments"
asp-route-id="#customer.CustomerId" #*From the model*#
class="text-center">
View Payment History
</a>
</td>
...
#* stuff *#
...
</tr>
}
</table>
</main>
</div>
The anchor and url it leads to are GET requests. The method you're showing us in your Controller is expecting POST. You need to either change the method to accept GET or create a GET method.
I am sorry if I ask a newbie question but i am new to asp.net mvc.
So, I have this view :
#model FirstProject.Models.SelectRolesViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm("SelectRolesOverall","SelectRoles"))
{
<table class="table">
<tr>
<th>Users</th>
<th>Roles</th>
</tr>
#Html.EditorFor(model=>model.UsersAndRoles)
<tr>
<td></td>
<td>
<input type="submit" />
</td>
</tr>
</table>
}
And the EditorTemplate :
#model FirstProject.Models.UserRole
<tr>
<td>#Model.User.UserName</td>
<td>
#Html.RadioButtonFor(model => model.Role, "Applicant") Applicant
<br/>
#Html.RadioButtonFor(model => model.Role, "Professor") Professor
</td>
</tr>
My question is next : How do I see what radio buttons have been checked in my controller after the submit button has been pressed? I want to have the following logic : If Applicant have been selected then userRole is Applicant , else if Professor has been selected then userrole is Professor. My controller is Empty momentarily because I don't know what to write in it.
If your action method is
public SelectRolesOverall(SelectRolesViewModel model)
then you can access the collection with
IEnumerable<UsersAndRoles> usesAndRoles = model.UsersAndRoles;
and access each item in the collection
foreach (UserRole userRole in model.UsersAndRoles)
{
string role = userRole.Role;
string name = userRole.UserName; // see note below
}
Note you have not included an input for the property UserName so this value wont post back and you might have trouble matching the role to the user. You might want to add #Html.HiddenFor(m => m.UserName) or change <td>#Model.User.UserName</td> to <td>#Html.TextBoxFor(m => m.UserName, new { #readonly = "readonly" })</td>
Try this
public ActionResult [YourActionName](string role)
{
switch(role){
case "Applicant": /*your applicant logic*/
break;
case "Professor": /*your Professor logic*/
break;
/*
Other logic here
*/
}
Replace the action name by your own
Note that the parameter role should have the same name of the radio button in your view, this is to allow data binding to work
how can i get the querystring id in there? is there any way
#using (Html.BeginForm("InformVendor", "Procurement",new {id="querystring Mode = "Inform" }, FormMethod.Post))
{
<tr>
<td>
#Html.DropDownListFor(m=>m.VendorId,new MultiSelectList(Model.VendorDropdownlist, "CustomerId", "ContactName"))
</td>
<td>
#Html.CheckBoxFor(m => m.IsEmail)
</td>
</tr>
<tr>
<td>
<input type="submit" id="btnsubmit" value="Nominate Vendor" />
</td>
<td></td>
</tr>
}
The easiest way is to have your id field be hidden. This way the user doesn't see the ID field but your post back controller does.
#Html.HiddenFor(x => x.YourID)
If you add the Id to your view model and render it as a hidden field.
#Html.HiddenFor(m => m.Id)
You will be able to retrieve it like this instead of using the querystring.
public ActionResult InformVendor(AViewModel model)
{
var Id = model.Id;
}
I have tables in my database called Sections and Fields. Sections contain Fields. Fields can be a textbox or checkbox. I am using this to dynamically create HTML forms for data entry.
I have a view which is bound to a Section:
#model Data.Section
#using (Html.BeginForm("SaveSection", "MyController"))
{
#Html.ValidationSummary(true)
<div class="fields">
#Html.EditorFor(m => m.Fields)
</div>
<input type="submit" value="Save">
}
I then have an editor template for Field in my EditorTemplates folder:
#model Photon.Data.Field
#if (Model != null)
{
<span class="label">#Html.DisplayFor(m => m.Name)</span>
<span class="field">
#Html.EditorFor(m => m.FieldValues)
</span>
}
(I have an editor template for FieldValues but that's not relevant here I don't think.)
The above solution works GREAT for listing textboxes and checkboxes. When a user edits them and then clicks Save, it posts back to my controller method:
[HttpPost]
public ActionResult SaveSection(Section model)
{
// do some magic
}
My Section model that is passed into my method is valid, and bound to the fields from the page.
Here's where I am stumped:
In some of these sections, the fields will need to be laid out in a specific html format (like horizontally or to look like a form from a document) -- basically they can't just be listed. So my thought was to assign a section a template name, then load that template with all the fields, like:
<div class="fields">
#if (Model.IsTemplated)
{
#Html.EditorFor(m => m.Fields, Model.TemplateName)
}
else
{
#Html.EditorFor(m => m.Fields)
}
</div>
What would my view for Model.TemplateName look like if I wanted to lay all of these fields out in a specific html format that is not just a list?
Here's where I'm at:
#model IEnumerable<Data.Field>
<table border="1">
<tr>
<td>
How do I display one field here with EditorFor?
</td>
<td>
How do I display another field here with EditorFor?
</td>
<td>
How do I display and another field here with EditorFor?
</td>
</tr>
</table>
Ideas? Am I going along the right path here?
I'm not sure if I understand your question correctly, but if you manage to arrive at the final View, then laying the fields out in the table row should be similar to this?
...
<tr>
#foreach(var fld in Model)
{
<td>
#Html.EditorFor(m => fld)
</td>
}
</tr>
...
(I don't sit in front of Visual Studio with a razor designer right now, so I apologize for any typos..)
#foreach (var item in Model.AllManagementActions)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Id)
</td>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
I want to pass all these values to my controller. The ID , the name, etc.
Can't i just pass the item itself?
This is my current code, but it doesn't work.
<a class="complete" title="My Action" data-url="#Url.Action("Submit", "Period", new { tpId = Model.Id ,it = item})" style="cursor: pointer;">
It works if I pass item.name, item.id,item.number,etc
Can i pass the model ?
Don't iterate in your views - use editor templates for the type.
Have a '~/shared/EditorTemplates/ManagementAction.ascx' (or .cshtml if using razor) that renders a single ManagementAction.
In the view, instead of iterating use:
#Html.EditorFor(model => model.AllManagementActions)
yes you can pass the entire model, but you should use a submit button instead of of an anchor tag and also put your code inside a
using(Html.BeginForm)
{
#foreach (var item in Model.AllManagementActions)
{
//your desire.
}
<input type="submit" value="Save" />
}
in your controller you will recieve the model on post
[HttpPost]
public ActionResult SaveViewData(YourModel model)
{
//your logic.
}