ASP.NET MVC Model collection binding with DropDownListFor - asp.net

Model
public class Customer
{
public string Name {get;set;}
public List<Product> Products {get;set;}
}
public class Product
{
public string Name {get;set;}
public ProductType {get;set;}
public IEnumerable<ProductType> ProductTypeList { get; set; }
}
Controller
[HttpGet]
public ActionResult Index(int id)
{
var customer = GetCustomer(id);
return View(customer);
}
View
#model Customer
#Html.TextBoxFor(x=>x.Name);
#for (int i = 0; i < Model.Products.Count; i++)
{
#Html.TextBoxFor(x => x.Products[i].Name)
#Html.TextBoxFor(x => x.Products[i].Price)
#Html.DropDownListFor(x => x.Products[i].ProductType, Model.ProductTypeList)
}
Result:
The name and price of the products in HTML are displayed correctly but the <select> does not have correct ProductType selected (the first item is selected even though model has other value).
When I submit the form the value is bound and when validation return the form is is also bound to selected value.
The only issue is that DropDownList selected value is not bound when the page is loaded first time.

I think the problem lies with the ProductType:
#Html.DropDownListFor(x => x.Products[i].ProductType, Model.ProductTypeList)
It appears to be a complex type.
Try changing it to this instead:
public class Product
{
public string Name {get;set;}
public string SelectedProductType {get;set;}
public IEnumerable<ProductType> ProductTypeList { get; set; }
}
In the above, SelectedProductType would be the Id of your ProductType.
Then setting it like this:
#Html.DropDownListFor(x => x.Products[i].SelectedProductType, Model.ProductTypeList)

Related

Saving dropdown list value to database ASP.net 5.0 MVC 6 using tag helpers

I am trying to save my dropdown list values to my database in an ASP.NET web application using MVC6 and Entity Framework 7 but the values are not saving.
I have two classes one called expenses and when a user creates an expense they need to select a country. I have the country dropdown list populating but when the expense is saved the countryid is not being saved to the database.
Models
public class Country
{ public int Countryid { get; set; }
public string CountryCode { get; set; }
}
public class Expense
{
public int ExpenseId { get; set; }
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}",
ApplyFormatInEditMode = true)]
public DateTime ExpenseDate { get; set; }
public virtual Country Countryid { get; set; }
Expense Controller
private void PopulateCountriesDropDownList(object selectedCountry = null)
{
var list = _context.Countries.OrderBy(r => r.CountryCode).ToList().Select(rr =>
new SelectListItem { Value = rr.Countryid.ToString(), Text = rr.CountryCode }).ToList();
ViewBag.Countries = list;
}
// GET: Expenses/Create
public IActionResult Create()
{
PopulateCountriesDropDownList();
return View();
}
// POST: Expenses/Create
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(Expense expense)
{
if (ModelState.IsValid)
{
_context.Expenses.Add(expense);
_context.SaveChanges();
return RedirectToAction("Index");
}
PopulateCountriesDropDownList(expense.Countryid);
return View(expense);
}
View
<div class="form-group">
<label asp-for="Countryid" class="col-md-2 control-label"></label>
<div class="col-md-10">
<select asp-for="Countryid" class="form-control"asp-items=#ViewBag.Countries></select>
<span asp-validation-for="Countryid" class="text-danger" />
</div>
</div>
First of all the Countryid property in your Expense model is a of a complex type (Country). The model binder cannot map the posted Countryid form value to this Complex object.
You should add a CountryId property to your Expense model of type Int
public class Expense
{
public int ExpenseId { get; set; }
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}",
ApplyFormatInEditMode = true)]
public DateTime ExpenseDate { get; set; }
public int CountryId { set;get;}
public virtual Country Country { get; set; }
}
While this will fix the problem, A more better & clean solution is to use a view model for transferring data between your view and action method. With this approach your view is not tightly coupled to the entity classes generated by your ORM.
So create a view model for your view with properties absolutely needed for the view.
public class CreateExpenseVm
{
public List<SelectListItem> Countries { set;get;}
public int CountryId { set;get;}
//Add other properties, if your view need them.
}
and in your GET action, you create an object of this class, load the Countries collection property and send it to your view.
public ActionResult Create()
{
var vm=new CreateExpenseVm();
vm.Countries = _context.Countries.OrderBy(r => r.CountryCode)
.Select(x=>new SelectListItem { Value=x.CountryId.ToString(),
Text=x.CountryCode}).ToList();
return View(vm);
}
And in your view,which is strongly typed to our new viewmodel,
#model CreateExpenseVm
<form asp-controller="Expense" asp-action="Create">
<label>Select Country</label>
<select asp-for="CountryId" asp-items="#Model.Countries" >
<option>Please select one</option>
</select>
<input type="submit"/>
</form>
and in your HttpPost action, Use CreateExpenseVm as the parameter type. When the form is submitted, the default model binder will be able to map the posted form data to the properties of this class object.
[HttpPost]
public ActionResult Create(CreateExpenseVm model)
{
var e=new Expense { CountryId=model.CountryId };
e.ExpenseDate = DateTime.Now;
dbContext.Expenses.Add(e);
dbContext.SaveChanges();
return RedirectToAction("Index");
}

