MVC3 Razor "For" model - contents duplicated - asp.net-mvc-3

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);
}

Related

MVC Razor View update Form on SelectedIndexChange

I have a form in a View that brings together a number of pieces of information (address, telephone etc). All these elements are wrapped up in a view model. There is one section that asks the user to select a county. On selection, I want to be able to show a price based on the county selected.
I came across the following SO question which is close to what I want, but it looks like the action submits the form to a 'change controller'. I naively need to be able to basically call two controllers - one onSelectedChange and the other onSubmit. I'm pretty sure ya can't do this!
Here' what I'm after:
#model ViewOrder
#using (Html.BeginForm("Order", "Home"))
{
#* - textboxes et al - *#
<p>
#Html.DropDownListFor(x => x.Counties,
new SelectList(Model.Counties, "CountyId", "County"),
new { #class = "form-control input-sm" })
</p>
<p>
#* - £Price result of dropdown list selection and
add to View Model to add to sub total - *#
</p>
<input type="submit" text = "submit"/>
}
I'm very new to MVC - Could do this easily in webforms (but I'm sticking with MVC!) There must be some form of Ajax action that would allow this. Any suggestions?
First you have a problem with you #Html.DropDownListFor() method. Model.Counties is a complex object (with properties CountyId and County) but you cannot bind a <select> (or any control) to a complex object, only a value type. Your model needs a property (say) public int SelectedCountry { get; set; } and then #Html.DropDownListFor(x => x.SelectedCountry, new SelectList(Model.Counties, "CountyId", "County"), ...)
To display the price, you need to handle the .change event of the dropdown, pass the selected value to a controller method, and update the DOM.
Script (based on the property being SelectedCountry)
var url = '#Url.Action("GetPrice", "yourControllerName")';
$('#SelectedCountry').change(function() {
$.getJSON(url, { ID: $(this).val() }, function(data) {
// do something with the data returned by the method, for example
$('#someElement').text(data);
});
});
Controller
public JsonResult GetPrice(int ID)
{
// ID contains the value of the selected country
var data = "some price to return";
return Json(data, JsonRequestBehavior.AllowGet);
}

Passing id of selected dropdown option to Controller

I am using mvc3 nhibernate and creating a search application...
Here i am creating a dropdown list containing all Hobby names and on click of search button the selected option's id should go to post method
i have written following code in my controller
public ActionResult Details()
{
ViewBag.h=new SelectList(new Hobby_MasterService().GetHobbies(),"Hobby_Id");
return View();
}
[HttpPost]
public ActionResult Details(int Hobby_Id)
{
Hobby_Master hm = new Hobby_MasterService().GetHobby_Data(Hobby_Id);
return RedirectToAction("Show");
}
and in view i'm only showing one drop down list as
<b>Select Hobby:</b>
#using (Html.BeginForm("Details", "Hobbies", FormMethod.Get))
{
<div class="Editor-field">
#Html.DropDownListFor(Model => Model.Hobby_Id, (IEnumerable<SelectListItem>)ViewBag.h)
</div>
<input type="submit" value="Search" />
}
My dropdown is populated through a function which has a normal sql statement...
and i can generate list....but how will i get the selected hobbies id...
Please help
maybe FormMethod.Post on your form?
and is your Model a class?
Perhaps you could accept that in your post action then you'll find the id on it.
without bothering with model binding, you can just add a FormCollection parameter to your POST method. That collection contains all form values posted.
[HttpPost]
public ActionResult Details(FormCollection collection)
{
Hobby_Master hm = new Hobby_MasterService().GetHobby_Data(Hobby_Id);
if (collection["Hobby_Id"] != null)
{
// collection["Hobby_Id"] contains the value selected in the dropdown box
}
return RedirectToAction("Show");
}

use Razor to fill dropdown with Linq2Sql data

