Submit a form with a collection of a Model - c#

I have a ViewModel which contains a collection of type of my Model, like so:
public class OrderConfirm
{
public ICollection<QuoteLine> SalesLines { get; set; }
public string Currency { get; set; }
public int EnquiryID { get; set; }
}
My QuoteLine Model looks like so:
public class QuoteLine
{
public int QuoteLineId { get; set; }
public int LostReasonId { get; set; }
public virtual LostReason LostReason { get; set; }
public string ItemName { get; set; }
}
In my View, I then Iterate through each of these QuoteLines, within a form, like so:
#using (Ajax.BeginForm("ConfirmLostOrder", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "LostOrders",
OnBegin = "LostOrderConfirm"
}))
{
<table class="completed-enq-table">
<tr>
<th>
Item Number
</th>
<th>
Reason
</th>
</tr>
#foreach (var sales in Model.SalesLines)
{
<tr>
<td>#sales.ItemName
#Html.HiddenFor(model => sales.QuoteLineID)
</td>
<td>#Html.DropDownListFor(model => sales.LostReasonId, ((IEnumerable<myApp.Models.LostReason>)ViewBag.LostReasons).Select(option => new SelectListItem
{
Text = (option == null ? "None" : option.LostReason),
Value = option.LostReasonId.ToString(),
Selected = (Model != null) && (option.LostReasonId == sales.LostStatusId)
}))
</td>
</tr>
}
</table>
<input type="submit" style="float: right;" value="Submit Lost Order" />
}
Then my HttpPost action looks like so:
[HttpPost]
public ActionResult ConfirmLostOrder(List<QuoteLine> models)
{
// process order
return PartialView("Sales/_ConfirmLostOrder");
}
The problem is, models is null. If I use a FormCollection I can see each of the values submitted but I'd like to use my model and not a FormCollection as I'd like to process and edit each of the line submitted individually as they may have different reason's

You can't use a foreach in this instance, it needs to be a for loop so that the name attributes of the fields contain the correct index so that default model binding knows it's binding to a list.
Firstly, I'm going to move your dropdown values out of the ViewBag (they should really be in there). That'll also take out some of that nasty logic in your view :)
So your model is now:
public class OrderConfirm
{
public List<QuoteLine> SalesLines { get; set; }
public string Currency { get; set; }
public int EnquiryID { get; set; }
public SelectList LostReasons { get; set; }
}
Try this instead of your foreach:
#for (var i = 0; i < Model.SalesLines.Count; i++)
{
<tr>
<td>
#Model.SalesLines[i].ItemName
#Html.HiddenFor(m => m.SalesLines[i].QuoteLineId)
#Html.HiddenFor(m => m.SalesLines[i].ItemName) //assuming you want this
</td>
<td>
#Html.DropDownListFor(m => m.SalesLines[i].LostReasonId, Model.LostReasons)
</td>
</tr>
}
Then change your post method to take your OrderConfirm model type:
[HttpPost]
public ActionResult ConfirmLostOrder(OrderConfirm model)

Related

Post List of lists of RadioButtons MVC

