MVC BeginCollectionItem Nested List Indexes not working - asp.net-mvc

I've got a BeginCollectionItem repeater that is working perfectly, however when I try and follow the Joe Stevens blog to add nested lists it doesn't work in the way I expected.
I've used the BeginCollectionItemCore as I'm using AspNetCore, I've taken the code from here as it says it has the built in elements from the blog already in there: https://github.com/saad749/BeginCollectionItemCore
I have a main BeginCollectionItem called Section and a nested collection called SingleLine. I expected the code to output something like: Section[long-code-here].SingleLine[another-code-here].SingleLineTextBlock
however what I get is SingleLine[long-code-here].SingleLineTextBlock
I've included my code below;
Model:
namespace project.Models.SetupViewModels
{
public class SOPTopTemplateBuilderViewModel
{
public List<SectionViewModel> Section { get; set; }
}
public class SectionViewModel {
public int SectionId { get; set; }
public string SectionText { get; set; }
public string TopTempId { get; set; }
public List<SingleLineViewModel> SingleLines { get; set; }
}
}
Partial view:
#model project.Models.SetupViewModels.SectionViewModel
#using HtmlHelpers.BeginCollectionItemCore
<div class="new-section form-row">
#using (Html.BeginCollectionItem("Section"))
{
<div class="top-line">
<div class="col-12 col-md-11">
#Html.HiddenFor(m => m.SectionId, new { #class="id" })
#Html.EditorFor(m => m.SectionText, new { #class = "form-control limit-form"})
</div>
<div class="col-12 col-md-1">
</div>
</div>
}
<div class="main-row">
<div class="buttons-div">
<button id="add-single-line" type="button" data-containerPrefix="#ViewData["ContainerPrefix"]">Add Single Text</button>
</div>
</div>
<div class="main-row">
</div>
</div>
Ajax to add new line:
var form = $('form');
var recipients = $('.main-row');
$("#add-single-line").click(function(){
$.ajax({
type: "POST",
url: '#Url.Action("GetNewSingleLine")',
data: { "containerPrefix": recipients.data("containerPrefix") },
success: function (data) {
recipients.append(data);
}
});
});
EditorFor:
#model project.Models.SetupViewModels.SingleLineViewModel
#using HtmlHelpers.BeginCollectionItemCore
#using (Html.BeginCollectionItem("SingleLine"))
{
<div class="single-line-row">
#Html.HiddenFor(m => m.SingleLinkTextID, new { #class="id" })
#Html.TextBoxFor(m => m.SingleLinkTextBlock, new { #class = "form-control limit-form"})
</div>
}
EditFor Controller:
public ActionResult GetNewSingleLine(string containerPrefix)
{
ViewData["ContainerPrefix"] = containerPrefix;
return PartialView("SingleLineViewModel", new SingleLineViewModel());
}

I struggled with this exact issue for a while until I happened upon this post - which ended up leading me to an answer (thank you!).
In the example above, it is storing the prefix in the button.add-single-line but attempting to load it from the div.main-row so you probably are getting 'undefined' as the prefix. By pulling from the same item which cached the value, the code above will do what is intended.

Related

How to pass different list of data from view to controller MVC

