MVC Model Binding EF 6 Missing Parent Reference - asp.net-mvc

I have an object with a collection. I am passing the model to the view and upon post the collection is there, however the reference (containing object Id) is not there and the navigation property is null.
Models
public class Order
{
public int OrderId { get; set;}
public List<OrderItem> OrderItems { get; set;}
}
public class OrderItem
{
public int OrderItemId { get; set; }
public int OrderId { get; set; }
public int SomeOtherValue { get; set; }
}
View:
#model Order
<h2>Edit Form</h2>
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.OrderId)
//Some other form fields for the order...
#for (int i = 0; i < Model.OrderItems.Count; i++)
{
#Html.TextBoxFor(x => Model.OrderItems[i].SomeOtherValue)
}
<input type="submit" value="Submit"/>
}
When I submit the form, the model gets bound correctly and the while debugging I can see the child OrderItems populated with updated information. However, the OrderId on each OrderItem is set to 0.
Controller
public ActionResult Edit(Order myOrder)
{
myOrder.OrderItems.Count(); // These are populated and the edited values are there.
myOrder.OrderItem[0].Order; // this is null (all order items)
myOrder.OrderItem[0].OrderId; // this is set to 0 (all order items)
//...more stuff and return view.
}
What is the best way to get around this problem? Is there some way to ensure that each OrderItem has a reference to the order when posting the form? I'm hoping I can avoid having to manually account for association in the controller when trying to save the object.

You should add hidden inputs for those fields. Check the code bolow:
..
#for (int i = 0; i < Model.OrderItems.Count; i++)
{
#Html.HiddenFor(x => Model.OrderItems[i].OrderId)
#Html.HiddenFor(x => Model.OrderItems[i].Order)
#Html.TextBoxFor(x => Model.OrderItems[i].SomeOtherValue)
}
..

Related

ASP MVC Nested Models

