Dynamically created forms submits null to controller (MVC) - c#

I'm creating multiple forms in a for in my view, the problem comes when I send to the controller on submit and it comes null.
here an example.
#model List<Project.ViewModels.ValidForm>
#if (Model != null)
{
for (int i = 0; i < Model.Count; i++)
{
<div>
#using (Html.BeginForm("Method", "Controller", FormMethod.Post, new { id = "valForm" + #Model[i].Id }))
{
<div class="col-md-4">
<label>Data 1</label><br />
#Html.TextBoxFor(m => m[i].Data1, new { #class = "form-control" })
</div>
<div class="col-md-4">
<label>Data 2 options</label><br />
#Html.TextBoxFor(m => m[i].Data2.Option1, new { #class = "form-control" })
</div>
<div>
<button type="submit" class="btn btn-success">Save this form</button>
</div>
}
</div>
}
}
And there are the viewmodels.
public class ValidForm{
public int data1 { get; set; }
public Data2 data2 {get;set;}
}
public class Data2{
public int option1 {get;set;}
public int option2 {get;set;}
}
and the controller.
[HttpPost]
public ActionResult validaciones(validform vm){
//do something.
return view();
}

The reason you get null is that your model is an array of ValidForm and not just one.
Change your controller to:
[HttpPost]
public ActionResult validaciones(ValidForm[] vms){
ValidForm vm = ValidForm[0];
//do something with vm.
return view();
}
If you want only one form at a time you can do this: (I've added Name = "PropName", mind the capital N in Name)
Then you're post action should expect a single VM
<div class="col-md-4">
<label>Data 1</label><br />
#Html.TextBoxFor(m => m[i].Data1, new { #class = "form-control", Name="Data1" })
</div>
For data2 the name needs to be Data2.option1, ect...
Fully working example:
HomeController:
public class HomeController : Controller
{
public class ValidForm
{
public string Id { get; set; }
public int data1 { get; set; }
public Data2 data2 { get; set; }
}
public class Data2
{
public int option1 { get; set; }
public int option2 { get; set; }
}
public ActionResult Index()
{
var model = new ValidForm[2] { new ValidForm { }, new ValidForm {data2 = new Data2{}} };
return PartialView(model);
}
[HttpPost]
public ActionResult Tester(ValidForm model)
{
return View("Index", new ValidForm[1] { model });
}
}
View:
#model MvcApplication1.Controllers.HomeController.ValidForm[]
#if (Model != null)
{
for (int i = 0; i < Model.Count(); i++)
{
<div>
#using (Html.BeginForm("Tester", "Home", FormMethod.Post, new { id = "valForm" + #Model[i].Id }))
{
<div class="col-md-4">
<label>Data 1</label><br />
#Html.TextBoxFor(m => m[i].data1, new { #class = "form-control", Name = "data1"})
</div>
<div class="col-md-4">
<label>Data 2 options</label><br />
#Html.TextBoxFor(m => m[i].data2.option1, new { #class = "form-control", Name = "data2.option1"})
</div>
<div>
<button type="submit" class="btn btn-success">Save this form</button>
</div>
}
</div>
}
}
Also note that the input boxes can only be numbers because you have all ints.

Related

Binding a Nested Object in ASP.NET MVC - Razor

My ASP.NET MVC viewmodel is designed as below
public class Customer
{
public int CustomerId{ get; set; }
public string CustomerName{ get; set; }
public Address CustomerAddress {get;set;}
}
public class Address
{
public int AddressId{ get; set; }
public string HouseName{ get; set; }
public string Location{get;set;}
}
How can I bind Address object properly in cshtml page so that it should be available after form Submit.
In cshtml page I want bind it the properties as below
#model CustomerManagement.Model.ViewModels.Customers.Customer
#using (Html.BeginForm())
{
#Html.EditorFor(model => model.CustomerName, new { htmlAttributes = new { #class = "form-
control readOnlySchdCode", #readonly = "readonly" } })
#Html.Hidden("AddressId", Model.Address.AddressId)
#Html.Hidden("HouseName", Model.Address.HouseName)
}
In controller form submit will look like as below
public async Task<ActionResult> AddCustomer(Customer model)
{
//How can i access Address properties eg:model.CustomerAddress.AddressId??
}
Can anyone share a sample code on how to bind the Above viewmodel properly in cshtml using razor template and how properties are properly retrieved in Action method while form submit.
You could try this way.
Client side:
#using Newtonsoft.Json.Linq
#using WebAppDemo.Models
#model WebAppDemo.Models.Customer
#{
ViewData["Title"] = "Home Page";
}
#{
ViewBag.Title = "Home Page";
}
<br />
#using (Html.BeginForm("AddCustomer", "Home", FormMethod.Post, new { id = "Form1" }))
{
<div class="row">
<div class="col-lg-2">Cust Id</div>
<div class="col-lg-10">
#Html.TextBoxFor(a => a.CustomerId, new { #class = "form-control" })
</div>
</div>
<br />
<div class="row">
<div class="col-lg-2">Customer Name</div>
<div class="col-lg-10">
#Html.TextBoxFor(a => a.CustomerName, new { #class = "form-control" })
</div>
</div>
<br />
<div class="row">
<div class="col-lg-2">Address</div>
<div class="col-lg-10">
#Html.TextBoxFor(a => a.CustomerAddress.HouseName, new { #class = "form-control" })
</div>
</div>
<br />
<div class="row">
<div class="col-lg-12"><input type="submit" value="Submit" class="btn btn-primary"></div>
</div>
}
Form Output Should Be Like:
Controller:
[HttpPost] //attribute to get posted values from HTML Form
public ActionResult AddCustomer(Customer model)
{
return Ok();// For testing I just kept it as `Ok` You can return `View()`;
}
Note: Please try to pass value on form as per Model property descriptions. Other than you might
get null value.
Output:
Hope it will help you. Let me know if you have any further concern.
Here is a little example
public class BigViewModel : IModelOptions
{
public bool Confirm { get; set; }
public SmallViewModel SmallView { get; set; }
}
public class SmallViewModel
{
public string Stuff{ get; set; }
}
public interface IModelOptions
{
SmallViewModel SmallView { get; set; }
}
and our controller would be like
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(BigViewModel model)
{
var smallVIewModelInfo = model.SmallView.Stuff;
var bigViewModelConfirm = model.Confirm;
return View();
}
}
using view like
#model MvcApplication1.Models.BigViewModel

