MVC Model binding with lists - asp.net-mvc-3

I have this model:
public class ReservationViewModel
{
public Flight InboundFlight { get; set; }
public Flight OutboundFlight { get; set; }
}
//Flight
public class Flight
{
public List<ISeat> Seats { get; set; }
}
//Seats
public interface ISeat
{
ECabin Cabin { get; }
int NumberOfSeatsAvailable { get; }
int SeatsChosen { get; set; }
string PropertyName { get; }
}
My HTML consist of the folliwing:
<select id="OutboundFlight__0__Seats_SeatsChosen" name="OutboundFlight.[0].Seats.SeatsChosen" class="valid"><option...
<select id="OutboundFlight__0__Seats_SeatsChosen" name="OutboundFlight.[1].Seats.SeatsChosen" class="valid"><option...
<select id="OutboundFlight__0__Seats_SeatsChosen" name="OutboundFlight.[2].Seats.SeatsChosen" class="valid"><option...
My Action:
[HttpPost]
public ActionResult Index(ReservationViewModel model, FormCollection form)
{
return View();
}
Upon submit I try to bind back to the model but the Seats of each flight returns null...
Help will be appreciated

The HTML being generated is incorrect to get it to bind to a list - the field name has to match what what accessing the property from c# would look like:
This should work:
name="OutboundFlight.Seats[0].SeatsChosen"

Related

How to show nested ViewModel from View to Controller

I have the following ViewModel
public class ProductsViewModel
{
public IEnumerable<ProductViewModel> Products;
public ProductViewModel Product;
}
which implements
public class ProductViewModel
{
public string Name { get; set; }
public string Description { get; set; }
public decimal Value { get; set; }
}
I am using the following form
<input asp-for="#Model.Product.Name" />
<label>Description</label>
<input asp-for="#Model.Product.Description" />
<label>Value</label>
<input asp-for="#Model.Product.Value" />
I implement this action
public async Task<IActionResult> Post(Product model)
{
await productService.Create(model.Product.Name, model.Product.Description, model.Product.Value);
return RedirectToAction(nameof(Index));
}
but the data I receive is null because the Action expects Product.Name, Product.Description, Product.Value.
Any idea how to receive the data from my View?
Fixed. This works
public IEnumerable<ProductViewModel> Products;
public string Name { get; set; }
public string Description { get; set; }
public decimal Value { get; set; }
but I don't like this mess. If someone has better solution?
In fact, your binding method is not wrong, you just forgot to add get and set to your ViewModel.
public class ProductsViewModel
{
public IEnumerable<ProductViewModel> Products { get; set; }
public ProductViewModel Product { get; set; }
}

Validation on View Model using Model

I have ViewModels and Models.
ViewModels use Models.
I have 1 ViewModel for 1 Controller.
ex:
public class ReferentielFournisseursViewModel
{
public Fournisseur monFournisseur { get; set; }
[...]
public Adresses ListeSites { get; set; }
}
public class Fournisseur
{
public int NoFournisseur { get; set; }
public string Name { get; set; }
public string Sigle { get; set; }
[Required]
public string Siren { get; set; }
}
In this case, I use validation by annotation: "Required" on the model.
But I have to create an other page using the same model without this validation.
public class DemandeDePrixViewModel
{
public Fournisseur monFournisseur { get; set; }
[...]
public Commande CommandeEnCours { get; set; }
}
How I can use Validation on the ViewModel instead of the Model ?
You should look into the IValidateableObject interface:
public class Model: IValidatableObject
{
public int Property { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
if (Property < 5)
{
results.Add(new ValidationResult("Prop1 must be larger than 5"));
}
return results;
}
}
This code will automatically run when ModelState.IsValid is called
See also: https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.ivalidatableobject?view=netcore-3.1

While filtering, how to POST a SearchModel, but GET a different ResultsModel back in the view

