Ok I give up after several hours of troubleshooting, but I am sure you all will see a solution right away. I have three models Person, Course and CoursePreference. The CoursePreference model has two foreign keys PersonId and CourseId.
What I want to do:
I want to create a view where the user can add course preferences in a top Create section, and when they click Add, the form would post and refresh the List in the same view. Essentially I am combining Index and Create in one view. So I created an Index View and a partial view called _CreatePartial in the CoursePreference folder.
The problem:
The view displays fine, but with two problems. 1) The CoursePreferenceId field shows a dropdownlist. I want it to be a hidden field since it's an identity. I copied the code exactly from the scaffolded Create View, which hides the Id correctly. Dont know why it's not working in the _CreatePartial view? 2) Most importantly, my _CreatePartial will not add any course preference. It looks as if the form is posting but no record is added.
What gives?
Here are the models, controllers and views:
---------------------------
Models (stripped down versions)
---------------------------
public class CoursePreference
{
public int CoursePreferenceId { get; set; }
public Nullable<int> CourseId { get; set; }
public Nullable<int> PersonId { get; set; }
public virtual Course Course { get; set; }
public virtual Person Person { get; set; }
}
public class Person
{
public int PersonId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public virtual ICollection<CoursePreference> CoursePreferences { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public Nullable<int> ProgramId { get; set; }
public string Name { get; set; }
public virtual ICollection<CoursePreference> CoursePreferences { get; set; }
}
------------------
Controllers
------------------
public ActionResult _CreatePartial()
{
ViewBag.CourseId = new SelectList(db.Courses, "CourseId", "Name");
ViewBag.PersonId = new SelectList(db.People, "PersonId", "LastName");
return View("_CreatePartial");
}
public ActionResult Index()
{
ViewBag.CourseId = new SelectList(db.Courses, "CourseId", "Name");
ViewBag.PersonId = new SelectList(db.People, "PersonId", "LastName");
var coursepreferences = db.CoursePreferences.Include(c => c.Course).Include(c => c.Person);
return View(coursepreferences.ToList());
}
---------------------------
Index View
---------------------------
#model IEnumerable<MyProj.Models.CoursePreference>
#{ ViewBag.Title = "Index";
}
<h4>Add Course Preferences</h4>
<div>
#Html.Partial("~/Views/CoursePreference/_CreatePartial.cshtml", new MyProj.Models.CoursePreference())
</div>
<br />
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Course.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Person.LastName)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Course.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Person.LastName)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.CoursePreferenceId }) |
#Html.ActionLink("Details", "Details", new { id=item.CoursePreferenceId }) |
#Html.ActionLink("Delete", "Delete", new { id=item.CoursePreferenceId })
</td>
</tr>
}
</table>
---------------------------
_CreatePartial View
---------------------------
#model MyProj.Models.CoursePreference
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>CoursePreference</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.CoursePreferenceId, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.CoursePreferenceId)
#Html.ValidationMessageFor(model => model.CoursePreferenceId)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.CourseId, "CourseId", new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("CourseId", String.Empty)
#Html.ValidationMessageFor(model => model.CourseId)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PersonId, "PersonId", new { #class = "control-label col-md-2" }
<div class="col-md-10">
#Html.DropDownList("PersonId", String.Empty)
#Html.ValidationMessageFor(model => model.PersonId)
</div>
</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>
Nothing is saved because you don't have a controller method to handle the post. Implement this in your controller:
[HttpPost]
public ActionResult Index(CoursePreference pref)
{
// Insert code to fetch database context into variable "db"
db.CoursePreferences.Add(pref);
db.SaveChanges();
return RedirectToAction("Index");
}
As for the hidden ID-field, you should use:
#Html.HiddenFor(model => model.CoursePreferenceId)
-not EditorFor or LabelFor.
Related
I am working on creating a website that accesses a table in SQL Management Server. The user needs to be able to view, edit, delete, and create new entries within the table using my site. The table that is being used is called DeviceUnderTest and it has these columns: DeviceUnderTest (pk), DeviceUnderTest (nk), Notes (nk), FaultApplication (fk), Firmware (fk), Hardware (fk), Power (fk), Location (fk), PreEventTime(nk), HandleRate (nk). So far, I have created the model that connects to the main DeviceUnderTest table, as well as the other tables that the foreign key columns reference. I have created the controller, as well as an index, details, delete, and create view page. My index view is a table, and I figured out how to replace all the foreign key values with their corresponding tables from the tables they reference. The one thing I can not figure out is the create page. Users will enter the information in the form of the values that the fk columns reference, rather than the foreign key values themselves. I can not figure out how to change the user input back to the correct foreign key value so that the entry can be correctly added to the table noth in the sql server as well as within my site. If anyone has any suggestions, they are much appreciated.
Controller Code:
// GET: Circuit/Create
public ActionResult Create()
{
return View();
}
// POST: Circuit/Create
[HttpPost]
public ActionResult Create(DeviceUnderTest device)
{
try
{
dbModel.DeviceUnderTests.Add(device);
dbModel.SaveChanges();
// TODO: Add insert logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
Create View Code:
#model Template_Website.Models.DeviceUnderTest
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>DeviceUnderTest</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.pkDeviceUnderTest, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.pkDeviceUnderTest, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.pkDeviceUnderTest, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.nkDeviceUnderTest, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.nkDeviceUnderTest, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.nkDeviceUnderTest, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.nkNotes, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.nkNotes, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.nkNotes, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.FaultApplication.nkFaultApplication, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FaultApplication.nkFaultApplication, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FaultApplication.nkFaultApplication, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Firmware.nkFirmware, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Firmware.nkFirmware, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Firmware.nkFirmware, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Hardware.nkHardware, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Hardware.nkHardware, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Hardware.nkHardware, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.fkPower, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.fkPower, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.fkPower, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Location.nkLocation, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Location.nkLocation, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Location, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.nkPreEventTime, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.nkPreEventTime, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.nkPreEventTime, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.nkHandleRating, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.nkHandleRating, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.nkHandleRating, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<<button type="submit" class="btn btn-default " value="Create" onclick="return confirm('Are you sure?')">Create</button>
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Index View Code:
#model IEnumerable<Template_Website.Models.DeviceUnderTest>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
</th>
<th>
</th>
<th>
</th>
<th>
</th>
<th>
#Html.DisplayNameFor(model => model.pkDeviceUnderTest)
</th>
<th>
#Html.DisplayNameFor(model => model.nkDeviceUnderTest)
</th>
<th>
#Html.DisplayNameFor(model => model.nkNotes)
</th>
<th>
#Html.DisplayNameFor(model => model.FaultApplication.nkFaultApplication)
</th>
<th>
#Html.DisplayNameFor(model => model.Firmware.nkFirmware)
</th>
<th>
#Html.DisplayNameFor(model => model.Hardware.nkHardware)
</th>
<th>
#Html.DisplayNameFor(model => model.fkPower)
</th>
<th>
#Html.DisplayNameFor(model => model.Location.nkLocation)
</th>
<th>
#Html.DisplayNameFor(model => model.nkPreEventTime)
</th>
<th>
#Html.DisplayNameFor(model => model.nkHandleRating)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.CheckBoxFor(modelItem => item.Selected)
</td>
<td>
<input type="button" value="Edit" class="btn btn-default" onclick="#("window.location.href='" + #Url.Action("Edit", "Circuit", new { id = item.pkDeviceUnderTest } ) + "'");" />
</td>
<td>
<input type="button" value="Details" class="btn btn-default" onclick="#("window.location.href='" + #Url.Action("Details", "Circuit", new { id = item.pkDeviceUnderTest }) + "'");" />
</td>
<td>
<input type="button" value="Delete" class="btn btn-danger" onclick="#("window.location.href='" + #Url.Action("Delete", "Circuit", new { id = item.pkDeviceUnderTest }) + "'");" />
</td>
<td>
#Html.DisplayFor(modelItem => item.pkDeviceUnderTest)
</td>
<td>
#Html.DisplayFor(modelItem => item.nkDeviceUnderTest)
</td>
<td>
#Html.DisplayFor(modelItem => item.nkNotes)
</td>
<td>
#Html.DisplayFor(modelItem => item.FaultApplication.nkFaultApplication)
</td>
<td>
#Html.DisplayFor(modelItem => item.Firmware.nkFirmware)
</td>
<td>
#Html.DisplayFor(modelItem => item.Hardware.nkHardware)
</td>
<td>
#Html.DisplayFor(modelItem => item.fkPower)
</td>
<td>
#Html.DisplayFor(modelItem => item.Location.nkLocation)
</td>
<td>
#Html.DisplayFor(modelItem => item.nkPreEventTime)
</td>
<td>
#Html.DisplayFor(modelItem => item.nkHandleRating)
</td>
</tr>
}
</table>
DeviceUnderTest Model Code:
namespace Template_Website.Models
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
public partial class DeviceUnderTest
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public DeviceUnderTest()
{
this.TestRecords = new HashSet<TestRecord>();
}
public bool Selected { get; set; }
[Required]
public int pkDeviceUnderTest { get; set; }
[Required]
public string nkDeviceUnderTest { get; set; }
[Required]
public string nkNotes { get; set; }
public Nullable<int> fkFaultApplication { get; set; }
public Nullable<int> fkFirmware { get; set; }
public Nullable<int> fkHardware { get; set; }
public Nullable<int> fkPower { get; set; }
public Nullable<int> fkLocation { get; set; }
public Nullable<int> nkPreEventTime { get; set; }
public Nullable<int> nkHandleRating { get; set; }
public virtual FaultApplication FaultApplication { get; set; }
public virtual Firmware Firmware { get; set; }
public virtual Hardware Hardware { get; set; }
public virtual Location Location { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<TestRecord> TestRecords { get; set; }
}
public class DeviceUnderTestModel
{
public List<DeviceUnderTest> device { get; set; }
}
}
Hardware Model Code:
namespace Template_Website.Models
{
using System;
using System.Collections.Generic;
public partial class Hardware
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Hardware()
{
this.DeviceUnderTests = new HashSet<DeviceUnderTest>();
}
public int pkHardware { get; set; }
public string nkHardware { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<DeviceUnderTest> DeviceUnderTests { get; set; }
}
}
Users will enter the information in the form of the values that the fk columns reference, rather than the foreign key values themselves.
Why do you want to avoid using the foreign key values? I notice you are using EditorFor but can't you simply use DropdownFor or even a JavaScript-powered autocomplete widget? This way, your users would enter the desired text but the values would be mapped in the background.
For an example of how to use DropdownFor, please see the MSDN docs: https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions/working-with-the-dropdownlist-box-and-jquery/examining-how-aspnet-mvc-scaffolds-the-dropdownlist-helper
For an example of using an autocomplete, there is a nice answer here which uses jQuery UI: Autocomplete dropdown in MVC5?
Using the first example for DropdownFor and applying it to your code, one of your model properties would continue to look the same:
public Nullable<int> fkFaultApplication { get; set; }
While you also create a ViewBag item to contain your SelectList which maps your texts and values:
ViewBag.FaultApplicationId = new SelectList(db.FaultApplications, "FaultAppId", "Name");
Finally, you could make use of these properties in your view as follows:
#Html.DropDownList("FaultApplicationId", String.Empty)
Note that the second parameter, String.Empty, "is the text to display when no item is selected."
Just like user1477388 says, you can use dropdowns for each item. This way the view displays the text, but the value sent back is the id.
For example using hardware. I'm using a viewmodel so we dont present the database models to the view. The VM has the properties you want in the specific view. In this case we have the Device Under Test stuff, a select list and a HardwareId.
public class DeviceUnderTestViewModel
{
public int pkDeviceUnderTest { get; set; }
[Required]
public string nkDeviceUnderTest { get; set; }
[Required]
public string nkNotes { get; set; }
public int HardwareId { get; set; } //pkHardware
public IEnumerable<SelectListItem> HardwareSelectList { get; set; } //dropdown of Hardware
}
In the controller, populate the select lists and attach to the viewmodel:
public ActionResult Index()
{
DeviceUnderTestViewModel vm = new DeviceUnderTestViewModel();
vm.HardwareSelectList = db.Hardware.ToList().Select(d => new SelectListItem()
{
Text = d.nkHardware,
Value = d.pkHardware.ToString()
});
// populate all the other dropdowns
return View();
}
In the view we render the select list:
<div class="form-group">
#Html.LabelFor(model => model.HardwareId)
#Html.DropDownListFor(model => model.HardwareIdId, Model.HardwareSelectList, "Select Hardware...", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.HardwareIdId, "", new { #class = "text-danger" })
</div>
DropDownListFor takes the value to set first, here we are setting the hardwareid which we will save to the DeviceUnderTest as the pkHardware, then we have the populate list of Hard items for the dropdown, third is an optional string place holder, then lastly I add a bootstrap class for formatting.
Then return the model to the controller and save it:
public ActionResult SaveDevice(DeviceUnderTestViewModel model)
{
using (var db = new dbContext())
{
DeviceUnderTest device = new DeviceUnderTest()
{
pkHardware = model.HardwareId,
// continue with other items
};
db.Entry<device>.State = EntiyState.Added;
db.SaveChanges();
}
return View();
}
I'm adding dynamically items to an Enquiry form. Used partial view to for adding/deleting the items but while submitting the main view the values are not bound. My question is how to do the same.
Have checked couple of similar questions here and here But could not find what's missing .
Using 2 ViewModels , for Main View ( Enquiry) and for partial view ( LineItems) and used BeginCollectionItem for dynamically adding items.
Code:
ViewModels
public class EnquiryVM
{
public int ID { get; set; }
[Required]
public string EnquiryNumber { get; set; }
public int ClientID { get; set; }
public IEnumerable<SelectListItem> Clients { get; set; }
public Client Client { get; set; }
public int ItemID { get; set; }
public List<EnquiryLineItem> LineItems { get; set; }
}
public class EnquiryLineItemVM
{
public int ID { get; set; }
[Required]
public string ItemDesc { get; set; }
public int Quantity { get; set; }
public int ManufacturerId { get; set; }
public IEnumerable<SelectListItem> ManufacturerList { get; set; }
}
Views :
Main:
#model ViewModel.EnquiryVM
#using (Html.BeginForm("Create", "Enquiries", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.EnquiryNumber, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-3">
#Html.EditorFor(model => model.EnquiryNumber, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.EnquiryNumber, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ClientID, "Client", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-3">
#Html.DropDownListFor(u => u.ClientID, (IEnumerable<SelectListItem>)Model.Clients, "--Select--")
#Html.ValidationMessageFor(model => model.ClientID, "", new { #class = "text-danger" })
</div>
</div>
<div id="LineItems">
// #using (Html.BeginForm()) // do we require again here since this will be like nested form? tested commenting still not working
// {
<div id="editorRowsLineitems">
#foreach (var item in Model.LineItems)
{
#Html.Partial("_CreateEnquiryItem", item)
}
</div>
#Html.ActionLink("Add Items", "CreateLineItem", null, new { id = "addItem", #class = "button" });
// }
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
$(function () {
$('#addItem').on('click', function () {
$.ajax({
url: '#Url.Action("CreateLineItem")',
cache: false,
success: function (html) {
$("#editorRowsLineitems").append(html);
$("form").removeData("validator");
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");
}
});
return false;
});
$('#editorRowsLineitems').on('click', '.deleteRow', function () {
$(this).closest('.editorRow').remove();
});
$('form').data('validator', null);
$.validator.unobtrusive.parse($('form'));
});
</script>
}
partial view :
#model ViewModels.EnquiryLineItemVM
<div class="editorRow">
#using (Html.BeginCollectionItem("ItemList"))
{
<table class="table">
<tr>
<td>
#Html.EditorFor(model => model.ItemDesc)
</td>
<td>
#Html.EditorFor(model => model.Quantity)
</td>
<td>
#Html.DropDownListFor(model => model.ManufacturerId, Model.ManufacturerList, "--Please Select--")
</td>
<td>
Delete
</td>
</tr>
</table>
}
Controller :
public ActionResult Create()
{
var viewModel = GetAllCategories();
return View(viewModel);
}
private EnquiryVM GetAllCategories()
{
var model = new EnquiryVM();
var clients = db.Clients.ToList();
model.Clients = clients.Select(s => new SelectListItem
{
Value = s.ID.ToString(),
Text = s.Name
});
var LineItems = new List<EnquiryLineItem>();
model.LineItems = LineItems;
return model;
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create( EnquiryVM enquiryVM)
{
var enquiry = new Enquiry();
enquiry.EnquiryNumber = enquiryVM.EnquiryNumber;
enquiry.ClientID = enquiryVM.ClientID;
enquiry.EnquiryLineItems = enquiryVM.LineItems; //line items are null
if (ModelState.IsValid)
{
db.Enquiries.Add(enquiry);
enquiryVM.ID = enquiry.ID;
foreach (var item in enquiry.EnquiryLineItems)
{
item.EnquiryID = enquiryVM.ID;
db.EnquiryLineItems.Add(item);
}
db.SaveChanges();
return RedirectToAction("Index");
}
var viewModel = GetAllCategories();
return View(enquiryVM);
}
How shall I map the dynamically added row's values to the ViewModel ( EnquiryVM ) so that I can insert it into the DB.
Thanks for your patience and time.
The name of your collection property is LineItems, therefore your code to generate its controls needs to be
#using (Html.BeginCollectionItem("LineItems")) // not ..("ItemList")
{
....
}
so that it generates inputs with name="LineItems[xxxx].ItemDesc" etc, rather than your current use which generates name="ItemList[xxxx].ItemDesc" (where xxxx is the Guid)
As a side note, the code in your POST method will throw an exception if ModelState is invalid because you return the view and have not repopulated the IEnumerable<SelectListItem> Clients property. Refer The ViewData item that has the key 'XXX' is of type 'System.Int32' but must be of type 'IEnumerable' for a detailed explanation.
In addition, the final 2 lines of your script to add items ($('form').data('validator', null); $.validator.unobtrusive.parse($('form')); should be removed (reparsing the validator is expensive and your doing it twice - once before you add the html (the 2 lines above) and once after you add the html
I am creating a DataEntry Screen which have a three dropdownlist at the top
which is cascaded using ajax . And A partial view is also rendered using Ajax which will show all the items against that selection user made on the dropdowns. The partialview consist of a table with multiple rolls.
I am not able to get the collection of data selected by the user in the partial view (table )in my controller.
My Controller
[HttpGet]
public ActionResult ApproveLaysheet()
{
LaySheetShortageViewModel mdl = new LaySheetShortageViewModel();
ViewBag.AtcID = new SelectList(db.AtcMasters.Where(o => o.IsClosed == "N"), "AtcId", "AtcNum");
return View(mdl);
}
[HttpGet]
public PartialViewResult GetRollView(decimal[] SelectedOurStyle)
{
LaySheetShortageViewModel model = new LaySheetShortageViewModel();
LaysheetRollRepository lyipores = new LaysheetRollRepository();
model.rolldetailcollection= lyipores.getlaysheetRollData(SelectedOurStyle);
return PartialView("LaySheetRollView",model);
}
[HttpPost]
public ActionResult ApproveLaysheet(LaySheetShortageViewModel Model)
{ // not gretting the value of rolldetailcollection here
return View();
}
My View
#model ArtWebApp.Areas.ArtMVC.Models.ViewModel.LaySheetShortageViewModel
<script type="text/javascript">
$(document).ready(function () {
$("#Show").click(function (e, params) {
debugger;
var SelectedOurStyle = new Array();
SelectedOurStyle = $("#LaySheetID").chosen().val();
if (SelectedOurStyle != null)
{
$.ajax({
url: "#Url.Action("GetRollView", "ApproveLaysheet")",
traditional: true,
data: { 'SelectedOurStyle': SelectedOurStyle },
type: "GET",
success: function (fooBarHTML) {
$("#output").html(fooBarHTML);
},
error: function (xhr, status, errorThrown) {
//...
}
});
}
});
});
</script>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>SampCutReqMaster</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<div class="row">
<div class="col-md-2">
#Html.Label("Atcc#", new { #class = "control-label col-md-2" });
</div>
<div class="col-md-10">
#Html.DropDownList("AtcID", null, htmlAttributes: new { #class = "chosen-select form-control" })
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<div class="col-md-2">
#Html.Label("OurStyle#", new { #class = "control-label col-md-2" });
</div>
<div class="col-md-10">
#Html.DropDownList("OurStyleID", new MultiSelectList(string.Empty, "Value", "Text") , null, htmlAttributes: new { #class = "chosen-select form-control", #multiple = "multiple" } )
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<div class="col-md-2">
#Html.Label("LaySheet#", new { #class = "control-label col-md-2" });
</div>
<div class="col-md-10">
#Html.DropDownList("LaySheetID", new MultiSelectList(string.Empty, "Value", "Text"), null, htmlAttributes: new { #class = "chosen-select form-control", #multiple = "multiple" })
</div>
</div>
</div>
<div id='output' class="">
<!-- Partialview Comes here -->
</div>
</div>
}
My Partial View
#using HtmlHelpers.BeginCollectionItem
#model ArtWebApp.Areas.ArtMVC.Models.ViewModel.LaySheetShortageViewModel
<script src="~/JQuery/GridJQuery.js"></script>
<script>
</script>
<div class="container">
<table class="table table-bordered table-striped table-responsive">
<tr>
<th>
#Html.CheckBox("SelectAll")
</th>
<th>
#Html.DisplayNameFor(model => model.approvelaysheetModel.LaySheetDet_PK)
</th>
<th>
#Html.DisplayNameFor(model => model.approvelaysheetModel.LayCutNum)
</th>
<th>
#Html.DisplayNameFor(model => model.approvelaysheetModel.RollNum)
</th>
</tr>
#if (Model != null)
{
for (int i = 0; i < Model.rolldetailcollection.Count; i++)
{
using (Html.BeginCollectionItem("rolldata"))
{
<tr>
<td>
#Html.EditorFor(modelItem => Model.rolldetailcollection[i].IsSelected, new { #onclick = "Check_ClickNew(this)" })
</td>
<td>
#Html.EditorFor(modelItem => Model.rolldetailcollection[i].LaySheetDet_PK)
#Html.HiddenFor(model => Model.rolldetailcollection[i].LaySheetDet_PK, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
#Html.DisplayFor(modelItem => Model.rolldetailcollection[i].LayCutNum)
</td>
<td>
#Html.DisplayFor(modelItem => Model.rolldetailcollection[i].RollNum)
</td>
</tr>
}
}
}
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input id="Submit" type="submit" value="Submit Fabric request" class="btn btn-default show" />
</div>
</div>
</div>
My viewModals
public class ApprovelaysheetModel
{
public Decimal ApprovelaysheetModelID { get; set; }
[Display(Name ="ID")]
public Decimal LaySheetDet_PK { get; set; }
public Boolean IsSelected { get; set; }
public Decimal LaySheet_PK { get; set; }
}
public class LaySheetShortageViewModel
{
[Key]
[Display(Name = "ID")]
public Decimal ShortageID { get; set; }
public int Location_pk { get; set; }
public int Atcid { get; set; }
public int OurstyleID { get; set; } }
public List<ApprovelaysheetModel> rolldetailcollection { get; set; }
}
Can anyone suggest whats my mistake or any better method for doing this dataentry as Iam new to MVC
Your use of BeginCollectionItem() inside the for loop is prefixing your name attributes so that they no longer relate to your models properties. Instead of generating
<input type="checkbox" name="rolldetailcollection[0].IsSelected" ... />
your now generating
<input type="checkbox" name="rolldata[####].rolldetailcollection[0].IsSelected" ... />
where #### is a Guid.
Remove the BeginCollectionItem() code so that your loop is
for (int i = 0; i < Model.rolldetailcollection.Count; i++)
{
<tr>
<td>
#Html.EditorFor(m => m.rolldetailcollection[i].IsSelected, new { #onclick = "Check_ClickNew(this)" })
</td>
<td>
#Html.EditorFor(m => m.rolldetailcollection[i].LaySheetDet_PK)
// Note there is no point adding html attributes for a hidden input
#Html.HiddenFor(m => m.rolldetailcollection[i].LaySheetDet_PK)
</td>
....
</tr>
}
Note that the BeginCollectionItem() method is used when you want to dynamically add and remove items from a collection in the view in conjunction with javascript/ajax (refer this answer for an example). If you do want to do that, then you need to create a partial view (say) _ApprovelaysheetModel.cshtml for the model
#model ApprovelaysheetModel
// Note the name needs to match the collection property name
#using (Html.BeginCollectionItem("rolldetailcollection"))
{
....
#Html.EditorFor(m => m.LaySheetDet_PK)
....
}
and then in the main view you use a foreach loop
#foreach(var item in Model.rolldetailcollection)
{
#Html.Partial("_ApprovelaysheetModel", item)
}
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
So Im working with a user interface where the user should be able to edit and delete it's data in the database. But when im trying to post the form, it doesnt save the changes. Here's some code:
Model:
namespace Aviato.Models
{
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
public partial class TimesheetEntry
{
[Key]
[Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int UserId { get; set; }
[Key]
[Column(Order = 1)]
[StringLength(50)]
public string ProjectId { get; set; }
[Key]
[Column(Order = 2, TypeName = "date")]
public DateTime EntryDate { get; set; }
public decimal HoursWorked { get; set; }
public virtual Project Project { get; set; }
public virtual User User { get; set; }
}
}
View:
(Index)
#model IEnumerable<Aviato.Models.TimesheetEntry>
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Tidrapportering</h1>
<p>
#Html.ActionLink("Skapa ny", "Create")
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.UserId)
</th>
<th>
#Html.DisplayNameFor(model => model.Project.ProjectName)
</th>
<th>
#Html.DisplayNameFor(model => model.EntryDate)
</th>
<th>
#Html.DisplayNameFor(model => model.HoursWorked)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.UserId)
</td>
<td>
#Html.DisplayFor(modelItem => item.Project.ProjectName)
</td>
<td>
#Html.DisplayFor(modelItem => item.EntryDate)
</td>
<td>
#Html.DisplayFor(modelItem => item.HoursWorked)
</td>
<td>
#Html.ActionLink("Redigera", "Edit", new { id=item.UserId, item.ProjectId, item.EntryDate }) |
#Html.ActionLink("Ta bort", "Delete", new { id=item.UserId, item.ProjectId, item.EntryDate})
</td>
</tr>
}
</table>
<div>
#Html.ActionLink("Tillbaka", "Index", "User")
</div>
(Edit)
#model Aviato.Models.TimesheetEntry
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Redigera</h1>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.UserId)
#Html.HiddenFor(model => model.Project)
#Html.HiddenFor(model => model.User)
<div class="form-group">
#Html.LabelFor(model => model.ProjectId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProjectId, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProjectId)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.EntryDate, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.EntryDate, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.EntryDate)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.HoursWorked, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.HoursWorked, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.HoursWorked)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Spara" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Tillbaka", "Index")
</div>
Controller:
public ActionResult Edit(int? id, string projectId, DateTime entryDate)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var timesheetentry = _db.TimesheetEntries.Find(id, projectId, entryDate);
if (timesheetentry == null)
{
return HttpNotFound();
}
ViewBag.ProjectId = new SelectList(_db.Projects, "ProjectId", "ProjectName", timesheetentry.ProjectId);
ViewBag.UserId = new SelectList(_db.Users, "UserId", "SocialSecurityNumber", timesheetentry.UserId);
return View(timesheetentry);
}
[HttpPost]
public ActionResult Edit(TimesheetEntry timesheetentry)
{
if (ModelState.IsValid) // So here's where the breakpoint skips. I get User and Project to be null in timesheetentry!
{
_db.Entry(timesheetentry).State = EntityState.Modified;
_db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.ProjectId = new SelectList(_db.Projects, "ProjectId", "ProjectName", timesheetentry.ProjectId);
ViewBag.UserId = new SelectList(_db.Users, "UserId", "SocialSecurityNumber", timesheetentry.UserId);
return View(timesheetentry);
}
Database:
CREATE TABLE [dbo].[TimesheetEntries] (
[UserId] INT NOT NULL,
[ProjectId] NVARCHAR (50) NOT NULL,
[EntryDate] DATE NOT NULL,
[HoursWorked] DECIMAL (8, 1) CONSTRAINT [DF_TimesheetEntries_HoursWorked] DEFAULT ((0.0)) NOT NULL,
CONSTRAINT [PK_TimesheetEntries] PRIMARY KEY CLUSTERED ([UserId] ASC, [ProjectId] ASC, [EntryDate] ASC),
CONSTRAINT [FK_TimesheetEntries_Users] FOREIGN KEY ([UserId]) REFERENCES [dbo].[Users] ([UserId]),
CONSTRAINT [FK_TimesheetEntries_Projects] FOREIGN KEY ([ProjectId]) REFERENCES [dbo].[Projects] ([ProjectId])
);
Ok, so I can kind of understand why its happening. In your edit page you have these lines
#Html.HiddenFor(model => model.Project)
#Html.HiddenFor(model => model.User)
But this is not enough for it to be able to round trip all the data associated with these sub-objects. If you view source on the HTML page, you should see what I mean. A hidden HTML field cannot cope with a complex object.
If you really want to round-trip these objects then you will need to output all their fields as hidden fields, or create a template to do this.
Or simply have a view model that does not contain the sub objects.
TimesheetEntryModel Model= new TimesheetEntryModel();
return (Model);
are you use like this?
I'm listing a series of items and i want top implement an option where you click and it add a child object for that entity, let me explain:
public class SupportItem
{
[Display(Name = "Categoría")]
[ConcurrencyCheck, Required]
public string Type { get; set; }
[Key, HiddenInput(DisplayValue = false)]
public int SupportItemId { get; set; }
[Display(Name = "Nombre")]
[ConcurrencyCheck,Required]
public string Name { get; set; }
[ConcurrencyCheck]
[Display(Name = "Descripción Corta")]
[DataType(DataType.MultilineText)]
[Required]
public string Description { get; set; }
[HiddenInput(DisplayValue = false)]
public virtual SupportItem Father { get; set; }
[Display(Name = "Descripción detallada")]
[DataType(DataType.MultilineText)]
[Required]
public string LongDescription { get; set; }
[HiddenInput(DisplayValue = false)]
public bool Children { get; set; }
}
Now as u can see, this entity has a Father which is of type SupporItem. Now what I want to do is to list them all and add an option which will let you easily add a child for that item you select, heres the view definition:
#model IEnumerable<Domain.Entities.SupportItem>
#{
ViewBag.Title = "IndexSupportItems";
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
<h2>Index Support Items</h2>
<p>
#Html.ActionLink("Crear nuevo item principal", "Create")
</p>
<table class="Grid">
<tr>
<th>
Tipo
</th>
<th>
Nombre
</th>
<th>
Descripción
</th>
<th>
Acciones
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>#item.Type</td>
#if(item.Children)
{
<td>#Html.ActionLink(item.Name,"ListChildren", new{item.SupportItemId})</td>
}
else
{<td>#item.Name</td>
}
<td>#item.Description</td>
<td>
#Html.ActionLink("Delete","DeleteSupportItem", new{item.Father.SupportItemId})<br />
#Html.ActionLink("Add subitem sub-item","AddSubitem", new{item.SupportItemId})<br />
#Html.ActionLink("Edit","EditSupportItem", new{item.SupportItemId})
</td>
</tr>
}
</table>
Now as you can see, the action link for doing this points to a method called AddSubitem, which is implemented as follows:
public ViewResult AddSubitem(int supportItemId)
{
SupportItem child = new SupportItem() { Father = repo.GetSupportItemFromId(supportItemId) };
return View(child);
}
As you can see, I recieve a supportItemId which is the id from the parent entitity (the one to whom i want to add the new child), find it on my database context and create the new object and point the Father object i just found. After doing that the view it returns is this:
#model Domain.Entities.SupportItem
#{
ViewBag.Title = "AddSubitem";
}
<h2>AddSubitem</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Support Item</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Type)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Type)
#Html.ValidationMessageFor(model => model.Type)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LongDescription)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LongDescription)
#Html.ValidationMessageFor(model => model.LongDescription)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
In this view, the user will set some of the variables such as name and description and then submit the object so I can persist it to the database, the problem is the object I get from this view has its fathers id as its own id and the Father attribute is null thus i end up updating the Father object to which i want to add a child with this method:
public bool SaveSupportItem(SupportItem supportItem)
{
bool retorno = false;
if (supportItem.SupportItemId == 0)
{
context.SupportItems.Add(supportItem);
supportItem.Father.Children = true;
retorno = true;
}
else
{
SupportItem itemDB = context.SupportItems.Find(supportItem.SupportItemId);
if (itemDB != null)
{
itemDB.Name = supportItem.Name;
itemDB.Type = supportItem.Type;
itemDB.LongDescription = supportItem.LongDescription;
itemDB.Description = supportItem.Description;
retorno = true;
}
}
context.SaveChanges();
return retorno;
}
What am I doing wrong here? why can't I create a new object?
Thanks for taking the time and reading this, any help will be really appreciated!
Well, try this:
Add this to your SupportItem class
public class SupportItem
{
[Key]
[HiddenInput(DisplayValue = false)]
[ForeignKey("Father"), DatabaseGenerated(DatabaseGeneratedOption.None)]
public int SupportItemId { get; set; }
public virtual Father Father { get; set; }
...................
...................
}
Then change:
#Html.ActionLink("Add subitem sub-item","AddSubitem", new{item.SupportItemId})<br />
to
#Html.ActionLink("Add subitem sub-item","AddSubitem", "Controller Name here" new{SupportItemId = #Model.FatherId})<br />
Also because we need FatherId here new{SupportItemId = #Model.FatherId}, the ActionLink needs to be in lets say Father view e.g in Father Details where only single Father is current or something as you have to associate the supportItem with to specific father.
Your controller might look like this, assuming that you are using a ViewModel:
[HttpGet]
public ActionResult CreateSuppo(int supportItemId)
{
var model = new CreateSupportItemViewModel ();
model.SupportItemId= supportItemId;
return View(model);
}
[HttpPost]
public ActionResult Create(CreateSupportItemViewModel viewModel)
{
if(ModelState.IsValid)
{
var father= db.Fathers.Single(f => f.FatherId == viewModel.SupportItemId);
var supportItem= new SupportItem();
supportItem.Name = viewModel.Name;
....................
.................
father.SupportItems.Add(supportItem);
db.SaveChanges();
}
return View(viewModel);
}