currently I facing a very tricky problem of passing different list of data from view to controller.
I have created two input box to submit my data to controller so that it can be saved into CreateAccountsDB and further display it in the list of
Selected Subcon when Create button is pressed.
The problem I face here is:
when pressing the Create button with entered data from NewCompanyName textbox and NewEmail textbox, those entered data do pass data from View to Controller and save data into CreateAccountDB (not showing in View), but the entered data is not displaying in the list of Selected Subcon.
Create View
Here is the model.
public class Tender
{
public int ID { get; set; }
public string CompanyName { get; set; }
public List<CreateAccount> FrequentCompanyName { get; set; }
public List<CreateAccount> SuggestCompanyName { get; set; }
public List<CreateAccount> SelectedCompanyName { get; set; }
public string CompanyNameNew { get; set; }
public string EmailNew { get; set; }
public int? TradeID { get; set; }
public virtual Trade Trade { get; set; }
public int? CreateAccountID { get; set; }
public virtual CreateAccount CreateAccount { get; set; }
}
Here is the Get Method of Create function in controller:
[httpGet]
public ActionResult Create(int? id)
{
Tender tender = new Tender();
tender.FrequentCompanyName = db.createaccountDB.Include(tm => tm.Trade).Where(td => td.Frequency == 32).ToList();
tender.SuggestCompanyName = db.createaccountDB.Include(tm => tm.Trade).ToList();
if (tender.SelectedCompanyName == null)
{
tender.SelectedCompanyName = new List<CreateAccount>().ToList();
}
return View(tender);
}
and Here is my Post Method of Create function:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,CompanyName,TradeID,FrequentCompanyName,SelectedCompanyName,CreateAccountID")] Tender tender ,string CompanyNameNew, string Emailnew)
{
CreateAccount accnew = new CreateAccount();
accnew.CompanyName = CompanyNameNew;
accnew.Email = Emailnew;
if(ModelState.IsValid)
{
db.createaccountDB.Add(accnew);
db.SaveChanges();
}
if (tender.SelectedCompanyName == null)
{
tender.SelectedCompanyName = new List<CreateAccount>().ToList();
}
tender.FrequentCompanyName = db.createaccountDB.Include(tm => tm.Trade).Where(td => td.Frequency == 32).ToList();
tender.SuggestCompanyName = db.createaccountDB.Include(tm => tm.Trade).ToList();
tender.SelectedCompanyName.ToList().Add(accnew);
return View(tender);
}
and Here is my Create View:
#model Tandelion0.Models.Tender
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-group">
#*#Html.LabelFor(model => model.ProjectName, htmlAttributes: new { #class = "control-label col-md-3" })*#
<div class="col-md-3">
<h5>New Company Name</h5>
#Html.EditorFor(model => model.CompanyNameNew, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CompanyNameNew, "", new { #class = "text-danger" })
</div>
<div class="col-md-3">
<h5>New Email</h5>
#Html.EditorFor(model => model.EmailNew, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.EmailNew, "", new { #class = "text-danger" })
</div>
<div class="container" align="center">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
<div class="container row">
<!--selected subcon column-->
<div class="container row col-sm-4">
<h4>
Selected Subcon
</h4>
<div style="overflow-y: scroll; height:250px;">
<table class="table table-hover">
#foreach (var item in Model.SelectedCompanyName)
{
<tbody>
<tr>
<td>
#Html.DisplayFor(modelItem => item.CompanyName)
</td>
</tr>
</tbody>
}
</table>
</div>
</div>
</div>
}
So far I manage to save data from view into CreateAccountsDB when create button is pressed, but those data just couldn't pass it from Post method Create function to Get method Create function in Controller. The data and the list become null immediate after come out from post method Create function.
Because of data becomes null, the view couldn't receive any data from controller.
May I know how can i solve the the problem of passing data from controller to view? Is the way I pass data totally wrong?
Any advice is truly appreciated.
In your HttpPost Action method :
Instead of :
tender.SelectedCompanyName.ToList().Add(accnew);
You should be doing:
tender.SelectedCompanyName.Add(accnew);
Calling ToList().Add(object) won't actually add to SelectedCompanyName.Instead it will add to the new list object created by calling ToList() method which you are not assigning back to tender.SelectedCompanyName.
A better approach however would be to use Post/Redirect/Get Pattern.
Instead of returning a view from your post method , do a temorary redirect to your [HttpGet]Create action method passing the id of the tender.

posting data from partial view on main view then submitting to controller

