ASP.NET MVC Relational Model Error - asp.net

I have a two relational Model first one is
Teacher.cs
public class Teachers
{
[Key]
public int TeacherID { get; set; }
public string TeacherName { get; set; }
public string TeacherLname { get; set; }
public int DepartmentID { get; set; }
public string Image { get; set; }
public Department Department { get; set; }
}
and second is Department.cs
public int DepartmentID { get; set; }
public string DepartmentName { get; set; }
public string Image { get; set; }
public List<Teachers> Teachers { get; set; }
When I'm creating a new record, I' choose a Department Name for teacher, and It's adding fine. But When I want to Delete a record there is a error like this
The ViewData item that has the key 'DepartmentID' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'.
Line 32: #Html.DropDownList("DepartmentID", String.Empty)
I don't understand what I need to do. Can you help me?
Thanks a lot
TeacherController
EDIT :
//
// GET: /Teachers/Delete/5
[Authorize(Roles = "A")]
public ActionResult Delete(int id = 0)
{
Teachers teachers = db.Teachers.Find(id);
if (teachers == null)
{
return HttpNotFound();
}
return View(teachers);
}
//
// POST: /Teachers/Delete/5
[Authorize(Roles = "A")]
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Teachers teachers = db.Teachers.Find(id);
db.Teachers.Remove(teachers);
db.SaveChanges();
return RedirectToAction("Index");
}

When you pass an empty string into Html.DropDownList() it looks for a list of items to populate the dropdownlist from the first parameter in the ViewData collection. However, there is already an item in that collection that is of type Int32.
This is one of the many confusing scenarios that happen when you use Html.DropDownList() rather than using a strongly typed model and Html.DropDownListFor()
I suggest you do this:
#Html.DropDownListFor(x => x.DepartmentID, Model.Departments)
You will need to populate your model with a Departments object that is a list of Departments

Related

asp.net - LINQ Query with relational Data

I have two tables Category and Document. See relationships in picture
See picture
I wrote the following query to select data from both tables based on relationship
public List<DocumentViewModel> All()
{
var docs = _context.Document.ToList();
List<DocumentViewModel> docList = docs.Select(x => new DocumentViewModel
{ DocumentId = x.DocumentId,
DocumentPath = x.DocumentPath,
CategoryId = x.CategoryId,
CategoryName = x.Category.CategoryName }).ToList();
return docList;
}
when this function is called , I get the following error
System.NullReferenceException: 'Object reference not set to an instance of an object.'
Here are my modals
document
public class Document
{
[Key]
public int DocumentId { get; set; }
[Required]
public string DocumentPath { get; set; }
public Nullable<int> CategoryId { get; set; }
public virtual Category Category { get; set; }
}
Category
public class Category
{
[Key]
public int CategoryId { get; set; }
[Required]
public string CategoryName { get; set; }
public virtual ICollection<Document> Documents { get; set; }
}
DocumentViewModel
public class DocumentViewModel
{
public int DocumentId { get; set; }
public string DocumentPath { get; set; }
public int? CategoryId { get; set; }
public string CategoryName { get; set; }
}
Any Idea where am doing mistake?
In this case there is no reason to get a List in memory and then do the projection, you can do this directly from EF instead. Even if there is no relationship defined EF will return null for CategoryName if you project the the results. If you go to memory first then an NRE is expected if there is no Category relationship.
public List<DocumentViewModel> All()
{
return _context.Document.Select(x => new DocumentViewModel
{ DocumentId = x.DocumentId,
DocumentPath = x.DocumentPath,
CategoryId = x.CategoryId,
CategoryName = x.Category.CategoryName}).ToList();
}
Original reason why it is failing.
There is at least one entity that does not have a corresponding relationship with Category.
You do not have lazy loading enabled (which is a good thing) and if that is the case you should use Include to return the relationship.

Field is required. even after filling the field - Validation