asp .net mvc 4 dropdownlist

I am using VS2012 MVC 4 with EF. I want to make a view where the user can upload movie title and type for it (actionmovie, scifi, etc that comes from a dropdownlist!), and store it in database.
Model:
public class Movie
{
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("Type")]
public int TypeId { get; set; }
public virtual Type Type { get; set; }
}
public class Type
{
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int TypeId { get; set; }
public string TypeName { get; set; }
public virtual ICollection<Movie> Movies { get; set; }
}
If I am right I successfully made One to Many relationship between the Type and Movie table.
Controller:
[HttpPost]
[Authorize]
public ActionResult NewMovie(Movie movie)
{
db.Movies.Add(movie);
db.SaveChanges();
return View(movie);
}
View:
#using (Html.BeginForm())
{
<table>
<tr>
<td>#Html.DisplayNameFor(model => model.Name)</td>
<td>#Html.DisplayNameFor(model => model.TypeId)</td>
</tr>
<tr>
<td>#Html.TextBoxFor(model => model.Name)</td>
<td>#Html.DropDownListFor.........
</tr>
</table>
<input type="submit" value="submit" />
}
I do not really know how should I make this Dropdownlist in the view, and make the specific details for it in the controller with the most secure way
(I think i should make a list/enumerable from the movie.typeid or the Type class)
I would be very glad if you could help me! Thank you
I use List for my dropdown list. to do it that way you can build the list like this on your controller
List<SelectListItem> ls = new List<SelectListItem>();
foreach(var temp in Movies){
ls.Add(new SelectListItem() { Text = temp.Text, Value = temp.Value });
}
Movie.MovieList = ls;
you can only pass one model to the view. from your code you are passing the movie model so put
public List<SelectListItem> MovieList { get; set; }
in your model. Then on the view you can build your dropdownlist like this
#Html.DropDownListFor(x => x.Id, Model.MovieList)
Let me know if you have any questions.

Validate dropdownlistfor MVC4

I'm having problems trying to validate a drop down list, I've looked at similar questions on here and tried the suggestions but still no luck. One I haven't tried is making my Benefit Id nullable, but is that a good idea? many thanks
Model I'm trying to validate:
public class Benefit
{
public int Id { get; set; }
public string Name { get; set; }
}
View model:
public class LookupVm
{
public SelectList Benefits { get; set; }
}
Controller set up:
var model = new LookupVm
{
Benefits = new SelectList(_repository.Benefits.OrderBy(n => n.Name).ToList(), "Id", "Name")
}
The view:
#Html.DropDownListFor(benefits => Model.Benefits.SelectedValue, Model.Benefits, "-Select-")
#Html.ValidationMessageFor(benefits => Model.Benefits.SelectedValue)
You can add a SelectedBenefit property to you view model
public class LookupVm
{
public int SelectedBenefit { get; set;}
public SelectList Benefits { get; set; }
}
Then add on top of the view
#model LookupVm
And then dropdown list must be something like this:
#Html.DropDownListFor(model => model.SelectedBenefit, model.Benefits, "-Select-")
#Html.ValidationMessageFor(model => model.SelectedBenefit)
You will get the selected id on SelectedBenefit property and it will be a required field.

MVC3 (Razor) Passing Model Data

