c# MVC - Populate DropDownList from class property - c#

I'm working with EF and MVC, new stuff to me.
I have the following class:
public class Client
{
[Key]
public int ClientId { get; set; }
public Category Category { get; set; }
}
and I want to show in the View related to the "Create" ActionResult, a selectable DropDownList with all the categories to choose one.
I have a ViewModel also:
public class CategoryViewModel
{
public int SelectedCategoryId { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
}
which I use in the controller:
private IEnumerable<SelectListItem> GetCategories()
{
var db = new MyDBContext();
var categories = db.Categories
.Select(x =>
new Category
{
CategoryId = x.CategoryId,
Name = x.Name
});
return new SelectList(categories, "Value", "Text");
}
...and in the Create():
public ActionResult Create()
{
var model = new CategoryViewModel();
model.Categories = GetCategories();
return View(model);
}
I dont know how to populate the dropdown in the view:
<div class="form-group">
#Html.LabelFor(model => model.Category, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(WHAT GOES HERE????)
</div>
</div>
Thanks for rescuing me! (RE5 quote)

There are different overloads but this is an option:
#Html.DropDownListFor(x => x.SelectedCategoryId, Model.Categories)

Related

Create Dropdown field form from List with ASP.NET MVC

During the next time, I could create some posts because I'm learning C# and ASP.NET MVC. I'm coming from Pythonic world, so some things are not clear for me.
I would like to generate a List of strings, then I would like to display this list in my form as a DropDownList.
This is my model:
public class Joueur
{
public int ID { get; set; }
[Required, Display(Name = "Nom"), StringLength(30)]
public string Lastname { get; set; }
[Required, Display(Name = "Prénom"), StringLength(30)]
public string Firstname { get; set; }
[Required, StringLength(15)]
public string Poste { get; set; }
public string Image { get; set; }
}
This is my controller according to Create Method:
// GET: Joueurs/Create
public ActionResult Create()
{
List<Strings> posteList = new List<SelectListItem>{ "Gardien", "Défenseur", "Milieu", "Attaquant" };
ViewBag.PosteList = posteList;
return View();
}
And this is my view:
<div class="col-md-10">
#*ViewBag.PosteList is holding all the postes values*#
#Html.DropDownListFor(model => model.Poste, ViewBag.PosteList as SelectList, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Poste, "", new { #class = "text-danger" })
</div>
But I get this issue:
#Html.DropDownListFor(model => model.Poste, ViewBag.PosteList as SelectList, new { #class = "form-control" })
There is no ViewData element of type « IEnumerable » with the key « Poste ».
How I could do that ?
With Django, it's pretty easy, in my model I create a dictionary and I pass this dict in the property, but with C# ASP.NET? I don't find a way to do that.
I assume your view will display a form to represent the Joueur object that you want the user to fill out, and your ViewBag.PosteList will have the values that the user can select from for the Joueur.Poste property. In order to accomplish this, you should create a new/empty Joueur object in your Create controller method and pass it to the view like so:
public ActionResult Create()
{
var model = new Joueur();
List<Strings> posteList = new List<SelectListItem>{ "Gardien", "Défenseur", "Milieu", "Attaquant" };
ViewBag.PosteList = posteList;
return View(model);
}
Then the rest of your original code should work.
I found a solution, hopefully it's a good way:
In my model I created an Enum:
public class Joueur
{
public int ID { get; set; }
[Required, Display(Name = "Nom"), StringLength(30)]
public string Lastname { get; set; }
[Required, Display(Name = "Prénom"), StringLength(30)]
public string Firstname { get; set; }
[Required, StringLength(15)]
public Position Poste { get; set; }
public string Image { get; set; }
}
public enum Position
{
Gardien,
Défenseur,
Milieu,
Attaquant
}
And in my view I added:
<div class="form-group">
#Html.LabelFor(model => model.Poste, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.Poste, new SelectList(Enum.GetValues(typeof(FCSL.Models.Joueur.Position))), "Sélectionner le poste", new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Poste, "", new { #class = "text-danger" })
</div>
</div>
And I applied migration commands. It seems to work now.
#Html.DropDownList("Poste", new SelectList( ViewBag.PosteList, "id", "Poste"))
and in controller
public ActionResult Create()
{
List<Strings> posteList = new List<SelectListItem>{ "Gardien", "Défenseur", "Milieu", "Attaquant" };
ViewBag.PosteList = posteList;
return View(ViewBag.PosteList); // return viewbag
}

FluentValidation Improperly Validating Model from DropDown

I have the following two models (stripped to relevant parts):
Models\Department.cs:
public class DepartmentValidator : AbstractValidator<Department> {
public DepartmentValidator() {
RuleFor(d => d.Name)
.NotEmpty().WithMessage("You must specify a name.")
.Length(0, 256).WithMessage("The name cannot exceed 256 characters in length.");
}
}
[Validator(typeof(DepartmentValidator))]
public class Department {
public int Id { get; set; }
[Column(TypeName = "nvarchar")]
[MaxLength(256)]
public string Name { get; set; }
}
Models\FacultyMember.cs:
public class FacultyValidator : AbstractValidator<FacultyMember> {
public FacultyValidator() {
RuleFor(f => f.Name)
.NotEmpty().WithMessage("You must specify a name.")
.Length(0, 64).WithMessage("The name cannot exceed 64 characters in length.");
}
}
[Validator(typeof(FacultyValidator))]
public class FacultyMember {
public int Id { get; set; }
[Column(TypeName = "nvarchar")]
[MaxLength(64)]
public string Name { get; set; }
public virtual ICollection<Department> Departments { get; set; }
public FacultyMember() {
Departments = new HashSet<Department>();
}
}
I have the following controller code:
Controllers\FacultyController.cs:
// GET: Faculty/Create
public ActionResult Create() {
// Get Departments.
var departmentList = db.Departments.ToList().Select(department => new SelectListItem {
Value = department.Id.ToString(),
Text = department.Name
}).ToList();
ViewBag.DepartmentList = departmentList;
var facultyMember = new FacultyMember();
facultyMember.Departments.Add(new Department()); // Create a single dropdown for a department to start out.
return View(facultyMember);
}
// POST: Faculty/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,Departments")] FacultyMember facultyMember) {
// Get Departments.
var departmentList = db.Departments.ToList().Select(department => new SelectListItem {
Value = department.Id.ToString(),
Text = department.Name
}).ToList();
ViewBag.DepartmentList = departmentList;
if (!ModelState.IsValid) { // Problem here...
return View(facultyMember);
}
db.Faculty.Add(facultyMember);
db.SaveChanges();
return RedirectToAction("Index");
}
Views\Faculty\Create.cshtml:
...
<div class="form-group">
#Html.LabelFor(model => model.Departments, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Departments, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Departments, "", new { #class = "text-danger" })
</div>
</div>
...
Views\Shared\EditorTemplates\Department.cshtml:
#model MyProject.Models.Department
#Html.DropDownListFor(model => model.Id, ViewBag.DepartmentList as IEnumerable<SelectListItem>, "Select...", new { #class = "form-control" })
So, when I navigate to the create faculty page, everything displays properly; the 'Departments' field has a dropdown list with the departments in my database. However, upon submitting the form, my model state is invalid (see comment in code above). Upon further inspection, it seems that FluentValidation is spitting out an error because my "Name" field is null. That's exactly what it should do when I'm creating/editing departments, but for this dropdown in faculty members, it shouldn't be validating the entire department, should it? The only thing the dropdown is sending back is the Id, as I've specified.
The only thing that this dropdown sends is the Id of the department, which is properly received. So, what do I need to do to make this work? My goal is to have a dynamic set of dropdown lists, each populated with existing departments in the database. Similar to this example.
Please let me know if anything else needs explaining.
The solution, as explained by Stephen Muecke, was to create a view model to represent all data I wanted to pass to the form and back.
ViewModel\FacultyMemberViewModel.cs:
public class FacultyMemberViewModelValidator : AbstractValidator<FacultyMemberViewModel> {
public FacultyMemberViewModelValidator() {
RuleFor(f => f.Name)
.NotEmpty().WithMessage("You must specify a name.")
.Length(0, 64).WithMessage("The name cannot exceed 64 characters in length.");
RuleFor(s => s.SelectedDepartments)
.NotEmpty().WithMessage("You must specify at least one department.")
}
}
[Validator(typeof(FacultyMemberViewModelValidator))]
public class FacultyMemberViewModel {
public int Id { get; set; }
public string Name { get; set; }
public int[] SelectedDepartments { get; set; }
[DisplayName("Departments")]
public IEnumerable<SelectListItem> DepartmentList { get; set; }
}
Views\Faculty\Create.cshtml:
...
<div class="form-group">
#Html.LabelFor(model => model.DepartmentList, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.ListBoxFor(model => model.SelectedDepartments, Model.DepartmentList, new { #class = "form-control" }) #Html.ValidationMessageFor(model => model.SelectedDepartments, "", new { #class = "text-danger" })
</div>
</div>
...
Controllers\FacultyController.cs:
// GET: Faculty/Create
public ActionResult Create() {
var facultyMemberViewModel = new FacultyMemberViewModel {
DepartmentList = GetDepartmentList()
};
return View(facultyMemberViewModel);
}
// POST: Faculty/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,SelectedDepartments,DepartmentList")] FacultyMemberViewModel facultyMemberViewModel) {
if (!ModelState.IsValid) {
// Re-set the Department list.
if (facultyMemberViewModel.DepartmentList == null) {
facultyMemberViewModel.DepartmentList = GetDepartmentList();
}
return View(facultyMemberViewModel);
}
var facultyMember = new FacultyMember {
Id = facultyMemberViewModel.Id,
Name = facultyMemberViewModel.Name,
};
foreach (var departmentId in facultyMemberViewModel.SelectedDepartments) {
// I'm assuming this is safe to do (aka the records exist in the database)...
facultyMember.Departments.Add(db.Departments.Find(departmentId));
}
db.Faculty.Add(facultyMember);
db.SaveChanges();
return RedirectToAction("Index");
}

