ViewModel post back a null Collection in asp.net mvc 5 - c#

public class SaleItem
{
public int Id { get; set; }
public string Name { get; set; }
}
public class SalesDBContext
{
public static List<SaleItem> GetItems()
{
var items = new List<SaleItem>(){
new SaleItem{Id=1,Name="Soap"},
new SaleItem{Id=2,Name="Milk Power"},
new SaleItem{Id=3,Name="Toothpaste"},
new SaleItem{Id=4,Name="Ice Cream"}
};
return items.ToList();
}
}
public class SalesViewModel
{
public string Item { get; set; }
public List<SaleItem> itemlist { get; set; }
}
I have above SalesViewModel class and SalesDBContext for dummy data generation. I want to add items to a list selected from a dropdown. For that I have created the following view:
#model MVCDropdown.Models.SalesViewModel
#using MVCDropdown.Models
<form method="post">
<p>
#Html.DropDownListFor(model => model.Item, new SelectList(ViewBag.Items, "Id", "Name"), "--select--")
<input type="submit" value="Add" />
</p>
<p>
#if (Model.itemlist != null)
{
<table>
#foreach (var s in Model.itemlist)
{
<tr>
<td>#s.Name</td>
</tr>
}
</table>
}
</p>
</form>
The Controller
[HttpGet]
public ActionResult Index()
{
SalesViewModel model = new SalesViewModel
{
Item = "",
itemlist = new List<SaleItem>()
};
PopDrodown();
return View(model);
}
[HttpPost]
public ActionResult Index(SalesViewModel vm)
{
var t = SalesDBContext.GetItems().Where(x => x.Id == Convert.ToInt32(vm.Item)).FirstOrDefault();
vm.itemlist.Add(t);
PopDrodown();
return View(vm);
}
private void PopDrodown()
{
ViewBag.Items = SalesDBContext.GetItems();
}
Items added to the list should be displayed in a table under the dropdown. However, when I post post back a selected item from the dropdown by pressing add, it returns a null itemlist to the controller, and previously added items are not there. How can I avoid this problem?

You only have a single input element inside your HTML form: that's the dropdown. So the only value that is sent to your controller action when the form is submitted is the Item property. If you want to send the collection you could use hidden fields:
<table>
#for (var i = 0; i < Model.itemlist.Count; i++)
{
<tr>
<td>
#Html.HiddenFor(x => x.itemlist[i].Id)
#Html.HiddenFor(x => x.itemlist[i].Name)
#Html.DisplayFor(x => x.itemlist[i].Name)
</td>
</tr>
}
</table>
Obviously if the user is not supposed to edit the values inside the HTML form, then a much better approach is to have a POST view model that will contain only the properties that the user can modify and you will retrieve the collection elements from the same place you retrieved them in the GET action.

Related

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

Add DropDownList selected item to a list dynamically in .NET MVC

I have a DropDownList in my razor view, it is getting the values from my model which is populated from Data Base. What I want to achieve is when selecting an option from that DropDownList and clicking a button, show a list of this options selected one below the other, all in the same view and next to every item a link for edit or delete. Then in another screen I can add this final "list" to my data base. This is part of my code:
Model
public class SchedulerViewModel
{
public string Frequency { get; set; }
public string SelectedReportValue { get; set; }
public IEnumerable<SelectListItem> ReportList
{
get
{
List<string> reportlist = DataAccess.retrieveReportList();
return new SelectList(reportlist);
}
}
public string NewCronName { get; set; }
[Display(Name = "OrderDate")]
[DataType(DataType.Date)]
public System.DateTime OrderDate { get; set; }
public IEnumerable<SelectListItem> FrequencyList
{
get
{
List<string> frequencyList = DataAccess.retrieveFrequencyList();
return new SelectList(frequencyList);
}
}
}
Controller
[HttpGet]
public ActionResult AddCron()
{
return View();
}
[HttpPost]
public ActionResult AddCron(SchedulerViewModel model)
{
return View(model);
}
View
<script>
function myFunction() {
$("#div1").load('#Html.LabelFor(model => model.SelectedReportValue)');
}
</script>
.
.
.
<tr>
<td><h5>Frequency: </h5></td>
<td> #Html.DropDownListFor(model => model.Frequency, Model.FrequencyList)</td>
<td><h5>First Run Date: </h5></td>
<td> #Html.TextBoxFor(m => m.OrderDate, new { #class = "form-control datepicker", #id = "datepicker" })</td>
</tr>
<tr class="blank_row">
</tr>
<tr>
<td><p><b>Add Report</b></p></td>
</tr>
<tr>
<td><h5>Report :</h5></td>
<td>#Html.DropDownListFor(model => model.SelectedReportValue, Model.ReportList)</td>
<td><input type="submit" name="Add" value="Add" onclick="myFunction()" /></td>
</tr>
<div id="div1"></div>
I can see the returning selected value in my model in the [HttpPost] method, but still can't make it work in the view. Any suggestion?
and this is what I'm trying to get...
Try get rid of jquery, just use razor :
<div id="div1">
#Html.LabelFor(model => model.SelectedReportValue)
</div>
Base of your comment I suggest you can use jquery to trigger change event and add item to the list manually :
$( "#filetype" ).change(function() {
//code handler to add to list
});

