displaying the input box for a different model - c#

I have following two model classes:
public partial class EmployeeInfo
{
public string LastName { get; set; } = null!;
public string FirstName { get; set; } = null!;
public virtual ICollection<EmergencyInfo> EmergencyInfos { get; } = new List<EmergencyInfo>();
}
public partial class EmergencyInfo
{
public string emailAddress { get; set; } = null!;
public string PhoneNumber { get; set; } = null!;
public virtual EmployeeInfo EmployeeInfo { get; set; } = null!;
}
My Razor view to create a new employee looks like this:
#model AckPackage.Models.EmployeeInfo
#{
ViewData["Title"] = "Create";
}
<div class="form-group row">
<div class="col-sm-4">
<label asp-for="LastName" class="control-label"></label>
<input asp-for="LastName" class="form-control input-lg" />
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<div class="col-sm-4">
<label asp-for="FirstName" class="control-label"></label>
<input asp-for="FirstName" class="form-control input-lg" />
<span asp-validation-for="FirstName" class="text-danger"></span>
</div>
</div>
Can I display the input box for emeregencyInfo, emailAddress and phone number in above view. I want to show both the input box for emailAddress and PhoneNumber in the same EmployeeInfo view so that users can input their information.
Any help will be highly appreciated.

You can create a compound class and pass it to the view as data model:
public class Info
{
public EmployeeInfo? Employee { get; set; }
public EmergencyInfo? Emergency { get; set; }
}
The view code:
#model Info;
#{
ViewData["Title"] = "Create";
}
<form method="post" asp-action="Create">
<div class="form-group row">
<div class="col-sm-4">
<label asp-for="#Model.Employee.LastName" class="control-label"></label>
<input asp-for="#Model.Employee.LastName" class="form-control input-lg" />
<span asp-validation-for="#Model.Employee.LastName" class="text-danger"></span>
</div>
...
<button type="submit">Save</button>
</form
On the controller side:
[HttpPost]
public IActionResult Create(Info data)
{
// Processing the entered data
....
return View(data);
}

Related

Class object property model is null?