I need a view that displays rows of header-detail info. For a trivial case I set up a Course-Student tiny database with 4 simple tables. The Courses and Students tables are linked with a Section table which also has a student grade in it. Also, the Student table is owned by a Student Type Table. There are helper classes to hold the list of "header" data for the course and a "detail" list for each student who takes that class.
namespace SchoolA.Models
{
public class CourseInfo2
{
public int CourseID { get; set; }
public int CourseNumber { get; set; }
public string CourseName { get; set; }
public IEnumerable<CourseSectionList> CourseStudentList { get; set; }
}
}
namespace SchoolA.Models
{
public class CourseSectionList
{
public string Student { get; set; }
public string Type { get; set; }
public string Grade { get; set; }
}
}
The controller is:
public ActionResult Courselisting()
{
List<CourseInfo2> courseInfo2 = new List<CourseInfo2>();
List<CourseSectionList> courseSectionList = new List<CourseSectionList>();
var Cquery = from c in db.Courses select c;
foreach (var item in Cquery)
{
Course course = db.Courses.Find(item.CourseID); // first find the selected course
// get the sections
foreach (var s in query) // go through each section
{
// get the section data
courseSectionList.Add(new CourseSectionList
{
Student = student.StudentName,
Type = studentType.StudentTypeName,
Grade = s.SectionStudentGrade
});
} // end of section loop
courseInfo2.Add(new CourseInfo2
{
CourseID = course.CourseID,
CourseNumber = course.CourseNumber,
CourseName = course.CourseName,
CourseStudentList = courseSectionList
});
} // end of Course loop
return View(courseInfo2); // Course List and Section list for each course
}
The View is:
#model SchoolA.Models.CourseInfo2
#* doesn't work either: model List<SchoolA.Models.CourseInfo2>*#
<div>
<table>
<tr><th>ID</th> <th>Number</th> <th>Course</th></tr>
#foreach (var CourseInfo2 in Model)
{
<tr>
<td>
#CourseInfo2.CourseID
</td>
<td>
#CourseInfo2.CourseNumber
</td>
<td>
#CourseInfo2.CourseName
</td>
<td>
<table class="table">
<tr><th>Student</th> <th>Type</th><th>Grade</th></tr>
#foreach (var s in Model.CourseStudentList)
{
<tr>
<td>
#s.Student
</td>
<td>
#s.Type
</td>
<td>
#s.Grade
</td>
</tr>
}
</table>
</td>
</tr>
}
</table>
</div>
The problem is that I get a variety of errors as I try to pass these two models to the view. In the code as shown above I get this error:
CS1579: foreach statement cannot operate on variables of type 'SchoolA.Models.CourseInfo2' because 'SchoolA.Models.CourseInfo2' does not contain a public definition for 'GetEnumerator'
I've tried a number of variations for passing the models but always run into one error other the other that prevents both models from working in the view. I should note that I tested each part of the view independently and the controller code works fine to deliver the correct data to the view. However, I can't combine the two.
The problem seems to be the way I create instances of the models an how they are passed to the view. What am I doing wrong?
You're passing your view a List<SchoolA.Models.CourseInfo2>, but the view expects a single SchoolA.Models.CourseInfo2.
Change your #model declaration to IEnumerable<SchoolA.Models.CourseInfo2>.
Then change your foreach loops.
Change the first loop to foreach (var courseInfo in Model)
Change the inner loop to foreach (var s in courseInfo.CourseStudentList)
You are passing a model which is list of object, while in view you have single object.
Change your view model bind as below:
#model List<SchoolA.Models.CourseInfo2>
Problem: I have to edit Level2Name (by UI it's a textbox inside the child grid).
I'm able to edit Level1name (parent grid text field) and able to get value in my controller.
Question: How do I able to edit the nested textbox.
What I've tried.
Code:
Model
public class Level0
{
public Level0()
{
Level1= new List<Level1>();
}
public int? ID{ get; set; }
public int? NameText{ get; set; }
public List<Level1> lev1{ get; set; }
}
public class Level1
{
public Level1()
{
Level2= new List<Level2>();
}
public int Lev1ID {get;set;}
public string Level1name{ get; set; }
public List<Level2> level2{ get; set; }
}
public class Level2
{
public string Level2_ID { get; set; }
public string Level2Name{ get; set; }
}
UI Code
#for (int i = 0; i < Model.Level1.Count; i++)
{
#Html.HiddenFor(modelItem => Model.Floors[i].Lev1ID )
#for (int j = 0; j < Model.Level1[i].Level2.Count; j++)
{
#Html.HiddenFor(m => m.Level1[i].Level2[j].OP)
#Html.TextBoxFor(m => m.Level1[i].Level2[j].Level2Name, new { #class = "form-control" })
}
}

How to set default value to select list in MVC during run time

I have view in which it loop through the model and display the details in editable mode. One of the model value is from a select list like below
#if (Model != null)
{
for (int i = 0; i < Model.provider_service_dtls.Count; i++)
{
<tr>
<td> #Html.DropDownListFor(m => m.provider_service_dtls[i].activity_code_type,
(SelectList)#ViewBag.activity_code_type, "--- Select Activity Code Type ---", new { #class = "m-wrap" })</td>
<td>#Html.TextBoxFor(m => m.provider_service_dtls[i].activity_name)</td>
</tr>
}
}
Here the ViewBag.activity_code_type contain the values Internal & Standard when submitting if user selected Internal its value 1 will pass to controller and if Standard it will be 2 and here the default value will be "--- Select Activity Code Type ---"
Now when i open the same request in edit mode if the model value for provider_service_dtls[i].activity_code_type is 1 the select list should be default select as Internal and Standard if it is 2.
I coded like this
#Html.DropDownListFor(m => m.provider_service_dtls[i].activity_code_type,
(SelectList)#ViewBag.activity_code_type, Model.provider_service_dtls[i].activity_code_type)
But it is not working as expected it is giving the result as below picture
Here it should default selected Internal. What is the change to do achieve the same?
Edited
Model
public partial class provider_service_dtls
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long service_id { get; set; }
public long preapproval_id { get; set; }
public string activity_code_type { get; set; }
public string activity_type { get; set; }
public string activity_type_name { get; set; }
public string activity_code { get; set; }
public string activity_name { get; set; }
public string internal_activity_code { get; set; }
public string internal_activity_name { get; set; }
[ForeignKey("preapproval_id"), InverseProperty("provider_service_dtls")]
public virtual provider_preapproval preapproval { get; set; }
}
Editor template
#model Provider.Models.provider_preapproval
#Html.DropDownListFor(m => m.activity_code_type, (SelectList)ViewData["options"])
View
Inside the for loop i coded like this
#Html.EditorFor(m => m.provider_service_dtls,
new { options = (SelectList)#ViewBag.activity_code_type })
I am getting an error
'System.Web.Mvc.HtmlHelper' does
not contain a definition for 'EditorFor' and the best extension method
overload
'System.Web.Mvc.Html.EditorExtensions.EditorFor(System.Web.Mvc.HtmlHelper,
System.Linq.Expressions.Expression>,
string, object)' has some invalid arguments
Unfortunately #Html.DropDownListFor() behaves a little differently than other helpers when rendering controls in a loop. This has been previously reported as an issue on CodePlex (not sure if its a bug or just a limitation)
Create a custom EditorTemplate for the type in the collection.
In /Views/Shared/EditorTemplates/provider_service_dtls.cshtml (note the name must match the name of the type)
#model yourAssembly.provider_service_dtls
#Html.HiddenFor(m => m.service_id)
#Html.DropDownListFor(m => m.activity_code_type, (SelectList)ViewData["options"], "--- Select Activity Code Type ---")
.... // html helpers for other properties of provider_service_dtls
and then in the main view, pass the SelectList to the EditorTemplate as additionalViewData
#model yourAssembly.provider_preapproval
#using (Html.BeginForm())
{
.... // html helpers for other properties of provider_preapproval
#Html.EditorFor(m => m.provider_service_dtls, new { options = (SelectList)#ViewBag.activity_code_type })
...
The EditorFor() methods accepts IEnumerable<T> and will generate the controls for each item in the collection
Edit
An alternative is to create a new SelectList in each iteration of the for loop, where you need to set the Selected property of SelectList. This means that your ViewBag property must be IEnumerable<T>, not a SelectList, for example in the controller
ViewBag.ActivityCodeTypeList = new[]
{
new { ID = 1, Name = "Internal" },
new { ID = 2, Name = "Standard" }
}
and in the view
for (int i = 0; i < Model.provider_service_dtls.Count; i++)
{
#Html.DropDownListFor(m => m => m.provider_service_dtls[i].activity_code_type,
new SelectList(ViewBag.ActivityCodeTypeList, "ID", "Name", Model.provider_service_dtls[i].activity_code_type),
"--- Select Activity Code Type ---")
}
Simply you can try this by using IEnumerable Model type list like
#Html.DropDownList("ReceiverID", (IEnumerable<SelectListItem>)(m => m.activity_code_type), "--- Select ---", new { #class ="form-control" })
Hope it will work

