NullReferenceException When Using a ViewModel - asp.net-mvc

I'm trying to use a ViewModel in my ASP.NET MVC project, but whenever I use it, I get a null reference error for some reason. The latest situation where I have this problem, is here (it works fine when I use the Model itself, but when I use the ViewModel, I get that error):
My Model:
public string FirstName { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
public string MaritalStatuse { get; set; }
public DateTime DateOfBirth { get; set; }
My ViewModel:
public class PersonFormViewModel
{
public Person Person { get; set; }
public string Title
{
get
{
if (Person != null && Person.Id != 0)
return "Edit";
return "New";
}
}
}
My Controler:
public ActionResult Edit(int id)
{
var person = _context.Persons.FirstOrDefault(p => p.Id == id);
var viewModel = new PersonFormViewModel
{
Person =
{
FirstName = person.FirstName,
LastName = person.LastName,
Gender = person.Gender,
DateOfBirth = person.DateOfBirth,
MaritalStatuse = person.MaritalStatuse
}
};
return View(viewModel);
}
When running this code, the "viewModel" in the Controller is null, even though the "person" is not. Can anyone please help me with this? (I am new to ASP.NET, as you might've guessed).

I think Person needs to be instantiated before assigning values to it.
public ActionResult Edit(int id)
{
var person = _context.Persons.FirstOrDefault(p => p.Id == id);
var viewModel = new PersonFormViewModel
{
Person = new Person
{
FirstName = person.FirstName,
LastName = person.LastName,
Gender = person.Gender,
DateOfBirth = person.DateOfBirth,
MaritalStatuse = person.MaritalStatuse
}
};
return View(viewModel);
}

Related

MVC Core How to correctly Edit Existing model using viewmodel

I am trying to Edit existing instance of class project using a viewmodel. I think I have no issues in the GET method of the Edit (everything renders fine on the View) but I am struggling with the POST method. The problem is the POST method creates new instance of the project instead updating the current one.
This is my model:
public class Project
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Budget { get; set; }
public string BusinessCase { get; set; }
public string StartDate { get; set; }
public string FinishDate { get; set; }
public int ClientId { get; set; }
public Client Client { get; set; }
public ICollection<ProjectMember> ProjectMembers { get; set; }
public Project()
{
}
}
}
This is my CreateProjectViewModel (some of the attributes are not used in the controller code below):
public class CreateProjectViewModel
{
//project
public int ProjectId { get; set; }
public string Name { get; set; }
public string Budget { get; set; }
public string BusinessCase { get; set; }
public string StartDate { get; set; }
public string FinishDate { get; set; }
//clients
public int ClientId { get; set; }
public Client Client { get; set; }
public ICollection<ProjectMember> ProjectMembers { get; set; }
//Members
public int MemberId { get; set; }
public Member Member { get; set; }
public Project Project { get; set; }
public List<SelectListItem> Members { get; set; }
public IEnumerable<SelectListItem> Clients { get; set; }
public IEnumerable<int> SelectedMembers { get; set; }
public CreateProjectViewModel() { }
}
}
This is My Edit GET method in ProjectController:
public async Task<IActionResult> Edit(int? id)
{
Project project = await _context.Project
.FirstOrDefaultAsync(m => m.ProjectId == id);
var members = _context.Member
.Select(m => new SelectListItem { Value = m.MemberId.ToString(), Text = m.MemberName }).ToList();
var clients = _context.Client
.Select(r => new SelectListItem { Value = r.ClientId.ToString(), Text = r.Name }).ToList();
CreateProjectViewModel viewmodel = new CreateProjectViewModel
{
Name = project.Name,
Budget = project.Budget,
BusinessCase = project.BusinessCase,
StartDate = project.StartDate,
FinishDate = project.FinishDate,
Project = project,
Clients = clients,
Members = members
};
return View(viewmodel);
This is my Edit POST method in ProjectController which is wrongly creating a new project instead of updating the current project:
(The controller is trying to save values to Project Model and at the same time in Join table containing MemberId and ProjectID - this works fine when creating a project, not sure if correct for updating)
public IActionResult Edit(int? id, CreateProjectViewModel model)
{
Project project = _context.Project
.Single(m => m.ProjectId == id);
//this is to post in many-to-many join table between Member and Project
var projectID = project.ProjectId;
var memberID = model.MemberId;
IList<ProjectMember> existingItems = _context.ProjectMembers
.Where(cm => cm.MemberId == memberID)
.Where(cm => cm.ProjectId == projectID).ToList();
if (existingItems.Count == 0)
{
foreach (var selectedId in model.SelectedMembers)
{
_context.ProjectMembers.Add(new ProjectMember
{
ProjectId = project.ProjectId,
MemberId = selectedId,
});
}
}
//this is to update the values in the project which refers to ProjectID
project.ProjectId = model.ProjectId;
project.Name = model.Name;
project.Budget = model.Budget;
project.BusinessCase = model.BusinessCase;
project.StartDate = model.StartDate;
project.FinishDate = model.FinishDate;
project.ClientId = model.ClientId;
_context.Entry(project).State = EntityState.Modified;
_context.SaveChanges();
return RedirectToAction("Index");
}
Can you guys advise me what should be changed in either method to get the expected result?
Thanks a lot.
After some digging done this is the working code.
Edit POST Method:
public IActionResult Edit(int? id, CreateProjectViewModel viewmodel)
{
if (ModelState.IsValid)
{
var project = _context.Project
.SingleOrDefault(m => m.ProjectId == id);
//this is to update the Project from the viewmodel
project.Name = viewmodel.Name;
project.Budget = viewmodel.Budget;
project.BusinessCase = viewmodel.BusinessCase;
project.StartDate = viewmodel.StartDate;
project.FinishDate = viewmodel.FinishDate;
project.ClientId = viewmodel.ClientId;
//code below is to validate if the matched primary keys of Project and Member are not already in ProjectMembers table
foreach (var selectedId in viewmodel.SelectedMembers)
{
var projectID = project.ProjectId;
var memberID = selectedId;
IList<ProjectMember> existingItems = _context.ProjectMembers
.Where(cm => cm.MemberId == memberID)
.Where(cm => cm.ProjectId == projectID).ToList();
if (existingItems.Count == 0)
{
//this is to add new entry into ProjectMembers table
_context.ProjectMembers.Add(new ProjectMember
{
ProjectId = project.ProjectId,
MemberId = selectedId,
});
}
}
_context.SaveChanges();
}
return RedirectToAction("Index");

Can't access foreignkey data of applicationUser, 'ICollection' does not contain a definition for 'Message'

Trying to add/Edit related data properties of applicationUser so notes can be recorded for users
ApplicationUser.cs
public class ApplicationUser : IdentityUser
{
public virtual ICollection<UserNote> UserNotes { get; set; }
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
}
UserNote.cs
public class UserNote
{
[Key]
public int UserNoteId { get; set; }
public string Message { get; set; }
public string ApplicationUserID { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
}
Controller
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(string id, ApplicationUser applicationUser)
{
if (ModelState.IsValid)
{
var userFromDb = _db.ApplicationUser
.Include(n => n.UserNotes)
.Include(r => r.UserRoles)
.AsNoTracking().FirstOrDefault(m => m.Id == id);
UserNote note = _db.UserNote.Single(x => x.UserNoteId == 1);
userFromDb.LastName = applicationUser.LastName;
userFromDb.FirstName = applicationUser.FirstName;
//Error accessing related data properties
userFromDb.UserNotes.Message = applicationUser.UserNotes.Message;
await _db.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(applicationUser);
}
I received 'ICollection' does not contain a definition for 'Message' and no accessible extension method 'Message' accepting a first argument of type 'ICollection'
UserNotes is a collection hence you need to access a certain index before getting its Message property.
// Use .Count to check if the list contains any number.
if(applicationUser.UserNotes.Count != 0){
// .FirstOrDefault() will return the first object in that list, if there is none, it would return null.
userFromDb.UserNotes.FirstOrDefault().Message = applicationUser.UserNotes.FirstOrDefault().Message;
}else{
// this is just to let you know if it's empty just in case if you don't debug
return Content("Your collection is empty.");
}
You could also use .IndexOf() or .Where() with your list if you're looking for a specific record.
It's not entirely clear to me with what you're trying, if you're adding a new note or you're editing an existing note. Let me know further in the comments if this doesn't work for you.
Try to edit the related data properties of applicationUser as follows :
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(string id, ApplicationUser applicationUser)
{
if (ModelState.IsValid)
{
var userFromDb = _context.ApplicationUser
.Include(n => n.UserNotes)
.AsNoTracking().FirstOrDefault(m => m.Id == id);
//UserNote note = _context.UserNote.Single(x => x.UserNoteId == 1);
userFromDb.LastName = applicationUser.LastName;
userFromDb.FirstName = applicationUser.FirstName;
foreach (var usernote in applicationUser.UserNotes.ToList())
{
if (userFromDb.UserNotes.Any(un => un.UserNoteId == usernote.UserNoteId))
{
userFromDb.UserNotes.Find(un => un.UserNoteId == usernote.UserNoteId).Message = usernote.Message;
}
}
_context.Update(userFromDb);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(applicationUser);
}

Two ViewModel Tuple

I have two viewmodels. One displays employee information and the other displays information about any emergency contacts added to the employee (1-to-Many).
I am managing to display their information separately, but for some reason when I try to combine them in one view things go wrong.
At first I thought the best approach to containing all of that information in one view would be to just create 1 ViewModel and add all of the required fields. But then when I added emergency contact fields which are the "many" part of the relationship, I wasn't sure how to go about iterating through them inside same returned model (1-to-many). Because of that I tried to attempt creating a combined view using Tuple<>. Sadly, this didn't go to well either. Any help greatly appreciated.
Model Class: PersonInfoViewModel
public class PersonInfoViewModel
{
public int PersonId { get; set; }
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Display(Name = "Surname")]
public string LastName { get; set; }
}
Model Class: EmergencyContactViewModel
public class EmergencyContactViewModel
{
public int EmergencyContactId { get; set; }
[Display(Name = "First Name")]
public string EmergencyContactFirstName { get; set; }
[Display(Name = "Surname")]
public string EmergencyContactLastName { get; set; }
}
Model Class: CombinedView
public class CombinedView
{
public Person PersonC { get; set; }
public List<EmergencyContact> EmergencyContactsList { get; set; }
}
Controller Action: CombinedView
public ActionResult CombinedView(int id)
{
var person = _context.People.Find(id);
ViewBag.PersonId = _context.People.Find(id);
if (person != null)
{
List<EmergencyContactViewModel> ec = new List<EmergencyContactViewModel>(id);
PersonInfoViewModel pi = new PersonInfoViewModel();
return View(Tuple.Create(pi, ec));
}
return View();
}
Controller Action: PersonInfo
public async Task<ActionResult> PersonInfoViewModel(int? id)
{
var person = await db.People.FindAsync(id);// pull record from DB by id
if (person != null)
return View(new PersonInfoViewModel()
{
FirstName = person.FirstName,
LastName = person.LastName
});
return View();
}
Controller Action: EmergencyContactViewModel
public ActionResult EmergencyContactViewModel(int? id)
{
List<EmergencyContact> emergecyContactsList = db.EmergencyContacts.ToList();
List<EmergencyContactViewModel> personVmList = emergecyContactsList.Select(x =>
new EmergencyContactViewModel
{
EmergencyContactId = x.EmergencyContactId,
EmergencyContactLastName = x.EmergencyContactLastName,
EmergencyContactFirstName = x.EmergencyContactFirstName,
}).ToList();
return View(personVmList);
}
So yeah, the answer was quite simple. As Stephen has mentioned above, all I had to do was return an instance of CombinedView. In case someone else is stuck like me, this might help you:
public ActionResult CombinedView(int id)
{
var person = _context.People.Find(id);
ViewBag.PersonId = _context.People.Find(id);
if (person != null)
{
return View(new CombinedView()
{
PersonC = person,
EmergencyContactsList = new List<EmergencyContact>(person.EmergencyContacts)
});
}
return View();
}
Also, in the CombinedView:
#model [appname].Models.CombinedView
Additionally, it's worth noting I've tried to do repeat the same steps, but this time with the actual ViewModels as mentioned in the title of this question, but so far have failed.
public ActionResult CombinedView2(int id)
{
var person = _context.People.Find(id);
if (person != null)
{
return View(new CombinedView2()
{
PersonInfo = new PersonInfoViewModel(),
EmergencyContactList = new List<EmergencyContactViewModel>()
});
}
return View();
}

mvc pass user id to table from FormsAuthentication

the problem is when i try to created item give me this error
The INSERT statement conflicted with the FOREIGN KEY constraint
here's my login action
public ActionResult Login(UserTb U, string ReturnUrl)
{
var count = db.UsersTb.Where(x => x.NameUser == U.NameUser && x.PassUser == U.PassUser).Count();
if (count == 0)
{
ViewBag.Msg = "invalde user";
return View();
}
else
{
FormsAuthentication.SetAuthCookie(U.NameUser + "|" + U.IdUser, false);
return RedirectToAction("Index", "Home");
}
}
and i want pass user id to table named items
and user id on this table named Uid so i used this below
[Table("Item")]
public class Item
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int id { get; set; }
[Required]
public string name { get; set; }
public string info { get; set; }
public decimal price { get; set; }
public int? Uid { get; set; }
public int CatId { get; set; }
public int? CountryId { get; set; }
public int? StateId { get; set; }
public int? CityId { get; set; }
[ForeignKey("Uid")]
public virtual UserTb UserTb { get; set; }
[ForeignKey("CatId")]
public virtual Category Category { get; set; }
[ForeignKey("CountryId")]
public virtual Country Country { get; set; }
[ForeignKey("StateId")]
public virtual States States { get; set; }
[ForeignKey("CityId")]
public virtual City City { get; set; }
public Item()
{
Uid = Convert.ToInt32(HttpContext.Current.User.Identity.Name.Split('|')[1]);
}
}
and here my create action result
public ActionResult Create()
{
var catlist = db.Categories.Select(x => new { id = x.id, name = x.name }).ToList();
SelectList sl = new SelectList(catlist.AsEnumerable(), "id", "name");
ViewBag.SelectCategories = sl;
var countrylist = db.CountryTb.Select(x => new { id = x.id, name = x.name }).ToList();
SelectList s2 = new SelectList(countrylist.AsEnumerable(), "id", "name");
ViewBag.SelectCountry = s2;
return View();
}
[HttpPost]
public ActionResult Create(Item i)
{
db.Items.Add(i);
db.SaveChanges();
return RedirectToAction("Index");
}
if another way to save user id when login and get it when i need it on other table without problem please tell me how because i'm newbie in mvc
Just use the UserId.ToString() as name.
FormsAuthentication.SetAuthCookie(U.IdUser.ToString(), false);
Later you can get the id by using :
HttpContext.Current.User.Identity.Name
EDIT :
Set it initially like this :
var userDetails = U.NameUser.ToString() + " | " + U.IdUser.ToString();
FormsAuthentication.SetAuthCookie(userDetails, false);
Later you can get the id by using :
var savedUserDetails = HttpContext.Current.User.Identity.Name;

MVC5: Foreign Key and data access

I am looking for selecting a list from my table based on another table. I need to retrieve system names that are part of a particular system family. i have already added foreign keys. I created a ViewModel containing both these classes but it throws a null pointer exception. I am new to MVC and I am not sure where I am wrong.
Model Class : Systems
public class Systems
{
public int SystemsID { get; set; }
public string SystemName { get; set; }
public DateTime CreatedOn { get; set;}
public string CreatedBy { get; set; }
public int SystemFamilyID { get; set; }
public virtual SystemFamily SystemFamily { get; set; }
}
Class SystemFamily
public class SystemFamily
{
public int SystemFamilyID { get; set;}
public int SystemsID {get;set;}
public string FamilyName { get; set; }
public DateTime DateCreated { get; set; }
public string CreatedBy { get; set; }
public virtual ICollection<Systems> Systems { get; set; }
}
ViewSystem is a method in my SystemFamilyController.
public ActionResult ViewSystem(int? id)
{
var viewmodel = new Sys_SysFam();
ViewBag.SystemFamilyID = id.Value;
//if (id != null)
//{
// ViewBag.SystemFamilyID = id.Value;
// viewmodel.Systems = viewmodel.SystemFamily.Where(
// i => i.SystemFamilyID == id.Value).Single().Systems;
//}
return View(viewmodel);
}
the view :
#model SystemFam_System.ViewModel.Sys_SysFam
#{
ViewBag.Title = "ViewSystem";
}
<h2>ViewSystem</h2>
<p>#ViewBag.SystemFamilyID</p>
<table>
#foreach (var item in Model.Systems)
{
string selectedRow = "";
if (item.SystemFamilyID == ViewBag.SystemFamilyID)
{
//{
// selectedRow = "success";
//}
<tr class="#selectedRow">
<td>
#item.SystemName
</td>
<td>
#item.SystemsID
</td>
<td>
#item.SystemFamily
</td>
</tr>
}
}
</table>
I get null pointer Exception. I want to view the system that belongs to a particular family in view system.
Thanks in advance!!
Vini
Edit :
public class Sys_SysFam
{
public IEnumerable<Systems> Systems { get; set; }
public SystemFamily SystemFamily { get; set; }
}
Ok i have checked Sys_SysFam class too. As per your current code it will always throw null reference exception becasue in your controller code you are using:
public ActionResult ViewSystem(int? id)
{
var viewmodel = new Sys_SysFam();
ViewBag.SystemFamilyID = id.Value;
//if (id != null)
//{
// ViewBag.SystemFamilyID = id.Value;
// viewmodel.Systems = viewmodel.SystemFamily.Where(
// i => i.SystemFamilyID == id.Value).Single().Systems;
//}
return View(viewmodel);
}
here you are creating an object of Sys_SysFam as viewmodel and as your if part is commented so you are returning same viewmodel in which viewmodel.Systems will always be null. Here i did not see any request to database for getting the data from db but i think your data in viewmodel will come from database and if i uncomment your if condition then too you are not sending any request to database you are using same viewmodel object created above.
viewmodel.Systems = viewmodel.SystemFamily.Where(
i => i.SystemFamilyID == id.Value).Single().Systems;
in right side you are using viewmodel.SystemFamily with where condition but as viewmodel.SystemFamily is null it will always throw exception. Your solution should be something like this:
public ActionResult ViewSystem(int? id)
{
DataContext context = new DataContext();
var viewmodel = new Sys_SysFam();
ViewBag.SystemFamilyID = id.Value;
if (id != null)
{
ViewBag.SystemFamilyID = id.Value;
var sysFamily = context.SystemFamily.Include(x => x.Systems).FirstOrDefault(x => x.SystemFamilyID == id.Value);
if (sysFamily != null)
{
viewmodel.Systems = sysFamily.Systems;
}
}
return View(viewmodel);
}
here first i am creating object of DataContext which is my main context to access the database using entity framework. so first i will get the system family based on passed id from database and if system family is not null then i will set the data of systems in viewmodel. Include method will bring data for Systems based on system family from database.
Also improve your Sys_SysFam class to initialize systems so that it will not throw exception in your view when there is no data in viewmodel.Systems like this:
public class Sys_SysFam
{
public Sys_SysFam()
{
this.Systems = new List<Systems>();
}
public SystemFamily SystemFamily { get; set; }
public IEnumerable<Systems> Systems { get; set; }
}
Hope this will help you.
Remove SystemsID property from SystemFamily class because it is not used for ICollection virtual property. so your SystemFamily class should be like this:
public class SystemFamily
{
public int SystemFamilyID { get; set;}
public string FamilyName { get; set; }
public DateTime DateCreated { get; set; }
public string CreatedBy { get; set; }
public virtual ICollection<Systems> Systems { get; set; }
}
A friend of mine could find me a way. But it doesnt use any ViewModel. I would like to know how it need to be done with ViewModel as well..
public ActionResult ViewSystem(int? id)
{
var model = from item in db.Systems
orderby item.SystemsID
where item.SystemFamilyID == id
select item;
return View(model);
}

Resources