Sent checked html table view back to client when invalid

When my Model.State is NOT valid I want to return the view WITH the checked checkboxes.
How would you change my code? Is it possible at all with my approach?
VIEW
#model ListTest.Models.PeopleListViewModel
#{
var hasMoreThanOnePerson = #Model.People.Count > 1;
}
#Html.BeginForm("Save", "Home")
{
#Html.ValidationSummary(false)
<table>
#foreach (var item in Model.People)
{
<tr>
#if (hasMoreThanOnePerson)
{
<td>
<input type="checkbox" name="SelectedIds" value="#item.PersonId" />
</td>
}
else
{
#Html.Hidden("SelectedIds", item.PersonId)
}
<td>
<input type="text" value="#item.Name" />
</td>
</tr>
}
</table>
<input type="submit" value="Save" />
}
VIEWMODEL
public class PeopleListViewModel
{
public PeopleListViewModel()
{
SelectedIds = new int[] { };
}
[MinLength(1, ErrorMessage = "Minimum one person must be selected!")]
public int[] SelectedIds { get; set; }
public List<Person> People { get; set; }
}
CONTROLLER
public ActionResult Index()
{
var people = new List<Person> {
new Person { Name = "Horst", PersonId = 10 },
new Person { Name = "Michael", PersonId = 20}
};
return View(new PeopleListViewModel { People = people });
}
[HttpPost]
public ActionResult Save(PeopleListViewModel viewModel)
{
if (ModelState.IsValid)
{
}
viewModel.People = new List<Person> { new Person { Name = "Horst", PersonId = 10 }, new Person { Name = "bernarnd", PersonId = 20 } };
return View("Index", viewModel);
}
Few things to change
Firstly, change your People model to include an IsSelected property, we want to do away with your SelectedIds method
Secondly, in order to post the data from the client, we need to rewrite your foreach to be a for so the fields are indexed correctly, we'll also add some extra HiddenFors for the properties that you want to keep (because we're no longer re-populating your model when validation fails), your table will be:
<table>
#for (int i = 0; i < Model.People.Count; i++)
{
<tr>
#Html.HiddenFor(m => m.People[i].PersonID)
#Html.HiddenFor(m => m.People[i].Name)
#if (hasMoreThanOnePerson)
{
<td>
#Html.CheckBoxFor(m => m.People[i].IsSelected)
</td>
}
else
{
#Html.HiddenFor(m => m.People[i].IsSelected)
}
<td>
<input type="text" value="#Model.People[i].Name" />
</td>
</tr>
}
</table>
Finally, we don't reassign your People list in your action method if validation fails just return the model that was passed in. If you want to get the selected people, use the code I've added below. Also, because we don't have the SelectedIds anymore we can perform our own validation:
[HttpPost]
public ActionResult Save(PeopleListViewModel viewModel)
{
List<People> selected = viewModel.People
.Where(p => p.IsSelected)
.ToList();
if (selected.Any())
{
//it's valid
List<int> selectedIds = selected
.Select(s => s.PersonID)
.ToList();
}
return View("Index", viewModel);
}

Submitting an actionLink to a form mvc4