Better way of creating repeating HTML section

I've asked this once before but without any code to look at, here I have an implementation and I'm wondering if there is a better way to accomplish this.
I want a repeating html section like so:
<div>
<input id=id1 name=id1 type=text/>
</div>
<div>
<input id=id2 name=id2 type=text/>
</div
etc
This could contain any number of input boxes which map to the List of 'something' classes I have in the model, I presently do this with a View
#using (Html.BeginForm())
{
for (int i = 0; i < Model.Somethings.Count; i++)
{
Model.Index = i;
#Html.Action("Index", "HtmlSection", Model);
}
// other stuff
}
and a partial view
#{
int index = Model.Index;
}
#Html.TextBoxFor(x => x.Somethings[index].TheProperty)
Where the model looks like this
public class HtmlSectionModel
{
public List<Something> Somethings { get; set; }
public int Index { get; set; }
}
Finally the action looks like this
public ActionResult Index(HtmlSectionModel model)
{
// do stuff
}
To me this works but isn't ideal
The partial view can now only be used within this context, it uses the top level model rather than just the 'Something' class
I have to pass an index in the model in order to get unique name's for binding, if I didn't do this then textbox would have the same name/id
This seems to me to be a common pattern so others must have solved it in other ways?
I guess what I'm after here is the MVC equivalent of Asp.Net UserControls/Webcontrols (which seem to be child actions/partial views), but, combined with model binding which seems to require unique names
What I wanted can be accomplished with editor templates
Controller
public class UsesEditorController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View(new SomeModel());
}
[HttpPost]
public ActionResult Index(SomeModel model)
{
return View(model);
}
}
Model
public class Blob
{
public string Name { get; set; }
public string Address { get; set; }
public Blob()
{
Name = string.Empty;
Address = string.Empty;
}
}
public class SomeModel
{
public List<Blob> Blobs { get; set; }
public SomeModel()
{
int count = 5;
this.Blobs = new List<Blob>(count);
for (int i = 0; i < count; i++)
{
this.Blobs.Add(new Blob());
}
}
}
View
#model MyProject.Areas.EditorTemplates.Models.SomeModel
#using (Html.BeginForm())
{
for (int i = 0; i < Model.Blobs.Count; i++)
{
#Html.EditorFor(m => m.Blobs[i], "CustomEditorForBlob");
}
<input type="submit" value="Send data back" />
}
And Editor, which can be anywhere in the view folder as I'm referring to it directly
#model MyProject.Areas.EditorTemplates.Models.Blob
#Html.TextBoxFor(m => m.Name)
#Html.TextBoxFor(m => m.Address)
This renders with ids like:
<input class="k-textbox" id="Blobs_1__Name" name="Blobs[1].Name" ...
So this gives me
List item
a repeating structure, just like UserControls in Asp.Net
The editor template only refers to the Blob class, it has no knowledge of the SomeModel class
Binding works (tested it)
It looks to me like what you are trying to accomplish is unique IDs for your inputs, and you certainly don't need a partial to do this. You can output your text box inside your for loop like the following:
#Html.TextBoxFor(x => x.Somethings[i].TheProperty)
This will generate a unique id something like id="Somethings_1_TheProperty". If you don't like that id, you can certainly make your own with something like this:
#Html.TextBoxFor(x => x.Somethings[i].TheProperty, new {id="id" + (i+1)})