mvc get virtual IEnumerable from database

I am trying to bind a dropdownlistfor to a list that comes from the database
In the model
public class Actor
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
...
// I have a database table called genre
[ForeignKey("Genre")]
public virtual IEnumerable<Genre> Genres { get; set; }
}
In the controller
// GET: Actors/Create
public ActionResult Create()
{
Actor actor = new Actor();
actor.Genres = new // Believe I need to do something here...
return View(actor);
}
In the view
<div class="form-group">
#Html.LabelFor(model => model.Genres, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="dropdown">
#Html.DropDownListFor(M => M.Genre, new SelectList(Model.Genres, "Value", "Text"))
</div>
</div>
</div>
What is it i need to do?
Thanks
I would usually add a property to my ViewModel that is of type IEnumerable<SelectListItem>:
public class ActorViewModel
{
public int GenreId { get; set; }
public IEnumerable<SelectListItem> Genres { get; set; }
}
Then in my Controller I would add a method that will return these for you:
public IEnumerable<SelectListItem> GetGenres()
{
foreach (var genre in dbContext.Genres)
{
yield return new SelectListItem
{
Value = genre.Id.ToString(),
Text = genre.Name
};
}
}
Then set as so:
viewModel.Genres = this.GetGenres();
Then in my View
#Html.DropDownListFor(m => m.GenreId, Model.Genres, "Please select")
You should create ViewModel for this
public class ActorViewModel
{
public Actor actor { get; set; }
public SelectList Genres { get; set; }
public int GenreId { get; set; }
}
Create a method that returns the genres as a selectlist
public SelectList GetAsSelectList()
{
var genres = (from s in GetAllGenres()
select new
{
s.Id, s.Name
}).ToList();
return new SelectList((IEnumerable)genres, "Id", "Name");
}
In your controller you populate the model properties:
var model = new ActorViewModel
{
actor = new Actor(),
Genres = GetAsSelectList(),
GenreId = 0
};
return View(model);
In the view:
#Html.DropDownListFor(M => M.GenreId, Model.Genres,"--Select--")