I have a MVC model as follows
public class ListSampleModel
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int SampleId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public IList<PointOfContact> lstPoc { get; set; }
}
public class PointOfContact
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int PocID { get; set; }
public string EmailAddress { get; set; }
public string PhoneNumber { get; set; }
}
What I have done is, create "PointOfContact" as a partial view on a jquery dialog and on "save" button click it shows the data on the main view in labels (I will have multiple point of contacts), now on submit I want this data along with the property values of ListSampleData to be posted back to the controller.
The issue is, the data related to simple properties are returned back but the list is always null.
below is my View
#model MVCDataFlowSample.Models.ListSampleModel
#using (Html.BeginForm("Create", "ListSample", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>ListSampleModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
<div id="dialog1" class="ui-dialog" style="background-color:gray;"></div>
<div id="data">
</div>
<p>
<input type="button" value="Add More..." id="btnAdd" />
</p>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Javascript on Main View
<script type="text/javascript">
$(document).ready(function () {
$('#btnAdd').on('click', function () {
$('#dialog1').dialog({
autoOpen: true,
position: { my: "center", at: "center", of: window },
width: 1000,
resizable: false,
title: 'Add User Form',
modal: true,
open: function () {
$(this).load('#Url.Action("PocPartial", "ListSample")');
},
buttons: {
"Save User": function () {
addUserInfo();
$(this).dialog("close");
},
Cancel: function () {
$(this).dialog("close");
}
}
});
return false;
});
function addUserInfo(email, phone) {
var text = "<div id='EmailAddress'>Email Address:" + $("#EAddress").val() + "</div><div id='PhoneNumber'>Phone Number:" + $("#PhNo").val() + "</div>";
$("#data").append(text);
}
});
</script>
Partial View
#model MVCDataFlowSample.Models.PointOfContact
<div>
#Html.Label("EmailAddress:")
<div>
#Html.TextBoxFor(x => x.EmailAddress, new { id = "EAddress" })
</div>
#Html.Label("PhoneNumber:")
<div>
#Html.TextBoxFor(x => x.PhoneNumber, new { id = "PhNo" })
</div>
</div>
any help will be appreciated.
The contents of DIV elements are not submitted as form data. If you'd like that data to be submitted, add it to the DOM as hidden INPUT elements in addition to your DIVs. You'll also need to format their names correctly so that MVC knows how to bind them. See this article for how complex objects are bound in MVC.
I posted partial view data to the main page's post action You can modify the idea to any of your suits
Partial View
<select name="Country">
<option>Indian</option>
<option>Africa</option>
</select>
<select name="State">
<option>Kerala</option>
<option>TamilNadu</option>
</select>
<select name="City">
<option>Thrissur</option>
<option>Palakkad</option>
</select>
Index Page
#{
ViewBag.Title = "IndexTestPost";
}
<h2>IndexTestPost</h2>
#using(Html.BeginForm()){
#Html.Partial("~/Views/_PartialPagePostCountry.cshtml");
<input type="submit" />
}
Class To Catch Post Data
public class CountryCityState
{
public string Country { get; set; }
public string State { get; set; }
public string City { get; set; }
}
Controller
public class TestPostPartialController : Controller
{
// GET: TestPostPartial
public ActionResult IndexTestPost()
{
return View();
}
[HttpPost]
public ActionResult IndexTestPost(CountryCityState CtnCtySta)
{
return View();
}
}

MVC jqgrid - cannot get Model Binding to work to edit object for cell update

