I have a problem while passing an object with HttpPost...
Once the form is submitted, the model is set "null" on the controller side, and I don't know where is the issue..
Here is my controller :
public ActionResult AddUser(int id = 0)
{
Group group = db.Groups.Find(id);
List<User> finalList = db.Users.ToList() ;
return View(new AddUserTemplate()
{
group = group,
users = finalList
});
//Everything is fine here, the object is greatly submitted to the view
}
[HttpPost]
public ActionResult AddUser(AddUserTemplate addusertemplate)
{
//Everytime we get in, "addusertemplate" is NULL
if (ModelState.IsValid)
{
//the model is null
}
return View(addusertemplate);
}
Here is AddUserTemplate.cs :
public class AddUserTemplate
{
public Group group { get; set; }
public User selectedUser { get; set; }
public ICollection<User> users { get; set; }
}
Here is the form which return a null value to the controller (note that the dropdown list is greatly populated with the good values) :
#using (Html.BeginForm()) {
<fieldset>
<legend>Add an user</legend>
#Html.HiddenFor(model => model.group)
#Html.HiddenFor(model => model.users)
<div class="editor-field">
//Here, we select an user from Model.users list
#Html.DropDownListFor(model => model.selectedUser, new SelectList(Model.users))
</div>
<p>
<input type="submit" value="Add" />
</p>
</fieldset>
}
Thanks a lot for your help
I tried your code and in my case the addusertemplate model was not null, but its properties were all null.
That's because of a few model binding issues: Html.HiddenFor and Html.DropDownListFor do not work with complex types (such as Group or User) (at least that's how it is by default).
Also, Html.HiddenFor cannot handle collections.
Here's how to solve these issues:
instead of #Html.HiddenFor(model => model.group) there should be one #Html.HiddenFor for each property of the group that you need bound
instead of #Html.HiddenFor(model => model.users) you need to iterate through the list of users and for each object add #Html.HiddenFor for each property of the user that you need bound
instead of #Html.DropDownListFor(model => model.selectedUser [...], create a property like int SelectedUserId {get;set;} and use that in the DropDownList (as it cannot handle complex types).
Here's the code that works:
1. The User and Group classes, as I imagined them to be:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Group
{
public int Id { get; set; }
public string Name { get; set; }
}
2. The adjusted AddUserTemplate class:
public class AddUserTemplate
{
public Group Group { get; set; }
public IList<User> Users { get; set; }
public int SelectedUserId { get; set; }
public User SelectedUser
{
get { return Users.Single(u => u.Id == SelectedUserId); }
}
}
The adjustments:
Users was changed from ICollection to IList, because we'll need to access elements by their indexes (see the view code)
added SelectedUserId property, that will be used in the DropDownList
the SelectedUser is not a readonly property, that returns the currently selected User.
3. The adjusted code for the view:
#using (Html.BeginForm())
{
<fieldset>
<legend>Add an user</legend>
#*Hidden elements for the group object*#
#Html.HiddenFor(model => model.Group.Id)
#Html.HiddenFor(model => model.Group.Name)
#*Hidden elements for each user object in the users IList*#
#for (var i = 0; i < Model.Users.Count; i++)
{
#Html.HiddenFor(m => m.Users[i].Id)
#Html.HiddenFor(m => m.Users[i].Name)
}
<div class="editor-field">
#*Here, we select an user from Model.users list*#
#Html.DropDownListFor(model => model.SelectedUserId, new SelectList(Model.Users, "Id", "Name"))
</div>
<p>
<input type="submit" value="Add" />
</p>
</fieldset>
}
Another option that does not require a bunch of hidden fields is to simply specify that you want the model passed to the controller. I think this is much cleaner.
#using(Html. BeginForm("action","controller", Model, FormMethod.Post)){
...
}
Related
I have a form in which user can select which shipping methods they want to support for they product that they are selling, e.g. first class letter, second class letter, parcel, etc. I only give users a collection of possible shipping methods, they declare how much each one will cost, so if someone wants to sell a toaster in a parcel, they will charge less than for a set of dumbbells.
My ProductViewModel:
public int Id { get; set; }
public ICollection<SelectedShippingMethodViewModel> SelectedShippingMethods { get; set; }
And SelectedShippingMethodViewModel:
public class SelectedShippingMethodViewModel
{
public string Name { get; set; }
public decimal Price { get; set; }
}
In my form I create a section with possible options like this:
<h3>Add new product</h3>
<hr />
#using (Html.BeginForm("AddNew", "ProductCreator", null, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<div class="form-group">
<label class="col-sm-2 control-label">Shipping methods</label>
<div class="col-sm-10">
#foreach (ShippingMethod shippingMethod in ViewBag?.ShippingMethods)
{
<div class="row">
<div class="col-md-3">
// I don't know what should be here
#Html.CheckBox("SelectedShippingMethods", false)
#shippingMethod.Name
</div>
<div class="col-md-2">
// I don't know what should be here
#Html.TextBox("SelectedShippingMethods.Price")
</div>
</div>
}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">Add product</button>
</div>
</div>
}
I have a database table with every possible shipping method that I acquire like this:
[HttpGet]
public async Task<ActionResult> AddNew()
{
ViewBag.ShippingMethods = await _shippingService.GetAllShippingMethodsAsync();
return View();
}
The problem is if checkbox is selected I have to bind Price and Name for each individual SelectedShippingMethodViewModel and I have no idea how to make it work.
Your view models are incorrect. To allow users to select the shipping methods they want and add a price, that view model needs to be
public class ShippingMethodViewModel
{
public string Name { get; set; }
public decimal Price { get; set; }
public bool IsSelected { get; set; } // your checkbox binds to this property
}
and the ProductViewModel should be
public class ProductViewModel
{
public int Id { get; set; }
....
public List<ShippingMethodViewModel> ShippingMethods { get; set; }
}
Then in the GET method, initialize your ProductViewModel and populate the ShippingMethods based on all available ShippingMethods, for example
var shippingMethods = await _shippingService.GetAllShippingMethodsAsync()
ProductViewModel model = new ProductViewModel
{
....
ShippingMethods = shippingMethods.Select(x => new ShippingMethodViewModel
{
Name = x.Name
}).ToList()
};
return View(model);
and in the view, use a for loop or EditorTemplate for typeof ShippingMethodViewModel to correctly generate your form controls
#for (int i = 0; i < Model.ShippingMethods.Count; i++)
{
#Html.LabelFor(m => m.ShippingMethods[i].IsSelected, Model[0].ShippingMethods.Name)
#Html.CheckBoxFor(m => m.ShippingMethods[i].IsSelected)
#Html.LabelFor(m => m.ShippingMethods[i].Price)
#Html.TextBoxFor(m => m.ShippingMethods[i].Price)
#Html.HiddenFor(m => m.ShippingMethods[i].Name) // if you want this to be submitted as well
}
Then in the POST method
public ActionResult AddNew(ProductViewModel model)
{
// Get the selected Shipping Methods and the associated price
var selectedMethods = model.ShippingMethods.Where(x => x.Selected);
I have this Model:
public class ClassRoom
{
public List<Student> Students { get; set; }
}
public class Student
{
public int ID { get; set; }
public int Type { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
}
The values of the ID and Type are already full, I need to create a view where the student can add his name and last name.
So I need to loop the list of students in the view.
I am doing the following:
#model Models.ClassRoom
#{
ViewBag.Title = "Classroom";
}
#if (Model != null)
{
<form action="Controller/Method">
foreach (var item in Model.Students)
{
#Html.HiddenFor(model => model.ID)
<div>
#Html.TextBoxFor(model => model.Name)
#Html.TextBoxFor(model => model.LastName)
</div>
}
<input type="submit">
</form>
}
I want to eventually submit a model of type Classroom with a list of students filled with Name and Last Name for each ID
But this is not working.
How can I bind values From the View to item on a certain index in a list?
For each ID in the hidden input,I want to save the written name and last name.
Please help
I need to create a form and submit the ClassRoom with a full List of Students eventually. What should be the types in my Controller method and views?
If you want to send your model back to controller, then you would need to generate naming correctly. There are several ways, one of them would look like this:
#using (Html.BeginForm("Index", "Home")) {
<div style="margin-top: 100px;">
#if (Model != null) {
for (var i = 0; i <= Model.Students.Count - 1; i++) {
#Html.HiddenFor(model => model.Students[i].ID)
<div>
#Html.TextBoxFor(model => model.Students[i].Name)
#Html.TextBoxFor(model => model.Students[i].LastName)
</div>
}
}
<input type="submit" value="Go"/>
</div>
}
And in controller dont forget to add:
[HttpGet]
public async Task<IActionResult> Index() {
var classroom = new ClassRoom();
... //add some students to the classroom
return View(classroom);
}
[HttpPost]
public async Task<IActionResult> Index(ClassRoom classRoom) {
...
}
Here can be found some more reading.
I have below Model structure
public class Quiz
{
public List<Question> Questions { get; set; }
}
public class Question
{
public int Id { get; set; }
public String QuestionText { get; set; }
public List<Option> Options { get; set; }
public int AnswerId { get; set; }
}
public class Option
{
public int Id { get; set; }
public String OptionText { get; set; }
public int DisplayOrder { get; set; }
}
And my view is like below where I am displaying all the questions and options
foreach (var question in Model.Questions)
{
#Html.DisplayFor(modelItem => question.QuestionText) <br />
foreach (var option in question.Options)
{
#Html.RadioButton("Id", option.Id)#option.OptionText
}
}
<input type="submit" value="Submit" />
I am not getting the values for all selected radiobuttons, It always returns one value in form collection
[HttpPost]
public ActionResult Quiz(Quiz userResponse, FormCollection form)
{
foreach (var item in form.AllKeys)
{
string value = form[item];
// save data
}
//var selectedOption = frm["Id"];
return View();
}
Can you please help?
You creating radio buttons that have no relationship to you model and all have the same name="Id" attribute (and you would only ever be able to select one option from all the questions). You need to use a for loop or EditorTemplate for typeof Question (refer this answer for a more detailed explanation). Using an EditorTemplate, your code will be
/Views/Shared/EditorTemplates/Question.cshtml
#model Question
#Html.HiddenFor(m => m.Id)
#Html.DisplayFor(m => m.QuestionText)
#foreach(var option in Model.Options)
{
<div>
<label>
#Html.RadioButtonFor(m => m.AnswerId , option,Id, new { id = "" })
<span>#option.OptionText</span>
</label>
</div>
}
and in the main view
#model Quiz
#using (Html.BeginForm())
{
#Html.EditorFor(m => m.Questions)
<input type="submit" value="Save" />
}
and the controller method
[HttpPost]
public ActionResult Quiz(Quiz model)
The model in the POST method will be bound with each question containing its Id and AnswerId (the Id of the selected Option) properties.
Side note: Your current POST method has return View() but this would fail because the value of each Options property will be null. If you need to return the view (which should only be necessary if ModelState is invalid), then you need to repopulate those collections.
I am new in MVC programming with razor and I need your advice on what is wrong with my code.
I have a Model
public class OrderDetails : OrderList
{
public string CompanyId { get; set; }
public List<OrderItems> OrdItems { get; set; }
}
public class OrderItems
{
public int Id { get; set; }
public string StopAddressName { get; set; }
}
I have a controller that populate data.
public ActionResult Edit()
{
OrderDetails ordDtl = new OrderDetails();
ordDtl.CompanyId = "1";
ordDtl.OrdItems = new List<OrderItems>();
for (int i = 1; i < 4; i++)
{
OrderItems tmp = new OrderItems();
tmp.Id = i;
tmp.StopAddressName = "Street " + i;
ordDtl.OrdItems.Add(tmp);
}
return View("EditOrder", ordDtl);
}
When I send it to view it shows all data. Here is my view.
#model OrderDetails
#using (Html.BeginForm("Edit", "Orderlayouts", FormMethod.Post))
{
<fieldset><legend>OrderDetail</legend>
<div class="editor-label">
#Html.LabelFor(model => model.CompanyId)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.CompanyId)
</div>
<table>
<tr>
<td>id</td>
<td>Address</td>
</tr>
#foreach (var itm in Model.OrdItems)
{
<tr>
<td>#itm.Id</td>
<td>#itm.StopAddressName</td>
</tr>
}
</table>
<p>
<input type="submit" value="Edit" />
</p>
</fieldset>
}
When I click the submit button I have in controller CompanyID, but OrdItems values become null. Can you point out what is going wrong with my code.
Here is my controller.
[HttpPost]
public ActionResult Edit(OrderDetails Orderdt)
{
return View("EditOrder", Orderdt);
}
Any suggestions will be greatly appreciated.
I would try doing Something like this.
#for (int i = 0; i < Model.OrdItems.Count; i++)
{
#Html.DisplayFor(model => model.OrdItems[i].Id)#Html.HiddenFor(model => model.OrdItems[i].Id)
#Html.DisplayFor(model => model.OrdItems[i].StopAddressName)#Html.HiddenFor(model => model.OrdItems[i].StopAddressName)
}
Model binding is honestly the bane of my MVC work.
Here, however, you're going to see a form style post that only contains companyID - no other form fields for it to be using to bind are in your view.
I'm assuming OrderDetails is an entity (tied to a database table via Entity Framework). For all entities with navigation properties of a list-type, you must use an ICollection. Additionally, all navigation properties, whether a single foreign key or a list, must be virtual. EF does not actually return your class, but rather a proxy of your class with the navigation properties overridden to return the proper objects. If it's not virtual, EF can't make the override and you get a null value instead. So your model should look like:
public class OrderDetails : OrderList
{
public string CompanyId { get; set; }
public virtual ICollection<OrderItems> OrdItems { get; set; }
}
Edit: Removed Partial View to make things simpler. Now I just need to find out why The View isn't Posting the Values
ViewModelProspectUsers
public class ViewModelProspectUsers
{
public int Id { get; set; }
public string User { get; set; }
public IEnumerable<ViewModelProspectSelect> Prospects { get; set; }
}
ViewModelProspectSelect
public class ViewModelProspectSelect
{
public int ProspectID { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
View
#model OG.ModelView.ViewModelProspectUsers
#using (Html.BeginForm())
{
#Html.HiddenFor(model => model.Id)
<h5>Please Select Prospects you wish to assign to this User.</h5>
-----HERE is where the partial used to be, these values aren't being posted to the [Post] Method------
-----------------------------------------However they are populating just fine----------------------------------------
#foreach (var item in Model.Prospects)
{
#Html.HiddenFor(x => item.ProspectID)
#Html.DisplayFor(x => item.Name)
#Html.EditorFor(x => item.IsSelected)
}
#*#Html.Partial("_ShowProspectCheckedForUser", Model.Prospects)*#
#*#Html.Partial("_ShowProspectCheckedForuser", new OG.ModelView.ViewModelProspectSelect())*#
<input type="submit" value="Save changes" />
#Html.ActionLink("Cancel", "Index")
}
Post
[HttpPost]
public ActionResult UsersInProspect(ViewModelProspectUsers viewModel)
If i were to look at viewModel.Prospects(m=>m.isSelected) //<- this value is Null shouldn't be
My viewmodel Variableis showing Data but not for the Ienumerable.
When dealing with list-type objects, you must reference them with array notation to have the field names generated in a way that the modelbinder can parse them back, i.e:
for (var i = 0; i < Model.Count(); i++)
{
#Html.LabelFor(m => Model[i].SomeProperty)
#Html.EditorFor(m => Model[i].SomeProperty)
}
In your scenario, you'd be better served by using a view model to contain your list and adding a Selected property to the items so that you can track which ones were or were not selected.
public class ViewModelProspects
{
public List<ViewModelProspectSelect> Prospects { get; set; }
}
public class ViewModelProspectSelect
{
// Whatever else you have
public bool Selected { get; set; }
}
Then, in your view:
#model ViewModelProspects
#using (Html.BeginForm())
{
for (var i = 0; i < Model.Prospects.Count(); i++)
{
<label>
#Html.HiddenFor(m => Model.Prospects[i].Id)
#Html.CheckboxFor(m => Model.Prospects[i].Selected, true)
#Model.Prospects[i].Name
</label>
}
}
And finally, change your action method signature:
[HttpPost]
public ActionResult UsersInProspect(ViewModelProspects model)
Then, you can easily get the list of selected ids inside the action with:
var selectedIds = model.Prospects.Where(m => m.Selected).Select(m => m.Id)