On posting to server all collections of complex types are null in mvc - asp.net-mvc

I'm fairly new to MVC but am progressing.
I have come across an issue that I can't seem to solve and would be greatful of any assistance.
When I post to the server my edits (in RoutineViewModel) are mostly lost, primitive data types are persisted (in class Routine) but collections of complex types (ICollection<RoutineExercise>) are lost.
I found this MVC Form not able to post List of objects and followed the advice to seperate the view into an EditorTemplate but this has not worked. Using the '#foreach' loop still produces all the page controls with the same id and name when you viewsource. I tried using a for (int i = 1; i <= 5; i++) type loop as many other posts suggest but get errors about not being able to apply index to my object.
Also the fact this #Html.DropDownListFor(model => Model.ExerciseId, Model.Exercises, "", new { #class = "input-sm col-md-12" }) does not select the correct list item (Model.ExerciseId has the correct value) concerns me.
Any help/advice would be great as I'm stuck and have been for 3 days now.
* POCO *
public partial class Routine
{
public Routine()
{
this.RoutineExercises = new List<RoutineExercise>();
}
public int Id { get; set; }
public string RoutineName { get; set; }
public string Description { get; set; }
...Other fields removed for clarity...
public virtual ICollection<RoutineExercise> RoutineExercises { get; set; }
}
public partial class RoutineExercise
{
public int Id { get; set; }
public int RoutineId { get; set; }
public int Exerciseid { get; set; }
public int SetsToDo { get; set; }
public int RepsToDo { get; set; }
...Other fields removed for clarity...
public virtual Exercise Exercise { get; set; }
public virtual Routine Routine { get; set; }
}
* VIEWMODEL *
public class RoutineViewModel
{
//Routine information
public int Id { get; set; }
[Display(Name = "Name")]
public string RoutineName { get; set; }
public string Description { get; set; }
//Exercise information
[Display(Name = "Exercise")]
public ICollection<RoutineExercise> RoutineExercises { get; set; }
public IEnumerable<SelectListItem> Exercises { get; set; }
public int ExerciseId { get; set; }
}
* FORM *
<div class="panel-body">
#using (Html.BeginForm("Edit", "Workout"))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Id)
#Html.EditorForModel()
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
</div>
* EDITOR TEMPLATE *
<div class="form-group">
#Html.LabelFor(model => model.RoutineName, new { #class = "control-label col-md-1" })
<div class="col-md-2">
#Html.EditorFor(model => model.RoutineName)
#Html.ValidationMessageFor(model => model.RoutineName)
</div>
#Html.LabelFor(model => model.Description, new { #class = "control-label col-md-1" })
<div class="col-md-2">
#Html.EditorFor(model => model.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
</div>
#foreach (var e in Model.RoutineExercises)
{
#Html.LabelFor(model => model.RoutineExercises, new { #class = "control-label col-md-1" })
<div class="col-md-3">
#*TO FIX This does NOT bind the selected value*#
#Html.DropDownListFor(model => Model.ExerciseId, Model.Exercises, "", new { #class = "input-sm col-md-12" })
</div>
<div class="col-md-12">
#Html.LabelFor(model => e.SetsToDo, new { #class = "control-label col-md-2" })
#Html.EditorFor(m => e.SetsToDo, new { #class = "control-label col-md-10" })
</div>
}
* CONTROLLER *
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(RoutineViewModel rvm) /*rvm always null for collections only*/
{
if (ModelState.IsValid)
{
//Save Routine
var r = new Routine
{
Id = rvm.Id,
RoutineName = rvm.RoutineName,
Description = rvm.Description,
RoutineFrequencyId = rvm.RoutineFrequencyId,
RoutineLengthId = rvm.RoutineLengthId
};
_repo.Update(r);
return RedirectToAction("Index");
}
return View(getRoutineViewModel(rvm.Id));
}