I am using JQGrid to edit a grid of data.
I want to send the data in the cell to be edited on the server.
So my view looks like;
#using Lib.Web.Mvc.JQuery.JqGrid
#using Tac.P3.Model
#model ContractType
#{
ViewBag.Title = "List of Contract Types";
var grid = new JqGridHelper<ContractType>(
"ContractTypes",
dataType: JqGridDataTypes.Json,
methodType: JqGridMethodTypes.Post,
pager: true,
rowsNumbers: true,
rowsNumber: 20,
shrinkToFit: true,
cellEditingEnabled: true,
cellEditingSubmitMode: JqGridCellEditingSubmitModes.Remote,
cellEditingUrl:Url.Action("Update", "ContractType"),
sortingName: "ContractTypeLabel", sortingOrder: JqGridSortingOrders.Asc,
url: Url.Action("GridData"),
viewRecords: true)
.Navigator(new JqGridNavigatorOptions()
{
Add = false,
Edit = false,
Delete = false,
Search = false
});
}
<div class="mainPage">
<fieldset>
<legend>Add New Contract Type</legend>
<form class="form-horizontal" method="POST" action="#Url.Action("Add")">
<div class="form-group">
<div class="col-xs-1">#Html.LabelFor(model => model.ContractTypeLabel)</div>
<div class="col-xs-10">#Html.TextBoxFor(model => model.ContractTypeLabel)</div>
</div>
<div class="form-group">
<div class="col-xs-1">#Html.LabelFor(model => model.Description)</div>
<div class="col-xs-10">#Html.TextBoxFor(model => model.Description)</div>
</div>
<div class="form-group">
<div class="col-xs-offset-1 col-xs-10">
<button class="btn btn-primary" id="btnSubmitForm">Add</button>
</div>
</div>
</form>
</fieldset>
<fieldset>
<legend>List of Contract Types</legend>
#Html.Partial("_Messages")
<div id="loadingMessage" class="errorHighlight">Loading list of Contract Types, please wait...</div>
#grid.GetHtml()
</fieldset>
</div>
My method on the controller looks like
public ActionResult Update(CellEditingViewModel viewModel)
{
var contractType = this.TacUoW.ContractType.GetById(viewModel.Id);
switch (viewModel.PropertyName)
{
case "ContractTypeLabel":
contractType.ContractTypeLabel = viewModel.PropertyValue.ToString();
break;
case "Description":
contractType.Description = viewModel.PropertyValue.ToString();
break;
}
this.TacUoW.ContractType.Update(contractType);
this.TacUoW.SaveChanges();
return this.Json(true);
}
My view model looks like;
[ModelBinder(typeof(CellEditingViewModelBinder))]
public class CellEditingViewModel
{
public int Id { get; set; }
public string PropertyName { get; set; }
public object PropertyValue { get; set; }
}
My ModelBinder looks like;
public class CellEditingViewModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var request = controllerContext.HttpContext.Request;
var model = new CellEditingViewModel { Id = Convert.ToInt32(request.Params["ContractTypeId"]) };
if (request.Params.AllKeys.Contains("ContractTypeLabel"))
{
model.PropertyName = "ContractTypeLabel";
model.PropertyValue = request.Params["ContractTypeLabel"];
}
else if (request.Params.AllKeys.Contains("Description"))
{
model.PropertyName = "Description";
model.PropertyValue = request.Params["Description"];
}
return model;
}
}
When I press transmitt in the grid, I get an empty object sent to the controller method, no model binding takes place. What am I doing wrong?
I found what the problem was, and I have not posted all the code to show it.
For my class CellEditingViewModel I was using
using System.Web.Http.ModelBinding;
Which is wrong. I should have been using
using System.Web.Mvc;

Retrieving values from partial view during post method