I'm making Memo web application.
and the main page contain 'Create and List and Modify' functions.
But I don't know how to pass Model (for create) and List (for List) to View(Razor) from controller.
this is my note model,
[Table("note")]
public class Note
{
[Key]
public int id { get; set; }
[Required(ErrorMessage="Content is required")]
[DisplayName("Note")]
public string content { get; set; }
public DateTime date { get; set; }
[Required(ErrorMessage = "User ID is required")]
[DisplayName("User ID")]
public string userId {get; set;}
public Boolean isPrivate { get; set; }
public virtual ICollection<AttachedFile> AttachedFiles { get; set; }
}
I tried,
1)
public ActionResult Index()
{
var notes = unitOfWork.NoteRepository.GetNotes();
return View(notes);
}
Then, in view,
#model Enumerable<MemoBoard.Models.Note>
//I can not use this, because the model is Enumerable type
#Html.LabelFor(model => model.userId)
So, I made viewModel
2)
public class NoteViewModel
{
public IEnumerable<Note> noteList { get; set; }
public Note note { get; set; }
}
In Controller,
public ActionResult Index()
{
var notes = unitOfWork.NoteRepository.GetNotes();
return View(new NoteViewModel(){noteList=notes.ToList(), note = new Note()});
}
and In View,
#model MemoBoard.Models.NoteViewModel
#Html.LabelFor(model => model.note.userId)
it looks well, BUT in source view, it's showing
<input data-val="true" data-val-required="User ID is required" id="note_userId" name="note.userId" type="text" value="" />
the name is note.userId not userId.
List this case, how should I do to make working?
Please advice me.
Thanks
[EDIT]
(First of all, thanks for all advices)
Then, How can I change this controller
[HttpPost]
public ActionResult Index(Note note)
{
try
{
if (ModelState.IsValid)
{
unitOfWork.NoteRepository.InsertNote(note);
unitOfWork.Save();
return RedirectToAction("Index");
}
}catch(DataException){
ModelState.AddModelError("", "Unable to save changes. Try again please");
}
return RedirectToAction("Index");
}
If I change parameter type to NoteViewModel, then how should I do for valid check?
[HttpPost]
public ActionResult Index(NoteViewModel data)
{
try
{
if (ModelState.IsValid) <===
#model Enumerable<MemoBoard.Models.Note>
//I can not use this, because the model is Enumerable type
#Html.LabelFor(model => model.userId)
You can use it in foreach loop or return list and use it in for loop
the name is note.userId not userId.
It's normal, this made for model binding
Try this:
Html.TextBox("userId", Model.note.userId, att)

Loading related data in ASP.net MVC

Something very simple but I am looking for the best way to do it. I have a Movie entity, each Movie can be in one Language only(a lookup table with English, French,etc...). Now I'm trying to load all the available languages in the lookup in the Movie Create Page, the Movie View Model:
namespace Project.ViewModels {
public class Movie {
[Key]
public int ID { get; set; }
public string Title { get; set; }
public string Rating { get; set; }
public string Director { get; set; }
public string Plot { get; set; }
public string Link { get; set; }
public string Starring { get; set; }
public int DateCreated { get; set; }
public string Genre { get; set; }
[Display(Name = "Language")]
public int LanguageID { get; set; }
// Navigational Properties
public virtual MovieLanguage Language { get; set; }
}
}
The MovieLanguage View model:
namespace MAKANI.ViewModels {
public class MovieLanguage {
[Key]
public int ID { get; set; }
public string Language { get; set; }
public virtual ICollection<Movie> Movies { get; set; }
}
}
The controller action:
public ActionResult MovieCreate() {
using (MAKANI.Models.Entities db = new MAKANI.Models.Entities()) {
List<Models.MoviesLanguages> enLanguages = db.MoviesLanguages.ToList();
IEnumerable<SelectListItem> selectList =
from m in enLanguages
select new SelectListItem {
Text = m.Language,
Value = m.ID.ToString()
};
ViewBag.SelectLanguage = selectList.ToList();
return View();
}
}
And in the View page i have
<div class="editor-field">
#Html.DropDownList("Language", ViewBag.SelectLanguage);
</div>
Howver I am getting this error in the View:
'System.Web.Mvc.HtmlHelper' has no applicable method named 'DropDownList' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax
Not sure what the problem might be?
Another questions regarding this approach:
Should a create a view model for the MovieLanguage entity in the first place, knowing that it servers only as a lookup table(so it doesnt require any Create/Edit/Delete action, Only List/Read maybe), should I be depending on the EF entities directly in that case?
Have a Languages Collection Property in your Movie ViewModel and a SelectedLanguage Property to get the selected Language ID when the form submits. It is not necessary that your ViewModel should have all the properties like your domain model. Have only those properties which the View needs.
public class Movie
{
public int ID { set;get;}
public string Title { set;get;}
//Other Relevant Properties also.
public IEnumerable<SelectListItem> Languages { set;get;}
public int SelectedLanguage { set;get;}
public Movie()
{
Languages =new List<SelectListItem>();
}
}
Now in your GET Action, Create an object of your Movie ViewModel and set the Languages Collection property and send that to the View. Try to avoid using ViewBag for passing data like this. ViewBag makes our code dirty.Why not use the strongly typed ViewModels to its full extent ?
public ActionResult CreateMovie()
{
var vm=new Movie();
// TO DO : I recommend you to abstract code to get the languages from DB
// to a different method so that your action methods will be
// skinny and that method can be called in different places.
var enLanguages = db.MoviesLanguages.ToList();
vm.Languages= = from m in enLanguages
select new SelectListItem {
Text = m.Language,
Value = m.ID.ToString()
};
return View(vm);
}
And in your view which is strongly typed to our Movie ViewModel, use the DropDownListFor Hemml helper method
#model Movie
#using(Html.Beginform())
{
#Html.DropDownListFor(x => x.SelectedLanguage,
new SelectList(Model.Languages, "Value", "Text"), "Select Language")
<input type="submit" />
}
Now when you post the form, you will get the selected languageId in the SelectedLanguage Property of your ViewModel
[HttpPost]
public ActionResult CreateMovie(Movie model)
{
If(ModelState.IsValid)
{
//check model.SelectedLanguage property here.
//Save and Redirect (PRG pattern)
}
//you need to reload the languages here again because HTTP is stateless.
return View(model);
}

Resources