Convention for adding items to a collection property from a select list in the create portion of CRUD?

My main entity is the Recipe which contains a collection of Ingredient items as follows:
public class Recipe {
[Key]
public virtual int RecipeId { get; set; }
public string RecipeName { get; set; }
...
public virtual ApplicationUser LastModifiedBy { get; set; }
public virtual IList<Ingredient> Ingredients { get; set; }
}
public class Ingredient {
public virtual int IngredientId { get; set; }
[Display(Name = "Name")]
public string IngredientName { get; set; }
....
public virtual IList<Recipe> Recipes { get; set; }
}
Which is fine. Then my controller and view for creating a new Recipe are as follows:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "stuff to include")] Recipe recipe)
{
IList<int> ingredientIds = (ModelState.Values.ElementAt(1).Value.AttemptedValue).Split(',').Select(int.Parse).ToList(); //[1,2,3,4,5]
foreach (int id in ingredientIds) {
Ingredient ing = db.Ingredients.Where(i => i.IngredientId == id).FirstOrDefault() as Ingredient;
recipe.Ingredients.Add(ing);
}
db.Recipes.Add(recipe);
db.SaveChanges();
return RedirectToAction("Index");
ViewBag.Ingredients = new MultiSelectList(db.Ingredients,
"IngredientId", "IngredientName", string.Empty);
ViewBag.CreatedById = new SelectList(db.Users, "Id", "Email", recipe.CreatedById);
return View(recipe);
}
And the view:
#for (Int16 i = 0; i < 5; i++) {
<div class="form-group">
#Html.LabelFor(model => model.Ingredients, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Ingredients", null, htmlAttributes: new { #class = "form-control" })
</div>
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="button" value="Add Ingredients" class="btn btn-default" />
</div>
</div>
So this sets ModelState.Values.ElementAt(1).Value.AttemptedValue = "1,3,5,4,5" where this is a list of id numbers. I know I can come in before the if (ModelState.IsValid) and iterate through the above and place it into recipe.Ingredients which is fine except...
It feels just so un ASP.NET MVC like, as if there's no way they could have thought of so much and not thought of this scenario? Am I missing something here? The ingredients list will be too long to make a multi select list any use.
You are creating arbitrary dropdownlists that all have the same id (invalid html) and name attribute that has no relationship to your model and wont bind on post back. You first need to create view models that represent what you want to display.
public class RecipeVM
{
[Required]
public string Name { get; set; }
[Display(Name = Ingredient)]
[Required]
public List<int?> SelectedIngredients { get; set; }
public SelectList IngredientList { get; set; }
}
Then in the controller
public ActionResult Create()
{
RecipeVM model = new RecipeVM();
// add 5 'null' ingredients for binding
model.SelectedIngredients = new List<int?>() { null, null, null, null, null };
ConfigureViewModel(model);
return View(model);
}
[HttpPost]
public ActionResult Create(RecipeVM model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
// Initialize new instance of your data model
// Map properties from view model to data model
// Add values for user, create date etc
// Save and redirect
}
private void ConfigureViewModel(RecipeVM model)
{
model.IngredientList = new SelectList(db.Ingredients, "IngredientId", "IngredientName");
}
View
#model RecipeVM
#using (Html.BeginForm())
{
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
#Html.ValidationMessageFor(m => m.Name)
for (int i = 0; i < Model.SelectedIngredients.Count; i++)
{
#Html.LabelFor(m => m.SelectedIngredients[i])
#Html.DropDownListFor(m => m.SelectedIngredients[i], Model.IngredientList, "-Please select-")
}
}
Note this is based on your current implementation of creating 5 dropdowns to select 5 ingredients. In reality you will want to dynamically add ingredients (start with none). The answers here and here give you a few options to consider.