We have a list of action links
Partial View
#foreach (var item in Model.Regions) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.RegionName)
</td>
<td>
<input type="submit" value="Select" />
</td>
#Html.HiddenFor(modelItem => Model.Id)
</tr>
}
</table>
I assume that this isn't the correct way to do this, but if you could point me in the right direction it would be appreciated. I want to submit this data into an existing form
Region View
#using (Html.BeginForm()){
<fieldset>
#Html.Partial("_RegionsPartial");
<legend>Create new region</legend>
<ol>
<li>#Html.LabelFor(m => m.RegionName)</li>
<li>#Html.EditorFor(m => m.RegionName)</li>
</ol>
<input type="submit" value="Next" />
#Html.HiddenFor(model => model.RegionId)
</fieldset>
}
So you can either submit a new one or submit an existing one. Im not sure how to get the id of an existing one into my model. Here is the controller:
public ActionResult Region()
{
var model = new WizardModel();
var getRegions = _facade.FetchRegion();
model.Regions = getRegions;
return View(model);
}
[HttpPost]
public ActionResult Region(WizardModel model)
{
if (model.RegionName != null)
{
var newRegion = _facade.CreateRegion(model.RegionName);
model.RegionId = newRegion.Id;
}
else
{
model.RegionName = _facade.FetchRegion(model.RegionId).RegionName;
}
TempData["suburbModel"] = model;
return RedirectToAction("Suburb");
}
Thanks for taking the time
So heres my example of passing an instance of a model. I've got a view with many courses so I need to click a button and fire an action, thus carrying all data (including relevant ID) of the course clicked. So in the end I carry the instance I need with the hidden fields.:)
My course model...
public class CourseModel
{
public int RecordId { get; set; }
public string StudentNameField { get; set; }
public string SubjectField { get; set; }
public string CatalogField { get; set; }
public string SectionField { get; set; }
public string InstrNameField { get; set; }
public string MtgStartField { get; set; }
public string MtgEndField { get; set; }
}
My main View...Called "CourseList" in Views folder
<div id="container">
<div class="selectLabel">Select a Course:</div><br />
#foreach (var item in Model)
{
#Html.DisplayFor(model=>item)
}
</div>
My Display template - Its a view called "CourseModel" in Shared\DisplayTemplates ...For your display template, you could make a unique model for existing & new. Using your "existing" model in the displaytemplate, it results in multiple forms, each using a button type=submit to submit the form with model instance. Use CSS to model the button like a link. If you still need to use actionlink, carry the iD as one of the params.
#using LecExamRes.Helpers
#model LecExamRes.Models.SelectionModel.CourseModel
#using (Html.BeginForm("CourseList", "Home", null, FormMethod.Post))
{
<div class="mlink">
#Html.AntiForgeryToken()
#Html.EncryptedHiddenFor(model => model.RecordId)
#Html.EncryptedHiddenFor(model => model.CatalogField)
#Html.EncryptedHiddenFor(model => model.SectionField)
#Html.EncryptedHiddenFor(model => model.SubjectField)
#Html.EncryptedHiddenFor(model => model.InstrNameField)
#Html.EncryptedHiddenFor(model => model.MtgStartField)
#Html.EncryptedHiddenFor(model => model.MtgEndField)
<p>
<input type="submit" name="gbtn" class="groovybutton" value="#Model.SubjectField - #Model.CatalogField - #Model.SectionField : #Model.InstrNameField">
</p>
</div>
}
My controller, Courselist [POST] Action...
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult CourseList(SelectionModel.CourseModel model)
{
//....do something with my model
}

How do i save a list of objects in my ViewModel using EF DB Context?

I have a model women which is part of my db context and a view Model womenEditmodel which conatins a list of women items. I am using a partialview to loop through this list and display an editable Grid or List in my view. These are my models:
public class Women
{
public string ID { get; set; }
public string FirstName {get; set;}
public string LastName {get; set;}
}
public class WomenEditModel
{
public List<Women> WomenList { get; set; }
}
My view has this loop for injecting into my view rows for the women records
#foreach (Women women in Model.Womens)
{
Html.RenderPartial("WomenEditor", women);
}
which i display using a table. So now users can edit the list and post or save changes.
my partialview looks like:
#model XXX.Models.Women
#using (Html.BeginCollectionItem("Women")) {
<td>
#Html.HiddenFor(model => model.ID)
</td>
<td>
#Html.TextBoxFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</td>
<td>
#Html.TextBoxFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</td>
}
My http post action method looks like this
[HttpPost]
public ActionResult PostWomen(WomenEditModel model)
{
/*I need to iterate through the returned list and save all
changes to the db.*/
return RedirectToAction("Index");
}
How do i loop through the model WomenEditModel recieived at the post action method and save changes to the women list back to db?
Thanks in advance!!
I just got back to my machine. Here is how you can acheive it, if you haven't already.
My Action which renders the list
public ActionResult Index()
{
List<Women> womens = new List<Women>
{
new Women
{
Id=1,
FirstName = "Women1",
LastName = "Lastname1"
},
new Women
{
Id=2,
FirstName = "Women2",
LastName = "Lastname2"
}
};
WomenList womenList=new WomenList();
womenList.Womens = womens;
return View(womenList);
}
The action where the list is posted.
public ActionResult SaveWomens(List<Women> womenList)
{
System.Diagnostics.Debugger.Break();
//Your save logic goes here
return View("");
}
Partial View (Dont know whether it is required)
#model List<MvcApplication1.Models.Women>
<table>
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Html.TextBoxFor(m => m[i].Id)
</td>
<td>#Html.TextBoxFor(m => m[i].FirstName)
</td>
<td>#Html.TextBoxFor(m => m[i].LastName)
</td>
</tr>
}
</table>
And here is the view
#model MvcApplication1.Models.WomenList
#{
ViewBag.Title = "Home Page";
}
#section featured {
}
#using (Html.BeginForm("SaveWomens", "Home", FormMethod.Post))
{
#Html.Partial("_Women", Model.Womens)
<input type="submit" value="save" />
}
You can achieve it as follows
foreach (var item in womenList)
{
var obj = new Womens();
//Assign values to obj for eg: obj.prop = item.prop
dataContext.Womens.AddObject(obj);
}
dataContext.SaveChanges();

Categories