I am creating a filter view to find records. This example on SO helps, but does not mention how handle the (Filtered) View.
The err below is because, the actions returns a List<ProductViewModel>, and it Errors/complains that the View is using a SearchViewModel, I need to this POST the searchmodel/variables, butGET back the list/results model
The model item passed into the dictionary is of type
'System.Collections.Generic.List`1[ViewModels.ProductVM]', but this
dictionary requires a model item of type 'ViewModels.SearchModel'.
Issue/Question: Since there are two models, the SearchViewModel passed to the controller & the ProductViewModel returned as a result, which model should be strongly typed to the view? and How can I create the view to handle both SearchModel & ProductModel If I stronglyType ProductVM, then I loose the submitform from the SearchVM.
I create the SearchView as the mainview, & the _ResultsPartialView as a partialView, is this wrong?
public ActionResult Index(SearchModel searchModel)
{
var filteredProdVMList = _Repository.GetFilteredProducts(searchModel);
return View(filteredProdVMList);
}
public class ProductVM
{
public int Id { get; set; }
public int Price { get; set; }
public string Name { get; set; }
// implicit const... blah.. removed
}
public class SearchModel
{
public int? Id { get; set; }
public int? PriceFrom { get; set; }
public int? PriceTo { get; set; }
public string Name { get; set; }
}
You need to modify your SearchModel to include a collection property for the products
public class SearchModel
{
public int? PriceFrom { get; set; }
public int? PriceTo { get; set; }
....
public IEnumerable<ProductVM> Products { get; set; } // add
}
then you return just SearchModel to your view
public ActionResult Filter(SearchModel filter)
{
filter.Products = _repository.GetFilteredProducts(filter);
return View(filter);
}
and your view will be
#model SearchModel
....
#using (Html.BeginForm("Filter", "yourControllerName", FormMethod.Get))
{
#Html.LabelFor(m => m.PriceFrom)
#Html.EditorFor(m => m.PriceFrom)
#Html.ValidationMessageFor(m => m.PriceFrom)
... // other form controls for properties you want to filter the results on
<input type="submit" value="Filter" />
}
#Html.Partial("_ResultsPartialView", Model.Products)

Populating a dropdownlist with values

Hello I'm trying to make my dropdownlist to work but it seems harder than expected. I have 3 domain classes Member, Rental and Movie. My Idea was to make a dropdownlist that will show a specific users rented movies and when I select a movie in the dropdownlist and submit it I will get back the selected movie and I can set bool IsInStock to true.
So I made a viewmodel and a controller action but would like some help how to go forward with this. Now I get a dropdownlist with the users "Jan" rented movies but when I klick submit I would like to get the values back in order to set the IsInStock to true. I know I will need method to handle the POST values but I'm trying to make this work first.
public class Member
{
public virtual int MemberId { get; set; }
public virtual string LastName { get; set; }
public virtual List<Rental> Rentals { get; set; }
}
public class Rental
{
public virtual int RentalId { get; set; }
public virtual int MovieId { get; set; }
public virtual Movie Movie { get; set; }
public virtual int MemberId { get; set; }
public virtual Member Member { get; set; }
}
public class Movie
{
public virtual int MovieId { get; set; }
public virtual string Name { get; set; }
public virtual bool IsInStock { get; set; }
}
public class RentalsViewModel
{
// Need something here.
public IEnumerable<SelectListItem> RentedMovies { get; set; }
}
public ActionResult ReturnMovie()
{
var rentedmovies = db.Rentals.Where(r => r.Member.Name == "Jan").ToList();
var model = new RentalsViewModel()
{
RentedMovies = rentedmovies.Select(x => new SelectListItem
{
Value = x.MovieId.ToString(),
Text = x.Movie.Name
})
};
return View(model);
}
// In the View
#using (Html.BeginForm())
{
#Html.DropDownListFor(x => x.RentedMovies, //Something here);
<p>
<input type="submit" value="Save" />
</p>
}
First parameter here:
#Html.DropDownListFor(x => x.RentedMovies, //Something here);
must be int, because you will send this value to the server:
public virtual int MovieId { get; set; }
So, your example could look like:
#Html.DropDownListFor(x => x.RentedMovieId, Model.RentedMovies);
(Add property RentedMovieId to RentalsViewModel)

Asp.Net MVC3 - How create Dynamic DropDownList