I am following this course, and the instructor added code to add records to the DB. The class that's going to be added to the DB looks like this:
public class Gig
{
public int Id { get; set; }
public ApplicationUser Artist { get; set; }
[Required]
public string ArtistId { get; set; }
public DateTime DateTime { get; set; }
[Required]
[StringLength(255)]
public string Venue { get; set; }
public Genre Genre { get; set; }
[Required]
public byte GenreId { get; set; }
}
And there's a view model that's attached to the view, to do the mapping, and it looks like this:
public class GigFormViewModel
{
[Required]
public string Venue { get; set; }
[Required]
[FutureDate]
public string Date { get; set; }
[Required]
[ValidTime]
public string Time { get; set; }
[Required]
public byte Genre { get; set; }
[Required]
public IEnumerable<Genre> Genres { get; set; }
public DateTime GetDateTime() => DateTime.Parse($"{Date} {Time}");
}
I have a create method, that gets form fields, and does the mapping from view model to the model itself, and then tries to add the records to the DB, my create action looks like this:
[Authorize]
[HttpPost]
public ActionResult Create(GigFormViewModel viewModel)
{
viewModel.Genres = _context.Genres.ToList();
if (!ModelState.IsValid)
{
return View(viewModel);
}
var gig = new Gig()
{
GenreId = viewModel.Genre,
ArtistId = User.Identity.GetUserId(),
DateTime = viewModel.GetDateTime(),
Venue = viewModel.Venue
};
_context.Gigs.Add(gig);
_context.SaveChanges();
return RedirectToAction("Index", "Home");
}
On the submit, I get the viewModel's property Genres and populate it with the records from the DB, then I check for ModelState.IsValid. but it's giving me:
Genres Field is required
Although I set it just 2 lines above.
Is there something wrong am doing here? Any guidance is appreciated.
Thanks.
You don't need to add a validation data annotation to Genres property in your view model because it is not a data that is set by users but it is set by you to help your view to get a collection of Genre and populate something like a dropdown list.
Firstly, remove the [Required] attribute that decorates your Genres property in GigFormViewModel.
Secondly, refactor your action method, specially the if bloc like below:
[Authorize]
[HttpPost]
public ActionResult Create(GigFormViewModel viewModel)
{
if (!ModelState.IsValid)
{
// re-populate Genres collection only is tha data is in invalid state.
viewModel.Genres = _context.Genres.ToList();
return View(viewModel);
}
// The remainder code does not change
}

ModelState is false for relations with required fields

I have two models Invoice and Client which has one to many relation. My problem is when I try to create a new invoice the modelstate becomes false because the required fields of the Client object are not filled. I tried to set default values e.g. int property {get; set;} = 1; but this causes to override the property value when I try to retrieve the relational model. I'm new to asp.net any help would be appreciated.
Here is my Client model code:
public class Client
{
public int ClientId { get; set; }//Primary key
[Required]
[Display(Name = "Client/Company Name")]
public string Name { get; set; }
//.....
public List<Invoice> Invoices { get; set; }
}
Invoice Model:
public class Invoice
{
public int InvoiceId { get; set; }//Primary key
[Required]
[Display(Name = "Client/Company Name")]
public int ClientId { get; set; }//Foreign key
public Client client { get; set; }
//....
}
My code to save an invoice:
[HttpPost]//Save quote & redirect to edit quote
public IActionResult Create(Invoice quote)
{
ViewData["Title"] = "Create Quotation";//Set title of the view!
if (ModelState.IsValid)//If quote is validated to true.
{
_context.Invoices.Add(quote);//insert quote
_context.SaveChanges();
//Redirect to edit page to add items to the invoice
return RedirectToAction("Edit", new { id = quote.InvoiceId });
}
//...
}
My code to retrieve invoices with their client names
public IActionResult Index()
{
ViewData["Title"] = "Quotations";//Set title of the view!
//Return with array of all quotations.
return View(_context.Invoices.Include(i => i.client).Where(i => i.isQuote == true).ToArray());
}
Any hints or help would be appreciated.
Found a simpler solution:
Just add virtual keyword and GG
in Invoice Model:
public class Invoice
{
public int InvoiceId { get; set; }//Primary key
[Required]
[Display(Name = "Client/Company Name")]
public int ClientId { get; set; }//Foreign key
public virtual Client client { get; set; }
//....
}

MVC ASP.NET Entity Framework Not Saving a List of Assocciated Objects