Submit data with dynamically added partial view to the controller using ViewModels not working

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

ASP MVC 5 Not getting a collection posted back to the controller

So far I can't figure out what is not working with my ViewModel and controller, since I have followed some examples on StackOverflow but for some reason my model is not filling up. Here is my Get and Post Controller Methods
public ActionResult Registrar(DateTime FechaTarja, long HaciendaId, long[] Pedidos)
{
if(Pedidos != null && Pedidos.Length > 0)
{
List<RegistroPedidoTarjaViewModel> data = new List<RegistroPedidoTarjaViewModel>();
foreach (long Pedido in Pedidos)
{
data.Add(new RegistroPedidoTarjaViewModel {
PedidoEmbarqueId = Pedido
});
}
return View(new RegistroTarjaViewModel {
HaciendaId = HaciendaId,
FechaTarja = FechaTarja,
Pedidos = data
});
}
return RedirectToAction("Index", new { FechaTarja = FechaTarja.ToString("yyyy-MM-dd"), HaciendaId = HaciendaId });
}
[HttpPost]
public ActionResult Registrar(RegistroTarjaViewModel model)
{
if (ModelState.IsValid)
{
}
return View(model);
}
My View Model
public class RegistroTarjaViewModel
{
[Required]
public long HaciendaId { get; set; }
[Required]
public DateTime FechaTarja { get; set; }
public List<RegistroPedidoTarjaViewModel> Pedidos { get; set; }
public long? ContenedorId { get; set; }
}
My Razor View
#model Project.ViewModels.RegistroTarjaViewModel
#{
ViewBag.Title = "Registrar Nueva Tarja";
}
<div class="row">
<div class="col-xs-12 col-md-12">
<h1>#ViewBag.Title</h1>
</div>
</div>
#using (Html.BeginForm())
{
#Html.HiddenFor(model => model.FechaTarja)
#Html.HiddenFor(model => model.HaciendaId)
<div class="row">
<div class="col-xs-12 col-md-12">
</div>
</div>
for (int i = 0; i < Model.Pedidos.Count(); i++)
{
#Html.HiddenFor(model => model.Pedidos[i].PedidoEmbarqueId)
<div class="row">
<div class="col-xs-12 col-md-12">
<div class="form-group">
#Html.LabelFor(model => model.Pedidos[i].Embarcadas, htmlAttributes: new { #class = "control-label" })
#Html.TextBoxFor(model => model.Pedidos[i].Embarcadas, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Pedidos[i].Embarcadas, "", new { #class = "text-danger" })
</div>
</div>
</div>
}
<input type="submit" value="Registrar Tarja" class="btn btn-primary" />
}
I am getting posted back the HaciendaId and FechaTarja properties, but not the Pedidos List, what am I missing to include on the Controller?
EDIT
Here is an example of the HTML output of the textbox
<input class="form-control" data-val="true" data-val-number="El campo Embarcadas debe ser un número." data-val-required="El campo Embarcadas es obligatorio." id="Pedidos_0__Embarcadas" name="Pedidos[0].Embarcadas" type="text" value="0">
Your code is working. Here is how I tested -
View is same as yours.
Controller
public class HomeController : Controller
{
public ActionResult Registrar()
{
List<RegistroPedidoTarjaViewModel> data = new List<RegistroPedidoTarjaViewModel>();
data.Add(new RegistroPedidoTarjaViewModel {PedidoEmbarqueId = 1, Embarcadas = "One"});
data.Add(new RegistroPedidoTarjaViewModel {PedidoEmbarqueId = 2, Embarcadas = "Two"});
data.Add(new RegistroPedidoTarjaViewModel {PedidoEmbarqueId = 3, Embarcadas = "Three"});
data.Add(new RegistroPedidoTarjaViewModel {PedidoEmbarqueId = 4, Embarcadas = "Four"});
return View(new RegistroTarjaViewModel
{
HaciendaId = 1,
FechaTarja = DateTime.Now,
Pedidos = data
});
}
[HttpPost]
public ActionResult Registrar(RegistroTarjaViewModel model)
{
if (ModelState.IsValid)
{
}
return View(model);
}
}
View Models
public class RegistroTarjaViewModel
{
[Required]
public long HaciendaId { get; set; }
[Required]
public DateTime FechaTarja { get; set; }
public List<RegistroPedidoTarjaViewModel> Pedidos { get; set; }
public long? ContenedorId { get; set; }
}
public class RegistroPedidoTarjaViewModel
{
public long PedidoEmbarqueId { get; set; }
public string Embarcadas { get; set; }
}
Posted Result