I'm experimenting with ASP.NET MVC3 and want to simply populate a dropdown list with data I get from a LINQ2SQL class, like so
controller (I know, Linq doesn't belong in the controller)
var allUsers = (from u in _userDataContext.Users
select u).ToList();
ViewBag.allUsers = allUsers.ToList();
return View();
view:
<select id="drop_heroes">
#foreach (var u in ViewBag.allUsers)
{
<option value="#u.pk_userid">#u.email</option>
}
</select>
That works fine, but I would like to use Razor #Html.Dropdownlist to create the same dropdown list, but can't find any info to make this work with Linq data.
I know, Linq doesn't belong in the controller
Then why are you using it in a controller? Anyway, at least it's fine that you know it.
Here's an example. As always in an ASP.NET MVC application you start by defining a view model which will represent the data that you need in the view. So in your case you need to display a dropdown so you define a list of users and a selected user id:
public class MyViewModel
{
public string SelectedUserId { get; set; }
public IEnumerable<SelectListItem> Users { get; set; }
}
then you define a controller action which will populate this view model from your repository and handle it to the view:
public ActionResult Index()
{
var model = new MyViewModel
{
Users = _userDataContext.Users.ToList().Select(x => new SelectListItem
{
Value = x.pk_userid.ToString(),
Text = x.email
})
}
return View(model);
}
and finally you will have a view which will be strongly typed to your view model and use HTML helpers to generate the dropdownlist:
#model MyViewModel
#using (Html.BeginForm())
{
#Html.DropDownListFor(x => x.SelectedUserId, Model.Users)
<button type="submit">OK</button>
}
Things to notice:
Usage of view models
Usage of a strongly typed view
Usage of strongly typed HTML helpers to generate markup such as form elements and input fields
Getting rid of weakly typed structures such as ViewBag
If you follow these simple rules you will see how much easier your life as an ASP.NET MVC developer will become.

MVC3: Batch edit attendence and pass the model to a Review Action

I have an Index.cshtml view:
#model AttendenceModel
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm("VisOppsummering", "Attendences", new { AttendenceModel = Model }, FormMethod.Post))
{
#Html.DisplayFor(m => m.ClassName)
#Html.EditorFor(m => m.Attendences)
<button type="submit">Next</button>
}
and an Editor Template Attendence.cshtml:
#model Attendence
#Html.DisplayFor(m => m.Student.Name)
#Html.RadioButtonFor(m => m.Attended, true, new { id = "attendence" })
Teachers can check off all students that attended school and than pass on the changed model to "Review" action where they can review all the attendended and not attended students and Submit. I want to use MVC best practice for this. AttendenceModel has several properties and a generic list Attendences which is List.
I've tried following without success. Model is empty.:
[HttpPost]
public ActionResult Review(AttendenceModel model)
{
if (TryUpdateModel(model))
{
return View(model);
}
}
The following argument to your BeginForm helper is meaningless:
new { AttendenceModel = Model }
you cannot pass complex objects like this. Only simple scalar values. You could use hidden fields in your form for all properties that cannot be edited and visible input fields for the other. Or even better: use a view model which will contain only the properties that can be edited on the form and an additional id which will allow you to fetch the original model from the database and using the TryUpdateModel method update only the properties that were part of the POST request:
[HttpPost]
public ActionResult Review(int id)
{
var model = Repository.GetModel(id);
if (TryUpdateModel(model))
{
return View(model);
}
...
}
as far as the view is concerned it would become:
#model AttendenceViewModel
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm("Review", "SomeControllerName"))
{
#Html.HiddenForm(x => x.Id)
#Html.DisplayFor(m => m.ClassName)
#Html.EditorFor(m => m.Attendences)
<button type="submit">Next</button>
}

My controller viewmodel isn't been populated with my dynamic views model

Im creating an application that allows me to record recipes. Im trying to create a view that allows me to add the basics of a recipe e.g. recipe name,date of recipe, temp cooked at & ingredients used.
I am creating a view that contains some jquery to load a partial view clientside.
On post im having a few troubles trying to get the values from the partial view that has been loaded using jquery.
A cut down version of my main view looks like (I initially want 1 partial view loaded)
<div id="ingredients">
#{ Html.RenderPartial("_AddIngredient", new IngredientViewModel()); }
</div>
<script type="text/javascript">
$(document).ready(function () {
var dest = $("#ingredients");
$("#add-ingredient").click(function () {
loadPartial();
});
function loadPartial() {
$.get("/Recipe/AddIngredient", {}, function (data) { $('#ingredients').append(data); }, "html");
};
});
</script>
My partial view looks like
<div class="ingredient-name">
#Html.LabelFor(x => Model.IngredientModel.IngredientName)
#Html.TextBoxFor(x => Model.IngredientModel.IngredientName)
</div>
<div class="ingredient-measurementamount">
#Html.LabelFor(x => Model.MeasurementAmount)
#Html.TextBoxFor(x => Model.MeasurementAmount)
</div>
<div class="ingredient-measurementtype">
#Html.LabelFor(x => Model.MeasurementType)
#Html.TextBoxFor(x => Model.MeasurementType)
</div>
Controller Post
[HttpPost]
public ActionResult Create(RecipeViewModel vm,IEnumerable<string>IngredientName, IEnumerable<string> MeasurementAmount, IEnumerable<string> MeasurementType)
{
Finally my viewmodel looks like
public class IngredientViewModel
{
public RecipeModel RecipeModel { get; set; }
public IEnumerable<IngredientModel> Ingredients { get; set; }
}
My controller is pretty ugly......im using Inumerble to get the values for MeasurementAmount & MeasurementType (IngredientName always returns null), Ideally I thought on the httppost Ingredients would be populated with all of the on I would be able Ingredients populated
What do I need to do to get the values from my partial view into my controller?
Why don't you take a look at the MVC Controlstoolkit
I think they would do what you want.
Without getting in too much detail. Can you change the public ActionResult Create to use FormCollection instead of a view model? This will allow you to see what data is coming through if any. It would help if you could post it then.
Your view model gets populated by using Binding - if you haven't read about it, it might be a good idea to do that. Finally I would consider wrapping your lists or enums into a single view model.
Possible Problem
The problem could lay with the fact that the new Partial you just rendered isn't correctly binded with your ViewModel that you post later on.
If you inspect the elements with firebug then the elements in the Partial should be named/Id'ed something like this: Ingredients[x].Property1,Ingredients[x].Property2 etc.
In your situation when you add a partial they are probably just called Property1,Property2.
Possible Solution
Give your properties in your partial the correct name that corresponds with your List of Ingredients. Something like this:
#Html.TextBox("Ingredients[x].Property1","")
Of, after rendering your partial just change all the names en ID's with jquery to the correct value.
It happens because fields' names from partial view do not fit in default ModelBinder convention. You should analyze what names fields have in your partial view.
Also you should implement correct way of binding collections to MVC controller. You could find example in Phil's Haack post
Assuming RecipeViewModel is the model being supplied to the partial view, try just accepting that back in your POST controller like this:
[HttpPost]
public ActionResult Create(RecipeViewModel vm)
{
//
}
You should get the model populated with all the values supplied in the form.

Resources