This question is in reference to the project discussed here. After resolving the previous problem I have run into a new one. When The Student object is saved, the list of courses associated with it is not saved. I can see the collection of course objects when I mouse over the student object after setting a breakpoint:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddCourseVM (AddCourseViewModel vModel)
{
Student stu = db.Students.Find(vModel.Student.ID);
foreach (Course c in vModel.PossibleCourses)
{
if (c.Selected)
{
BaseCourse bc = db.BaseCourses.Find(c.BaseCourse.ID);
c.BaseCourse = bc;
c.Student = stu;
stu.CoursesTaken.Add(c);
}
}
if (stu != null)
{
db.Entry(stu).State = EntityState.Modified; //breakpoint here
db.SaveChanges();
}
return RedirectToAction("ListTakenCourses", stu);
}
public ActionResult ListTakenCourses (Student stu)
{
List<Course> taken = stu.CoursesTaken.ToList();
foreach (Course c in taken)
{
c.BaseCourse = db.BaseCourses.Find(c.BaseCourse.ID);
}
ViewBag.CoursesTaken = taken;
return View(stu);
}
But when I pass the object to the next method, the list of courses taken comes back null. The courses are being saved to the database, I can see them when I go into the SQL Server explorer, but for some reason they are not being attached to the student object. The code for the objects:
public class Student
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string WNumber { get; set; }
public int HoursCompleted { get; set; }
public double GPA { get; set; }
public Concentration StudentConcentration { get; set; }
public virtual ICollection<Course> CoursesTaken { get; set; }
public virtual ICollection<Course> CoursesRecommended { get; set; }
}
and:
public class Course
{
public int ID { get; set; }
public string Semester { get; set; }
public Grade? Grade { get; set; }
public bool Selected { get; set; }
public BaseCourse BaseCourse { get; set; }
public Student Student { get; set; }
}
Something that may be important, but that I don't really understand: when I look at the table for the Course object in the database, there are three columns, called Student_ID, Student_ID1, and Student_ID2. I assume they relate to the student associated with the object and the two ways it can be associated (recommended or taken), but the odd thing is that Student_ID is always null, while the other two sometimes have a value and sometimes do not. I have not even begun to implement the recommendation process, so there is no way that list is being filled.
I reworked the classes and now it seems to be working. I changed the Course object to:
public class Course
{
public int ID { get; set; }
public string Semester { get; set; }
public Grade? Grade { get; set; }
public bool Selected { get; set; }
public int BaseCourseID { get; set; }
public int StudentID { get; set; }
public BaseCourse BaseCourse { get; set; }
public Student Student { get; set; }
}
and the controller methods to:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddCourseVM (AddCourseViewModel vModel)
{
Student stu = db.Students.Find(vModel.Student.ID);
foreach (Course c in vModel.PossibleCourses)
{
if (c.Selected)
{
BaseCourse bc = db.BaseCourses.Find(c.BaseCourse.ID);
c.BaseCourse = bc;
c.Student = stu;
stu.CoursesTaken.Add(c);
db.Entry(c).State = EntityState.Added;
}
}
if (stu != null)
{
db.Entry(stu).State = EntityState.Modified;
db.SaveChanges();
}
return RedirectToAction("ListTakenCourses", stu);
}
public ActionResult ListTakenCourses (Student stu)
{
List<Course> taken = db.Courses.Where(c => c.StudentID == stu.ID).ToList();
foreach (Course c in taken)
{
c.BaseCourse = db.BaseCourses.Find(c.BaseCourseID);
c.Student = stu;
stu.CoursesTaken.Add(c);
}
ViewBag.CoursesTaken = taken;
return View(stu);
}
And it is now displaying the courses I add on the next page, but it seems odd that I have to save the child objects separately from the parent and that I have to get the list from the database manually instead of being able to use the object structure. Is this intended behavior, or is there a better way of doing what I'm trying to do (add a list of child objects (courses) to a student object, save the relationship to the database, and then display the list of added objects)?
You are not "passing the object to the next method". You are serializing the object and passing it on the URL, then deserializing it on the other end with this method:
return RedirectToAction("ListTakenCourses", stu);
This is not the way to go about things. What you should be doing is passing a single id, such as the student id. Then, in ListTakenCourses you look up the student again in the database, which if you are doing your query correctly will fully populate the objects.
return RedirectToAction("ListTakenCourses", new { id = stu.StudentID });
public ActionResult ListTakenCourses (int id)
{
List<Course> taken = db.Courses.Where(c => c.StudentID == id).ToList();
//...
}

How to Preserve/Protect Certain Fields in Edit in ASP.NET MVC

In an Edit action in ASP.NET MVC, certain fields can be hidden from user with HiddenFieldFor. However this doesn't protect the fields (such as ID, data creation date) from being edited.
For example, a model Student has fields Id, Name and Birthday. I like to allow users to update the Name, but not Id nor Birthday.
For an Edit action like this
public ActionResult Edit(Student student)
{
if (ModelState.IsValid)
{
db.Entry(student).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(student);
}
How can I prevent Id and Birthday from being edited? Thanks!
You should use a view model which contains only the properties that you want to be edited:
public class EditStudentViewModel
{
public string Name { get; set; }
}
and then:
public ActionResult Edit(StudentViewModel student)
{
...
}
Another technique which I don't recommend is to exclude certain properties from binding:
public ActionResult Edit([Bind(Exclude = "Id,Birthday")]Student student)
{
...
}
or include:
public ActionResult Edit([Bind(Include = "Name")]Student student)
{
...
}
I assume you have to have the properties in your Model so that in View you can use them to render useful information e.g. an ActionLink with ID or some readonly Text.
In this case you can define your model with an explicit binding:
[Bind(Include = "Name")]
public class Student
{
int Id { get; set; }
int Name { get; set; }
DateTime Birthday { get; set; }
}
This way when updating your model, if the user submits an extra Id it will not be bound.
Another idea I like is having your model know its bindings per scenario and have them compiler validated:
public class ModelExpression<T>
{
public string GetExpressionText<TResult>(Expression<Func<T, TResult>> expression)
{
return ExpressionHelper.GetExpressionText(expression);
}
}
public class Student
{
public static string[] EditBinding = GetEditBinding().ToArray();
int Id { get; set; }
int Name { get; set; }
DateTime Birthday { get; set; }
static IEnumerable<string> GetEditBinding()
{
ModelExpression<Student> modelExpression = new ModelExpression<Student>();
yield return modelExpression.GetExpressionText(s => s.Name);
}
}
This way in your Action when calling TryUpdateModel you can pass this information.

Resources