I am trying to use the Html.ListBoxFor helper to show a list box and return the selected Id. Is there a problem with the dataValueField not being a string?
If the SelectList contained in the model uses integers as the dataValueField then I get a "Value cannot be null - Parameter name: Source" exception raised when the list is rendered in the view.
If the Id is changed to a string then everything works and the selected Id is passed back to the view.
Any ideas?
Here is the controller (based on a cut down new project)
namespace Mvc2.Controllers
{
public class ViewModel
{
public int TestId { get; set; } // if this is a string it works ok
public SelectList ListData {get; set;}
}
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new ViewModel();
model.TestId = 1; // code corrected after Lazarus' comment
var lst = new[] { new { Id = 1, Name = "cat" }, new { Id = 2, Name = "dog" } };
model.ListData = new SelectList(lst, "Id", "Name");
return View("TestView", model);
}
public ActionResult TestSubmit(ViewModel returnedModel)
{
int i = 99; // break here - returnedModel has correct TestId when declared as string
}
}
}
here is the View - crashes on the ListBoxFor line
<%using (Html.BeginForm("TestSubmit", "Home")) { %>
<%=Model.TestId %><br />
<%=Html.ListBoxFor(m => m.TestId, Model.ListData) %>
<br />
<input type="submit" value="Save" />
<%} %>
The expression you are passing for the selected values needs to be IEnumerable because ListBoxFor supports multiple selected items.
Answering my own question;
I am unconviced by the comments that this might be a bug which is waiting to be fixed because I get it in RC2 and in MVC 1 (I copied the code back to a project in that release).
Anyway I have implemented a work around for now which is to:-
(a) Add a dummy string version of the Id to the model (TestId)
public class ViewModel
{
public string TestId { get; set; } // dummy Id as a string
public List<DataToShow> Data { get; set; }
public SelectList ListData {get; set;}
}
(b) Display the list but retrieve the value as the dummy TestId - note that the list still dumps the data values as integers!
<%=Html.ListBoxFor(m => m.TestId, Model.ListData) %>
(c) Copy the dummy string value into its proper integer location in the action
public ActionResult TestSubmit(ViewModel returnedModel)
{
MyModel.DataId = Int32.Parse(returnedModel.TestId);
Hope this is of some Help.
This is a known issue with ASP.NET MVC 2. It should be fixed in the March release.
Related
I want to create a parent object with child/related objects in the same view.
An example would be: create one Father (with some name) along with all his sons (with their names). I have created a view model:
public class FatherViewModel {
public Father father {get; set;} // has 1 property Name
public List<Son> {get; set;} // has 1 property Name
}
My question is, how do I get the list of Sons back from the view when the post is performed?
I have tried using HiddenFor for each Son id, but no matter what, the list is empty when returned to the controller.
UPDATE:
I tried the Editor Template example by Shyju described below, but my editor is never called.
I have 1 object:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int? FatherId { get; set; }
public virtual ICollection<Person> Children { get; set; }
}
I did this:
Scaffolded a full controller for Person with index, create, edit...
Created EditorTemplates folder in Views->Person
Created Person.cshtml:
#model TestEditorTemplate.Models.Person
<div>
<h4>Child</h4>
#Html.TextBoxFor(s => s.Name)
#Html.HiddenFor(s => s.Id)
</div>
Added #Html.EditorFor(m => m.Children) to Create.cshtml
Questions:
How can #Html.EditorFor(m => m.Children)possibly work with the
editor template when m.Children is a collection of Person and not a single
Person?
I want to create (not edit) a father including children at the same time. That means that I have no Ids to pass to the Create view to start with. How can this work? From the example by Shyju, the Ids are already created beforehand?? Or did I just misunderstand the example?
You can use EditorTemplates to handle this. Here is a working sample.
So i have a viewmodel to represent the father-child relationship
public class PersonVM
{
public int Id { set; get; }
public string Name { set; get; }
public int? ParentId { set; get; }
public List<PersonVM> Childs { set; get; }
}
And in my GET action method, i create an object of my view model and load the Father -childs data to it.
public ActionResult EditorTmp(int id = 1)
{
//Hard coded for demo, you may replace with actual DB values
var person = new PersonVM {Id = 1, Name = "Mike"};
person.Childs = new List<PersonVM>
{
new PersonVM {Id = 2, Name = "Scott", ParentId = 11},
new PersonVM {Id = 2, Name = "Gavin", ParentId = 12}
};
return View(person);
}
Now i will create an EditorTemplate. To do that, Go to your Views folder, and Create a directory called EditorTemplates under the directory which has same name as the controller, and add a view with name PersonVM.cshtml
Now, go to this view and add the below code.
#model ReplaceWithYourNameSpaceNameHere.PersonVM
<div>
<h4>Childs </h4>
#Html.TextBoxFor(s => s.Name)
#Html.HiddenFor(s => s.Id)
</div>
Now let's go back to our main view. We need to make this view strongly typed to our original PersonVM. We will use the EditorFor html helper method in this view to call our editor template
#model ReplaceWithYourNameSpaceNameHere.PersonVM
#using (Html.BeginForm())
{
<div>
#Html.TextBoxFor(s => s.Name)
#Html.HiddenFor(s => s.Id)
</div>
#Html.EditorFor(s=>s.Childs)
<input type="submit"/>
}
Now have an HttpPost method in the controller to handle the form posting
[HttpPost]
public ActionResult EditorTmp(PersonVM model)
{
int fatherId = model.Id;
foreach (var person in model.Childs)
{
var id=person.Id;
var name = person.Name;
}
// to do : Save ,then Redirect (PRG pattern)
return View(model);
}
Now, If you put a break point in your HttpPost action method, you can see the Id's of childs are passed to this action method.
One important thing to remember is, Your Editor Template view's name should be same as the type you are binding to it.
I have a scenario where I would like to have a list of editable items with per-item submit button, i.e. it should be allowed to submit only one item at a time.
I tried different approaches and the best answer I could find was this one: How to use multiple form elements in ASP.NET MVC, but the problem is that it assumes a single form and therefore a single Submit button.
So, here is what I tried so far. The Model:
namespace TestWebApplication4.Models
{
public class Entity
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
public class TestModel
{
public IEnumerable<Entity> Entities { get; set; }
}
}
so I have a collection of Entity objects, which I want to display collectively, and edit and submit individually. The way I'm trying to do it is to have multiple forms on one View:
#using TestWebApplication4.Models
#model TestWebApplication4.Models.TestModel
#{
int i = 0;
foreach (Entity entity in Model.Entities)
{
i++;
<div>
#using (Html.BeginForm("Index"))
{
#Html.HiddenFor(model => entity.Id)
#Html.TextAreaFor(model => entity.Name, new {id = "entity_Name" + i})
#Html.ValidationMessageFor(model => entity.Name)
<button type="submit">Submit</button>
}
</div>
}
}
and the Controller:
namespace TestWebApplication4.Controllers
{
public class TestController : Controller
{
public ActionResult Index()
{
var model = BuildModel();
return View(model);
}
[HttpPost]
public ActionResult Index(Entity entity)
{
if (!ModelState.IsValid)
{
var model = BuildModel(entity);
return View(model);
}
return RedirectToAction("Index");
}
private static TestModel BuildModel(Entity entity = null)
{
var entities = new List<Entity>
{
new Entity {Id = 11, Name = "Product A"},
new Entity {Id = 12, Name = "Product B"},
new Entity {Id = 13, Name = "Product C"},
};
if (entity != null)
entities[entities.IndexOf(entities.Single(e => e.Id == entity.Id))] = entity;
return new TestModel {Entities = entities};
}
}
}
As you can see, I'm trying to receive individual Entity object in [HttpPost] Index action, and if it is invalid (Name value is not provided) to re-build the entire list and replace specific Entity with the invalid one (all of this happens in BuildModel method), to display the validation message. This however does not work properly, as the resulting view contains 3 entries, all with the same invalid Entity, i.e. I get three empty TextAreas and three "The Name field is required." validation messages.
Can anyone help me figure this out? I assume I'm doing something against the conventions and that it is probably completely wrong approach to this problem, however I could not find a solution anywhere myself, so any directions would be appreciated. Thanks in advance.
My scenario is much complicated so i simplified it with the example below. The main problem is binding collection properties of a model, specifying the path of the property like Html.TextBox("List[0].Name") and not the Html.TextBoxFor(t => t.List[0].Name). So, for the current view i will only know some of the metadata of the model so i will have to construct it this way. Here is the scenario :
Model
public class ModelTest
{
public int Id {get;set;}
public List<Foo> Collection {get;set;}
}
public class Foo
{
public string Value1 {get;set;}
public string Value2 {get;set;}
}
Controller
public class TestController: Controller
{
[HttpGet]
public ActionResult Test()
{
var model = new ModelTest()
{
Id = 455,
Collection = new List<Foo>()
{
new Foo(){ Value1 = "sagasga", Value2 = "Beul"},
new Foo(){ Value1 = "dgdsgds", Value2 = "fhfhd" }
}
};
return View(model);
}
[HttpPost]
public ActionResult Test( ModelTest model)
{
//....
return View();
}
View:
#using (Html.BeginForm())
{
#Html.TextBox("Id")
#Html.TextBox("Collection[0].Value1")
#Html.TextBox("Collection[0].Value2")
<input type="submit" value="Add" />
}
For the code above i get empty textboxes for the collection values. However, when the page is submited i get the model built correct in the Post method.
Many thanks,
Alex
This is the way to name you input fields when you wanna post a collection to your controller. However, you have to specify the initial value yourself. Your code is currently just creating textbox with the name property set to Collection[0].Value1. You still need to specify the input this way,
#Html.TextBox("Collection[0].Value1", Model.Collection.FirstOrDefault().Value1)
#Html.TextBox("Collection[0].Value2", Model.Collection.FirstOrDefault().Value2)
ASP.Net MVC 4
I am trying to populate a list of Countries (data from Country table in DB) in a dropdownlist. I get the following error:
The model item passed into the dictionary is of type
System.Collections.Generic.List`1[System.Int32]', but this dictionary requires a model item of type 'BIReport.Models.Country'.
I am new to ASP.Net MVC and I don't understand that error. What I feel is what Index method is returning doesn't match with the model that I am using in the View.
Model::
namespace BIReport.Models
{
public partial class Country
{
public int Country_ID { get; set; }
public string Country_Name { get; set; }
public string Country_Code { get; set; }
public string Country_Acronym { get; set; }
}
}
Controller::
public class HomeController : Controller
{
private CorpCostEntities _context;
public HomeController()
{
_context = new CorpCostEntities();
}
//
// GET: /Home/
public ActionResult Index()
{
var countries = _context.Countries.Select(arg => arg.Country_ID).ToList();
ViewData["Country_ID"] = new SelectList(countries);
return View(countries);
}
}
View::
#model BIReport.Models.Country
<label>
Country #Html.DropDownListFor(model => model.Country_ID, ViewData["Country_ID"] as SelectList)
</label>
Where am I going wrong?
You are selecting CountryIDs, therefore you will have a list of integers passed into the view.
I think you really want something like this:
public ActionResult Index()
{
var countries = _context.Countries.ToList();
ViewData["Country_ID"] = new SelectList(countries, "Country_ID", "Country_Name");
return View();
}
I'm not really sure why you have single country as a model for your view.
Update:
I'm still not sure why the model is a country, if you are just going to post the ID of the selected country you don't necessarily need a model at all (or just have an integer). This will be just fine though:
View
#model MvcApplication1.Models.Country
#Html.DropDownListFor(m => m.Country_ID, ViewData["Country_ID"] as SelectList)
the problem is in line 1 of your view. change it like this :
#model IEnumerable<BIReport.Models.Country>
also there is no need to pass the model to view if you already did it by :
ViewData["Country_ID"] = new SelectList(countries);
When you say #model BIReport.Models.Country it means your view is expecting a model consisting single country details. On the contrary you need a list of countries to be displayed in the drop-down list. Hence you should tell the view to look for a list of country details instead.
Therefore #model IEnumerable.
I have created one mvc3 application.
There is one form where i'm taking values from user.
There is one field called Gender
i want to give a dropdown list for that with values Male and Female
but i dont understand where and how i specify there values please help.
I need an Ajax Helper method for this or any asynchronous script
the easiest way is to do this in Html :
<select id="selection" name="selection">
<option value="1">Male</option>
<option value="2">Female</option>
</select>
You can also do this with razor :
#Html.DropDownLiwt("GenderSelection", "Male")
after having done this inside the controler :
Dim genderSelection As List(Of String) = New List(Of String)
genderSelection.Add("Male")
genderSelection.Add("Female")
ViewData("GenderSelection") = New SelectList(genderSelection)
First create Entity class for your dropdown. It will return a list of value
public class KeyValueEntity
{
public string Description { get; set; }
public string Value { get; set; }
}
public class MyViewModel
{
public List<KeyValueEntity> Status { get; set; }
}
On your controller write the following code
[HttpGet]
public ActionResult Dropdown()
{
MyViewModel model = GetDefaultModel();
return View(model);
}
}
public MyViewModel GetDefaultModel()
{
var entity = new MyViewModel();
entity.Status = GetMyDropdownValues();
return entity;
}
private List<KeyValueEntity> GetMyDropdownValues()
{
return new List<KeyValueEntity>
{
new KeyValueEntity { Description = "Yes" , Value ="1" },
new KeyValueEntity { Description = "No" , Value ="0"}
};
}
Code for your cshtml page : Now you need to bind your view with your model for this on top of your view you define your model class
#model MyViewModel
Following is the code for dropdown binding
#Html.LabelForModel("Status:")
#Html.DropDownListFor(m => m.Status, new SelectList(Model.Status, "Value", "Description"), "-- Please Select --")