MVC4 DropDownList from DB

I'm trying to make very simple forum, but I have problem with DropDownList. I have two models:
ForumThread.cs
public partial class ForumThread
{
public ForumThread()
{
this.ForumCategory = new HashSet<ForumCategory>();
}
public int TH_ID { get; set; }
public System.DateTime DATE { get; set; }
public string TOPIC { get; set; }
public string USER { get; set; }
public virtual ICollection<ForumCategory> ForumCategory { get; set; }
}
ForumCategory.cs
public partial class ForumCategory
{
public ForumCategory()
{
this.ForumThread = new HashSet<ForumThread>();
}
public int CA_ID { get; set; }
public string CATEGORY { get; set; }
public bool isSelected { get; set; }
public virtual ICollection<ForumThread> ForumThread { get; set; }
}
I tried to make "Create" function with view:
Create
#model AnimeWeb.Models.ForumThread
#{
ViewBag.Title = "Create";
}
<h2>New Thread</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<div class="editor-field">
#Html.HiddenFor(model => model.TH_ID)
</div>
<div class="editor-label">
TOPIC
</div>
<div class="editor-field">
#Html.EditorFor(model => model.TOPIC)
#Html.ValidationMessageFor(model => model.TOPIC)
</div>
<div class="editor-label">
CATEGORY
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ForumCategory)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
And PartialView for ForumCategory:
ForumCategory
#model AnimeWeb.Models.FORUMCATEGORY
#Html.HiddenFor(model => model.CA_ID)
#Html.HiddenFor(model => model.CATEGORY)
<div>
#Html.DropDownListFor(item => Model.CA_ID, ViewBag.CA_ID as SelectList, "-- Select --")
</div>
ForumController
public ActionResult Create()
{
var db = new MainDatabaseEntities();
var viewModel = new ForumThread
{
ForumCategory = db.ForumCategory.Select(c => new { CA_ID = c.CA_ID, CATEGORY = c.CATEGORY, isSelected = false }).ToList().Select(g => new ForumCategory
{
CA_ID = g.CA_ID,
CATEGORY = g.CATEGORY,
isSelected = false
}).ToList(),
};
return View(viewModel);
}
//
// POST: /Forum/Create
[HttpPost]
public ActionResult Create(ForumThread forumthread, String user, int id)
{
var db = new MainDatabaseEntities();
var newthread = new ForumThread
{
TH_ID = forumthread.TH_ID,
DATE = DateTime.Now,
TOPIC = forumthread.TOPIC,
USER = forumthread.USER,
ForumCategory = new List<ForumCategory>()
};
foreach (var selectedCategory in forumthread.FORUMCATEGORY.Where(c => c.isSelected))
{
var category = new ForumCategory { CA_ID = selectedCategory.CA_ID };
db.ForumCategory.Attach(category);
newthread.ForumCategory.Add(category);
}
db.ForumThread.Add(newthread);
db.SaveChanges();
return RedirectToAction("Index");
}
And it obviously doesn't work. I tried to use other threads on this forum but nothing helped. Could someone explain me how to make this work?
The error is in partial view of ForumCategory:
The ViewData item that has the key 'CA_ID' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'.
In your PartialView for ForumCategory, your cast is not correct:
#Html.DropDownListFor(item => Model.CA_ID, ViewBag.CA_ID as SelectList, "-- Select --")
You have to use a SelectList (List of SelectListItem) that you can implement for example in a method in your model:
public List<SelectListItem> GetCategories()
{
var db = new MainDatabaseEntities();
List<SelectListItem> list = new List<SelectListItem>();
// Add empty item if needed
SelectListItem commonItem = new SelectListItem();
commonItem.Text = "--- Select ---";
commonItem.Value = "-1";
commonItem.Selected = true;
list.Add(commonItem);
// Add items from Database
foreach (ForumCategory fc in db.ForumCategory)
{
SelectListItem i = new SelectListItem();
i.Text = fc.CATEGORY;
i.Value = fc.CA_ID.ToString();
list.Add(i);
}
return list;
}
And then you can have you dropdown like that:
#Html.DropDownList("DropName", Model.GetCategories())
There may be other errors in some parts of your code, I just answered to the one you quoted
In your editortemplate, you have:
ViewBag.CA_ID as SelectList
But you don't show where you fill the ViewBag. Instead you might want to do something like this:
#Html.DropDownListFor(m => m.CA_ID,
new SelectList(Model.ForumCategory,
"CA_ID", "CATEGORY", Model.CA_ID))
As also explained in MVC3 DropDownListFor - a simple example?.

Categories