I have an Attendance program in which I want to assign Students to AttendanceTakers. I am using a table where the headers are the AttendanceTakers and the rows are Students and each cell has a RadioButton. It is basically a double array of RadioButtons. My problem is I can't get it to post.
My AttendanceTaker class
public class SessionAttendanceTaker
{
public int Id { get; set; }
[ForeignKey("Session")]
public int SessionId { get; set; }
public Session Session { get; set; }
[Display(Name="Attendance Taker")]
[ForeignKey("User")]
public string AttendanceTakerId { get; set; }
[Display(Name = "Attendance Taker")]
public User User { get; set; }
public List<Student> Students { get; set; }
}
And the Student that is in the course class
public class StudentSession
{
public int Id { get; set; }
[ForeignKey("Session")]
[DisplayName("Session")]
public int SessionId { get; set; }
public Session Session { get; set; }
[ForeignKey("Student")]
[DisplayName("Student")]
public int StudentId { get; set; }
public Student Student { get; set; }
[DisplayName("Credits Awarded")]
public int Credit { get; set; }
}
Student class
public class Student
{
public int Id { get; set; }
[ForeignKey("User")]
public string UserId { get; set; }
[DisplayName("Name")]
public virtual User user { get; set; }
public Student()
{
}
}
The View
#using (Html.BeginForm())
{
<div class="form-horizontal">
<table>
<thead>
<tr>
<th> Name </th>
#{
foreach (var attendanceTaker in Model.SessionAttendanceTakers)
{
<th>#attendanceTaker.User.LastName, #attendanceTaker.User.FirstName </th>
}
}
</tr>
</thead>
<tbody>
#{
//See https://stackoverflow.com/questions/7667495/mvc-radiobuttons-in-foreach to try and clean the foreach
foreach (var studentSession in Model.StudentSessions)
{
<tr>
<td>
#studentSession.Student.User.LastName, #studentSession.Student.User.FirstName
</td>
#foreach (var attendanceTaker in Model.SessionAttendanceTakers)
{
#Html.EditorFor(Model => Model.SessionAttendanceTakers, "StudentsToAttendanceTakersModel", "" + studentSession.StudentId, new { htmlAttributes = new { #class = "form-control" } })
}
</tr>
}
}
</tbody>
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Assign" class="btn btn-default" />
</div>
</div>
</div>
}
and EditorTemplate
#model IEnumerable<SessionAttendanceTaker>
#using Attendance.Models
<td>
#Html.RadioButtonFor(model => model, new { htmlAttributes = new { #class = "form-control" } })
</td>
As an aside I would love to get rid of the foreaches as per this post but since I don't know how many attendance takers or students there will be until runtime I can't figure out how to do that besides for just moving them to the Editor and I don't see a point to that.
Also the Controller
[HttpPost]
public ActionResult Assign(StudentsToAttendanceTakersModel model)
{
return RedirectToAction("Index");
}
I have a breakpoint on the return and the attendanceTakers is null and Student sessions has a count of 0.
Additionally, using FormCollection
public ActionResult Assign(FormCollection o)
only gives me the Students who's RadioButton was clicked but not the AttendanceTaker. If more info is needed let me know. Thanks.
EDIT
Model
public class StudentsToAttendanceTakersModel
{
public IEnumerable<StudentSession> StudentSessions { get; set; }
public IEnumerable<SessionAttendanceTaker> SessionAttendanceTakers { get; set; }
public StudentsToAttendanceTakersModel() { }
}
You're creating radio buttons which do not relate to your model, and you're trying to bind them to a complex object (SessionAttendanceTaker) - a radio button posts back a simple value (and you are not even giving the radio buttons a valid value - the 2nd parameter of RadioButtonFor() is the value).
You are editing data, so you should start by creating view models which represent what you want to display in the view.
public class StudentVM
{
public int ID { get; set; }
public string Name { get; set; }
[Required(ErrorMessage = "Please select an attendance taker")]
public int? SelectedAttendanceTaker { get; set; }
}
public class AttendanceTakerVM
{
public int ID { get; set; }
public string Name { get; set; }
}
public class StudentAttendanceTakersVM
{
public List<StudentVM> Students { get; set }
public IEnumerable<AttendanceTakerVM> AttendanceTakers { get; set; }
}
So that your view will be
#model StudentAttendanceTakersVM
....
#using (Html.BeginForm())
{
<table>
<thead>
<tr>
<th>Student</th>
#foreach(var taker in Model.AttendanceTakers)
{
<th>#taker.Name</th>
}
<th></th>
</tr>
</thead>
<tbody>
#for(int i = 0; i < Model.Students.Count; i++)
{
<tr>
<td>
#Model.Students[i].Name
#Html.HiddenFor(m => m.Students[i].ID)
#Html.HiddenFor(m => m.Students[i].Name)
</td>
#foreach(var taker in Model.AttendanceTakers)
{
<td>#Html.RadioButtonFor(m => m.Students[i].SelectedAttendanceTaker, taker.ID, new { #class = "form-control" })</td>
}
<td>#Html.ValidationMessageFor(m => m.Students[i].SelectedAttendanceTaker)</td>
</tr>
}
</tbody>
</table>
<input type="submit" ... />
}
Your GET method will then initialize an instance of you view model and pass it to the view, for example, for a 'Create' method
public ActionResult Create()
{
var students = db.Students.Select(x => new StudentVM
{
ID = x.Id,
Name = x.User.FirstName + " " + x.User.LastName // adjust as required
}).ToList();
var attendanceTakers = db.SessionAttendanceTakers.Select(x => new AttendanceTakerVM
{
ID = x.Id,
Name = x.User.FirstName + " " + x.User.LastName // adjust as required
});
StudentAttendanceTakersVM model = new StudentAttendanceTakersVM
{
Students = students,
AttendanceTakers = attendanceTakers
};
return View(model);
}
And the POST method will be
public ActionResult Create(StudentAttendanceTakersVM model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// loop through model.Students to get the ID of the Student and its selected AttendanceTaker
// initialize the data models and save to the database
return RedirectToAction("Index");
}