mvc 4 complex list returning null

In other parts of my web site I'm using nested lists and am able to display and save without problem.
However, I can't seem to figure out why it is not working this time.
ViewModel
public class ActivityEditViewModel
{
//Activity
public int ActivityID { get; set; }
//... more properties
public List<ActivityService> ActivityServices { get; set; }
public class ActivityService
{
public int ServiceID { get; set; }
[DisplayName("Outcome Comment")]
public string OutcomeComment { get; set; }
//... more properties
}
}
View (Relevant part)
#for (int i = 0; i < Model.ActivityServices.Count; i++)
{
#Html.HiddenFor(x => x.ActivityServices[i].ServiceID)
#Html.LabelFor(x => Model.ActivityServices[i].OutcomeComment)
#Html.TextBoxFor(x => Model.ActivityServices[i].OutcomeComment)
}
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(ViewModels.ActivityEditViewModel vm)
{
//vm.ActivityServices <== is always null
Question
Why does vm.ActivityServices return null?
You can create a partial view for ActivityService and render this in foreach:
In your actual view:
#foreach(ActivityService item in Model.ActivityServices)
{
#Html.Partial("ActivityService", item)
}
In a new partial view ActivityService.cshtml:
#model Proyect.Models.ActivityEditViewModel.ActivityService
#Html.HiddenFor(m => m.ServiceID)
#Html.LabelFor(m => m.OutcomeComment)
#Html.TextBoxFor(m => m.OutcomeComment)
So your design Razor syntax must be as below:
You could first remove all your skipping elements by adding a line of code to remove and then use it normally to obtain desired record.
#
{
Model.ActivityServices.RemoveAll(o=> //your skip condition);
for (int i = 0; i < Model.ActivityServices.Count; i++)
{
#Html.HiddenFor(x => Model.ActivityServices[i].ServiceID)
#Html.LabelFor(x => Model.ActivityServices[i].OutcomeComment)
#Html.TextBoxFor(x => Model.ActivityServices[i].OutcomeComment)
}
}

referencing a list<Model> in a looping ParialView

I have been trying to loop partial views, which share the same model as the main view. Basically the idea is that there is a registration page where a parent can register multiple children, the children info is in partial view so that i can add multiple instances of it on the main view.
I can get the partial view to display multiple times but it only saves the first student. if i replace the partial view (#Html.Partial...) with #Html.EditorFor(f => f.student[i].Person.FirstName) in the main view then it works fine and i can add multiple textboxes and save multiple students
how can i use the partial views and be able to pass in the ParentModel and correctly reference it?
hope all this makes sense... any help is appreciated. thanks!
Model:
public partial class Person()
{
public string FirstName { get; set; }
}
public partial class Student
{
public int Student_PersonID { get; set; }
public int Father_PersonID { get; set; }
public virtual Person Father { get; set; }
public virtual Person Person { get; set; }
}
public class ParentModel
{
public List<Student> student { get; set; }
}
Main View
#model WebPortal.Models.ParentModel
<div>
#for (int i = 0; i < cnt; i++)
{
#Html.Partial("_StudentPartial", Model, new ViewDataDictionary { { "loopIndex", i.ToString() } });
}
</div>
<div>
#Html.EditorFor(f => f.student[0].Father.FirstName)
</div>
Partial View (_StudentPartial)
#model WebPortal.Models.ParentModel
#{
int i = Convert.ToInt32(ViewData["loopIndex"].ToString());
}
#using (Html.BeginForm())
{
<div class="editor-field">
#Html.EditorFor(m => m.student[i].Person.FirstName)
</div>
}
Controller
public ActionResult Register(ParentModel pm)
{
using (var db = new SMEntities())
{
for (int i = 0; i < pm.student.Count; i++)
{
if (i != 0)
{
pm.student[i].Father = pm.student[0].Father;
}
db.Students.Add(pm.student[i]);
}
db.SaveChanges();
}
}
A better way to structure things will be like this :
Type the _StudentPartial View to Student like this -
#model WebPortal.Models.Student
#using (Html.BeginForm())
{
<div class="editor-field">
#Html.EditorFor(m => m.Person.FirstName)
</div>
}
Then model your main view like this :
#model WebPortal.Models.ParentModel
<div>
#for (int i = 0; i < cnt; i++)
{
#Html.Partial("_StudentPartial", Model.student[i]);
}
</div>
<div>
#Html.EditorFor(f => f.student[0].Father.FirstName)
</div>
Try to use this as a guideline : Give to the view only the information it needs, not more.

Resources