I have this this View for rendering form
#using ExpertApplication.ViewModels
#model IEnumerable<QuestionViewModel>
#{
ViewBag.Title = "GetQuestions";
}
#using(Html.BeginForm("ProcessAnswers", "Home", FormMethod.Post))
{
foreach(QuestionViewModel questionViewModel in Model)
{
Html.RenderPartial("QuestionPartialView", questionViewModel);
}
<input type="submit" value="Send data"/>
}
}
<h2>GetQuestions</h2>
And partial view
#using ExpertApplication.ViewModels
#model QuestionViewModel
<div id="question">
#Model.Text
<br />
<div id="answer">
#foreach(var answer in Model.AnswerViewModels)
{
#(Model.IsMultiSelected
? Html.CheckBoxFor(a => answer.Checked)
: Html.RadioButtonFor(a => answer.Checked, false))
#Html.LabelFor(a => answer.Text)
<br />
}
</div>
</div>
I want get data from From
[HttpPost]
public ActionResult ProcessAnswers(IEnumerable<QuestionViewModel> answerForQuesiton)
{
//answerForQuestion always is null
}
but parameter answerForQuesiton is null. How to fix this problem ?
MVC binds lists by using zero indexed names. Unfortunately, for this reason, while foreach loops will create inputs that contain the correct values, they will not create input names that use the correct names. Therefore, you cannot bind lists using foreach
For example:
for (int i = 0; i< Model.Foo.Count(); i++)
{
for (int j = 0; j < Model.Foo[i].Bar.Count(); j++)
{
#Html.TextBoxFor(m => m.Foo[i].Bar[j].myValue)
}
}
Will create textboxes with names like "Foo[1].Bar[2].myValue" and correctly bind. However,
foreach (var foo in Model.Foo)
{
foreach (var bar in foo.Bar)
{
#Html.TextBoxFor(m => bar.myVal);
}
}
will create text boxes with the exact same values as the prior loop, but all of them will have "name="bar.myVal" so none of them can bind.
So to fix your issue:
1) You could replace your foreach loops with for loops. Note: this requires using IList or List instead of IEnumerable
2) You could use EditorTemplates which automatically apply the correct names for you.
You are using the wrong mechanism. You should be using EditorTemplates rather than partial views. Editor Templates know how to deal with collections and creating properly formatted name attributes so they can be bound on post-back.
http://coding-in.net/asp-net-mvc-3-how-to-use-editortemplates/
Related
In my controller I set the items in the ViewBag:
List<ShopItemModel> items = new List<ShopItemModel>();
/* populate my items */
ViewBag.Items = items;
So on the cshtml i run thru the list, but how do I connect it so on postback sets the argument of the Post method in the controller?
The CSHTML:
#model Models.ShopItemModel
<h2>Webshop</h2>
#foreach( var item in ViewBag.Items)
{
using (Html.BeginForm())
{
<p>#item.Name</p> <!-- List the item name, but not bounded? -->
#Html.LabelFor(model => model.Name, new { Name = item.Name }) <!-- outputs just "Name", not the items name -->
<input type="submit" value="Buy" />
}
}
The post version of the method in the controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(ShopItemModel m)
{
return View();
}
But how do I fix this binding? So I fetch the selected item from the list?
In your view:
using (Html.BeginForm())
{
for (int i = 0; i < Model.Count; i++)
{
#Html.LabelFor(model => model[i].Name)
}
}
This will produce html controls like this:
<input name="ShopItemModel[3].Name" ...
If you're using this in a form, in your controller, iterate over the POSTed model data:
foreach (var item in model)
{
... do something to each item
}
You can use a foreach loop in the view rather than a for loop, example here
I have an MVC project and I have a case where i need to update a parent and multiple child entities at the same time. In the post action I am receiving "an attempt was made to remove a relationship between x and x however one of the relationship's foreign keys" which is strange, all I'm doing is update, I'm not droping any entity whatsoever. I am using Linq to SQL and MVC3. The pseudocode is like the following:
#model Project.Models.ParentModel
...
#using (Html.BeginForm()) {
#Html.Label("Parent property")
#Html.EditorFor(model => model.ParentProperty)
#foreach (var child in Model.Childs)
{
Html.RenderPartial("_EditChild", child)
// Which is nothing more than a label and an editor for a property like:
// #model Project.Models.ChildModel
// #Html.Label("Child property")
// #Hteml.EditorFor(model => model.ChildProperty)
}
...
}
The Action looks like:
public ActionResult Edit(int id, FormCollection collection)
{
var parent = new Parent();
TryUpdateModel(Parent()); // which updates the parent and the child properties correctly
dataContext.SubmitChanges();
}
Can anybody explaing this behavior. Once again, I'm not removing or dropping any child entities!
The binding over a list can be pretty nasty, I had some problems with it myself. I modified my list editing code to work with childs and tested it, it worked and the data is correctly bound and visible in the post action:
#model MvcApplication2.Models.Parent
#using (Html.BeginForm())
{
<table>
#{
<tr>
<td>
#Html.TextBoxFor(m => m.Text)
#Html.HiddenFor(m => m.ID)
</td>
</tr>
for (int i = 0; i < Model.Children.Count; i++)
{
<tr>
<td>
#Html.TextBoxFor(x => x.Children[i].Title)
#Html.HiddenFor(x => x.Children[i].ID)
</td>
</tr>
}
}
</table>
<div class="button">
<input class="submit" type="submit" name="btnSave" id="btnSave" value="Save" />
</div>
}
Controller for my test looks like this:
[HttpGet]
public ActionResult EditingChildren()
{
Parent parent = new Parent() { Text = "" };
parent.Children = new List<Child>();
parent.Children.Add(new Child() { Title = "" });
parent.Children.Add(new Child() { Title = "" });
parent.Children.Add(new Child() { Title = "" });
return View(parent);
}
[HttpPost]
public ActionResult EditingChildren(Parent parent)
{
// save parent with children
}
Editing my post about saving data back with linq to sql:
If you are not binding the ID on the view, it will be left empty in the object in the post method. That gives you troubles saving back the data.
I usally bind therefore the ID to an hidden field so it will not be empty any more (view code above edited and added HiddenFor beneath TextBoxFor).
Have also a look about updating data with linq to sql on this website:
http://davedewinter.com/2009/04/07/linq-to-sql-updating-entities/ (under Attach an Entity, Change Properties, Update)
and this post:
enter link description here
i have problem to pass data from view to controller , i have view that is strongly typed with my viewmodel "TimeLineModel", in the first i passed to this view my viewmodel from action on my controller
public ActionResult confirmation(long socialbuzzCompaignId)
{
return View(new TimeLineModel() { socialBuzzCompaignId = socialbuzzCompaignId, BuzzMessages = model });
}
with this i can get info from my action and display it on view , but i have other action POST which i won't get my view model to do some traitement
[HttpPost]
public ActionResult confirmation(TimeLineModel model)
{
}
i can get some propretie of the model but in others no , for example i can get the properti "socialBuzzCompaignId" of model , but other propertie like "IEnumerable BuzzMessages" i can't get it , i dont now why !!
this is the content of my view
#model Maya.Web.Models.TimeLineModel
#{
ViewBag.Title = "confirmation";
}
#using (Html.BeginForm())
{
<h2>confirmation</h2>
<fieldset>
#foreach (var msg in Model.BuzzMessages)
{
<div class="editor-label">
#msg.LongMessage
</div>
<br />
}
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
You need to include BuzzMessages properties within a form element. Since it's not editable, you'd probably want to use hiddens. There are two ways to do this. Easiest is instead of doing a foreach loop, do a for loop and insert them by index.
#for (int i =0; i<Model.BuzzMessages.Count(); i++v)
{
<div class="editor-label">
#Model.BuzzMessages[i].LongMessage
#Html.HiddenFor(m => m.BuzzMessages[i].LongMessage);
</div>
<br />
}
but to do this you'd need to use an IList instead of an IEnumerable in your view model to access by index.
Alternatively, you could create an Editor Template named after your BuzzMessages class (whatever its name is).
#model BuzzMessagesClass
#Html.HiddenFor(m => m.LongMessages)
<!-- Include other properties here if any -->
and then in your main page
#Html.EditorFor(m => m.BuzzMessages)
Check out http://coding-in.net/asp-net-mvc-3-how-to-use-editortemplates/ or search stack overflow if the details of editor templates confuse you.
Just like any HTML POST method, you have to get the data back to the Controller somehow. Just simply "showing" the data on the page doesn't rebind it.
You have to put the data in an input (or a control that will post back) to the appropriate model property name.
So, if you have a model property with name FirstName and you want this data to be rebound to the model on POST, you have to supply it back to the model by placing an "input hidden" (or similar control that postbacks) with the ID of FirstName will rebind that property to the model on POST.
Hope that explains it.
#foreach (var msg in Model.BuzzMessages)
{
<div class="editor-label">
#msg.LongMessage
<input type="hidden" name="BuzzMessages.LongMessage" value="#msg.LongMessage" />
</div>
}
It will post array of LongMessages. Get values like this:
[HttpPost]
public ActionResult confirmation(TimeLineModel model, FormCollection collection)
{
var longMessages = collection["BuzzMessages.LongMessage"];
}
It has been intriguing that my MVC3 razor form renders duplicated values inside a foreach code block in spite of correctly receiving the data from the server. Here is my simple form in MVC3 Razor...
-- sample of my .cshtml page
#model List<Category>
#using (#Html.BeginForm("Save", "Categories", FormMethod.Post))
{
foreach (Category cat in Model)
{
<span>Test: #cat.CategoryName</span>
<span>Actual: #Html.TextBoxFor(model => cat.CategoryName)</span>
#Html.HiddenFor(model => cat.ID)
<p>---</p>
}
<input type="submit" value="Save" name="btnSaveCategory" id="btnSaveCategory" />
}
My controller action looks something like this -
[HttpPost]
public ActionResult Save(ViewModel.CategoryForm cat)
{
... save the data based on posted "cat" values (I correctly receive them here)
List<Category> cL = ... populate category list here
return View(cL);
}
The save action above returns the model with correct data.
After submitting the form above, I expect to see values for categories similar to the following upon completing the action...
Test: Category1, Actual:Category1
Test: Category2, Actual:Category2
Test: Category3, Actual:Category3
Test: Category4, Actual:Category4
However #Html.TextBoxFor duplicates the first value from the list. After posting the form, I see the response something like below. The "Actual" values are repeated even though I get the correct data from the server.
Test: Category1, Actual:Category1
Test: Category2, Actual:Category1
Test: Category3, Actual:Category1
Test: Category4, Actual:Category1
What am I doing wrong? Any help will be appreciated.
The helper methods like TextBoxFor are meant to be used with a ViewModel that represent the single object, not a collection of objects.
A normal use would be:
#Html.TextBoxFor(c => c.Name)
Where c gets mapped, inside the method, to ViewData.Model.
You are doing something different:
#Html.TextBoxFor(c => iterationItem.Name)
The method internall will still try to use the ViewData.Model as base object for the rendering, but you intend to use it on the iteration item. That syntax, while valid for the compiler, nets you this problem.
A workaround is to make a partial view that operates on a single item: inside that view you can use html helpers with correct syntax (first sample), and then call it inside the foreach, passing the iteration item as parameter. That should work correctly.
A better way to do this would be to use EditorTemplates.
In your form you would do this:
#model List<Category>
#using (#Html.BeginForm("Save", "Categories", FormMethod.Post))
{
#Html.EditorForModel()
<input type="submit" value="Save" name="btnSaveCategory" id="btnSaveCategory" />
}
Then, you would create a folder called EditorTemplates, either in the ~/Views/Shared folder or in your Controllers View folder (depending on whether you want to share the template with the whole app or just this controller), and in the EditorTemplates folder, create a Category.cshtml file which looks like this:
#model Category
<span>Test: #Model.CategoryName</span>
<span>Actual: #Html.TextBoxFor(model => model.CategoryName)</span>
#Html.HiddenFor(model => model.ID)
<p>---</p>
MVC will automatically iterate over the collection and call your template for each item in it.
I've noticed that using foreach loops within Views causes the name attributes of text boxes to be rendered the same for every item in the collection. For your example, every text box will be rendered with the following ID and Name attributes:
<input id="cat_CategoryName" name="cat.CategoryName" value="Category1" type="text">
When your controller receives the form data collection, it won't be able reconstruct the collection as different values.
The solution
A good pattern I've adopted is to bind your View to the same class you want to post back. In the example, model is being bound to List<Category> but the controller Save method receives a model ViewModel.CategoryForm. I would make them both the same.
Use a for loop instead of a foreach. The name/id attributes will be unique and the model binder will be able to distinguish the values.
My final code:
View
#model CategoryForm
#using TestMvc3.Models
#using (#Html.BeginForm("Save", "Categories", FormMethod.Post))
{
for (int i = 0; i < Model.Categories.Count; i++)
{
<span>Test: #Model.Categories[i].CategoryName</span>
<span>Actual: #Html.TextBoxFor(model => Model.Categories[i].CategoryName)</span>
#Html.HiddenFor(model => Model.Categories[i].ID)
<p>---</p>
}
<input type="submit" value="Save" name="btnSaveCategory" id="btnSaveCategory" />
}
Controller
public ActionResult Index()
{
// create the view model with some test data
CategoryForm form = new CategoryForm()
{
Categories = new List<Category>()
};
form.Categories.Add(new Category() { ID = 1, CategoryName = "Category1" });
form.Categories.Add(new Category() { ID = 2, CategoryName = "Category2" });
form.Categories.Add(new Category() { ID = 3, CategoryName = "Category3" });
form.Categories.Add(new Category() { ID = 4, CategoryName = "Category4" });
// pass the CategoryForm view model
return View(form);
}
[HttpPost]
public ActionResult Save(CategoryForm cat)
{
// the view model will now have the correct categories
List<Category> cl = new List<Category>(cat.Categories);
return View("Index", cat);
}
If this was a problem with MVC3, there would be posts out there about this, but I can't find any. I must be doing something wrong. I have a simple view (Index.cshtml) that iterates through a list using a for loop. In each iteration, I output two text inputs with values from one of the list items.
#{Html.BeginForm();}
#Html.Encode("\n")
#for (int i = 0; i < Model.SortOptions.Count; i++ )
{
#Html.TextBoxFor(m => m.SortOptions[i].ColumnName);
#Html.Encode("\n");
#Html.TextBoxFor(m => m.SortOptions[i].Direction);
#Html.Encode("\n");
}
<input type="submit" value="Submit" />
#{Html.EndForm();}
I have two controllers for the view, one for GET requests and one for POST. The POST version adds different items to the list than the GET version. This is where the problem comes in. After the page has re-loaded, the text boxes have the same value as when the page loaded on the GET.
At first I thought it must be a caching issue, but if I modify the code (as seen below), to manually add the text inputs and inject the values into the html, the new values are sent to the browser.
#{Html.BeginForm();}
#Html.Encode("\n")
#for (int i = 0; i < Model.SortOptions.Count; i++ )
{
var columnNameName = string.Format("SortOptions[{0}].ColumnName", i);
var columnNameID = string.Format("SortOptions_{0}__ColumnName", i);
var directionName = string.Format("SortOptions[{0}].Direction", i);
var directionID = string.Format("SortOptions_{0}__Direction", i);
<input type="hidden" name="#columnNameName" id="#columnNameID" value="#Model.SortOptions[i].ColumnName" />
<input type="hidden" name="#directionName" id="#directionID" value="#Model.SortOptions[i].Direction" />
}
<input type="submit" value="Submit" />
#{Html.EndForm();}
I've stepped through the code to ensure that the model contains the expected values at the time they are sent to the view. I even inspected the values of the list by stepping through the code in the view. It appears to have the correct values, but when I view it in the browser, it has the values that should correspond to when the page responded to the GET request. Is this a problem with the editor templates? I just started using mvc3 and the razor engine, so there is a lot I don't know. Any help would be appreciated.
----- UPDATE: ADDED CONTROLLER CODE ----
[HttpGet]
public ActionResult Index()
{
var inv = new InventoryEntities();
var model = new IndexModel(inv);
model.SortOptions = new List<SortOption>();
model.SortOptions.Add(new SortOption { ColumnName = "Model", Direction = SortDirection.Ascending });
model.SortOptions.Add(new SortOption { ColumnName = "Make", Direction = SortDirection.Ascending });
//Load data
model.LoadEquipmentList();
return View(model);
}
[HttpPost]
[OutputCache(Duration = 1)]
public ActionResult Index(List<SortOption> sortOptions, SortOption sort)
{
var inv = new InventoryEntities();
var model = new IndexModel(inv);
ModelState.Remove("SortOptions");
model.SortOptions = new List<SortOption>();
model.SortOptions.Add(new SortOption { ColumnName = "Type", Direction = SortDirection.Descending });
model.SortOptions.Add(new SortOption { ColumnName = "SubType", Direction = SortDirection.Descending });
model.EquipmentList = new List<EquipmentListItem>();
model.EquipmentList.Add(new EquipmentListItem { ID = 3, AssignedTo = "Mike", Location = "Home", Make = "Ford", Model = "Pinto", Selected = false, SubType = "Car", Type = "Vehicle" });
return View(model);
}
Remember that Html helpers such as TextBoxFor first use the model state when binding their values and after that the model. Let's consider a very simple example in order to illustrate what this means:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel { Name = "foo" });
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
model.Name = "bar";
return View(model);
}
}
and the view:
#model MyViewModel
#using (Html.BeginForm())
{
#Html.TextBoxFor(x => x.Name)
<input type="submit" value="OK" />
}
Now when you submit the form you will expect that the value in the textbox changes to "bar" as that's what you've put in your POST action but the value doesn't change. That's because there is already a value with the key Name in the model state which contains what the user entered. So if you want this to work you need to remove the original value from the model state:
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// remove the original value if you intend to modify it here
ModelState.Remove("Name");
model.Name = "bar";
return View(model);
}
The same thing happens in your scenario as well. So you might need to remove the values you are modifying from the model state in your POST action.
A couple things pop out at me - without seeing a bit more it's hard to say but...both of these could be rewritten as such. The extra # symbols are not necessary.
#using(Html.BeginForm()) {
Html.Encode("\n")
for (int i = 0; i < Model.SortOptions.Count; i++ ) {
Html.TextBoxFor(m => m.SortOptions[i].ColumnName);
Html.Encode("\n");
Html.TextBoxFor(m => m.SortOptions[i].Direction);
Html.Encode("\n");
}
<input type="submit" value="Submit" />
}
#using (Html.BeginForm()) {
Html.Encode("\n");
for (int i = 0; i < Model.SortOptions.Count; i++ ) {
var columnNameName = string.Format("SortOptions[{0}].ColumnName", i);
var columnNameID = string.Format("SortOptions_{0}__ColumnName", i);
var directionName = string.Format("SortOptions[{0}].Direction", i);
var directionID = string.Format("SortOptions_{0}__Direction", i);
<input type="hidden" name="#columnNameName" id="#columnNameID" value="#Model.SortOptions[i].ColumnName" />
<input type="hidden" name="#directionName" id="#directionID" value="#Model.SortOptions[i].Direction" />
}
<input type="submit" value="Submit" />
}
Otherwise your stuff looks right and I can't see anything wrong off the top of my head.