Asked by:
[Beginner] Create a model class field containing a list of objects from another table with ASP.NET MVC

Question
-
User-1740307832 posted
I'm learning
C#
andASP.NET MVC Framework
from 2 weeks through books, lessons, ... and I would like to apply these knowledges through a small web application. I need your help/advices in order to set a new field between 2 classes.Objective:
This application should be useful for my soccer team. It should let to handle players and create my team composition before each match.
My first class Joueur:
This class lets me to create players. Each player has some properties like : JoueurID, Firstname, Lastname, Poste and Image.
When the table will be filled out, I should get all licencies players from my soccer team.
My class looks like:using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Web; namespace FCSL.Models.Joueur { public class Joueur { [Key, Display(Name = "ID")] public int JoueurID { 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] [Display(Name = "Poste")] public string Poste { get; set; } public string Image { get; set; } } }
My second class Match:
I have this class in order to create the Match object containing all informations according to the specific match like: MatchID, MatchDay, MatchTime, Format, Adversaire, Terrain, Tresse and Slug
My class looks like:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Web.Mvc; namespace FCSL.Models.Match { public class Match { [Key] public int MatchID { get; set; } [Required, Display(Name = "Date du match")] [DataType(DataType.Date)] public DateTime MatchDay { get; set; } [Required, Display(Name = "Heure du match")] [DataType(DataType.Time)] [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:HH:mm}")] public DateTime MatchTime { get; set; } [Required, Display(Name = "Adversaire")] public string Adversaire { get; set; } [Required, Display(Name = "Type de match")] public string Format { get; set; } [Required, Display(Name = "Terrain")] public string Terrain { get; set; } [Display(Name = "Tresse")] public string Tresse { get; set; } [Required, Display(Name = "Mot-clé")] public string Slug { get { return Format + "_" + Adversaire + "_" + MatchDay.ToString("dd_MM_yyyy"); } } } }
My difficulties according to link between both classes:
Now, this is the point a little bit obscur for me and where I have issues and unknowledges. I would like to have a new field which let to select players from Joueur class when I create a Match object from my ASP.NET MVC Form. Something like a dropdownlist with MultiSelect option.
To my mind, I have to add in my class Match this field:public virtual ICollection<Joueur.Joueur> ListeJoueurs { get; set; }
This field lets to make a link between Joueur and Match classes right ?
Now, I have my Controller MatchController which looks like this:using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Linq; using System.Net; using System.Web; using System.Web.Mvc; using FCSL.Models; using FCSL.Models.Match; namespace FCSL.Controllers { public class MatchesController : Controller { private FCSLContext db = new FCSLContext(); // GET: Matches public ActionResult Index() { return View(db.Matches.ToList()); } // GET: Matches/Details/5 public ActionResult Details(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } Match match = db.Matches.Find(id); if (match == null) { return HttpNotFound(); } return View(match); } // GET: Matches/Create public ActionResult Create() { var listejoueurs = db.Joueurs.Select(c => new { JoueurName = c.Firstname }).ToList(); ViewBag.Joueur = new MultiSelectList(listejoueurs, "Firstname"); return View(); } // POST: Matches/Create // Afin de déjouer les attaques par sur-validation, activez les propriétés spécifiques que vous voulez lier. Pour // plus de détails, voir https://go.microsoft.com/fwlink/?LinkId=317598. [HttpPost] [ValidateAntiForgeryToken] public ActionResult Create([Bind(Include = "MatchID,MatchDay,MatchTime,Adversaire,Format,Terrain,Tresse,Slug,ListeJoueurs")] Match match) { if (ModelState.IsValid) { db.Matches.Add(match); db.SaveChanges(); return RedirectToAction("Index"); } return View(match); } // GET: Matches/Edit/5 public ActionResult Edit(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } Match match = db.Matches.Find(id); if (match == null) { return HttpNotFound(); } return View(match); } // POST: Matches/Edit/5 // Afin de déjouer les attaques par sur-validation, activez les propriétés spécifiques que vous voulez lier. Pour // plus de détails, voir https://go.microsoft.com/fwlink/?LinkId=317598. [HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit([Bind(Include = "MatchID,MatchDay,MatchTime,Adversaire,Format,Terrain,Tresse,Slug,ListeJoueurs")] Match match) { if (ModelState.IsValid) { db.Entry(match).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } return View(match); } // GET: Matches/Delete/5 public ActionResult Delete(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } Match match = db.Matches.Find(id); if (match == null) { return HttpNotFound(); } return View(match); } // POST: Matches/Delete/5 [HttpPost, ActionName("Delete")] [ValidateAntiForgeryToken] public ActionResult DeleteConfirmed(int id) { Match match = db.Matches.Find(id); db.Matches.Remove(match); db.SaveChanges(); return RedirectToAction("Index"); } protected override void Dispose(bool disposing) { if (disposing) { db.Dispose(); } base.Dispose(disposing); } } }
How I can create mecanism to add a Dropdownlist in order to select multi players and link these players for a specific match ?
Thank you very much !
EDIT:
The form should looks like this (with multiselection on ListeJoueurs field):Tuesday, July 30, 2019 9:37 AM
All replies
-
User1120430333 posted
IMHO, you should be using a Viewmodel. You like many others make the classic mistake of using the EF ORM model as a Viewmodel sending it into a view..
The EF ORM model is for data persistence aka CRUD operations with the database, and IMO shouldn't be used in the view. . You you are trying to treat the persistence model like it's a domain model.
https://www.tutlane.com/tutorial/aspnet-mvc/how-to-use-viewmodel-in-asp-net-mvc-with-example
A couple of other things that your doing that you might want to reconsider.
https://www.c-sharpcorner.com/UploadFile/56fb14/understanding-separation-of-concern-and-Asp-Net-mvc/
https://en.wikipedia.org/wiki/Separation_of_concerns
<copied>
An MVC model contains all of your application logic that is not contained in a view or a controller. The model should contain all of your application business logic, validation logic, and database access logic. For example, if you are using the Microsoft Entity Framework to access your database, then you would create your Entity Framework classes (your .edmx file) in the Models folder.
A view should contain only logic related to generating the user interface. A controller should only contain the bare minimum of logic required to return the right view or redirect the user to another action (flow control). Everything else should be contained in the model.
In general, you should strive for fat models and skinny controllers. Your controller methods should contain only a few lines of code. If a controller action gets too fat, then you should consider moving the logic out to a new class in the Models folder.
<end>
You don't have any restriction in using a VM with a view as opposed to the EF model where you can't arbitrarily do what you want to make it work with a view.
The VM was used in multiple, using the List<T> Students for a grid while on the other hand EnrollsandCourses was sent into the view that was using Student and EnrollsandCourses
in the Student view.
namespace MVC.Models { public class StudentViewModels { public List<Student> Students { get; set; } public class Student { public Int32 StudentID { get; set; } [Required(ErrorMessage = "Last Name is required")] [StringLength(50)] public string LastName { get; set; } [Required(ErrorMessage = "First Name is required")] [StringLength(50)] public string FirstName { get; set; } [Required(ErrorMessage = "Enrollment Date is required")] [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM-dd-yyyy}")] public DateTime? EnrollmentDate { get; set; } public virtual ICollection<EnrollandCourseViewModel.EnrollandCourse> EnrollsandCourses { get; set; } } }
If you want to do s dropdown list, then learn ho to use the SelectListItem in the viewmodel
using Microsoft.AspNetCore.Mvc.Rendering; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace PublishingCompany.Models { public class PayRollVM { public class Payroll { public int PayrollId { get; set; } public int AuthorId { get; set; } public string AuthorFirstName { get; set; } public string AuthorLastName { get; set; } [Required(ErrorMessage = "Author is required")] public string AuthorTypeId { get; set; } [Required(ErrorMessage = "Salary is required")] public int? Salary { get; set; } public List<SelectListItem> AuthorTypes { get; set; } = new List<SelectListItem>(); } public List<Payroll> Payrolls { get; set; } = new List<Payroll>(); } }
@model PayRollVM.Payroll <!DOCTYPE html> <style type="text/css"> .editor-field > label { float: left; width: 150px; } .txtbox { font-family: Arial, Helvetica, sans-serif; font-size: 12px; background: white; color: black; cursor: text; border-bottom: 1px solid #104A7B; border-right: 1px solid #104A7B; border-left: 1px solid #104A7B; border-top: 1px solid #104A7B; padding-top: 10px; } </style> <html> <head> <title>Create</title> </head> <body> <h1>Author Payroll</h1> @using (Html.BeginForm()) { @Html.ValidationSummary(false, "", new { @class = "text-danger" }) <fieldset> <legend>Create</legend> @Html.HiddenFor(model => model.PayrollId) @Html.HiddenFor(model => model.AuthorId) <div class="form-group"> <div class="editor-field"> @Html.Label("Author:") @Html.DropDownListFor(m => m.AuthorTypeId, Model.AuthorTypes, "Select....") </div> </div> <div class="form-group"> <div class="editor-field"> @Html.Label("Salary:") @Html.TextBoxFor(model => model.Salary) @Html.ValidationMessageFor(model => model.Salary, "", new { @class = "text-danger" }) </div> </div> <br /> <p> <input type="submit" name="submit" value="Save" /> <input type="submit" name="submit" value="Cancel" /> </p> </fieldset> } </body> </html>
Here is a controller that is following the principles talked about in the links.
using Microsoft.AspNetCore.Mvc; using PublishingCompany.Models; namespace PublishingCompany.Controllers { public class PayRollController : Controller { private IPayRollDM pdm; public PayRollController(IPayRollDM payRollDM) { pdm = payRollDM; } public IActionResult Index() { return View(pdm.GetAll()); } public IActionResult Detail(int id = 0) { return id == 0 ? null : View(pdm.Find(id)); } public ActionResult Create() { return View(pdm.Add()); } [HttpPost] public ActionResult Create(PayRollVM.Payroll payroll, string submit) { if (submit == "Cancel") return RedirectToAction("Index"); if (!ModelState.IsValid) return View(pdm.PopulateSelectedList(payroll)); if (pdm.BlnFindPayRollByAuthorId(int.Parse(payroll.AuthorTypeId))) { ModelState.AddModelError(string.Empty, "Author has an existing PayRoll record."); } if (!ModelState.IsValid) return View(pdm.PopulateSelectedList(payroll)); pdm.Add(payroll); return RedirectToAction("Index"); } public ActionResult Edit(int id = 0) { return id == 0 ? null : View(pdm.Update(id)); } [HttpPost] public ActionResult Edit(PayRollVM.Payroll payroll, string submit) { if (submit == "Cancel") return RedirectToAction("Index"); if (!ModelState.IsValid) return View(payroll); pdm.Update(payroll); return RedirectToAction("Index"); } public IActionResult Delete(int id = 0) { if (id > 0) pdm.Delete(id); return RedirectToAction("Index"); } public ActionResult Cancel() { return RedirectToAction("Index", "Home"); } } }
using ServiceLayer; using System.Linq; using Entities; using Microsoft.AspNetCore.Mvc.Rendering; namespace PublishingCompany.Models { public class PayRollDM : IPayRollDM { private IPayRollSvc svc; private IAuthorSvc svcauth; public PayRollDM(IPayRollSvc payRollSvc, IAuthorSvc authorSvc) { svc = payRollSvc; svcauth = authorSvc; } public PayRollVM GetAll() { var vm = new PayRollVM(); var dtos = svc.GetAll().ToList(); vm.Payrolls.AddRange(dtos.Select(dto => new PayRollVM.Payroll() { PayrollId = dto.PayrollId, AuthorId = dto.AuthorId, AuthorFirstName = dto.AuthorFirstName, AuthorLastName = dto.AuthorLastName, Salary = dto.Salary }).ToList()); return vm; } public PayRollVM.Payroll Find(int id) { var dto = svc.Find(id); var payroll = new PayRollVM.Payroll { PayrollId = dto.PayrollId, AuthorId = dto.AuthorId, AuthorFirstName = dto.AuthorFirstName, AuthorLastName = dto.AuthorLastName, Salary = dto.Salary }; return payroll; } public bool BlnFindPayRollByAuthorId(int id) { bool blnflag = false; var dto = svc.FindPayRollByAuthorId(id); if (dto.PayrollId != 0) { blnflag = true; } return blnflag; } public PayRollVM.Payroll Add() { return PopulateSelectedList( new PayRollVM.Payroll()); } public void Add(PayRollVM.Payroll payroll) { var dto = new DtoPayroll { AuthorId = int.Parse(payroll.AuthorTypeId), Salary = payroll.Salary }; svc.Add(dto); } public PayRollVM.Payroll Update(int id) { var dto = Find(id); var payroll = new PayRollVM.Payroll { PayrollId = dto.PayrollId, AuthorId = dto.AuthorId, AuthorFirstName = dto.AuthorFirstName, AuthorLastName = dto.AuthorLastName, Salary = dto.Salary }; return payroll; } public void Update(PayRollVM.Payroll payroll) { var dto = new DtoPayroll { PayrollId = payroll.PayrollId, AuthorId = payroll.AuthorId, Salary = payroll.Salary }; svc.Update(dto); } public void Delete(int id) { var dto = new DtoId { Id = id }; svc.Delete(dto); } public PayRollVM.Payroll PopulateSelectedList(PayRollVM.Payroll payroll) { var dtos = svcauth.GetAuthorTypes().ToList(); payroll.AuthorTypes.AddRange(dtos.Select(dto => new SelectListItem() { Value = dto.Value, Text = dto.Text }).ToList()); var selected = (from a in payroll.AuthorTypes.Where(a => a.Value == payroll.AuthorTypeId) select a) .SingleOrDefault(); if (selected != null) selected.Selected = true; return payroll; } } }
Tuesday, July 30, 2019 10:37 AM -
User-1740307832 posted
Thank you for your answer ! Description part from your answer is understandable, but parts with lot of codes, without the context to understand is a bit hard, mainly by trying to make a comparison to my code.
Tuesday, July 30, 2019 12:54 PM -
User1120430333 posted
Thank you for your answer ! Description part from your answer is understandable, but parts with lot of codes, without the context to understand is a bit hard, mainly by trying to make a comparison to my code.
Well lets start on there should be two types of models, a persistence model used by Entity Framework and there is a viewmodel used by the view. The other models have to do with F I would assume. So you need to remove the attribute scuff of the properties for the EF models.
namespace FCSL.Models { public class JoueurVM { public int JoueurID { 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] [Display(Name = "Poste")] public string Poste { get; set; } public string Image { get; set; } } }
namespace FCSL.Models { public class MatchVM { public int MatchID { get; set; } [Required, Display(Name = "Date du match")] [DataType(DataType.Date)] public DateTime MatchDay { get; set; } [Required, Display(Name = "Heure du match")] [DataType(DataType.Time)] [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:HH:mm}")] public DateTime MatchTime { get; set; } [Required, Display(Name = "Adversaire")] public string Adversaire { get; set; } [Required, Display(Name = "Type de match")] public string Format { get; set; } [Required, Display(Name = "Terrain")] public string Terrain { get; set; } [Display(Name = "Tresse")] public string Tresse { get; set; } [Required, Display(Name = "Mot-clé")] public string Slug { get { return Format + "_" + Adversaire + "_" + MatchDay.ToString("dd_MM_yyyy"); } } }
Tuesday, July 30, 2019 7:30 PM -
User-1740307832 posted
Ok I understand, I have to use only ViewModels. But once it's done, How I can make a link between my JoueurVM class and my MatchVM class which has to contain a list of JoueurVM object through a dropdownlist with multiselect ? I have to add another ViewModel in order to make the link between both previous classes ?
Another thing, is it better to set both classes in the same ViewModel file or I have to separate both classes ?
What I think about the process:
I have my both classes JoueurVM and MatchVM.
Then I create a new VM class called CompositionVM which could look like:public class CompositionVM { List<MatchVM> SelectedMatch {get;set;} List<JoueurVM> SelectedJoueurs {get;set;} }
Then, I need to think about my controller. How I can set the possibility to select one or many players in order to add these objets to SelectedJoueurs field.
Wednesday, July 31, 2019 7:29 AM -
User1120430333 posted
What is the Main VM of this solution that are going the fields used in the view?
In this example, the PayrollVM not only is dealing with the Salary but it also has the SelectListItem collection of AuthorID and AuthorName, , a dropdownlist for Author.in the VM used by the view
The only physical link is when the PayRoll record is created that has the selected AuthorID in the Payroll record.
As far as some physically link for other data that needs to be selected in the view is the fact that the SelectListItem is on the VM and is being populated when the view is shown using the VM.
You have several dropdownlist you were showing, the SelectedListItem is the means to get a dropdownlist on the view. I suggest you find some tutorials concering how to use the SelectedListItem with a dropdownlist.
So now, it's a matter of how are you going to get the dropdownlists populated in the VM, but first figure out how to use a dropdownlist using a SelectListItem
using System.ComponentModel.DataAnnotations; namespace PublishingCompany.Models { public class PayRollVM { public class Payroll { public int PayrollId { get; set; } public int AuthorId { get; set; } public string AuthorFirstName { get; set; } public string AuthorLastName { get; set; } [Required(ErrorMessage = "Author is required")] public string AuthorTypeId { get; set; } [Required(ErrorMessage = "Salary is required")] public int? Salary { get; set; } public List<SelectListItem> AuthorTypes { get; set; } = new List<SelectListItem>(); } public List<Payroll> Payrolls { get; set; } = new List<Payroll>(); } }
@model PayRollVM.Payroll <!DOCTYPE html> <style type="text/css"> .editor-field > label { float: left; width: 150px; } .txtbox { font-family: Arial, Helvetica, sans-serif; font-size: 12px; background: white; color: black; cursor: text; border-bottom: 1px solid #104A7B; border-right: 1px solid #104A7B; border-left: 1px solid #104A7B; border-top: 1px solid #104A7B; padding-top: 10px; } </style> <html> <head> <title>Create</title> </head> <body> <h1>Author Payroll</h1> @using (Html.BeginForm()) { @Html.ValidationSummary(false, "", new { @class = "text-danger" }) <fieldset> <legend>Create</legend> @Html.HiddenFor(model => model.PayrollId) @Html.HiddenFor(model => model.AuthorId) <div class="form-group"> <div class="editor-field"> @Html.Label("Author:") @Html.DropDownListFor(m => m.AuthorTypeId, Model.AuthorTypes, "Select....") </div> </div> <div class="form-group"> <div class="editor-field"> @Html.Label("Salary:") @Html.TextBoxFor(model => model.Salary) @Html.ValidationMessageFor(model => model.Salary, "", new { @class = "text-danger" }) </div> </div> <br /> <p> <input type="submit" name="submit" value="Save" /> <input type="submit" name="submit" value="Cancel" /> </p> </fieldset> } </body> </html>
Wednesday, July 31, 2019 8:32 AM -
User1520731567 posted
Hi Andromedae93,
According to your descriptions,you could refer to one to many entity.
You not only have to add the below in Match model:
public virtual ICollection<Joueur.Joueur> ListeJoueurs { get; set; }
but also add the below in Joueur model:
public virtual Match match{ get; set; }
If you want to implement mutiselect function,you could use @Html.ListBox and MultiSelectList,like:
define ViewBag in controller:
ViewBag.Joueur = db.Joueurs.ToList();
and render in View:
@Html.ListBox("xxx", new MultiSelectList(ViewBag.Joueur, "JoueurID", "Firstname"), new { @class = "col-md-2"})
Like the picture:
Best Regards.
Yuki Tao
Wednesday, July 31, 2019 8:45 AM -
User-1740307832 posted
Hi Yuki Tao !
Before to answer you, thank you very much for your message.
Now, I did same things like you mentionned in your answer. It seems to work because I can display ListBox, but each time I submit my form, players selected from List of players in Match class give me a NULL field all the time.
Do you want any parts from my ASP.NET project in order to get more details ?Wednesday, July 31, 2019 10:27 AM -
User1120430333 posted
Hi Yuki Tao !
Before to answer you, thank you very much for your message.
Now, I did same things like you mentionned in your answer. It seems to work because I can display ListBox, but each time I submit my form, players selected from List of players in Match class give me a NULL field all the time.
Do you want any parts from my ASP.NET project in order to get more details ?One resorts to the viewbag, becuase one doesn't know how to uses models effectively.
https://tech.trailmax.info/2013/12/asp-net-mvc-viewbag-is-bad/
It is not called Viewbag View Controller. It's called Model View Controller.
Wednesday, July 31, 2019 12:02 PM