First, avoid the term "complex type" unless you're actually talking about a complex type in Entity Framework. It just creates confusion, and honestly, nothing you have here is really "complex" anyways.
You will indeed need to employ a for loop with an index instead of foreach to get the proper field names for the modelbinder to work with. However, the reason you're getting an error is that ICollection is not subscriptable ([N]). You can use ElementAt(N) to pull out the item at an index, but unfortunately, Razor will still not create the right field names with that. As a result, you need to use something like List for your collection properties to edit them inline. Since you're already using a view model this is trivial. Just change the property type from ICollection<RoutineExcercise> to List<RoutineExcercise> on your view model.

Related

complex type model wont pass list propery

hi guys i am having trouble with my mvc app. its a simple quiz app and i am stuck at creating create view for question model.
I have Question and Option model with appropriate view models(in my case they are QustionDTO and OptionDTO) and i want to make cshtml create view for Question with list of Options.like this but when i submit form, my list of options is null.
this is my Question and Option model
public class Question
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string QuestionText { get; set; }
public virtual ICollection<Option> Options { get; set; }
}
public class Option
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[Display(Name ="Answer text")]
public string OptionText { get; set; }
[Required]
public bool IsCorrect { get; set; }
}
this is my DTO models
public class QuestionDTO
{
public int Id { get; set; }
public string QuestionText { get; set; }
public List<OptionDTO> Options { get; set; }
}
public class OptionDTO
{
public int Id { get; set; }
public string OptionText { get; set; }
public bool IsCorrect { get; set; }
}
and this is my view with editor template located in "~/views/shared/editortemplate/OptionDTO.cshtml"
#model Quiz.BusinessEntites.QuestionDTO
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>QuestionDTO</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.QuestionText, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.QuestionText, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.QuestionText, "", new { #class = "text-danger" })
</div>
</div>
<table class="table" style="width:50%">
#for (int i = 0; i < 3; i++)
{
#Html.EditorFor(model=>model.Options[i])
}
</table>
<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>
this is OptionDTO editor template
#using Quiz.BusinessEntites
#model Quiz.BusinessEntites.OptionDTO
<tr>
<th class="col-md-2">
#Html.DisplayNameFor(m => m.OptionText)
</th>
<th class="col-md-2">
#Html.DisplayNameFor(m => m.IsCorrect)
</th>
</tr>
<tr>
<td class="col-md-2">
#Html.EditorFor(m => m.OptionText)
</td>
<td class="col-md-2">
#Html.EditorFor(m => m.IsCorrect)
</td>
</tr>
from the image above u can see that options list is null. if u have any suggestion it will be appreciated.
In your http post action method, the Bind attribute with Include list is telling the Model binder to bind only "Id","QuestionText" and "IsCorrect" properties of QuestionDto object from the posted form data. So the model binder will not bind the Options property value.
Remove the Bind attribute from your Http post action method.
There is no need to use the Bind attribute if your view model is specific to your view, means you have only properties needed for your view (In your case it looks like so)
public ActionResult Create(QuestionDTO model)
{
// to do :return something
}
If you want to use a non view specific view model, but still want to use Bind attribute to specify only subset of properties, Include just those properties. In your case, your code will be like
public ActionResult Create([Bind(Include="Id,QuestionText",Options"] QuestionDTO model)
{
// to do :return something
}
Also you should editer template view should be in a directory called EditorTemplates , not EditorTemplate

ViewModel Contents are Null after Posting Form to Controller

So the ViewModel has 2 sets of data.
The CurrentDetails and UpdatedDetails. Both are instances of the same class which carries strings and whatnot inside etc.
This method has worked with all other views and models I've attempted with, but for THIS one instance, when the form is posted to the controller, its contents (CurrentDetails and UpdatedDetails) are both found to be null.
I've tried changing the parameter name from model to test and to other arbitrary things, but to no avail.
The one thing that worked (but is not a solution to me) is NOT having instances of the class inside the ViewModel, and just having the data there (but I don't see why I should be forced to do things this way.
Here's the controller:
[HttpPost]
public ActionResult FloristProfile(MerchantFloristProfileViewModel test)
{
if (!ModelState.IsValid)
return View(test);
using (var db = new ApplicationDbContext())
{
Florist florist = db.Florists.Find(MerchantBase.FloristID);
if (Request.Form["editSubmit"] != null)
{
florist.Name = test.UpdatedDetails.Name;
florist.Website = test.UpdatedDetails.Website;
db.SaveChanges();
return RedirectToAction("FloristProfile");
}
else if (Request.Form["photoSubmit"] != null)
{
if (test.CurrentDetails.File.ContentLength > 0)
{
CloudBlobContainer container = FlowerStorage.GetCloudBlobContainer();
string blobName = String.Format("florist_{0}.jpg", Guid.NewGuid().ToString());
CloudBlockBlob photoBlob = container.GetBlockBlobReference(blobName);
photoBlob.UploadFromStream(test.CurrentDetails.File.InputStream);
florist.LogoPath = blobName;
florist.isRendering = true;
db.SaveChanges();
return RedirectToAction("FloristProfile");
}
}
}
return Content("Invalid Request");
}
View:
#using (Html.BeginForm("FloristProfile", "Merchant", FormMethod.Post, new { #class = "form-horizontal" }))
{
#Html.ValidationSummary(false, "", new { #class = "text-danger" })
#Html.HiddenFor(x => x.CurrentDetails.FloristID)
#Html.HiddenFor(x => x.CurrentDetails.Name)
#Html.HiddenFor(x => x.CurrentDetails.StaffCount)
#Html.HiddenFor(x => x.CurrentDetails.StoreCount)
#Html.HiddenFor(x => x.CurrentDetails.Website)
<div class="form-group">
#Html.LabelFor(x => x.UpdatedDetails.Name, new { #class = "col-sm-2 control-label" })
<div class="col-sm-10">
#Html.TextBoxFor(x => x.UpdatedDetails.Name, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(x => x.UpdatedDetails.Website, new { #class = "col-sm-2 control-label" })
<div class="col-sm-10">
#Html.TextBoxFor(x => x.UpdatedDetails.Website, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" name="editSubmit" class="btn btn-success">Save</button>
</div>
</div>
}
ViewModel:
public class MerchantFloristProfileViewModel
{
public class FloristProfileDetails
{
public int FloristID { get; set; }
[Required(ErrorMessage = "Please Enter a Name")]
public string Name { get; set; }
[DataType(DataType.Url)]
[Required(ErrorMessage = "Please Enter a Website")]
public string Website { get; set; }
public int StoreCount { get; set; }
public int StaffCount { get; set; }
// For Picture Upload
public HttpPostedFileBase File { get; set; }
}
public FloristProfileDetails CurrentDetails;
public FloristProfileDetails UpdatedDetails;
}
Both CurrentDetails and UpdatedDetails in your MerchantFloristProfileViewModel model are fields, not properties (no getter/setter) so the DefaultModelBinder cannnot set the values. Change them to
public FloristProfileDetails CurrentDetails { get; set; }
public FloristProfileDetails UpdatedDetails { get; set; }
But you should not be sending all that extra data to the view, then sending it all back again unchanged. Apart from the extra overhead, any malicious user could alter the values in the hidden fields causing your app to fail. Just get the original from the repository again if you need it in the POST method

Store update, insert, or delete statement affected an unexpected number of rows (0)

I'm working on an application for insurance requests. When I log in as CommitteeMember and try to Edit a request(editing only its status to "approved" for ex) I get this error:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded.
The line posing a problem is a like in the context class in the Data Project.
public virtual void Commit()
{
base.SaveChanges();
}
Here's my model:
public class Request
{
[Key]
public int RequestId { get; set; }
public DateTime Date { get; set; }
public string nomBeneficiary { get; set; }
[DataType(DataType.Upload)]
public byte[] file { get; set; }
public Member beneficiary { get; set; }
public string status { get; set; }
public string eMailBeneficiairy { get; set; }
}
And here's the controller:
public ActionResult EditRequest (int id)
{
RequestService requestService = new RequestService();
Request request =requestService.GetRequestById(id);
return View(request);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult EditRequest([Bind(Exclude = "RequestId,Date,nomBeneficiairy,file,beneficiare,eMailBeneficiairy")]Request request)
{
if (ModelState.IsValid) {
RequestService requestService = new Requestervice();
requestService.UpdateRequest(request);
return View("requests");
}
return View(request);
}
And here's the view:
#model Mutuelle.Domain.Entities.Request
#{
ViewBag.Title = "EditRequest";
}
<h2>EditRequest</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Request</h4>
<hr />
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.RequestId)
<div class="form-group">
#Html.LabelFor(model => model.status, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.status)
#Html.ValidationMessageFor(model => model.status)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Since you are editing, the object should already exist. But it's not attached to the context; try using Attach to attach it first, or query the object from the context and copy the properties over. See this for more info.
EDIT: Per your date comment, you need to either change this to nullable:
public DateTime? Date { get; set; }
And also make null in the DB, or in your controller, before attaching, default it to a date greater than 1/1/1753 (a minimum value allowed in SQL for datetime).

Adding/Update Related Data ASP.NET MVC 5

I'm new to programming so I'm still learning.
I need to add Items to Grocery from a single view. But I can't get the data to save.
When I hit save I don't get any exceptions, the page just loads but nothing is saved to the
database. Can I get some help/guidance as to what I am doing wrong?
Data Class
public class Grocery
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Item> Items { get; set; }
}
public class Item {
public int ItemId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
View Model
public class GroceryViewModel
{
public Grocery Grocery { get; set; }
public virtual Item Item { get; set; }
public GroceryViewModel(int GroceryId)
{
using (var db = new MyContext())
{
Grocery = db.Groceries
.Include("Items")
.SingleOrDefault(a => a.Id == GroceryId);
}
}
}
Controller
public ActionResult Edit(int GroceryId, GroceryViewModel groceryViewModel)
{
var model = new GroceryViewModel(GroceryId);
var plusItems = new Item
{
Name = groceryViewModel.Item.Name,
Description = groceryViewModel.Item.Description,
};
db.Items.Add(plusItems);
db.SaveChanges();
return RedirectToAction(model);
View
#model Project.Models.GroceryViewModel
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Groceries</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Item.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Item.Name)
#Html.ValidationMessageFor(model => model.Item.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Item.Description)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Item.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
<p>
<input type="submit" value="Add Item" />
</p>
</fieldset>
try
using (var db = new MyContext())
{
var grocery = db.Groceries.Single(a => a.Id == groceryId);
var plusItems = new Item
{
Name = groceryViewModel.Item.Name,
Description = groceryViewModel.Item.Description,
};
grocery.Items.Add(plusItems);
db.SaveChanges();
}

MVC model not bound to collection in POST

Visual Studio 2012
MVC 5.2.0
jQuery 2.1.1
jQuery UI Combined 1.10.4
Json.Net 6.0.3
Kendo UI MVC 2014.1.528
I've read through many similar posts. I've tried to apply what I've seen to no avail. I throw myself at feet of your keyboards for your mercy and help.
I have a survey-like site, so for simplicity here, I made up a sample project to duplicate the issue. I can upload that too.
I have a model with a child object of Address - no problemo there - it binds.
The model also has a collection of questions. It never binds on the post, and this is the issue here.
Lets start with models:
The Survey itself:
public class Survey
{
[ScaffoldColumn(false)]
public int Id { get; set; }
public string Name { get; set; }
[DisplayName("Phone #")]
[StringLength(15)]
[DataType(DataType.PhoneNumber)]
public string ContactMethodPhone { get; set; }
[DisplayName("Email")]
[StringLength(120)]
[EmailAddress]
public string ContactMethodEmail { get; set; }
public virtual Address Address { get; set; }
public virtual List<Question> Questions { get; set; }
}
and the address:
public class Address
{
[ScaffoldColumn(false)]
public int AddressId { get; set; }
[DisplayName("Address line 1")]
[Required]
[StringLength(200)]
public string Street { get; set; }
[DisplayName("Address line 2")]
[StringLength(200)]
public string Street2 { get; set; }
[DisplayName(" ")]
[StringLength(50)]
public string ApartmentNum { get; set; }
[DisplayName("Type")]
[StringLength(50)]
public string Tenement { get; set; }
[DisplayName("City")]
[Required]
[StringLength(200)]
public string City { get; set; }
[DisplayName("Province/State")]
[StringLength(20)]
public string State { get; set; }
[Required]
[DisplayName("Postal/Zip Code")]
[StringLength(10)]
public string MailCode { get; set; }
[DisplayName("Country")]
[StringLength(30)]
public string Country { get; set; }
[NotMapped]
public List<SelectListItem> Cities { get; set; }
[NotMapped]
public List<SelectListItem> Provinces { get; set; }
[NotMapped]
public List<SelectListItem> Countries { get; set; }
[NotMapped]
public List<SelectListItem> MailCodes { get; set; }
[NotMapped]
public List<SelectListItem> Tenements { get; set; }
}
and the questions:
public class Question
{
[ScaffoldColumn(false)]
public int Id { get; set; }
[ScaffoldColumn(false)]
public int ImageID { get; set; } // Question Image
[DisplayName("Question: ")]
public string InformationIntakeGroupValue { get; set; } // Question: ie Did you graguate high school
public int ImageID_Topic { get; set; } // Topic Image
[DisplayName("Topic: ")]
public string InformationIntakeTopicValue { get; set; } // Topic: ie Education
[ScaffoldColumn(false)]
public string InformationIntakeTypeCode { get; set; } // Type of question (date, bool, text)
// below not filled by select;
// the key from the EntityAffilliateIntake record insert
public int PersonId { get; set; } // Person anwering question
// this is the user response area
[DisplayName("Answer: ")]
public string InformationIntakeValue { get; set; }
[DisplayName("Choice: ")]
public string InformationIntakeValueBool { get; set; }
[DisplayName("Date: ")]
public DateTime InformationIntakeValueDate { get; set; }
[ForeignKey("Survey")]
public int SurveyId { get; set; }
public virtual Survey Survey { get; set; }
}
(Note: fyi, I've tried the models without foreign keys as well - but perhaps it's not defined correctly)
The controller :
// GET: /Inquiry/Create
public ActionResult Create()
{
var geoIpData = Strings._download_serialized_json_data<GeoData>(StringConstants.UrlForGeoService);
SurveyModel = new Survey
{
Id=1,
Address = new AddressController().GetAddressModel(geoIpData.country, geoIpData.regionName, geoIpData.city, ""),
Questions = new QuestionController().GetQuestions(1).ToList()
};
return View(SurveyModel);
}
// POST: /Inquiry/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]
public ActionResult Create([Bind(Include = "Id, Name, ContactMethodPhone, ContactMethodEmail, Address, Questions")] Survey survey)
{
if (ModelState.IsValid)
{
int i = 0;
}
if (survey.Address.Cities == null)
{
survey.Address.Cities = SurveyModel.Address.Cities;
survey.Address.Countries = SurveyModel.Address.Countries;
survey.Address.MailCodes = SurveyModel.Address.MailCodes;
survey.Address.Provinces = SurveyModel.Address.Provinces;
survey.Address.Tenements = SurveyModel.Address.Tenements;
}
if (survey.Questions == null)
{
survey.Questions = SurveyModel.Questions;
}
return View(survey);
}
The view:
#model BindCollection.Models.Survey
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
// I use <div class="container">, <fieldset> and <div class="row">
// instead of <div class="form-horizontal"> and <div class="form-group">
<div class="container">
<fieldset>
<legend></legend>
<h4>Survey</h4>
<hr />
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Id)
<div class="row">
#Html.LabelFor(model => model.Name, new { #class = "control-label col-md-2 col-md-2 col-lg-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
Your address information:<br />
<br />
</div>
</div>
#* no problem here with address *#
#{ var vddAddress = new ViewDataDictionary(ViewData) { TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = "Address" } };}
#Html.Partial("_AddressPartial", Model.Address, #vddAddress)
<hr />
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
How we can contact you? :<br />
<br />
</div>
</div>
<div class="row">
#Html.LabelFor(model => model.ContactMethodPhone, new { #class = "control-label col-sm-2 col-md-2 col-lg-2" })
<div class="col-sm-10 col-md-10 col-lg-10">
#Html.EditorFor(model => model.ContactMethodPhone)
#Html.ValidationMessageFor(model => model.ContactMethodPhone)
</div>
</div>
<div class="row">
#Html.LabelFor(model => model.ContactMethodEmail, new { #class = "control-label col-sm-2 col-md-2 col-lg-2" })
<div class="col-sm-10 col-md-10 col-lg-10">
#Html.EditorFor(model => model.ContactMethodEmail)
#Html.ValidationMessageFor(model => model.ContactMethodEmail)
</div>
</div>
<hr />
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
Some Questions<br />
<br />
</div>
</div>
#*Here is the evil one! Beware!*#
#{ var vddQuestions = new ViewDataDictionary(ViewData) { TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = "Questions" } };}
#Html.Partial("_QuestionsPartial", Model.Questions, #vddQuestions)
<hr />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</fieldset>
</div>
}
The address partial view is insignificant as there's no problem there.
Here's the partial view for the questions:
#model IEnumerable<BindCollection.Models.Question>
#using BindCollection
#using Kendo.Mvc.UI
#{
string CurrentTopic = string.Empty;
bool FirstTime = true;
}
#foreach (var item in Model)
{
if (CurrentTopic != item.InformationIntakeTopicValue)
{
CurrentTopic = item.InformationIntakeTopicValue;
if (!FirstTime)
{
FirstTime = false;
item.InformationIntakeTopicValue = string.Empty;
}
}
else
{
item.InformationIntakeTopicValue = string.Empty;
}
#Html.EditorFor(m=>item, "Question")
<br />
<br />
}
and, of course, I made an EditorTemplate for a question, as you can see a few lines above...
#model BindCollection.Models.Question
#Html.HiddenFor(m=>m.Id)
#{if (!string.IsNullOrWhiteSpace(Model.InformationIntakeTopicValue))
{
<hr />
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
<br />
<br />
<h4>
#Html.DisplayFor(m => m.InformationIntakeTopicValue, new { #class = "control-label col-sm-10 col-md-10 col-lg-10" })
</h4>
<br />
</div>
</div>
}}
#*type of value to ask for is denoted by item.InformationIntakeTypeCode*#
<div class="row">
<div class="control-label col-sm-9 col-md-9 col-lg-9">
#Html.DisplayFor(m => m.InformationIntakeGroupValue, null)
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
#{
var outputBool = false;
var outputDate = false;
var outputText = false;
if(Model.InformationIntakeTypeCode.ToLower().Contains("date") )
{
outputDate = true;
}
else if (Model.InformationIntakeTypeCode.ToLower().Contains("bool"))
{
outputBool = true;
}
else if (Model.InformationIntakeTypeCode.ToLower().Contains("string"))
{
outputText = true;
}
}
#if(outputBool)
{
#(Html.Kendo().DropDownListFor(m => m.InformationIntakeValueBool)
.HtmlAttributes(new { style ="width: 100px;", id="InformationIntakeValueBool"+Model.Id.ToString() })
.DataTextField("Text")
.DataValueField("Value")
.BindTo(new List<SelectListItem>() {
new SelectListItem() {
Text = "Please Select...",
Value = "0"
},
new SelectListItem() {
Text = "Yes",
Value = "true"
},
new SelectListItem() {
Text = "No",
Value = "false"
}
}).Value("0")
)
}
#if(outputDate)
{
#(Html.Kendo().DatePickerFor(m => m.InformationIntakeValueDate)
.HtmlAttributes(new { style = "width: 100px;", id="InformationIntakeValueDate"+Model.Id.ToString() })
)
}
#if (outputText)
{
#Html.Kendo().AutoCompleteFor(m => m.InformationIntakeValue).HtmlAttributes(new { style = "width: 80%;", id="InformationIntakeValue"+Model.Id.ToString()})
}
</div>
</div>
So... When I POST, an odd thing occurs. All form values are passed, but the ones for Questions look strange:
Id 1
Name sally
Address.Street 123 Avenue Steet
Address.Street2 Building C
Address.Tenement Suite
Address.ApartmentNum 111
Address.City_input Sarnia
Address.City Sarnia
Address.State_input Ontario
Address.State Ontario
Address.Country_input Canada
Address.Country Canada
Address.MailCode_input N6B 2K0
Address.MailCode N6B 2K0
ContactMethodPhone 555-555-5555
ContactMethodEmail r#r.com
Questions.item.Id 1
Questions.item.InformationIntakeValueBool true
Questions.item.Id 2
Questions.item.InformationIntakeValueDate 2/4/2014
Questions.item.Id 3
Questions.item.InformationIntakeValue Speckled
Questions.item.Id 4
Questions.item.InformationIntakeValueBool true
Questions.item.Id 5
Questions.item.InformationIntakeValue Lightly Toasted
Questions.item.Id 7
Questions.item.InformationIntakeValueBool true
Questions.item.Id 8
Questions.item.InformationIntakeValue Nothing!
Questions.item.Id 6
Questions.item.InformationIntakeValueBool true
Questions.item.Id 9
Questions.item.InformationIntakeValueDate 6/29/2014
I thought, as I've seen in other posts, that the Question items should look like:
Questions[0].Id 1
Questions[0].InformationIntakeValueBool true
Questions[1].Id 2
Questions[1].InformationIntakeValueDate 2/4/2014
Questions[2].Id 3
Questions[2].InformationIntakeValue Speckled
So I'm not sure why mine looks like this.
On the server side, the Request only shows one variable for each:
Request.Form.Results View Expanding the Results View will enumerate the IEnumerable
[0] "__RequestVerificationToken" object {string}
[1] "Id" object {string}
[2] "Name" object {string}
[3] "Address.Street" object {string}
[4] "Address.Street2" object {string}
[5] "Address.Tenement" object {string}
[6] "Address.ApartmentNum" object {string}
[7] "Address.City_input" object {string}
[8] "Address.City" object {string}
[9] "Address.State_input" object {string}
[10] "Address.State" object {string}
[11] "Address.Country_input" object {string}
[12] "Address.Country" object {string}
[13] "Address.MailCode_input" object {string}
[14] "Address.MailCode" object {string}
[15] "ContactMethodPhone" object {string}
[16] "ContactMethodEmail" object {string}
[17] "Questions.item.Id" object {string}
[18] "Questions.item.InformationIntakeValueBool" object {string}
[19] "Questions.item.InformationIntakeValueDate" object {string}
[20] "Questions.item.InformationIntakeValue" object {string}
See the last 4 items? What about the other records?
I'm guessing that there's something strange with the ViewDataDictionary that I'm sending to the Question Partial View.
Any help would be appreciated. This should be simple...
Neither Partial Views or foreach statements contain the necessary information to properly bind collections. I always use EditorTemplates for everything. Especially since EditorTemplates will automatically iterate over a collection.
However, if you're bound and determined to use a loop, then use a for loop, and then index the model. In your case:
#for(int i = 0; i < Model.Count; i++)
{
if (CurrentTopic != Model[i].InformationIntakeTopicValue)
...
#Html.EditorFor(m => Model[i]) // don't have to specify the template name
// since it's the same name as the type
}
However, i'd just do this:
In your view, do this:
#*Here is the evil one! Beware!*#
#Html.EditorFor(m => m.Questions)
Then have your Question.cshtml as usual, which will automatically get iterated over by the EditorFor
However, at the top of the Question.cshtml (after the model declaration) add the following code, it's all that's necessary to achieve what you're trying to do. You don't need the partial view at all.
#{
if (ViewBag.CurrentTopic != Model.InformationIntakeTopicValue)
{
ViewBag.CurrentTopic = Model.InformationIntakeTopicValue;
}
else
{
Model.InformationIntakeTopicValue = string.Empty;
}
}

Resources