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>
}
Related
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;
NOTE: Code updated to take into account the comments and answer made below.
In my MVC app I need sometimes to make references to other objects (like a many-to-many relationship, or one-to-many relationship).
So I have this model:
public class ObjInfo
{
[Required(ErrorMessage = "Obj ID is required.")]
[Display(Name = "ObjID")]
public int m_Id { get; set; }
[Required(ErrorMessage = "Obj Name is required.")]
[Display(Name = "Obj Name")]
public string m_Name { get; set; }
[Display(Name = "Obj Number")]
public int m_Number { get; set; }
(...)
[Required(ErrorMessage = "Other Obj is required.")]
[Display(Name = "OtherObj")]
public int m_OtherObjID { get; set; }
public OtherObjInfo m_OtherObj { get; set; }
(...)
}
I have default and parameters constructors as well and can show them as needed, though I am not sure if they are at fault. Anyway.
In my controller, I have the two create methods following MVC methods:
//
// GET: /Obj/Create
public ActionResult Create()
{
ViewBag.List = new SelectList(PopulateDDLs(), "OtherObj", "Other obj ID");
return View();
}
//
// POST: /Obj/Create
[HttpPost]
public ActionResult Create(Objinfo obj)
{
if (ModelState.IsValid)
{
m_ObjManager.CreateObj(obj);
return RedirectToAction("SearchIndex");
}
ViewBag.List = new SelectList(PopulateDDLs(), "OtherObj", "Other obj ID");
return View(obj);
}
And, finally, here's how my "Create" view is coded:
#model MyApp.Models.ObjInfo
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>OBJ</legend>
<div class="editor-label">
#Html.LabelFor(model => model.m_Id)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.m_Id)
#Html.ValidationMessageFor(model => model.m_Id)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.m_Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.m_Name)
#Html.ValidationMessageFor(model => model.m_Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.m_Number)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.m_Number)
#Html.ValidationMessageFor(model => model.m_Number)
</div>
(...)
<div class="editor-label">
#Html.LabelFor(model => model.m_OtherObj , "Other Obj")
</div>
<div class="editor-field">
#Html.DropDownList(model => model.m_OtherObjID, ViewBag.List as SelectList, "--- Select Other Obj ---", new {#class = "OtherObjInfo "})
#Html.ValidationMessageFor(model => model.m_OtherObj)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Basically, the problem is that each time I click on the "create" button, the ModelState validation is always false for the OtherObj even if something is selected in the dropdownlist. Every other values are correct except this one.
I don't understand why and I would greatly appreciate your help on this one, thank you for your help!
After code edited **
Now I get a crash as I enter the "Create" view:
DataBinding: 'System.String' does not contain a property with the name 'OtherObj'.
Exactly on the line where my dropdownlistfor is located.
The datavalueField and dataTextField are supposed to refer to what exactly?
add otherObjectId to your model
public class ObjInfo
{
public int m_Id { get; set; }
...
[Required(ErrorMessage = "Other Obj is required.")]
[Display(Name = "OtherObj")]
public int otherObjectId { get; set; }
public OtherObjInfo m_OtherObj { get; set; }
...
}
controller
public ActionResult Create()
{
ViewBag.List = new SelectList(PopulateDDLs(), "Id", "Name");
return View();
}
[HttpPost]
public ActionResult Create(Objinfo obj)
{
if (ModelState.IsValid)
{
m_ObjManager.CreateObj(obj);
return RedirectToAction("SearchIndex");
}
ViewBag.List = new SelectList(PopulateDDLs(), "Id", "Name");
return View(obj);
}
view
<div class="editor-label">
#Html.LabelFor(model => model.m_OtherObj , "Other Obj")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.otherObjectId, ViewBag.List as SelectList, "--- Select Category ---", new { #class = "some_class" })
#Html.ValidationMessageFor(model => model.m_OtherObj)
</div>
better way is using strongly typed helpers. All your helpers are strongly-typed (editorfor, textboxfor, dropdownlistfor,...) except dropdownlist.
if you want to bind DDL value to your model, You should use dropdownlistfor instead of dropdownlist.
your model state is not valid, because you dont bind required value as DDL to model.
I am using asp.net mvc 4
Is there a way to update the form based on selection made by the user?
(in this case I want to fill in address fields if something is picked from the dropdown list, otherwise a new address would need to be typed in)
My model:
public class NewCompanyModel
{
[Required]
public string CompanyName { get; set; }
public bool IsSameDayRequired { get; set; }
public int AddressID { get; set; }
public Address RegisterOfficeAddress { get; set; }
}
View:
#model ViewModels.NewCompanyModel
#using (Html.BeginForm(null, null, FormMethod.Post, new { name = "frm", id = "frm" }))
{
#Html.ValidationSummary(true)
<fieldset id="test">
<legend>Company</legend>
<h2>Register office address</h2>
<div class="editor-label">
#Html.LabelFor(model => model.AddressID)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.AddressID, (IList<SelectListItem>)ViewBag.Addresses, new {id = "address", onchange = "window.location.href='/wizard/Address?value=' + this.value;" })
</div>
<div class="editor-label">
#Html.LabelFor(model => model.RegisterOfficeAddress.BuildingNameOrNumber)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.RegisterOfficeAddress.BuildingNameOrNumber)
#Html.ValidationMessageFor(model => model.RegisterOfficeAddress.BuildingNameOrNumber)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.RegisterOfficeAddress.StreetName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.RegisterOfficeAddress.StreetName)
#Html.ValidationMessageFor(model => model.RegisterOfficeAddress.StreetName)
</div>
and controller:
public ActionResult Address(string value)
{
//get the address from db and somehow update the view
}
The question is how do you update the 'model.RegisterOfficeAddress.StreetName' etc
Just to make clear this is just part of the form so I cannot submit it just yet.
Many thanks
Thanks for your help; I have decided to take a different approach:
On dropdown change I submit the form:
<div class="editor-label">
#Html.LabelFor(model => model.ServiceAddress.AddressID)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.ServiceAddress.AddressID, (IEnumerable<SelectListItem>)ViewBag.Addresses, new { onchange = "this.form.submit();" })
#Html.ValidationMessageFor(model => model.ServiceAddress.AddressID)
</div>
and then in controller:
[HttpPost]
public ActionResult NewDirector(NewDirectorVM vm, string value)
{
ModelState.Clear();
if (vm.ServiceAddress.AddressID > 0)
{
//Updates the properties of the viewModel
vm.ServiceAddress = _Repository.GetAddress(vm.ServiceAddress.AddressID);
}
return View("NewDirector", vm);
}
Please notice ModelState.Clear(); which actually allows the view to be updated from the controller (otherwise all the changes made the the viewModel by the controller would have been overwritten by the values in the view).
Common way in such cases is to update other fields via javascript:
$('##Html.IdFor(model => model.AddressID)').on('change',function(){
$.get(...,function(data){
$('##Html.IdFor(model => model.RegisterOfficeAddress.BuildingNameOrNumber)').val(data)
})
})
I created a partial view that should display a list of user with a check box , so i can reuse this partial view in various pages.
The problem is that, i'm not able to have the correct htmlprefix the input generated
(I would like to remove the . of the prefix )
Model:
public class CircleEditViewModel
{
[Key]
public int CircleId { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
public bool IsSystem { get; set; }
public List<SimpleUserListViewModel> Users { get; set; }
public CircleEditViewModel()
{
Users = new List<SimpleUserListViewModel>();
}
}
public class SimpleUserListViewModel
{
public SimpleUserListViewModel()
{
}
public SimpleUserListViewModel(User user)
{
this.UserId = user.UserId;
FullName = user.FullName;
}
public int UserId { get; set; }
public byte[] Picture { get; set; }
public string FullName { get; set; }
public bool IsCheckedForAction { get; set; }
}
'Main view':
#model Wims.Website.ViewModels.CircleEditViewModel
<script type="text/javascript">
$(document).ready(function () {
$.validator.unobtrusive.parse('form');
});
</script>
#using (Ajax.BeginForm(Html.ViewContext.RouteData.Values["Action"].ToString(), null, new AjaxOptions { HttpMethod = "POST", OnSuccess = "SaveDone(data)" }, new { id = "editform" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Circle</legend>
#Html.Label(DateTime.Now.ToString());
<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>
</fieldset>
#Html.Partial("~/Views/Shared/_UserList.cshtml", Model.Users,
new ViewDataDictionary(Html.ViewDataContainer.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "Users" }
})
#Html.GenerateSecureDataControls(model => model.CircleId)
<input type="submit" value="Save" />
}
Partial view:
#model List<Wims.Website.ViewModels.Shared.SimpleUserListViewModel>
#{
if (Model != null)
{
for (int i = 0; i < Model.Count; i++)
{
<div class="userDetail">
<div>
<div>
#Html.CheckBoxFor(model => model[i].IsCheckedForAction)
</div>
<div class="iconDiv">
#Html.Image("~/Content/Images/defaultUser.jpg", Model[i].FullName, null)
</div>
<div>
#Html.TextBoxFor(model => model[i].FullName)
#Html.HiddenFor(model => model[i].UserId)
</div>
</div>
</div>
<div style="clear: both"></div>
}
}
}
I am almost there, the input generated id's are
id="Users.[0].FullName
Is there any way i can remove the first dot?
I've found some solution yesterday on a blog (which i can't find anymore...) but it was for MVC3 and I couldn't make it work anyway...
Thanks for the help!
EDIT:
Maybe I should use EditorFor instead of partial view:
.NET MVC 4 Strongly typed ViewModel containing Strongly typed Model with EditorFor and EditorTemplate partial view not binding
Will check tonight
Alrighty, The EditorFor worked perfectly..
I need to read more about this.
This is my first MVC3 project and thought it'd be a bit simpler to accomplish this, but I've had a lot of issues. This seems to be the one trouble for which I've not encountered a solution. Posting here as a last resort.
My issue is that the Ingredients list never seems to populate in the database when a new Recipe is created. I've even tried hard-coding a list in the controller when the rest of the data is saved, but it still doesn't do anything. As far as I can tell, there's not even a field for Ingredients in the DB anywhere. Can anyone point out what I'm doing wrong? Much thanks.
Model:
public class Recipe
{
public int ID { get; set; }
[Required]
public string Title { get; set; }
[Required]
[Display(Name = "Type of Meal")]
public int TypeID { get; set; }
[Required]
public string Instructions { get; set; }
[Display(Name = "Submitted By")]
public string UserName { get; set; }
public IList<string> Ingredients { get; set; }
public virtual MealType Type { get; set; }
}
Create View:
#model final.Models.Recipe
#{
ViewBag.Title = "Recipe Finder - New Recipe";
}
<h2>New Recipe</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script type="text/javascript">
//adds an ingredient field on the fly
var numIngredients = 0;
function addIngredient() {
$('#ingredientlist').append('<tr><td><input type="text" name="Ingredients[' + numIngredients++ + ']" value="' + $('#AddIngredient').val() + '" readonly="true" tabindex="-1" /></tr></td>');
$('#AddIngredient').val('');
$('#AddIngredient').focus();
}
function onKeyPress(e) {
var keycode;
if (window.event) {
keycode = window.event.keyCode;
}
else if (e) {
keycode = e.which;
}
else {
return true;
}
//for addingredient field, add ingredient and not submit
// else mimic submit
if (keycode == 13) {
if (document.activeElement.id == "AddIngredient") {
addIngredient();
return false;
}
document.getElementById('btnSubmit').click();
}
return true;
}
//intercepts form submit instead of using submit button to disable addingredient textbox
// this prevents the value/field from posting
function preSubmit() {
$('#AddIngredient').attr('disabled', 'true');
document.getElementsByTagName('form')[0].submit();
}
//intercepts users pressing the enter key
if (document.layers) document.captureEvents(Event.KEYPRESS);
document.onkeypress = onKeyPress;
</script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>by #User.Identity.Name</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.TypeID, "Type")
</div>
<div class="editor-field">
#Html.DropDownList("TypeID", String.Empty)
#Html.ValidationMessageFor(model => model.TypeID)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Ingredients)
</div>
<div class="editor-field">
#Html.TextBox("AddIngredient")
<input type="button" value="Add Ingredient" onclick="addIngredient()" tabindex="-1" />
<table id="ingredientlist">
</table>
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Instructions)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.Instructions, new { rows = "7", cols = "77" })
#Html.ValidationMessageFor(model => model.Instructions)
</div>
#Html.HiddenFor(model => model.UserName)
<p>
<input type="button" value="Submit" onclick="preSubmit()" id="btnSubmit" />
</p>
</fieldset>
}
Controller:
[HttpPost]
public ActionResult Create(Recipe recipe)
{
if (ModelState.IsValid)
{
db.Recipes.Add(recipe);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.TypeID = new SelectList(db.MealTypes, "ID", "Type", recipe.TypeID);
return View(recipe);
}
The POST fields I can see send all the information successfully, as seen below, I'm not sure what the issue is, or at this point what I haven't already tried.
Title:test recipe
TypeID:2
Ingredients[0]:test0
Ingredients[1]:test1
Ingredients[2]:test2
Ingredients[3]:test3
Instructions:this is a test
UserName:tym
You may want to check this page about storing a list of strings associated with an entity.