I am new to MVC and using MVC4 for project.
Here is the piece of code in view -
#model SomeModel
#{
for (var i = 0; i < Model.NumberOfQuestions; i++)
{
<div class="ques">
<div class="leftCol">
<label>#Resources.Enroll.Question</label>
</div>
<div class="rightCol">
#Html.DropDownListFor(m => m.Question, Model.Questions, new { id = "ddlQuestion" + i, #data_native_menu = "false", onchange = "SelectedIndexChanged(id)" })
#Html.ValidationMessageFor(m => m.Question)
</div>
<div class="clear" />
<div id="#("customQuestion" + i)" class="hide">
<div class="leftCol">
<label>#Resources.Enroll.CustomQuestion</label>
</div>
<div class="rightCol">
#Html.TextBoxFor(m => m.CustomQuestion, new { id = "txtCustomQues" + i })
#Html.ValidationMessageFor(m => m.CustomQuestion)
</div>
</div>
<div class="clear" />
<div class="leftCol">
<label>#Resources.Enroll.Answer</label>
</div>
<div class="rightCol">
#Html.TextBoxFor(m => m.Answer, new { id = "txtAnswer" + i })
#Html.ValidationMessageFor(m => m.Answer)
</div>
<div class="clear" />
</div>
}
}
The problem here is I'm not able to get some way of managing selected value for all dropdownlist and passing them to controller.
I am able to get single value retained in Question which is a public property in Model. But I am not getting how to do for all dynamically.
Can you please guide me how to do same?
Create a view model that represents the questions you are going to select i.e.
public class QuestionViewModel
{
public string QuestionId { get; set; }
}
Then add them to your existing model, without seeing your current one it should resemble this:
public class QuestionsViewModel
{
public QuestionsViewModel()
{
SelectedQuestions = new List<QuestionViewModel>();
}
public List<SelectListItem> QuestionsSelectList { get; set; }
public List<QuestionViewModel> SelectedQuestions { get; set; }
}
Use indexing in your for loop with the count of the Questions. The important bit is how the QuestionId is used by the model binder to send it back in the post:
#for(var i = 0; i < Model.SelectedQuestions.Count; i++)
{
#Html.DropDownListFor(m => m.SelectedQuestions[i].QuestionId, Model.QuestionsSelectList as List<SelectListItem>, "Select..", new { #data_native_menu = "false", onchange = "SelectedIndexChanged(id)" })
}
When the form is submitted you will have a collection of selected questions i.e.
var selectedQuestion1 = model.SelectedQuestions[0].QuestionId;
Related
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
Wonder if someone could point me in the right direction please?
Basically I'm trying to create a page to allow someone to enter data (golf) on a number (18) of holes and basically I'm unsure of how it's done.
In the model I've created the following:
namespace BGSociety.Models
{
public class CreateCourseHolesViewModel
{
public int holeNumber { get; set; }
public int par { get; set; }
public int si { get; set; }
public int distance { get; set; }
}
}
In the Index event of the controller I've got:
TempData["holes"] = getHoles();
protected List<CreateCourseHolesViewModel> getHoles()
{
var holes = new List<CreateCourseHolesViewModel>();
for (int i =1; i < 19; i++)
{
holes.Add(new CreateCourseHolesViewModel { holeNumber = i });
}
return holes;
}
And I'm passing this into the view:
return PartialView("CreateCourseHoles", TempData["holes"]);
In the view I can loop through the list and display the whole number with a textbox next to each one to allow for the entry of par, si and distance.
var zHoles = TempData["holes"] as IEnumerable<BGSociety.Models.CreateCourseHolesViewModel>;
foreach (var hole in zHoles)
{
<div class="row">
<div class="col-sm-1">
<p>#hole.holeNumber</p>
</div>
<div class="col-sm-6">
<div class="form-group">
<div class="col-xs-12">
#Html.ValidationMessageFor(m => hole.par)
#Html.TextBox("par", TempData["par"], new { #class = "form-control", placeholder = "Par", name = "Par" })
</div>
</div>
</div>
</div>
}
But I just can't work out how to pass this populated list back to the controller for me to enter the data into the DB.
There's a great chance that I've gone about this the wrong way (!) but if someone could spare a few minutes to assist that would be marvelous!
Thanks,
Sx
TempData is not meant to be passed as parameters to your PartialView. Even if you dont pass it as parameter; you can still access it. I suggest that you do this instead:
return PartialView("CreateCourseHoles", getHoles());
then your partial view you can you have
#model IEnumerable<BGSociety.Models.CreateCourseHolesViewModel>
and wrap all that for loop in a form:
<form method="post" action="wherever you want to post it to">
foreach (var hole in Model)
{
<div class="row">
<div class="col-sm-1">
<p>#hole.holeNumber</p>
</div>
<div class="col-sm-6">
<div class="form-group">
<div class="col-xs-12">
#Html.ValidationMessageFor(m => hole.par)
#Html.TextBox("par", TempData["par"], new { #class = "form-control", placeholder = "Par", name = "Par" })
</div>
</div>
</div>
</div>
}
</form>
I am trying to create a best possible solution for this but as this is the first time I am encountering this scenario so I am not sure how to best implement this.
I have a very simple model,
public class Feedback
{
[Required]
[Display(Name = "Current ID")]
public int? PreviousID { get; set; }
[Required]
[Display(Name = "Next ID")]
public int? NextID { get; set; }
[Required]
public int ScenarioID { get; set; }
[Display(Name = "Select you scenario")]
public IEnumerable<SelectListItem> YourScenario { get; set; }
}
When User first loads the view then only dropdownlist for YourScenario and TextBox for PreviousID is displayed. When User select dropdownlist then based on its value the TextBox for NextID is displayed to user. Here is my view,
<div class="form-group">
#Html.LabelFor(m => m.YourScenario, new { #class = "col-sm-3 control-label" })
<div class="col-sm-9">
#Html.DropDownListFor(m => m.ScenarioID, m.YourScenario, "Choose Scenario", new { #class = "form-control chosen-select" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.PreviousID, new { #class = "col-sm-3 control-label" })
<div class="col-sm-9">
#Html.TextBoxFor(m => m.PreviousID)
</div>
</div>
<div class="form-group" style="display:none">
#Html.LabelFor(m => m.NextID, new { #class = "col-sm-3 control-label" })
<div class="col-sm-9">
#Html.TextBoxFor(m => m.NextID)
</div>
</div>
To show/hide the NextID I use the Jquery on the view,
$('#YourScenario').change(function () {
var selectedValue = $(this).val();
var nextID = $('#NextID');
if (selectedValue = "3") {
nextID .show();
}
else {
nextID .hide();
}
});
All this works great. Now to use the same View as Edit Mode I pass the model to the view from controller. What I want is that the TextBox for NextID should be displayed or hidden automatically based on the model values. If I use if condition on the View then the control is not available through Javascript so how can I achieve this?
Here is a simple example: https://jsfiddle.net/jma71jf7/
When you run the code, it will hide the input, because there is no selected value, and this line will trigger the change event function:
$('#YourScenario').trigger('change');
But when editing, the select will have some value, and it will hide/show the input according to the value.
I have a couple of classes like below -
public class P
{
public int Id { get; set; }
public virtual ICollection<PDetail> PDetails { get; set; }
}
public class PDetail
{
public int Id { get; set; }
public int Type { get; set; }
public double Price { get; set; }
}
Now, in my View, I am displaying it as -
#foreach (var detail in Model.PDetails)
{
<div class="row">
<div class="col-sm-6">
#Html.DropDownListFor(m => detail.Type, (IEnumerable<SelectListItem>)ViewData["Types"], "--Type--", htmlAttributes: new { #class = "form-control" })
</div>
<div class="col-sm-6">
#Html.TextBoxFor(m => detail.Price, "", htmlAttributes: new { #class = "form-control", placeholder = "Price" })
</div>
</div>
}
Here, I am able to display detail.Price for each detail object, but detail.Type is not getting selected from ViewData["Types"] dropdownlist.
PS: ViewData["Types"] is just a dictionary of typeIds = {1,2,3...}
Info1
I also tried changing the View to -
#for (int i = 0; i < Model.PDetails.Count(); i++)
{
<div class="row">
<div class="col-sm-6">
#Html.DropDownListFor(m => m.PDetails.ElementAt(i).Type, (IEnumerable<SelectListItem>)ViewData["Types"], "--Type--", htmlAttributes: new { #class = "form-control" })
</div>
</div>
}
But it is still not working. How can I go about fixing it?
Thanks for sticking through my barrage of questions. I was able to reproduce your issue, and after a whole lot of attempts to get it to work, it seems that this may just be a bug in the MVC framework that hasn't been fixed in over 4 years.
This answer provides two workarounds:
https://stackoverflow.com/a/3529347/1945651
One involves using a somewhat verbose bit of code to manually add the value to the ModelState.
The other requires your list of items to be indexable with square brackets (e.g. an array or IList/IList<T>), and then involves adding the current value as a default value in the SelectList passed to the HTML helper:
#for (int i = 0; i < Model.PDetails.Count(); i++)
{
<div class="row">
<div class="col-sm-6">
#Html.DropDownListFor(m => m.PDetails[i].Type,
new SelectList(ViewData["Types"] as IEnumerable,
"Value", "Text", Model.PDetails[i].Text),
"--Type--",
htmlAttributes: new { #class = "form-control" })
</div>
</div>
}
Could you give that a try?
This question already has answers here:
ASP.NET MVC 4 - for loop posts model collection properties but foreach does not
(2 answers)
Closed 8 years ago.
I am new in ASP.MVC4 and I am facing with problem to pass object from view to controller:
Let me explain my problem from beginning:
My class which is used by is for example: UserAndRolesModel
public class UserAndRolesModel
{
public user User { get; set; }
public IEnumerable<UserAndRoles> AsignedRoles { get; set; }
}
As You can see class UserAndRolesModel persists of 2 objects: user and Enumerable
public class user
{
[HiddenInput(DisplayValue=false)]
public virtual int user_id { get; set; }
[Required(ErrorMessage = "Please provide name")]
[Display(Name = "Name")]
public virtual string user_name { get; set; }
[Display(Name = "Is active?")]
public virtual bool user_active { get; set; }
}
public class UserAndRoles
{
public string RoleName { get; set; }
public bool IsAssigned { get; set; }
}
My controller action is simple, it assign user to UserWithRoles.User and creates tempList with UserAndRoles objects.
public ActionResult Edit(int userid=0)
{
//object which will be passed to View
UserAndRolesModel UserWithRoles = new UserAndRolesModel();
//user
UserWithRoles.User = repoUsers.Users.FirstOrDefault(x => x.user_id == userid);
//roles
IList<UserAndRoles> tempList = new List<UserAndRoles>();
UserAndRoles temp1 = new UserAndRoles();
temp1.RoleName="Admin";
temp1.IsAssigned=true;
UserAndRoles temp2 = new UserAndRoles();
temp2.RoleName="User";
temp2.IsAssigned=false;
tempList.Add(temp1);
tempList.Add(temp2);
//assign tempList to model
UserWithRoles.AsignedRoles = tempList;
return View(UserWithRoles);
)
At this stage I am successfully passing to View:
UserWithRoles.User.user_id=1;
UserWithRoles.User.user_name="UserName1";
UserWithRoles.User.user_active=true;
UserWithRoles.AsignedRoles[1].RoleName = "Admin";
UserWithRoles.AsignedRoles[1].IsAssigned = true ;
UserWithRoles.AsignedRoles[2].RoleName = "User";
UserWithRoles.AsignedRoles[2].IsAssigned = false;
I am able to display above View properly:
#model Models.UserAndRolesModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<div class="editor-field">
#Html.EditorFor(model => model.User.user_id)
#Html.ValidationMessageFor(model => model.User.user_id)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.User.user_name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.User.user_name)
#Html.ValidationMessageFor(model => model.User.user_name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.User.user_active)
</div>
<div class="editor-field">
#Html.CheckBoxFor(model => model.User.user_active)
#Html.ValidationMessageFor(model => model.User.user_active)
</div>
<br /> ROLES below is piece of code which makes me cry<br />
#foreach (var item in Model.AsignedRoles)
{
<div class="editor-field">
<div class="editor-field">
#Html.LabelFor(model => item.RoleName)
#Html.CheckBoxFor(model => item.IsAssigned)
</div>
</div>
}
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
When i Click submit all data regarding user was passed properly but IEnumerable AsignedRoles is always null.
Here is my controller method on post:
[HttpPost]
public ActionResult Edit(UserAndRolesModel UserWithRoles)
{
if (ModelState.IsValid)
{
if (UserWithRoles.AsignedRoles==null)
Console.WriteLine("I am still crying");
else
Console.WriteLine("Got it!");
}
return View(UserWithRoles);
}
In View I tried to use other loops for example:
#for (int i = 0; i < Model.AsignedRoles.Count(); i++)
{
<div class="editor-field">
#Html.LabelFor(model => item[i].IsAssigned)
#Html.CheckBoxFor(model => item[i].IsAssigned)
</div>
}
But above also does not pass IEnumerable.
Can anyone help me to resolve this issue? How can I pass back to controller UserAndRolesModelwhich contains IEnumerable?
I will be very grateful. Advance thanks for the help!
You do need the for loop, but the one you tried you have referenced item[i], yet item[i] no longer exists. Try this, note that I have also added a HiddenFor for RoleName otherwise that won't get passed back:
#for (int i = 0; i < Model.AsignedRoles.Count(); i++)
{
<div class="editor-field">
#Html.LabelFor(model => model.AssignedRoles[i].IsAssigned)
#Html.CheckBoxFor(model => model.AssignedRoles[i].IsAssigned)
#Html.HiddenFor(model => model.AssignedRoles[i].RoleName)
</div>
}