How to get RadioButton values from form?

I have a form that writes out an Approve/Deny radio button for each record. I'm trying to figure out how to use the HttpPost to loop through each and determine if the radio button is selected and if so, which one was selected.
Doing some research I see that some use the Form collection for the form and in one example I found where the user used the forms ViewModel (which is what I normally do). However, when I try either one I'm coming up empty handed.
Here is my form. I'm writing out each record in a List to a table. I've tried both the Html.RadioButton and Html.RadioButtonFor to create them. I also have a comments textbox underneath the table where someone can put in some comments. Here is a snippet of the View.
<tbody>
#foreach (var item in Model.listPendingExceptions)
{
<tr>
<td>#Html.RadioButton("rdo" + item.RID, "A")</td>
<td>#Html.RadioButton("rdo" + item.RID, "D")</td>
<td>#item.Shift_Date.ToShortDateString()</td>
</tr>
}
</tbody>
#Html.TextAreaFor(m => m.ExceptionComment, new { cols = 200, #rows = 4, #maxlength = "100", #class = "form-control", #placeholder = "100 character limitation", #autofocus = "autofocus" })
In my HttpPost I've tried using the form collection. However, what I've found is to look in the AllKeys list. When I view my Post the only thing in the AllKeys is the comment's TextBox value.
When I use a ViewModel in the HttpPost, the list of exceptions that I used to populate the table in the View is NULL. I would expect that since I didn't store the list in a hidden field.
How can I loop through each record, determine which if any radio button has been selected, as well as get the text from the Comments textbox?
UPDATE for EditTemplate
I created the folder structure for EditorTemplates in the Views.
I already had a ViewModel with a List of Exceptions but I did move the SelectedApproval from the main VM to the list of Exceptions.
public class ReportPendingExceptionsViewModel
{
public List<PendingException> listPendingExceptions { get; set; }
public bool IsAdmin { get; set; }
[Required(ErrorMessage = "*Required")]
public string ExceptionComment { get; set; }
}
public class PendingException
{
public int RID { get; set; }
public DateTime Shift_Date { get; set; }
public string Shift_Text { get; set; }
public string Emp_Id { get; set; }
public string Emp_Name { get; set; }
public string Last_Name { get; set; }
public string First_Name { get; set; }
public string Comment_Text { get; set; }
public string SelectedApproval { get; set; }
}
I then created a Razor View for the Table rows.
#model ProjectName.Models.ViewModels.PendingException
<tr>
<td>#Html.RadioButtonFor(e=>e.SelectedApproval,"A")</td>
<td>#Html.RadioButtonFor(e => e.SelectedApproval, "D")</td>
<td>#Model.Shift_Date.ToShortDateString()</td>
<td>#Model.Emp_Name</td>
<td>#Model.Shift_Text</td>
<td>#Model.Comment_Text</td>
<td></td>
</tr>
I then updated my main View to use the EditFor.
<thead>
<tr>
<th style="width:80px;">Approve</th>
<th style="width:80px;">Deny</th>
<th>Shift Date</th>
<th>Employee</th>
<th>Schedule</th>
<th>Comments</th>
<th></th>
</tr>
</thead>
<tbody>
#Html.EditorFor(f => f.listPendingExceptions)
</tbody>
However, when I run it, all I get is the RID values. So, I must be missing something. Here is the output from the View Source.
Did I miss a step?
public class ExceptionModel
{
public int Id { set; get; }
public bool IsApproved { set; get; }
public DateTime ShiftDate { set; get; }
}
public class MainModel
{
public string Comment { set;get;}
public List<ExceptionModel> lst_Exception { set;get;}
}
//this is get request action method
public ActionResult Create()
{
MainModel model = new MainModel();
model.lst_Exception = new List<ExceptionModel>()
{
new ExceptionModel() {Id = 1,IsApproved = false, ShiftDate = DateTime.Now},
new ExceptionModel() {Id = 2,IsApproved = false, ShiftDate = DateTime.Now},
new ExceptionModel() {Id = 3,IsApproved = false, ShiftDate = DateTime.Now}
};
return View(model);
}
//this is view for action method
#model MainModel
#using(Html.BeginForm())
{
<table>
<thead>
<tr>
<th>Approve</th>
<th>Deny</th>
<th>Shift Date</th>
</tr>
</thead>
<tbody>
#for (var item = 0; item < Model.lst_Exception.Count(); item++)
{
<tr>
<td>#Html.RadioButtonFor(model=>model.lst_Exception[item].IsApproved, "Approve")</td>
<td>#Html.RadioButtonFor(model=>model.lst_Exception[item].IsApproved, "Deny")</td>
<td><span>#Model.lst_Exception[item].ShiftDate</span>
#Html.HiddenFor(model => model.lst_Exception[item].ShiftDate})
</td>
</tr>
}
</tbody>
</table>
#Html.TextBoxFor(model=>model.Comment)
<input type="Submit" value="Submit" />
}
//this is Post action method
[HttpPost]
public ActionResult Create(MainModel model)
{
//here you can loop through model.lst_Exception to get the select values
//from the view
}
It is very easy to do this with Editor Templates.
Start with creating a view model for pending exception items
public class ExceptionVm
{
public int Id { set; get; }
public bool? IsApproved { set; get; }
public DateTime ShiftDate { set; get; }
}
and in your main view model, you will add a collection property which of of type
List<ExceptionVm>.
public class MyViewModel
{
public string Comment { set;get;}
public List<ExceptionVm> PendingExceptions { set;get;}
public MyViewModel()
{
PendingExceptions = new List<ExceptionVm>();
}
}
And in your GET action you initialize the view model object, load the PendingExceptions property
public ActionResult Create()
{
var vm = new MyViewModel();
vm.ExceptionVms = new List<ExceptionVm>()
{
new ExceptionVm() {Id = 1, ShiftDate = DateTime.Now.AddDays(-3)},
new ExceptionVm() {Id = 2, ShiftDate = DateTime.Now.AddDays(-2)},
new ExceptionVm() {Id = 3, ShiftDate = DateTime.Now.AddDays(-1)}
};
return View(vm);
}
Now, let's create an editor template. Create a new directory called EditorTemplates under ~/Views/YourControllerName/ or ~/Views/Shared/ and add a new razor view under that. Give the file the same name as our view model class, ExceptionVm.cshtml
Now add the below code to the editor template view. This basically render the 2 radio buttons and the date
#model ExceptionVm
<tr>
<td>#Html.RadioButtonFor(b=>b.IsApproved, true)</td>
<td>#Html.RadioButtonFor(b => b.IsApproved, false) </td>
<td> #Model.ShiftDate #Html.HiddenFor(x=>x.Id) </td>
</tr>
Now go to your main view, which is strongly typed to our MyViewModel class, and call the Html.EditorFor helper method and pass the PendingExceptions collection property to that
#model MyViewModel
#using(Html.BeginForm())
{
<table class="table">
<tbody>
#Html.EditorFor(f => f.PendingExceptions)
</tbody>
</table>
#Html.TextBoxFor(f => f.Comment)
<input type="Submit" value="Submit" class="btn btn-default" />
}
The call to the EditorFor will render a table row for each item in the PendingExceptions collection. When you submit the form, you can use the same MyViewModel class as the parameter and inspect the PendingExceptions property, iterate through each item and see whether it is true or false or null(if they have not selected anything)
[HttpPost]
public ActionResult Create(MyViewModel model)
{
// check model.PendingExceptions collection and each items IsApproved prop value
// to do : return something
}
If you do not want to allow null selection, change the IsApproved property type from bool? to bool