I found many articles on this but still I don´t know how exactly to do this. I am trying to create my own blog engine, I have View for create article (I am using EF and Code first) and now I must fill number of category in which article should be add but I want to change it to dropdownlist with names of categories. My model looks this:
public class Article
{
public int ArticleID { get; set; }
[Required]
public string Title { get; set; }
[Required]
public int CategoryID { get; set; }
public DateTime Date { get; set; }
[Required()]
[DataType(DataType.MultilineText)]
[AllowHtml]
public string Text { get; set; }
public virtual Category Category { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Category
{
public int CategoryID { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Article> Articles { get; set; }
}
I know I must use Enum (or I think) but I am not exactly sure how. I don´t know which tutorial from that I found is best for me.
Edit:
Thanks for your answers but I found something else. I am trying this:
This is my model:
public class Article
{
[Key]
public int ArticleID { get; set; }
[Display(Name = "Title")]
[StringLength(30, MinimumLength = 5)]
[Required]
public string Title { get; set; }
public DateTime Date { get; set; }
public int CategoryID { get; set; }
[Required()]
[DataType(DataType.MultilineText)]
[AllowHtml]
public string Text { get; set; }
public Category Category { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
public IEnumerable<Category> Categories { get; set; }
}
public class Category
{
[Key]
public int CategoryId { get; set; }
[Required]
public string CategoryName { get; set; }
public virtual ICollection<Article> Articles { get; set; }
}
This is my controller to create article:
public ActionResult Vytvorit()
{
IEnumerable<Category> categories = GetCaregories();
var view = View(new Article() { Categories = categories });
view.TempData.Add("Action", "Create");
return view;
}
private static IEnumerable<Category> GetCaregories()
{
IEnumerable<Category> categories;
using (BlogDBContext context = new BlogDBContext())
{
categories = (from one in context.Categories
orderby one.CategoryName
select one).ToList();
}
return categories;
}
private Category GetCategory(int categoryID)
{
return db.Categories.Find(categoryID);
}
//
// POST: /Clanky/Vytvorit
[HttpPost]
public ActionResult Vytvorit(Article newArticle)
{
try
{
if (newArticle.CategoryID > 0)
{
newArticle.Category = GetCategory(newArticle.CategoryID);
}
if (TryValidateModel(newArticle))
{
db.Articles.Add(newArticle);
db.SaveChanges();
return RedirectToAction("Index");
}
else
{
newArticle.Categories = GetCaregories();
var view = View(newArticle);
view.TempData.Add("Action", "Create");
return view;
}
}
catch
{
return View();
}
}
And this is part of my view:
#Html.DropDownListFor(model => model.CategoryID, new SelectList(Model.Categories,"CategoryID","CategoryName"))
#Html.ValidationMessageFor(model => model.CategoryID)
I have problem with NullReferenceExeption but I don´t know why. Can I do it this way? It looks very easy for me.
Your model seems quite strange. It contains properties such as CategoryID and Category which seem redundant. It also contains a SelectListItem collection property called Categories. So, is this a model or a view model? It looks quite messed up. Let's assume it's a model. In this case it would more likely look something like this:
public class Article
{
public int ArticleID { get; set; }
[Required]
public string Title { get; set; }
public DateTime Date { get; set; }
[Required()]
[DataType(DataType.MultilineText)]
[AllowHtml]
public string Text { get; set; }
public virtual Category Category { get; set; }
public IEnumerable<Category> Categories { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Category
{
public int CategoryID { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Article> Articles { get; set; }
}
Now that the model is clear we could define a view model which will be passed to the view. A view model is a class which is specifically designed for the view. So depending on what you intend to put in this view you define it in this view model. So far you have talked only about a drop down, so let's do it:
public class ArticleViewModel
{
public int SelectedCategoryId { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
}
and then we have a controller:
public class ArticlesController: Controller
{
private readonly IArticlesRepository _repository;
public ArticlesController(IArticlesRepository repository)
{
_repository = repository;
}
public ActionResult Index()
{
Article article = _repository.GetArticle();
ArticleViewModel viewModel = Mapper.Map<Article, ArticleViewModel>(article);
return View(viewModel);
}
}
So the controller uses a repository to fetch the model, maps it to a view model (in this example I use AutoMapper) and passes the view model to the view which will take care of showing it:
#model AppName.Models.ArticleViewModel
#using (Html.BeginForm())
{
#Html.DropDownListFor(
x => x.SelectedCategoryId,
new SelectList(Model.Categories, "Value", "Text"),
"-- Select category --"
)
<input type="submit" value="OK" />
}
I have gone through this as well and I have to agree that at first it seems odd (In my explanation I'm assuming you want to select one category only, but the process is very similar for a multi select).
Basically you need to perform 3 steps:
1:
You need two properties on your viewmodel
One will hold the selected category id (required for postback) and the other will a SelectList with all possible categories:
public class Article
{
public int ArticleID { get; set; }
public int CategoryID { get; set; }
public SelectList Categories { get; set; }
}
2:
Also before passing the viewmodel on to the view you need to initialize the SelectList (Best practivce is to prepare as much as possible before passing a model into the view):
new SelectList(allCategories, "CategoryID", "Name", selectedCategoryID)
3:
In the view you need to add a ListBox for the CategoryID property, but using the Categories property too fill the ListBox with values:
#Html.ListBoxFor(model => model.CategoryID , Model.Categories)
Thats it! In the post back action of the controller you will have the CategoryID set. You can do whatever you need to from there to persist things in your db.

Resources