I am building a to-do-list project as a practice. It has one relationship to the Member model and the Member model has many relationships to 'to-do-list'
Member controller create method works without any issue but the to-do-list controller throws model state is invalid on Member object property of to-do-list
ToDoList
using System.ComponentModel.DataAnnotations;
namespace To_Do_List.Models
{
public class ToDoList
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(200, MinimumLength = 1, ErrorMessage = "To Do List Item cannot be longer than 200 characters.")]
public string Title { get; set; }
public string Description { get; set; }
[DataType(DataType.Date)]
public DateTime DueDate { get; set; }
public string Priority { get; set; }
public int AssignToId { get; set; }
public Member AssignTo { get; set; }
[Required]
[StringLength(15, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.")]
[RegularExpression("^(Completed|Not Completed)$", ErrorMessage = "The status must be Completed or Not Completed")]
public string Status { get; set; }
}
}
Member
using Microsoft.Build.Framework;
namespace To_Do_List.Models
{
public class Member
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
[Required]
public ICollection<ToDoList> ToDoLists { get; set; }
}
}
create method of to do list controller
public async Task<IActionResult> Create([Bind("Id,Title,Description,DueDate,Priority,AssignToId,AssignTo, Status")] ToDoList toDoList)
{
if (ModelState.IsValid)
{
_context.Add(toDoList);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
ViewData["AssignToId"] = new SelectList(_context.Members, "Id", "Id", toDoList.AssignToId);
return View(toDoList);
}
View method of to do list
public IActionResult Create()
{
return View();
}
Create.cshtml
#model To_Do_List.Models.ToDoList
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>ToDoList</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Description" class="control-label"></label>
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="DueDate" class="control-label"></label>
<input asp-for="DueDate" class="form-control" />
<span asp-validation-for="DueDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Priority" class="control-label"></label>
<input asp-for="Priority" class="form-control" />
<span asp-validation-for="Priority" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Status" class="control-label"></label>
<input asp-for="Status" class="form-control" />
<span asp-validation-for="Status" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Member controller create method works without any issue but the
to-do-list controller throws model state is invalid on Member object
property of to-do-list
Well, I have succssfully reproducced your issue and your ModelState.IsValid false is logical. Because, when you leave public Member AssignTo { get; set; } with default annotation it means required. whilist, you haven't pass any property from your view consequently, your bindings always be false as you have defined it into the [Bind] property that is AssignTo However, value has not been pass to it.
How to resolve:
In this scenario, you either has to pass AssignTo to your create action or make it nullable using ? annotation as following:
public Member? AssignTo { get; set; }
Note: If you don't want to set AssignTo as nullable then you have to pass all property value from your view as following:
<div class="form-group">
<label asp-for="AssignTo.Name" class="control-label"></label>
<input asp-for="AssignTo.Name" class="form-control" />
<span asp-validation-for="AssignTo.Name" class="text-danger"></span>
</div>
Here, I am passing only AssignTo.Name you have to pass rest of the values.
Output:
Note: If you would like to know more details on it you could check our official document here.

Why File upload is stopped working after convert multiple models into a view model entity framework?

Controller
public async Task<IActionResult> Create(IFormFile? StaffPhoto, CollectionViewModel collectionModel)
{
if (StaffPhoto != null){...} // issue is StaffPhoto value is null
}
View Model
namespace Website.Models
{
public class CollectionViewModel
{
public Staff staff { get; set; }
public Contact contact { get; set; }
}
}
Entity Model
public class Staff
{
public int StaffId { get; set; }
[DisplayName("First Name")]
[Required]
public string StaffFirstName { get; set; }
[DisplayName("Last Name")]
[Required]
public string StaffLastName { get; set; }
[DisplayName("Photo")]
public string? StaffPhoto { get; set; }
}
View
#model CollectionViewModel
<form asp-action="Create" enctype="multipart/form-data" method="post" class="row g-3 mt-0">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="col">
<label asp-for="staff.StaffFirstName" class="form-label"></label>
<input asp-for="staff.StaffFirstName" class="form-control" />
<span asp-validation-for="staff.StaffFirstName" class="text-danger"></span>
</div>
<div class="col">
<label asp-for="staff.StaffLastName" class="form-label"></label>
<input asp-for="staff.StaffLastName" class="form-control" />
<span asp-validation-for="staff.StaffLastName" class="text-danger"></span>
</div>
<div class="col-md-3">
<label asp-for="staff.StaffPhoto" class="form-label"></label>
<input asp-for="staff.StaffPhoto" type="file" accept="image/*" class="form-control" />
<span asp-validation-for="staff.StaffPhoto" class="text-danger"></span>
#{if (ViewBag.fileUploadErrorMessage != null)
{
<span class="text-danger">#ViewBag.fileUploadErrorMessage</span>
}
}
</div>
<div class="col">
<input type="submit" value="Create" class="btn btn-primary" />
<a asp-action="Create" class="btn btn-secondary">Reset All</a>
</div>
</form>
You should add IFormFile in model.
public class CollectionViewModel
{
public Staff staff { get; set; }
public IFormFile StaffPhoto { get; set; }
public Contact contact { get; set; }
}
set StaffPhoto to asp-for in view.
<input asp-for="StaffPhoto" type="file" accept="image/*" class="form-control" />

Automatically select the options of multiple selection drop down for the edit page in ASP.NET Core 6.0 Razor Pages