Way to Edit Properties in a List collection in MVC

public class VerifyLicensorModel
{
public int TempId { get; set; }
public string Licensor { get; set; }
public string Address { get; set; }
public int LicensorId { get; set; }
public int ActionId { get; set; }
public int ReferenceId { get; set; }
}
This is my model and i am getting this list as a result of excel import and i am asking user to verify the data
So for each licensor users should select an action [ actionId ] and there is a reference based on action user selcted [ ReferenceId ] as well
So to pick ActionId and ReferenceId i am using a modal popup which allow user to select action and referenceID [ The process of deciding these 2 params is but complex include a lot of conditional checks and parallel selection of inputs ,
Example First decide liceonsor is new or existing . If decision is existing find existing id , related addressses. Then decide use new address or replace existing address . If decision is to replace existing select the one to replace
Thats why planning tokeep its logic as a seprate one and return with result only ie 2 Ids actionid and referenceID]
On submission how can i set the values of these 2 properties
My view i am planning like this
#model IList<ApplicationLab.Models.VerifyLicensorModel>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<table class="table">
<tr>
<th>
Licensor
</th>
<th>
Address
</th>
<th>
Action
</th>
<th>
Verify
</th>
</tr>
#for (int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
#Html.HiddenFor(modelItem => Model[i].TempId)
#Html.HiddenFor(modelItem => Model[i].LicensorId)
#Html.DisplayFor(modelItem => Model[i].Licensor)
</td>
<td>
#Html.TextAreaFor(modelItem => Model[i].Address)
</td>
<td>
#Html.HiddenFor(modelItem => Model[i].ActionId)
#Html.HiddenFor(modelItem => Model[i].ReferenceId)
</td>
<td>
<a >Verify</a> // onclick plannig to show popup
</td>
</tr>
}
</table>
<div class="form-group">
<div class="col-md-10" style="text-align:center;">
<input type="submit" value="Submit" class="btn btn-primary" />
#Html.ActionLink("Cancel", "LicenseImport", "Import", new { #id = #ViewBag.EditionId }, new { #class = "btn btn-link" })
</div>
</div>
}
So is my approach is correct ? if so how can i set the selected row property ?
Or is any better approach or examples to suggest to implement the same?
Typically, your view model should be a representation of the page and the elements on the page. If this was to be used on the modal, You could use a partial view template.
You need to create new properties of List to represent the options available for ActionIds and ReferenceIds and pass as parameters for use in the helper functions #Html.DropDownListFor in the view
public class VerifyLicensorModel
{
public int TempId { get; set; }
public string Licensor { get; set; }
public string Address { get; set; }
public int LicensorId { get; set; }
public int ActionId { get; set; }
public List<SelectListItem> ActionOptions { get; set; }
public int ReferenceId { get; set; }
public List<SelectListItem> ReferenceOptions { get; set; }
}
On the View:
#Html.DropDownListFor(m => m.ActionId, Model.ActionOptions);
#Html.DropDownListFor(m => m.ReferenceId, Model.ReferenceOptions);