I have a view which contains a dropdown list and on dropdownlist item being selected I load a partial view. And when the form is submitted I want to be able to get both the values from main view and partial view during form submit.
Here is the main view
#model AdminPortal.Areas.Hardware.Models.CreateModule
#{
ViewBag.Title = "Create Module";
Layout = "~/Views/shared/_BootstrapLayout.basic.cshtml";
}
#Html.ValidationSummary(true)
<fieldset class="form-horizontal">
<legend>Add a Module <small>Create</small></legend>
#using (Html.BeginForm("CreateModule", "Module", new{id="AddModuleForm"}))
{
#Html.ValidationSummary(true)
<div class ="controls">
<div class="input-block-level">#Html.TextBoxFor(model => model.ModuleId, new {#placeholder = "ModuleID"})</div>
<br/>
<div class ="input-block-level" id="selectedModuleTypeName">#Html.DropDownListFor(model => model.SelectedModuleTypeName, Model.TypeNames,"Select Moduletype", new{id = "ModuleList"})</div>
<br/>
<div id="partialDiv"></div>
</div>
<div class="form-actions" id="buttons">
<button type="submit" class="btn btn-primary" id="Submit">Save changes</button>
#Html.ActionLink("Cancel", "ModuleList", null, new { #class = "btn " })
</div>
}
</fieldset>
<div>
#Html.ActionLink("Back to List", "ModuleList")
</div>
<script>
$("#buttons").hide();
$("#ModuleList").on("change", function() {
var modId = $(this).val();
$.get('#Url.Action("GetModulePropertyName", "Module")', { moduleTypeValue: modId }, function(result) {
$("#partialDiv").html(result);
});
//uncomment following section to check if the partial view is working properly
/*.done(function() { alert("done"); })
.fail(function() { alert("fail"); })
.always(function() { alert("completed"); });*/
});
$("#buttons").show();
</script>
and here is the partial view
#model IEnumerable<string>
#foreach(var names in Model)
{
<div class="input-block-level">#Html.TextBoxFor(m=>names, new{Value="", placeholder=names})</div>
<br/>
}
Here is my model
public class CreateModule
{
//Empty form to handle form serialization
public CreateModule()
{
}
[Required]
public string ModuleId { get; set; }
[DataType(DataType.DateTime)]
public DateTime DateEntered { get; set; }
[Required]
public string SelectedModuleTypeName { get; set; }
public IEnumerable<SelectListItem> TypeNames { get; set; }
public List<Property> Properties { get; set; }
}
public class Property
{
public string Name { get; set; }
public string Value { get; set; }
}
Here is the method that script in main view forwards to
[HttpGet]
public ActionResult GetModulePropertyName(string moduleTypeValue)
{
var moduleKindId = _repository.GetModuleKindId(moduleTypeValue);
var modulePropertyNames = _repository.GetModuleKindPropertyNames(moduleTypeValue);
return PartialView("GetModulePropertyName",modulePropertyNames);
}
and finally here is httppost method for the main view
[HttpPost]
public ActionResult CreateModule(CreateModule moduleV)
{
var module = new Module
{
ModuleTypeId = Convert.ToInt64(moduleV.SelectedModuleTypeName),
ModuleId = moduleV.ModuleId,
DateEntered = moduleV.DateEntered,
};
if (ModelState.IsValid)
{
_repository.AddModule(module);
Success("Module added successfully!");
return RedirectToAction("ModuleList", "Module", new {area = "Hardware"});
}
Error("Something went wrong!");
return RedirectToAction("CreateModule", "Module", new { area = "Hardware" });
}
Current situation:
When the form is posted, the properties value of the model that is being passed via partial view is null. I get other values, like typename, Module ID.
What I'd want:
I also want to get the value of properties that is being passed via partial view.
You don't have any input field for the Properties property anywhere in your form. So it will always be null. That's normal.
Here's how you could proceed. Start by setting the correct navigational property so that the helper generates correct names of the corresponding input fields.
Also make sure that you are passing an IEnumerable<Property> model to the partial if you want to be able to get them back correctly:
[HttpGet]
public ActionResult GetModulePropertyName(string moduleTypeValue)
{
var moduleKindId = _repository.GetModuleKindId(moduleTypeValue);
IList<Property> model = ...
return PartialView("GetModulePropertyName", model.ToList());
}
and in your partial view use an editor template:
#model IList<Property>
#{
// This indicates the current navigational context to the helpers
ViewData.TemplateInfo.HtmlFieldPrefix = "Properties";
}
#Html.EditorForModel()
and the last step is to define a custom editor template for the Property class: ~/Views/Shared/EditorTemplates/Property.cshtml (note that the name and location of the template is important)
#model Property
<div class="input-block-level">
#Html.HiddenFor(m => m.Name)
#Html.TextBoxFor(m => m.Value, new { placeholder = Model.Name })
</div>
<br />
Try using the
List<Property>
as a model in your partial view and pass the CreateModule.Properties as model from your View
The problem is model binder can not figure out there
#Html.TextBoxFor(m=>names, new{Value="", placeholder=names})
belongs to as the "names" is not a property on your model class. If you need to bind to the CreateModule.Properties you need to change the partial view to emit textboxes with aproprate names, like this one:
#model IEnumerable<string>
#
{
int i=0;
}
#foreach(var names in Model)
{
<div class="input-block-level">#Html.TextBox("Properties[" + i + "].Value")</div>
<br/>
}