I am beginner in ASP.NET Core. I need help with automatically selecting the options of a multi-selection dropdown.
I have a class model and a class can have multiple students. When I am creating the class, I have a dropdown that shows the student list and I can add the students to the class by selecting the options form the drop down.
But I am having issues automatically selecting those values for the edit page. The dropdown should keep those students selected from the list when the edit page loads so that the user can see which students are currently enrolled in the class. I am only seeing the students that are enrolled in the class as option of the dropdown. It should show all the students in the list but select the ones that belongs to the class.
My class model:
public class ClassModel
{
[Key]
[Required]
[Display(Name ="Class ID")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ClassID { get; set; }
//[ForeignKey("UserProfile")]
//[Display(Name = "User ID")]
//public virtual int ID { get; set; }
[Required]
public string Description { get; set; }
[Required]
public int Occurence { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime Startdate { get; set; }
[Required]
[DataType(DataType.Time)]
public DateTime From { get; set; }
[Required]
[DataType(DataType.Time)]
//[GreaterThanOrEqualTo("From")]
public DateTime To { get; set; }
[Required]
[DataType(DataType.Currency)]
public double Fees { get; set; }
[DisplayFormat(NullDisplayText = "No Instructor Assigned")]
[ForeignKey("InstructorID")]
public virtual int InstructorID { get; set; }
public Instructor? Instructor { get; set; }
// [DisplayFormat(NullDisplayText = "No Student Assigned")]
[NotMapped]
public ICollection<int> StudentID { get; set; }
public ICollection<Enrollment>? Enrollment { get; set; }
}
EditClass.cshtml.cs code:
public class EditModel : PageModel
{
private readonly TestProject.Data.TestProjectContext _context;
public EditModel(TestProject.Data.TestProjectContext context)
{
_context = context;
}
[BindProperty]
public Model.ClassModel Class_Info { get; set; }
[BindProperty]
public ClassModelStudent Class_Student { get; set; }
public Model.Instructor instructor { get; set; }
public SelectList Instructors { get; set; }
public SelectList Students { get; set; }
public MultiSelectList Class_Student { get; set; }
public SelectList SelectedStudents { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Class_Info = await _context.Class.FirstOrDefaultAsync(m => m.ClassID == id);
var instructors = from i in _context.Instructor
orderby i.FirstName
select i;
Instructors = new SelectList(instructors, "InstructorID", "FirstName");
var selectedstudents = from cs in _context.ClassModelStudent
join s in _context.User_Profile on cs.StudentsStudentID equals s.StudentID
select new { cs.StudentsStudentID, s.FullName, s.PhoneNumber };
SelectedStudents = new SelectList(selectedstudents, "StudentsStudentID", "FullName");
Class_Student = SelectedStudents;
var students = from cs in _context.ClassModelStudent
join s in _context.User_Profile on cs.StudentsStudentID equals s.StudentID
select new { cs.StudentsStudentID, s.FullName, s.PhoneNumber };
Students = new SelectList(students, "StudentsStudentID", "FullName", "PhoneNumber");
// Class_Student.StudentsStudentID = id;
//from s in _context.ClassModelStudent
// orderby s.StudentsStudentID
// select s;
if (Class_Info == null)
{
return NotFound();
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Class_Info).State = EntityState.Modified;
_context.Attach(Class_Student).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!Class_InfoExists(Class_Info.ClassID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool Class_InfoExists(int id)
{
return _context.Class.Any(e => e.ClassID == id);
}
}
EditClass.cshtml view markup:
#page
#model TestProject.Pages.Classes.EditModel
#{
ViewData["Title"] = "Edit";
Layout = "~/Pages/Shared/_Layout.cshtml";
}
<h1>Edit</h1>
<h4>Class_Info</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Class_Info.ClassID" />
#*<div class="form-group">
<label asp-for="Class_Info.ID" class="control-label"></label>
<input asp-for="Class_Info.ID" class="form-control" />
<span asp-validation-for="Class_Info.ID" class="text-danger"></span>
</div>*#
<div class="form-group">
<label asp-for="Class_Info.Description" class="control-label"></label>
<input asp-for="Class_Info.Description" class="form-control" />
<span asp-validation-for="Class_Info.Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Class_Info.Occurence" class="control-label"></label>
<input asp-for="Class_Info.Occurence" class="form-control" />
<span asp-validation-for="Class_Info.Occurence" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Class_Info.Startdate" class="control-label"></label>
<input asp-for="Class_Info.Startdate" class="form-control" />
<span asp-validation-for="Class_Info.Startdate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Class_Info.From" class="control-label"></label>
<input asp-for="Class_Info.From" class="form-control" />
<span asp-validation-for="Class_Info.From" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Class_Info.To" class="control-label"></label>
<input asp-for="Class_Info.To" class="form-control" />
<span asp-validation-for="Class_Info.To" class="text-danger"></span>
</div>
<div class="form-group">
#* <label asp-for="instructor.FirstName" class="control-label"></label>
<input asp-for="instructor.FirstName" class="form-control" />*#
<label asp-for="Class_Info.InstructorID" class="control-label"></label>
<select asp-for="Class_Info.InstructorID" class="form-control" asp-items="#Model.Instructors">
<option value="">-- Select --</option>
</select>
<span asp-validation-for="Class_Info.InstructorID" class="text-danger"></span>
</div>
<div class="form-group">
#* <label asp-for="instructor.FirstName" class="control-label"></label>
<input asp-for="instructor.FirstName" class="form-control" />*#
-- Select --
<div>
<a asp-page="./Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Please advise. I would really appreciate your help.

How to populate dropdown list from nested Models? (Asp.net Core MVC)

I have a nested model and I use one model named (BootstrapTool) to make the view strongly typed.
I use ViewBag to populate a dropdown list with the nested Model named (BootstrapCategory). But my problem is when I select from it always returns validation null exception.
BootstrapTool Model
public class BootstrapTool
{
public Guid Id { get; set; }
[MaxLength(100,ErrorMessage ="Max 100")]
[Required]
public string tool { get; set; }
[MaxLength(300, ErrorMessage = "Max 300")]
[Required]
public string descreption { get; set; }
[Required]
public string html { get; set; }
[Required] // here where nisting the other Model BootstrapCategory
public BootstrapCategory category { get; set; }
}
BootstrapCategory Model
public class BootstrapCategory
{
[Key]
public Guid Id { get; set; }
[MaxLength(20)]
[Required]
public string Category { get; set; }
}
The Controller
public IActionResult Create()
{
List<BootstrapCategory> bootstrapCategories = _context.bootstrapCategories.ToList();
ViewBag.bpCategories = new SelectList(bootstrapCategories, "Id", "Category");
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(BootstrapTool bootstrapTool)
{
if (ModelState.IsValid)
{
bootstrapTool.Id = Guid.NewGuid();
_context.Add(bootstrapTool);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(bootstrapTool);
}
Here where the dropdown list is to display BootstrapCategory
The View
#model BootstrapTool
<div class="row">
<div class="col-10 offset-1">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="gap-2 d-flex justify-content-between">
<div class="form-group w-50">
<label asp-for="tool" class="control-label"></label>
<input asp-for="tool" class="form-control" />
<span asp-validation-for="tool" class="text-danger"></span>
</div>
<div class="form-group w-50">
<label class="control-label">category</label>
<select asp-for="category.Id" class="form-control" asp-items="#ViewBag.bpCategories">
<option value="" disabled hidden selected>Select Section ...</option>
</select>
<span asp-validation-for="category" class="text-danger"></span>
#*<select asp-for="Id" asp-items="#Html.GetEnumSelectList<Enum>()" class="form-control">
<option value="" hidden disabled selected> -- please selecgt -- </option>
</select>*#
</div>
</div>
<div class="form-group">
<label asp-for="descreption" class="control-label"></label>
<textarea asp-for="descreption" class="form-control" rows="2"></textarea>
<span asp-validation-for="descreption" class="text-danger"></span>
</div>
<div class="form-group mb-2">
<label asp-for="html" class="control-label"></label>
<textarea asp-for="html" class="form-control" rows="5"></textarea>
<span asp-validation-for="html" class="text-danger"></span>
</div>
<div class="form-group mt-3">
<input type="submit" value="Create" class="btn btn-primary d-grid col-6 mx-auto" />
</div>
</form>
</div>
</div>
Add CategoryId
public class BootstrapTool
{
[Key]
public Guid Id { get; set; }
[Required]
public Guid? CategoryId { get; set; }
[ForeignKey(nameof(CategoryId))]
[InverseProperty("BootstrapTools")]
public BootstrapCategory Category { get; set; }
}
public class BootstrapCategory
{
[Key]
public Guid Id { get; set; }
[MaxLength(20)]
[Required]
public string Category { get; set; }
[InverseProperty(nameof(BootstrapTool.Category))]
public virtual ICollection<BootstrapTool> BootstrapTools { get; set; }
}
and fix the view
<select asp-for="CategoryId" class="form-control" asp-items="#ViewBag.bpCategories">
</select>
```

can't get the string from enum

I'm trying to get the result of my enum for a location as a string but for some reason, it's only showing it as an integer in the index page.
Model
public class UserLicense
{
[Key]
public string Location { get; set; }
}
Enum Class
public enum LocationType
{
Brazil,
USA,
UK
//etc .. here i will add around another 30 items
}
Controller
public async Task<IActionResult> Create(IFormCollection col)
{
if (!User.Identity.IsAuthenticated) RedirectToAction("Index", "Home");
UserLicense uc = new UserLicense();
if (ModelState.IsValid)
{
try
{
DateTime? ends = null;
if (DateTime.TryParse(col["ends"], out DateTime tmp)) ends = tmp;
string username = col["user.username"].ToString().Normalize();
var user = _context.Users.FirstOrDefault(x => x.NormalizedUsername == username);
uc = new UserLicense
{
Ends = ends,
Starts = DateTime.Parse(col["starts"]),
UserId = user.Id,
LicenseId = int.Parse(col["licenseid"]),
Location = Enum.GetName(typeof(LocationType),col["Location"]), //the issue is here.
};
_context.Add(uc);
await _context.SaveChangesAsync();
Create HTML
<div class="form-group">
<label asp-for="Location" class="control-label"></label>
<select asp-for="Location" class="form-control" asp-items="#Html.GetEnumSelectList<LocationType>()"></select>
<span asp-validation-for="Location" class="text-danger"></span>
</div>
Since you are using IFormCollection to transfer the parameters to the controller, not matter using public string Location { get; set; } or public string Location { get; set; }, in the controller the Location value in the IFormCollection will be always an integer. So, you could based on the int value to get string value from the Enum:
var location = ((LocationType)int.Parse(col["location"])).ToString();
The result like this:
Besides, there has another solution, you could use the Strongly typed data (use UserLicense model in the action method, instead of IFormCollection) to transfer the parameter to the controller.
Code like this:
public class UserLicense
{
[Key]
public int License { get; set; }
public DateTime Ends { get; set; }
public DateTime Starts { get; set; }
public int UserId { get; set; }
public LocationType Location { get; set; }
}
public enum LocationType
{
Brazil,
USA,
UK
}
and
public IActionResult Create()
{
return View();
}
[HttpPost]
public IActionResult Create(UserLicense userLicense)
{
if (ModelState.IsValid)
{
var location = userLicense.Location;
return RedirectToAction(nameof(Index));
}
return View();
}
Code in the Create.cshtml:
#model WebApplication1.Models.UserLicense
#{
ViewData["Title"] = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Create</h1>
<h4>UserLicense</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="License" class="control-label"></label>
<input asp-for="License" class="form-control" />
<span asp-validation-for="License" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Ends" class="control-label"></label>
<input asp-for="Ends" class="form-control" />
<span asp-validation-for="Ends" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Starts" class="control-label"></label>
<input asp-for="Starts" class="form-control" />
<span asp-validation-for="Starts" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="UserId" class="control-label"></label>
<input asp-for="UserId" class="form-control" />
<span asp-validation-for="UserId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Location" class="control-label"></label>
<select asp-for="Location" class="form-control" asp-items="#Html.GetEnumSelectList<LocationType>()"></select>
<span asp-validation-for="Location" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
The screenshot as below:
Make your Location property a type of LocationType instead of string.
public class UserLicense
{
[Key]
public LocationType Location { get; set; }
}
Pass Location to the view from the Create action like this
uc = new UserLicense
{
Ends = ends,
Starts = DateTime.Parse(col["starts"]),
UserId = user.Id,
LicenseId = int.Parse(col["licenseid"]),
Location = uc.Location
};
Then in the view
<select asp-for="Location" class="form-control" asp-items="#Html.GetEnumSelectList<LocationType>()">
<option selected="selected" value="">Please Select</option>
</select>

Categories