Model binding generic list is null in asp.net mvc

I am binding objects in a razor foreach in the index.html:
VIEW
#using (Ajax.BeginForm("Save", "Unit", new AjaxOptions { OnSuccess = "onSuccess" }))
{
<button type="submit" class="btn btn-default" id="saveUnits"><i class="fa fa-save"></i></button>
<table>
<tbody>
#foreach (var item in Model)
{
<tr>
#Html.HiddenFor(modelItem => item.UnitId)
<td>
#Html.EditorFor(modelItem => item.Name)
</td>
<td>
#Html.EditorFor(modelItem => item.ErrorText)
</td>
</tr>
}
</tbody>
</table>
}
I have grabbed the data sent to my action parameter with fiddler and got this:
item.UnitId=5&
item.Name=111111111111&
item.ErrorText=fsdddddddddddddddd+&
item.UnitId=5&
item.Name=+&
item.ErrorText=dddddd+&
ACTION
public ActionResult Save(List<Unit> units )
{
return new EmptyResult();
}
VIEWMODEL
public class Unit
{
[HiddenInput(DisplayValue = false)]
public int UnitId { get; set; }
[DataType(DataType.MultilineText)]
public string Name { get; set; }
[DataType(DataType.MultilineText)]
public string ErrorText { get; set;
}
Why is my units instance null? The properties match so they should be bound!
Did I overlook something?
You need to use a for loop not a foreach loop. Also, it would be better to make your Model class have a property which is a collection.
Your model could be something like:
public class UnitsViewModel
{
public List<Unit> Units { get; set; }
public class Unit
{
[HiddenInput(DisplayValue = false)]
public int UnitId { get; set; }
[DataType(DataType.MultilineText)]
public string Name { get; set; }
[DataType(DataType.MultilineText)]
public string ErrorText { get; set; }
}
}
And you could do the following in your cshtml:
#for (int i = 0; i < Model.Count; i++)
{
<tr>
#Html.HiddenFor(m => m.Units[i].UnitId)
<td>
#Html.EditorFor(m => m.Units[i].Name)
</td>
<td>
#Html.EditorFor(m => m.Units[i].ErrorText)
</td>
</tr>
}

Model Binding on dynamic created DropDownList

Model1:
public class Model1
{
public int Id { get; set; }
public int VariantId { get; set; }
public string Name { get; set; }
public string Language { get; set; }
public IList<Model2> ListModel2{ get; set; }
public VmSysVariantResource()
{
ListModel2=new List<Model2>();
}
}
Model2:
public class Model2
{
public int Id { get; set; }
public int ParamterId { get; set; }
public int ValueId { get; set; }
[Required]
public string SelectedValue { get; set; }
public string ParameterName { get; set; }
public IList<PossibleValue> Values { get; set; }
public IList<SelectListItem> ValuesSelectListItem
{
get
{
var list = (from item in Values
select new SelectListItem()
{
Text = item.ValueName,
Value = item.Id
}).ToList();
return list;
}
set { }
}
}
PossibleValue:
public class PossibleValue
{
public int Id { get; set; }
public string ValueName { get; set; }
}
Now explication about what I want to do:
Suppose we have one Model "Model1". This model have a list of Model2 .
Model2 contains ParameterName and a list of possibleValues.
Now I have form like this:
#using (Ajax.BeginForm("Action", "Controller", null,
new AjaxOptions
{
HttpMethod = "POST"
}))
{
<table class="sample" style="margin: 0 auto; width: 400px">
<tr>
<td>
#Resource.Name:
</td>
<td> #Html.TextBoxFor(c => c.Name)
</td>
</tr>
#foreach (var item in Model.ParamtersToValue)
{
<tr>
<td>
#item.Parameter:
</td>
<td>
#Html.DropDownList("ParamtersToValue", #item.ValuesSelectListItem)
</td>
</tr>
}
</table>
<input type="submit" value="Submit"/>
}
On post method in controller I want to get Model1 with List of ListModel2.
This is my post method:
[HttpPost]
public ActionResult MyAction(Model1 obj)
{ }
I was expected to get on post method Model1 with ListModel2 that will contain SelectedValue chosen from dropdown, but I am not shure how to construct dropdown inside foreach block to enforce model binding to work.
Any suggestion will be appreciated.Thanks..
The default model binder works with indexes. So, to have it model bind, change your foreach loop to a for loop and render the control with an index. Like this:
#for (int i = 0; i < Model.ParamtersToValue.Count; i++) {
#* .. stuff here *#
<td>
#Html.DropDownListFor(x => Model.ParametersToValue[i], Model.ParametersToValue[i].ValuesSelectListItem)
</td>
#* .. stuff here *#
}
This will result in element names such as [0].ParametersToValue, [1].ParametersToValue, etc. The model binder will bind them in order of appearance into an enumerable of your choice.

Categories