When I'm posting my form my list of objects is always empty and I can't figure out why. I searched and read through the dozen similar questions and still no go. The fields in my model are posted back with the exception of the TimeAdds list. Any help is appreciated.
My controller
public ActionResult TimeAdd()
{
TimeAddModel model = new TimeAddModel();
model.StartDate = DateTime.Now;
model.EndDate = DateTime.Now;
model.TypeId = 1;
model.TimeAdds = new List<TimeAdd>();
return View(model);
}
[HttpPost]
public ActionResult TimeAdd(TimeAddModel model)
{
if (Request.Form["dateRange"] != null) {
ModelState.Clear();
TimeAdd t = new TimeAdd();
t.Id = Guid.NewGuid();
t.TypeId = model.TypeId;
model.TimeAdds.Add(t);
}
else {
//save
}
return View(model);
}
My Class
public class TimeAdd
{
[Key]
public Guid Id { get; set; }
public int TypeId { get; set; }
}
My Model
public class TimeAddModel
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int TypeId { get; set; }
public List<TimeAdd> TimeAdds { get; set; }
public TimeAddModel()
{
this.TimeAdds = new List<TimeAdd>();
}
}
and my page
#model metrosales.Models.TimeAddModel
#using metrosales.Data.TimeOff.Model
<div class="container excess">
#using (Html.BeginForm("TimeAdd", "Service", FormMethod.Post)) {
<div class="row">
<div class="col-sm-6">
#Html.LabelFor(model => model.StartDate)
#Html.TextBoxFor(x => x.StartDate, "{0:yyyy-MM-dd}", new { #class = "form-control", #type = "date", #id = "StartDate", #name = "StartDate" })
</div>
<div class="col-sm-6">
#Html.LabelFor(model => model.EndDate)
#Html.TextBoxFor(x => x.EndDate, "{0:yyyy-MM-dd}", new { #class = "form-control", #type = "date", #id = "EndDate", #name = "EndDate" })
</div>
</div>
<input type="submit" class="btn text-center" name="dateRange" value="Submit1" />
<input type="submit" class="btn text-center" name="submit" value="Submit2" />
}
<div>
<br />
<table id="tblExcess" class="table" cellpadding="0" cellspacing="0">
<tr>
<th>ID</th>
<th>Start</th>
</tr>
#for (var i = 0; i < Model.TimeAdds.Count(); i++) {
<tr>
<td>#Html.HiddenFor(model => model.TimeAdds[i].Id)</td>
<td>#Html.TextBoxFor(model => model.TimeAdds[i].TypeId)</td>
</tr>
}
</table>
</div>
To be able the MVC perform the data binding correctly it is necessary to prepare the context in the TimeAdd view.
Therefore, to provide an index for each item in the TimeAdds list make following changes in the TimeAdd.cshtml:
#Html.Hidden("TypeId", Model.TypeId)
#for (var i = 0; i < Model.TimeAdds.Count(); i++)
{
#Html.Hidden("TimeAdds[" + i + "].Id", Model.TimeAdds[i].Id)
#Html.Hidden("TimeAdds[" + i + "].TypeId", Model.TimeAdds[i].TypeId)
<tr>
<td>#Html.TextBoxFor(model => Model.TimeAdds[i].Id)</td>
<td>#Html.TextBoxFor(model => Model.TimeAdds[i].TypeId)</td>
</tr>
}
Related
I want users to be able to filter data by a few different properties. The application works fine when you:
Only filter by the datetimes.
Only filter by all the other properties, excluding the datetimes
Or, filtering by one string property and one of the datetimes
For example, let's say I want to filter by type, branch and a datetime ascending, the parameters show in the URL but, the list shows all the data entries. But, if I just filter by type and datetime, type, branch and search or just datetime, the list filters correctly.
**Controller**
public async Task<ActionResult> Index(string requestType, string requestBranch,string subDateSort,string installDateSort, string searchCustomer, int page = 1)
{
IQueryable<string> typeQuery = from t in db.imperialWebRequests
select t.type;
IQueryable<string> branchQuery = from b in db.imperialWebRequests
select b.branchSite;
List<SelectListItem> SubDateSort = new List<SelectListItem>()
{
new SelectListItem() {Text="--Asc Or Desc--", Value=""},
new SelectListItem() {Text="Sub Date Ascending", Value="sub_date_asc"},
new SelectListItem() { Text="Sub Date Descending", Value="sub_date_desc"},
};
ViewBag.SubDateList = SubDateSort;
List<SelectListItem> InstallDateSort = new List<SelectListItem>()
{
new SelectListItem() {Text="--Asc Or Desc--", Value=""},
new SelectListItem() {Text="Install Date Ascending", Value="install_date_asc"},
new SelectListItem() { Text="Install Date Descending", Value="install_date_desc"},
};
ViewBag.InstallDateList = InstallDateSort;
ViewBag.TypeParam = requestType;
ViewBag.BranchParam = requestBranch;
ViewBag.SearchParam = searchCustomer;
ViewBag.InstallDateParam = installDateSort;
ViewBag.SubDateParam = subDateSort;
var webRequests = from wr in db.imperialWebRequests
select wr;
if (!string.IsNullOrEmpty(requestType))
{
webRequests = webRequests.Where(wr => wr.type == requestType);
}
else if (!string.IsNullOrEmpty(requestBranch))
{
webRequests = webRequests.Where(wr => wr.branchSite == requestBranch);
}
else if (!string.IsNullOrEmpty(searchCustomer))
{
webRequests = webRequests.Where(wr => wr.shiftsTextInput.Contains(searchCustomer));
}
else if (!string.IsNullOrEmpty(subDateSort))
{
switch (subDateSort)
{
case "sub_date_asc":
webRequests = webRequests.OrderBy(di => di.dateInserted);
break;
case "sub_date_desc":
webRequests = webRequests.OrderByDescending(di => di.dateInserted);
break;
}
}
else if (!string.IsNullOrEmpty(installDateSort))
{
switch (installDateSort)
{
case "install_date_asc":
webRequests = webRequests.OrderBy(dp => dp.datepicker);
break;
case "install_date_desc":
webRequests = webRequests.OrderByDescending(dp => dp.datepicker);
break;
}
}
else if (string.IsNullOrEmpty(requestType) && string.IsNullOrEmpty(requestBranch) && string.IsNullOrEmpty(searchCustomer) && string.IsNullOrEmpty(subDateSort) && string.IsNullOrEmpty(installDateSort))
{
webRequests = webRequests.OrderBy(di => di.dateInserted);
}
var requestVM = new ImperialFilterVM
{
RequestPerPage = 5,
ImperialWebRequests = webRequests,
CurrentPage = page
};
requestVM.types = new SelectList(await typeQuery.Distinct().ToListAsync());
requestVM.branches = new SelectList(await branchQuery.Distinct().ToListAsync());
requestVM.ImperialWebRequests = await webRequests.ToListAsync();
return View(requestVM);
}
**View Model**
public class ImperialFilterVM
{
public imperialWebRequest ImperialWebRequest { get; set; }
public List<imperialWebRequest> imperialWebRequestList { get; set; }
public IEnumerable<SelectListItem> types;
public string requestType { get; set; }
public IEnumerable<SelectListItem> branches { get; set; }
public string requestBranch { get; set; }
public string subDateSort { get; set; }
public string installDateSort { get; set; }
public string searchCustomer { get; set; }
public IEnumerable<imperialWebRequest> ImperialWebRequests { get; set; }
public int RequestPerPage { get; set; }
public int CurrentPage { get; set; }
public int PageCount()
{
return Convert.ToInt32(Math.Ceiling(ImperialWebRequests.Count() / (double)RequestPerPage));
}
public IEnumerable<imperialWebRequest> PaginatedWebRequests()
{
int start = (CurrentPage - 1) * RequestPerPage;
return ImperialWebRequests.Skip(start).Take(RequestPerPage);
}
}
**Route Config**
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "IWRControllerIndex",
url: "imperialWebRequests/Index/{id}/{requestType}/{requestBranch}/{subDateSort}/{installDateSort}/{searchCustomer}",
defaults: new { controller = "imperialWebRequests", action = "Index", id = UrlParameter.Optional, requestType = UrlParameter.Optional,
requestBranch = UrlParameter.Optional,subDateSort = UrlParameter.Optional,installDateSort = UrlParameter.Optional,searchCustomer = UrlParameter.Optional
}
);
}
}
**Index**
#model imperialWebInstallRequestsCRUD.Models.ImperialFilterVM
#{
ViewBag.Title = "Recent";
}
<h2>Recent Request Install/Change Outs</h2>
<div class="row">
<div class="col-md-9">
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.ImperialWebRequest.type)
</th>
<th>
#Html.DisplayNameFor(model => model.ImperialWebRequest.salesPerson)
</th>
<th>
#Html.DisplayNameFor(model => model.ImperialWebRequest.branchSite)
</th>
<th>
#Html.DisplayNameFor(model => model.ImperialWebRequest.shiftsTextInput)
</th>
<th>
#Html.DisplayNameFor(model => model.ImperialWebRequest.ExisitingCustomerCheckBox)
</th>
#Html.DisplayNameFor(model => model.ImperialWebRequest.datepicker)
</th>
<th>
#Html.DisplayNameFor(model => model.ImperialWebRequest.dateInserted)
</th>
<th></th>
</tr>
#foreach (var item in Model.PaginatedWebRequests())
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.type)
</td>
<td>
#Html.DisplayFor(modelItem => item.salesPerson)
</td>
<td>
#Html.DisplayFor(modelItem => item.branchSite)
</td>
<td>
#Html.DisplayFor(modelItem => item.shiftsTextInput)
</td>
<td>
#Html.DisplayFor(modelItem => item.ExisitingCustomerCheckBox)
</td>
<td>
#Html.DisplayFor(modelItem => item.datepicker)
</td>
<td>
#Html.DisplayFor(modelItem => item.dateInserted)
</td>
<td>
#Html.ActionLink("Details", "Details", new { id = item.requestId }) |
#Html.ActionLink("Delete", "Delete", new { id = item.requestId })
</td>
</tr>
}
</table>
<ul class="pagination">
#for (int i = 1; i <= Model.PageCount(); i++)
{
<li class="#(i == Model.CurrentPage ? "page-item active" : "page-item")">
<a class="page-link" href="#Url.Action("Index", new { page = i, requestType = ViewBag.TypeParam,
requestBranch = ViewBag.BranchParam, subDateSort = ViewBag.SubDateParam,
installDateSort = ViewBag.InstallDateParam, searchCustomer = ViewBag.SearchParam})">#i</a>
</li>
}
</ul>
</div>
<div id="sidebar" class="col-md-3">
#using (Html.BeginForm("Index", "imperialWebRequests", FormMethod.Get))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.requestType, "Request Type")
<div class="col-md-10">
#Html.DropDownListFor(model => model.requestType, Model.types, "Select A Type", new { #class = "form-control form-control-lg" })
#Html.ValidationMessageFor(model => model.requestType, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.requestBranch, "Request Branch")
<div class="col-md-10">
#Html.DropDownListFor(model => model.requestBranch, Model.branches, "Select A Branch", new { #class = "form-control form-control-lg" })
#Html.ValidationMessageFor(model => model.requestBranch, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.Label("Submitted Date")
<div class="col-md-10">
#Html.DropDownListFor(model => model.subDateSort, (IEnumerable<SelectListItem>)ViewBag.SubDateList,new { #class = "form-control form-control-lg" })
</div>
</div>
<div class="form-group">
#Html.Label("Install Date")
<div class="col-md-10">
#Html.DropDownListFor(model => model.installDateSort, (IEnumerable<SelectListItem>)ViewBag.InstallDateList, new { #class = "form-control form-control-lg" })
</div>
</div>
<div class="form-group">
<div class="col-md-10">
#Html.TextBoxFor(model => model.searchCustomer, new { placeholder = "Enter A Customer's Name", #class = "form-control" })
</div>
</div>
</div>
<button type="submit" value="Filter" class="btn btn-default">Search</button>
<span>|</span>
#Html.ActionLink("Back to List", "Index")
}
</div>
</div>
I have PartialView which display pricing information and allow users to enter promocode. when clicking the button on _ListItem.cshtml on the first time, the HttpPost method manage to grab the OrderDetailsViewModel.Details and process in the controller. on the second click of the button, it couldn't capture the data in the Model.
Here are my codes.
NewPurchaseViewModel
public class NewPurchaseViewModel{
public NewPurchaseViewModel()
{
Details = new OrderDetailsViewModel();
Shipping = new Address();
Billing = new Address();
}
public string Email { get; set; }
public string Name { get; set; }
...
public OrderDetailsViewModel Details { get; set; }
}
OrderDetailsViewModel
public class OrderDetailsViewModel{
public OrderDetailsViewModel()
{
Items = new List<ItemsOrderViewModel>();
Shipping = 0.00M;
}
public string Promocode { get; set; }
public string SubTotal{ get; set; }
public string Total{ get; set; }
public string Discount{ get; set; }
public string Shipping{ get; set; }
public List<ItemsOrderViewModel> Items { get; set; }
}
Info.cshtml
#model ~.ViewModels.NewPurchaseViewModel
...
#Html.Partial("~/Views/CheckOut/_ListItem.cshtml", Model.Details, new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "Details"} })
_ListItem.cshtml
#model Nutric.hive.eStore.ViewModels.OrderDetailsViewModel
#using (Ajax.BeginForm("Verify", "PromoCode", null, new AjaxOptions
{
HttpMethod = "POST",
UpdateTargetId = "divPromoCode",
InsertionMode = InsertionMode.Replace
}))
{
<table>
...
<tbody>
#for (var i = 0; i < Model.Items.Count; i++)
{
<tr>
<td>#Model.Items[i].ProductPackage.Name</td>
<td class="qty">#Model.Items[i].Quantity</td>
<td class="price">#Model.Items[i].ItemTotal</td>
#Html.HiddenFor(Model => Model.Items[i].ProductPackage.Id)
#Html.HiddenFor(Model => Model.Items[i].ProductPackage.Name)
#Html.HiddenFor(Model => Model.Items[i].ProductPackage.Description)
#Html.HiddenFor(Model => Model.Items[i].ProductPackage.ImageURL)
#Html.HiddenFor(Model => Model.Items[i].ProductPackage.Status)
#Html.HiddenFor(Model => Model.Items[i].ProductPackage.RetailPrice)
#Html.HiddenFor(Model => Model.Items[i].ProductPackage.TaxCode)
#Html.HiddenFor(Model => Model.Items[i].ProductPackage.PackageCode)
#Html.HiddenFor(Model => Model.Items[i].Quantity)
#Html.HiddenFor(Model => Model.Items[i].ItemTotal)
</tr>
}
...
<tr>
<td colspan="3">
<div class="col-lg-10">
<div class="input-group">
#Html.TextBoxFor(Model => Model.Promocode, new { #class = "form-control", maxlength = 15, placeholder = "Type Voucher Code Here...", type = "text" })
<span class="input-group-btn">
<input type="submit" class="btn btn-secondary" value="Apply !">
</span>
</div>
</div>
</td>
</tr>
</tbody>
</table>
#Html.HiddenFor(Model => Model.Discount)
#Html.HiddenFor(Model => Model.Shipping)
#Html.HiddenFor(Model => Model.Subtotal)
#Html.HiddenFor(Model => Model.Total)
}
#section Script{
#Scripts.Render("~/bundles/jqueryval")
#Scripts.Render("~/bundles/jquery")
}
PromoCodeController.cs
[HttpPost]
public ActionResult Verify(NewPurchaseViewModel postModel)
{
OrderDetailsViewModel odvm = postModel.Details;
//some logic here to check the promo code.
return PartialView("~/Views/CheckOut/_ListItem.cshtml",odvm );
}
I'm adding dynamically items to an Enquiry form. Used partial view to for adding/deleting the items but while submitting the main view the values are not bound. My question is how to do the same.
Have checked couple of similar questions here and here But could not find what's missing .
Using 2 ViewModels , for Main View ( Enquiry) and for partial view ( LineItems) and used BeginCollectionItem for dynamically adding items.
Code:
ViewModels
public class EnquiryVM
{
public int ID { get; set; }
[Required]
public string EnquiryNumber { get; set; }
public int ClientID { get; set; }
public IEnumerable<SelectListItem> Clients { get; set; }
public Client Client { get; set; }
public int ItemID { get; set; }
public List<EnquiryLineItem> LineItems { get; set; }
}
public class EnquiryLineItemVM
{
public int ID { get; set; }
[Required]
public string ItemDesc { get; set; }
public int Quantity { get; set; }
public int ManufacturerId { get; set; }
public IEnumerable<SelectListItem> ManufacturerList { get; set; }
}
Views :
Main:
#model ViewModel.EnquiryVM
#using (Html.BeginForm("Create", "Enquiries", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.EnquiryNumber, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-3">
#Html.EditorFor(model => model.EnquiryNumber, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.EnquiryNumber, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ClientID, "Client", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-3">
#Html.DropDownListFor(u => u.ClientID, (IEnumerable<SelectListItem>)Model.Clients, "--Select--")
#Html.ValidationMessageFor(model => model.ClientID, "", new { #class = "text-danger" })
</div>
</div>
<div id="LineItems">
// #using (Html.BeginForm()) // do we require again here since this will be like nested form? tested commenting still not working
// {
<div id="editorRowsLineitems">
#foreach (var item in Model.LineItems)
{
#Html.Partial("_CreateEnquiryItem", item)
}
</div>
#Html.ActionLink("Add Items", "CreateLineItem", null, new { id = "addItem", #class = "button" });
// }
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
$(function () {
$('#addItem').on('click', function () {
$.ajax({
url: '#Url.Action("CreateLineItem")',
cache: false,
success: function (html) {
$("#editorRowsLineitems").append(html);
$("form").removeData("validator");
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");
}
});
return false;
});
$('#editorRowsLineitems').on('click', '.deleteRow', function () {
$(this).closest('.editorRow').remove();
});
$('form').data('validator', null);
$.validator.unobtrusive.parse($('form'));
});
</script>
}
partial view :
#model ViewModels.EnquiryLineItemVM
<div class="editorRow">
#using (Html.BeginCollectionItem("ItemList"))
{
<table class="table">
<tr>
<td>
#Html.EditorFor(model => model.ItemDesc)
</td>
<td>
#Html.EditorFor(model => model.Quantity)
</td>
<td>
#Html.DropDownListFor(model => model.ManufacturerId, Model.ManufacturerList, "--Please Select--")
</td>
<td>
Delete
</td>
</tr>
</table>
}
Controller :
public ActionResult Create()
{
var viewModel = GetAllCategories();
return View(viewModel);
}
private EnquiryVM GetAllCategories()
{
var model = new EnquiryVM();
var clients = db.Clients.ToList();
model.Clients = clients.Select(s => new SelectListItem
{
Value = s.ID.ToString(),
Text = s.Name
});
var LineItems = new List<EnquiryLineItem>();
model.LineItems = LineItems;
return model;
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create( EnquiryVM enquiryVM)
{
var enquiry = new Enquiry();
enquiry.EnquiryNumber = enquiryVM.EnquiryNumber;
enquiry.ClientID = enquiryVM.ClientID;
enquiry.EnquiryLineItems = enquiryVM.LineItems; //line items are null
if (ModelState.IsValid)
{
db.Enquiries.Add(enquiry);
enquiryVM.ID = enquiry.ID;
foreach (var item in enquiry.EnquiryLineItems)
{
item.EnquiryID = enquiryVM.ID;
db.EnquiryLineItems.Add(item);
}
db.SaveChanges();
return RedirectToAction("Index");
}
var viewModel = GetAllCategories();
return View(enquiryVM);
}
How shall I map the dynamically added row's values to the ViewModel ( EnquiryVM ) so that I can insert it into the DB.
Thanks for your patience and time.
The name of your collection property is LineItems, therefore your code to generate its controls needs to be
#using (Html.BeginCollectionItem("LineItems")) // not ..("ItemList")
{
....
}
so that it generates inputs with name="LineItems[xxxx].ItemDesc" etc, rather than your current use which generates name="ItemList[xxxx].ItemDesc" (where xxxx is the Guid)
As a side note, the code in your POST method will throw an exception if ModelState is invalid because you return the view and have not repopulated the IEnumerable<SelectListItem> Clients property. Refer The ViewData item that has the key 'XXX' is of type 'System.Int32' but must be of type 'IEnumerable' for a detailed explanation.
In addition, the final 2 lines of your script to add items ($('form').data('validator', null); $.validator.unobtrusive.parse($('form')); should be removed (reparsing the validator is expensive and your doing it twice - once before you add the html (the 2 lines above) and once after you add the html
I am creating a MVC application. I am having a view with a form to fill, but it seems that controller does not get data that was entered (it is null when debugging).
My controller:
public ActionResult AddGroupsQty(AddGroupsQtyViewModel value)
{
var model = new AddGroupsQtyViewModel();
model.subject_id = value.subject_id;
model.qty = value.qty;
ClassDeclarationsDBEntities1 entities1=new ClassDeclarationsDBEntities1();
var subj = entities1.Subjects
.Where(b => b.class_id == model.subject_id)
.FirstOrDefault();
model.subject_name = subj.name;
if (ModelState.IsValid)
{
int maxId = 0;
int total = 0;
total = entities1.Groups.Count();
if (total == 0)
{
maxId = 0;
}
else
{
maxId = entities1.Groups.Max(u => u.group_id);
}
for (int i = 0; i < value.qty; i++)
{
var teacher = entities1.Users
.Where(b => b.email.Replace(" ", String.Empty) == model.teacher_emails[i].Replace(" ", String.Empty))
.FirstOrDefault();
var group=new Models.Group(value.subject_id, maxId+1, model.group_names[i], teacher.user_id);
entities1.Groups.Add(group);
entities1.SaveChangesAsync();
}
return RedirectToAction("OperationSuccess", "Account");
}
return View(model);
}
My View model:
public class AddGroupsQtyViewModel
{
public int qty { get; set; }
public int subject_id { get; set; }
public string subject_name { get; set; }
[Required]
[Display(Name = "Name of group")]
public List<string> group_names { get; set; }
[Required]
[Display(Name = "Email of teacher")]
public List<string> teacher_emails { get; set; }
}
and the View:
#using System.IdentityModel.Configuration
#using System.Web.UI.WebControls
#model ClassDeclarationsThsesis.Models.AddGroupsQtyViewModel
#{
ViewBag.Title = "AddGroupsQty";
}
<h2>Add Groups to #Model.subject_name</h2>
#if (Model != null)
{
using (Html.BeginForm("AddGroupsQty", "Account", new { qty = Model.qty, Model.subject_id }, FormMethod.Post, new { #class = "form-horizontal", role = "form", }))
{
#Html.AntiForgeryToken()
<h4>Insert data</h4>
<hr />
<table>
<tr>
<th>
#Html.ValidationSummary("", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.group_names, new { #class = "col-md-2 control-label" })
</div>
</th>
<th>
#Html.ValidationSummary("", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.teacher_emails, new { #class = "col-md-2 control-label" })
</div>
</th>
</tr>
#for (int i = 0; i < Model.qty; i++)
{
<tr>
<th>
<div class="form-group">
<div class="col-md-10">
#Html.TextBoxFor(m => m.group_names[i], new { #class = "form-control" })
</div>
</div>
</th>
<th>
<div class="form-group">
<div class="col-md-10">
#Html.TextBoxFor(m => m.teacher_emails[i], new { #class = "form-control" })
</div>
</div>
</th>
</tr>
}
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Submit" />
</div>
</div>
}
}
The problem is that when running the application, filling the form and submitting by pressing the button, it throws a null exception in controller that both lists:
List teacher_emails
and:
List group_names
are null. I do not see any error in my form. How can I solve it?
model.teacher_emails and model.group_names are always null, because model is new.
var model = new AddGroupsQtyViewModel();
Try using value.teacher_emails and value.group_names instead.
Also tidy that code up. A blank line after the variable declaration, and you would have spotted it yourself.
I have the following class defined as my ViewModel
public class CreateApplicationViewModel
{
public Step1ViewModel Step1 { get; set; }
public Step2StandAloneViewModel Step2StandAlone { get; set; }
public Step2ChildViewModel Step2Child { get; set; }
public Step3ViewModel Step3 { get; set; }
public Step4ViewModel Step4 { get; set; }
}
I'm trying to display items in the Step4ViewModel which consists of the following:
public class Step4ViewModel
{
public List<DataDetails> DataDetails = new List<DataDetails>();
}
public class DataDetails
{
public string GroupCode { get; set; }
public string GroupDesc { get; set; }
public decimal DetailSequence { get; set; }
public string DetailCode { get; set; }
public string DetailDesc { get; set; }
public string YesNoFlag { get; set; }
public string NumberFlag { get; set; }
public string ValueFlag { get; set; }
public string DateFlag { get; set; }
public string ListValuesFlag { get; set; }
public string CommentFlag { get; set; }
public string CalcRateFlag { get; set; }
public string ColumnSequence { get; set; }
public string TextFlag { get; set; }
public string CheckboxFlag { get; set; }
public string YesNoValue { get; set; }
public int NumberValue { get; set; }
public DateTime DateValue { get; set; }
public string ListValue { get; set; }
public string CommentValue { get; set; }
public string TextValue { get; set; }
public bool CheckboxValue { get; set; }
}
In my controller I populate the Step4ViewModel.DataDetails like so:
private Step4ViewModel GetCaseDataDetails(string caseType)
{
Step4ViewModel model = new Step4ViewModel();
List<DataDetails> data = new List<DataDetails>();
List<DataDetailsValues> values = new List<DataDetailsValues>();
var dataDetails = (from tb1 in db.DEFAULT_CASE_DATA_VW
join tb2 in db.CASE_DATA_DETAIL on tb1.CASE_DATA_GROUP_ID equals tb2.CASE_DATA_GROUP_ID
where tb1.BUS_CASE_CODE == caseType
orderby tb2.DETAIL_SEQUENCE
select new { tb1, tb2 });
foreach (var detail in dataDetails.ToList())
{
DataDetails i = new DataDetails();
DataDetailsValues j = new DataDetailsValues();
i.CalcRateFlag = detail.tb2.CALC_RATE_FLAG;
i.CheckboxFlag = detail.tb2.CHECKBOX_FLAG;
i.ColumnSequence = detail.tb2.COLUMN_SEQUENCE;
i.CommentFlag = detail.tb2.COMMENT_FLAG;
i.DateFlag = detail.tb2.DATE_FLAG;
i.DetailCode = detail.tb2.DETAIL_CODE;
i.DetailDesc = detail.tb2.DETAIL_DESC;
i.DetailSequence = detail.tb2.DETAIL_SEQUENCE;
i.GroupCode = detail.tb1.GROUP_CODE;
i.GroupDesc = detail.tb1.GROUP_DESC;
i.ListValuesFlag = detail.tb2.LIST_VALUES_FLAG;
i.NumberFlag = detail.tb2.NUMBER_FLAG;
i.TextFlag = detail.tb2.TEXT_FLAG;
i.ValueFlag = detail.tb2.VALUE_FLAG;
i.YesNoFlag = detail.tb2.YES_NO_FLAG;
data.Add(i);
}
model.DataDetails = data;
return model;
}
My thought process with the Step4ViewModel is that for every DataDetail I will display the DetailDesc as a label and then beside of it I will have an input for the NumberValue, YesOrNoValue, NumberValue, DateValue, ListValue, CommentValue, TextValue, or CheckboxValue depending on the control type and then post that data to server. I am able to successfully display each DataDetail.DetailDesc, but for each input, which also renders, the values I enter into the inputs never post back to the server. Here is what my view looks like:
#model Portal.Models.ViewModel.CreateApplicationViewModel
#{
ViewBag.Title = "Step 4/5";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using System.Linq
<h4>Case Data Details</h4>
#using (Html.BeginForm("Step4", "CreateApplication", FormMethod.Post, new { #class = "col-sm-12" }))
{
foreach (var group in Model.Step4.DataDetails.GroupBy(item => item.GroupDesc))
{
<div class="panel panel-primary">
<div class="panel-heading">#Html.Encode(group.Key)</div>
<div class="panel-body">
#for (var i = 0; i < group.Count(); i++)
{
<div class="form-group">
<div class="row">
<div class="col-xs-6">
<label class="form-label">#Model.Step4.DataDetails[i].DetailDesc</label>
</div>
<div class="col-xs-6">
#if (Model.Step4.DataDetails[i].TextFlag == "Y")
{
#Html.TextBoxFor(val => Model.Step4.DataDetails[i].TextValue, new { #class = "form-control" })
}
else if (Model.Step4.DataDetails[i].CheckboxFlag == "Y")
{
#Html.CheckBoxFor(val => Model.Step4.DataDetails[i].CheckboxValue, new { #class = "checkbox" })
}
</div>
</div>
</div>
}
</div>
</div>
}
<div class="col-sm-12">
<div class="row">
#Html.ActionLink("Cancel", "Welcome", "Home", null, new { #class = "btn btn-default" })
<button class="btn btn-default" onclick="history.go(-1);">Previous</button>
<button type="submit" class="btn btn-default">Next</button>
</div>
</div>
Controller to which post data
[HttpPost]
public ActionResult Step4(Step4ViewModel step4)
{
if (ModelState.IsValid)
{
CreateApplicationViewModel model = (CreateApplicationViewModel)Session["case"];
// model.Step4 = step4;
Session["case"] = model;
return View();
}
return View();
}
I was thinking this could be due the grouping, which I do to separate each group into a separate HTML panel element, but my inputs are rendering with the index number in the name. Any help or suggestions on a better way to accomplish this would be greatly appreciated. Cheers!
UPDATE
Here is my updated post controller and view:
#model Portal.Models.ViewModel.CreateApplicationViewModel
#{
ViewBag.Title = "Step 4/5";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using System.Linq
<h4>Case Data Details</h4>
#using (Html.BeginForm("Step4", "CreateApplication", FormMethod.Post, new { #class = "col-sm-12" }))
{
int index = 0;
foreach (var group in Model.Step4.DataDetails.GroupBy(item => item.GroupDesc))
{
<div class="panel panel-primary">
<div class="panel-heading">#Html.Encode(group.Key)</div>
<div class="panel-body">
<input type="hidden" name="Step4.DataDetails.Index" value="#index" />
#for (var i = 0; i < group.Count(); i++)
{
<div class="form-group">
<div class="row">
<div class="col-xs-6">
<label class="form-label">#Model.Step4.DataDetails[i].DetailDesc</label>
</div>
<div class="col-xs-6">
#if (Model.Step4.DataDetails[i].TextFlag == "Y")
{
#Html.TextBoxFor(val => val.Step4.DataDetails[i].TextValue, new { #class = "form-control" })
}
else if (Model.Step4.DataDetails[i].CheckboxFlag == "Y")
{
#Html.CheckBoxFor(val => val.Step4.DataDetails[i].CheckboxValue, new { #class = "checkbox" })
}
</div>
</div>
</div>
}
</div>
</div>
index++;
}
<div class="col-sm-12">
<div class="row">
#Html.ActionLink("Cancel", "Welcome", "Home", null, new { #class = "btn btn-default" })
<button class="btn btn-default" onclick="history.go(-1);">Previous</button>
<button type="submit" class="btn btn-default">Next</button>
</div>
</div>
}
[HttpPost]
public ActionResult Step4(CreateApplicationViewModel step4)
{
if (ModelState.IsValid)
{
CreateApplicationViewModel model = (CreateApplicationViewModel)Session["case"];
// model.Step4 = step4;
Session["case"] = model;
return View();
}
return View();
}
UPDATE 2
I am able to get the form input if I pass a FormCollection to the HttpPost controller. Any ideas as to why I can get these values as a FormCollection but not as the model?
You are posting list of complex objects. But MVC DefaultModelBinder can’t able to bind to your DataDetails object because Index must be in sequence when posting the form with list of complex objects. In your case due to nested for loop, this sequence is broken. So what you can do is take one separate variable and initialize with default 0 value like this - I have tried to modify your code.
#using (Html.BeginForm("Step4", "CreateApplication", FormMethod.Post, new { #class = "col-sm-12" }))
{
int index = 0;
foreach (var group in Model.Step4.DataDetails.GroupBy(item => item.GroupDesc))
{
<div class="panel panel-primary">
<div class="panel-heading">#Html.Encode(group.Key)</div>
<div class="panel-body">
<input type="hidden" name="Step4.DataDetails.Index" value="#index" />
#for (var i = 0; i < group.Count(); i++)
{
<div class="form-group">
<div class="row">
<div class="col-xs-6">
<label class="form-label">#Model.Step4.DataDetails[i].DetailDesc</label>
</div>
<div class="col-xs-6">
#if (Model.Step4.DataDetails[i].TextFlag == "Y")
{
#Html.TextBoxFor(val => val.Step4.DataDetails[i].TextValue, new { #class = "form-control" })
}
else if (Model.Step4.DataDetails[i].CheckboxFlag == "Y")
{
#Html.CheckBoxFor(val => val.Step4.DataDetails[i].CheckboxValue, new { #class = "checkbox" })
}
</div>
</div>
</div>
}
</div>
</div>
index++;
}
<div class="col-sm-12">
<div class="row">
#Html.ActionLink("Cancel", "Welcome", "Home", null, new { #class = "btn btn-default" })
<button class="btn btn-default" onclick="history.go(-1);">Previous</button>
<button type="submit" class="btn btn-default">Next</button>
</div>
</div>
}
Look at the hidden field i have added in the view. That do the trick to post your data even with the broken sequences. Hope this help you.
I was able to get the model back to the controller by taking the idea of using an index integer and incrementing it from the answer above and implementing the idea in a different way in my view:
#model Portal.Models.ViewModel.CreateApplicationViewModel
#{
ViewBag.Title = "Step 4/5";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using System.Linq
<h4>Case Data Details</h4>
#using (Html.BeginForm("Step4", "CreateApplication", FormMethod.Post, new { #class = "col-sm-12" }))
{
int index = 0;
foreach (var group in Model.Step4.DataDetails.GroupBy(item => item.GroupDesc))
{
<div class="panel panel-primary">
<div class="panel-heading">#Html.Encode(group.Key)</div>
<div class="panel-body">
#for (var i = 0; i < group.Count(); i++)
{
<div class="form-group">
<div class="row">
<div class="col-xs-6">
<label class="form-label">#Model.Step4.DataDetails[i].DetailDesc</label>
</div>
<div class="col-xs-6">
#Html.TextBoxFor(val => val.Step4.DataDetails[index].TextValue)
#Html.HiddenFor(val => val.Step4.DataDetails[index].GroupCode)
</div>
</div>
</div>
index++;
}
</div>
</div>
}
<div class="col-sm-12">
<div class="row">
#Html.ActionLink("Cancel", "Welcome", "Home", null, new { #class = "btn btn-default" })
<button class="btn btn-default" onclick="history.go(-1);">Previous</button>
<button type="submit" class="btn btn-default">Next</button>
</div>
</div>
}
The above code in the view gives me the proper index of every element and allows me to post