MVC validation not working as expected in popup window

I'm trying to utilize the built-in validation in MVC and it doesn't appear to be working. If I leave the fields in my form empty I get back my "Successfully Saved." message.
Shouldn't the fields that are 'Required' be marked as required in the form?
Controller:
public ActionResult Create([DataSourceRequest] DataSourceRequest request, ACore.Asset assetForm)
{
var results = new
{
value = false,
Message = ""
};
if (ModelState.IsValid)
{
results = new
{
value = true,
Message = "Successfully Saved."
};
return Json(results);
}
results = new
{
value = false,
Message = "Please check the form values."
};
return Json(results);
}
View (condensed):
#using (Html.BeginForm("Create", "Asset", FormMethod.Post, new { id = "frmAsset"}))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="tempStyle">
<div class="editor-label fl">
#Html.LabelFor(model => model.AssetName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AssetName)
#Html.ValidationMessageFor(model => model.AssetName)
</div>
</div>
<div style="position: relative;">
<input type="button" value="Save" id="btnSave" />
</div>
JavaScript that handles my save:
var saveAsset = function (e) {
var form = $("#frmAsset");
$.ajax({
type: "POST",
url: "/Asset/Create",
data: $(form).serialize(),
success: function (data) {
if (data.value == true) {
alert(data.Message);
// Close popup window
var window = $('#AssetEditorPopUp').data("kendoWindow");
window.close();
// Refresh grid to show changes
$('#grid').data("kendoGrid").dataSource.read();
return;
}
alert(data.Message);
},
error: function () {
alert("There was an error editing the asset.");
}
});
};
Model:
public class Asset
{
[ScaffoldColumn(false)]
public int AssetId { get; set; }
[Required]
[Display(Name="Asset Name:")]
public string AssetName { get; set; }
public string AssetFormerName { get; set; }
public string Seg1Code { get; set; }
public string Seg3Code { get; set; }
public bool? ActiveFlag { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string AssetType { get; set; }
[Display(Name = "ZipCode:")]
[RegularExpression("([a-zA-Z0-9 .&'-]+)", ErrorMessage = "Enter only alphabets and numbers of First Name")]
public string ZipCode { get; set; }
}
This is what I get doing a POST rather than ajax.
your button is set to be of type button, it should be submit. you aren't firing the forms submit handler, so validation isn't being performed.
create your form like this:
#using (Html.BeginForm("Create", "Asset", FormMethod.Post, new { id = "frmAsset"}))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="tempStyle">
<div class="editor-label fl">
#Html.LabelFor(model => model.AssetName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AssetName)
#Html.ValidationMessageFor(model => model.AssetName)
</div>
</div>
<div style="position: relative;">
<input type="submit" value="Save" id="btnSave" />
</div>
Then you can use the following javascript:
$(function(){
$("#frmAsset").submit(function(evt){
var form = $(evt.currentTarget);
if(form.validate().isvalid()
{
// your handler here
}
evt.preventDefault(); // prevent the form from posting normally
return false;
};
};
I would look at the AJAX.BeginForm helper, it is more suited to what you are doing here.
nb. I haven't typed this in the ide, so i don't know if it is 100% valid, you will need to test it.

Resources