Display list of objects and set a value field for each input MVC

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

How to send dropdown value from view to controller

I have the following create view
#model Inspinia_MVC5.Areas.GlobalAdmin.Models.Propiedad
#{
ViewBag.Title = "Create";
Layout = "~/Areas/GlobalAdmin/Views/Shared/_LayoutGlobalAdmin.cshtml";
}
<div class="row wrapper border-bottom white-bg page-heading">
<div class="col-sm-4">
<h2>Create</h2>
<ol class="breadcrumb">
<li>
#Html.ActionLink("List", "Index")
</li>
<li class="active">
<strong>Create</strong>
</li>
</ol>
</div>
<div class="col-sm-8">
<div class="title-action">
#Html.ActionLink("Back to List", "Index", null, new { #class = "btn btn-primary"})
</div>
</div>
</div>
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>Create Propiedad</h5>
</div>
<div class="ibox-content">
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.Entidad, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Entidades", (IEnumerable<SelectListItem>)ViewData["Entidades"], new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Codigo, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Codigo)
#Html.ValidationMessageFor(model => model.Codigo)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Nombre, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Nombre)
#Html.ValidationMessageFor(model => model.Nombre)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TipoDeDatos, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.TipoDeDatos)
#Html.ValidationMessageFor(model => model.TipoDeDatos)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-primary" />
#Html.ActionLink("Cancel", "Index", null, new { #class = "btn btn-white"})
</div>
</div>
</div>
}
</div>
</div>
</div>
</div>
</div>
and I have this controller
public class PropiedadesController : Controller
{
private AppDataContext db = new AppDataContext();
public ActionResult SelectCategory()
{
List<SelectListItem> items = new List<SelectListItem>();
foreach(Entidad entidad in db.Entidades.ToList())
{
items.Add(new SelectListItem { Text = entidad.Nombre, Value = entidad.Id.ToString() });
}
ViewBag.Entidades = items;
return View();
}
// GET: /GlobalAdmin/Propiedades/
public ActionResult Index()
{
return View(db.Propiedades.ToList());
}
// GET: /GlobalAdmin/Propiedades/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Propiedad propiedad = db.Propiedades.Find(id);
if (propiedad == null)
{
return HttpNotFound();
}
return View(propiedad);
}
// GET: /GlobalAdmin/Propiedades/Create
public ActionResult Create()
{
AppDataContext db = new AppDataContext();
var entidades = from c in db.Entidades select c;
List<SelectListItem> items = new List<SelectListItem>();
foreach (Entidad entidad in entidades)
{
items.Add(new SelectListItem { Text = entidad.Nombre, Value = entidad.Id.ToString() });
}
ViewBag.Entidades = items;
return View();
}
// POST: /GlobalAdmin/Propiedades/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include="Id,Codigo,Nombre,TipoDeDatos")] Propiedad propiedad)
{
if (ModelState.IsValid)
{
db.Propiedades.Add(propiedad);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(propiedad);
}
// GET: /GlobalAdmin/Propiedades/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
AppDataContext db = new AppDataContext();
var entidades = from c in db.Entidades select c;
List<SelectListItem> items = new List<SelectListItem>();
foreach (Entidad entidad in entidades)
{
items.Add(new SelectListItem { Text = entidad.Nombre, Value = entidad.Id.ToString() });
}
ViewBag.Entidades = items;
Propiedad propiedad = db.Propiedades.Find(id);
if (propiedad == null)
{
return HttpNotFound();
}
return View(propiedad);
}
// POST: /GlobalAdmin/Propiedades/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="Id,Codigo,Nombre,TipoDeDatos")] Propiedad propiedad)
{
if (ModelState.IsValid)
{
db.Entry(propiedad).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(propiedad);
}
// GET: /GlobalAdmin/Propiedades/Delete/5
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Propiedad propiedad = db.Propiedades.Find(id);
if (propiedad == null)
{
return HttpNotFound();
}
return View(propiedad);
}
// POST: /GlobalAdmin/Propiedades/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Propiedad propiedad = db.Propiedades.Find(id);
db.Propiedades.Remove(propiedad);
db.SaveChanges();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
However when I created a row, the database shows the foreign KEY as null (Entidad ID)
What am I missing here?
Propiedad Model is this
public class Propiedad
{
[Key]
public int Id { get; set; }
public virtual Entidad Entidad { get; set; }
public string Codigo { get; set; }
public string Nombre { get; set; }
public string TipoDeDatos { get; set; }
}
Screenshot
http://screencast.com/t/B5m6X8mtbSdd
Update 1: I modified the controller like this:
public ActionResult Create()
{
ViewBag.Entidad = new SelectList(db.Entidades, "Id", "Nombre");
return View();
}
and the view like this:
<div class="form-group">
#Html.LabelFor(model => model.Entidad, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.Entidad.Id, new SelectList(ViewBag.Entidad, "Id", "Nombre", Model.Entidad.Id), "Seleccionar", new { #class = "form-control" })
</div>
</div>
However on the view I get an object reference not set to an instance of an object.
This is the entity model
public class Entidad
{
[Key]
public int Id { get; set; }
public string Nombre { get; set; }
public virtual ICollection<Propiedad> Propiedades { get; set; }
}
and the property model
public class Propiedad
{
[Key]
public int Id { get; set; }
public virtual Entidad Entidad { get; set; }
public string Codigo { get; set; }
public string Nombre { get; set; }
public string TipoDeDatos { get; set; }
}
I don't see where print the dropdownlist in your view.
Well try this:
In your controller load the list:
ViewBag.entities = new SelectList(db.Entidades, "Id", "Nombre") ;
And then in your view inside the form:
#Html.DropDownListFor(model => model.Entidad.Id, (SelectList) ViewBag.entities, "Seleccionar")
Model equivalent a your class: Propiedad
With this you will automatically receive the object with the selected value.

Categories