I have two view models:
public class PersonViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Dob { get; set; }
public string Email { get; set; }
public ICollection<PetViewModel> Pets { get; set; }
}
public class PetViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public int PersonId { get; set; }
public PersonViewModel Person { get; set; }
}
When creating/editing a pet I want to have a drop down list of all the people to choose from. This is my pets controller:
\\ Other methods omitted for brevity
[HttpGet]
public IActionResult Create()
{
var people = _personService.AsQueryable().ToList();
ViewBag.PeopleList = new SelectList(people , "Id", "FirstName");
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create([Bind("Id,Name,PersonId")] PetViewModel vm)
{
if (!ModelState.IsValid) return View(vm);
var pet = _petService.Create(vm.Name, vm.PersonId);
return RedirectToAction("Details", pet);
}
I am trying to do it via ViewBag and doing the following in the view as such:
#model UI.ViewModels.PetViewModel
<h2>Create</h2>
<form asp-action="Create">
<div class="form-horizontal">
<h4>Create a Pet</h4>
<div class="form-group">
<label asp-for="PersonId" class="col-md-2 control-label"></label>
<div class="col-md-10">
#Html.DropDownListFor(model => model.PersonId, ViewBag.PeopleList, "--Select--", new { #class = "form-control"})
<span asp-validation-for="PersonId" class="text-danger"></span>
</div>
</div>
</div>
</form>
Upon trying to do so I get the following error: Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type.
Any help is appreciated. Thanks!
I managed to fix it by doing the following:
<div class="form-group">
<label asp-for="PersonId" class="col-md-2 control-label"></label>
<div class="col-md-10">
#Html.DropDownListFor(model => model.PersonId, (SelectList)ViewBag.PeopleList, "--Select--", new { #class = "form-control"})
<span asp-validation-for="PersonId" class="text-danger"></span>
</div>
</div>
I wasn't casting the ViewBag.PeopleList to a SelectList, changing the second parameter in my Html.DropDownList to (SelectList)ViewBag.PeopleList fixed the issue for me.
Related
I've made many to many relationship in ASP.NET Core and there are two tables Category and Subject
This is Category Model
public class Category
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public List<CategorySubject> CategorySubjects { get; set; } = new List<CategorySubject>();
}
This is subject model
public class Subject
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Exam_Time { get; set; }
public List<CategorySubject> CategorySubjects { get; set; }
}
This is CategorySubject Model
public class CategorySubject
{
public int CategoryId { get; set; }
public int SubjectId { get; set; }
public Category Category { get; set; }
public Subject Subject { get; set; }
}
This is part of DatabaseContext
public DbSet<CategorySubject> CategorySubjects { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CategorySubject>().HasKey(pt => new { pt.CategoryId, pt.SubjectId });
modelBuilder.Entity<CategorySubject>().HasOne(pt => pt.Category)
.WithMany(pt => pt.CategorySubjects).HasForeignKey(p => p.CategoryId);
modelBuilder.Entity<CategorySubject>().HasOne(pt => pt.Subject)
.WithMany(pt => pt.CategorySubjects).HasForeignKey(p => p.SubjectId);
}
I made one helper class by the name of Helper
public class Helpers:Profile
{
public Helpers()
{
CreateMap<Subject, SubjectViewModel>().ReverseMap();
CreateMap<SubjectViewModel, Subject>();
CreateMap<Category, CategoryViewModel>().ReverseMap();
}
}
this is category service:
public void Insert(Category category)
{
_context.Categories.Add(category);
}
public void Update(Category category)
{
_context.Categories.Update(category);
}
This is CategoryController :
// GET: CategoryController/Create
public IActionResult Create()
{
var subjectFromRepo = _categorySubject.Subject.GetAll();
var selectList = new List<SelectListItem>();
foreach (var item in subjectFromRepo)
{
selectList.Add(new SelectListItem(item.Name, item.Id.ToString()));
}
var vm = new CategoryViewModel()
{
Subjects = selectList
};
return View(vm);
}
// POST: CategoryController/Create
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(CategoryViewModel vm )
{
try
{
Category category = new Category()
{
Name = vm.Name
};
foreach(var item in vm.SelectedSubjects)
{
category.CategorySubjects.Add(new CategorySubject()
{
SubjectId = Int32.Parse(item)
});
}
_categorySubject.Category.Insert(category);
_categorySubject.Save();
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
// GET: CategoryController/Edit/5
public IActionResult Edit(int id)
{
var category = _categorySubject.Category.GetCategoryById(id);
var subjects = _categorySubject.Subject.GetAll();
var selectsubjects = category.CategorySubjects.Select(x => new Subject()
{
Id = x.Subject.Id,
Name = x.Subject.Name
});
var selectlist = new List<SelectListItem>();
subjects.ForEach(i => selectlist.Add(new SelectListItem(i.Name, i.Id.ToString(),
selectsubjects.Select(x => x.Id).Contains(i.Id))));
var vm = new CategoryViewModel()
{
Id= category.Id,
Name = category.Name,
Subjects = selectlist
};
return View(vm);
}
// POST: CategoryController/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(CategoryViewModel vm)
{
try
{
var category = _categorySubject.Category.GetCategoryById(vm.Id);
category.Name = vm.Name;
var selectedSubjects = vm.SelectedSubjects;
var existingSubjects = category.CategorySubjects.Select(x => x.SubjectId.ToString()).ToList();
var toAdd = selectedSubjects.Except(existingSubjects).ToList();
var toRemove = existingSubjects.Except(selectedSubjects).ToList();
var CategorySubjects = category.CategorySubjects.Where(x => !toRemove.Contains(x.SubjectId.ToString())).ToList();
foreach (var item in toAdd)
{
category.CategorySubjects.Add(new CategorySubject()
{
SubjectId = Int32.Parse(item),
CategoryId = Int32.Parse(item)
});
}
_categorySubject.Save();
return RedirectToAction("Index", "Category");
}
catch
{
return View();
}
}
This is Create.cshtml of Category :
<div class="style-form">
<h2 class="text-center mt-3 mb-lg-3">Create New Category</h2>
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="row">
<div class="col-md-3 col-lg-3 col-sm-3"></div>
<div class="col-md-6 col-lg-6 col-sm-6">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><strong>Name:</strong></span>
</div>
<input asp-for="Name" class="form-control input-hover" placeholder="Enter Name.." />
<span asp-validation-for="Name" class="text-danger"></span>
</div><br />
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><strong>Subject:</strong></span>
</div>
<select asp-for="SubjectId" class="form-control input-hover" asp-items="#Model.Subjects ">
<option value="">Please choose a Subject...</option>
</select>
<span asp-validation-for="SubjectId" class="text-danger"></span>
</div><br />
</div>
</div>
<div class="row">
<div class="col-md-3 col-lg-3 col-sm-3"></div>
<div class="col-md-6 col-lg-6 col-sm-6">
<div class="form-group">
<button type="button" class="btn btn-backToList">
<a asp-action="Index">Back to List</a>
</button>
<button type="submit" class="btn btn-create">Create</button>
</div>
</div>
</div>
</form>
There when I click on the create new category button I can get data of subject form drop down list, but when I want to submit it I face this error:
NullReferenceException: Object reference not set to an instance of an object.
AspNetCore.Views_Category_Create.b__20_0() in Create.cshtml, line 27
<select asp-for="SubjectId" class="form-control input-hover" asp-items="#Model.Subjects ">
I think there is an exception thrown in the Create (POST) method, it then goes to the catch, which returns a view without a model
catch
{
return View();
}
The next exception comes while rendering the page trying to bind to #Model.Subjects where Model is null.
Remove try/catch or handle the catch to find if there is any exception.
I want to pass a new booking via a create view where the user can select different locations. I can select the locations in the view, but the location attribute is still set to null, when I click submit, everything else works.
The locations are stored in a List in an infrastructure model, I am guessing, the problem is, that my values are LocationIds and not Location objects, but I don't know how to create an selected list with object values, since all the examples are with ids/names.
These are my models:
public class Infrastructure
{
public int InfrastructureId { get; set; }
[Required]
public List<Location> Locations { get; set; }
}
public class Location
{
public int LocationId { get; set; }
public Address Address { get; set; }
public int CountEmployee { get; set; }
public GPS Coordinates { get; set; }
public List<ChargingStation> ChargingStations { get; set; }
}
this is the Create in the Controller:
public IActionResult Create()
{
Infrastructure infrastructure = _infrastructure.GetRealInfrastructure();
List<Location> locations = infrastructure.Locations;
ViewBag.Locations = new SelectList(locations, "LocationId", "Address.City");
return View();
}
and this is the view:
<div class="form-group">
<label asp-for="end" class="control-label"></label>
<input asp-for="end" class="form-control" id="endTime"/>
<span asp-validation-for="end" class="text-danger"></span>
</div>
</div>
<div class="col-3">
<div class="form-group">
<label asp-for="location" class="control-label"></label>
#Html.DropDownListFor(model => model.location, (IEnumerable<SelectListItem>)ViewBag.Locations, "Choose Location", new { #class = "form-control" })
<span asp-validation-for="location" class="text-danger"></span>
</div>
</div>
The values for endtime for example are saved, but location is still set too null, so I am guessing I am missing some select/submit tag? Or as written before its because I pass an string value (LocationId) instead of an actual location object?
I have a form in which user can select which shipping methods they want to support for they product that they are selling, e.g. first class letter, second class letter, parcel, etc. I only give users a collection of possible shipping methods, they declare how much each one will cost, so if someone wants to sell a toaster in a parcel, they will charge less than for a set of dumbbells.
My ProductViewModel:
public int Id { get; set; }
public ICollection<SelectedShippingMethodViewModel> SelectedShippingMethods { get; set; }
And SelectedShippingMethodViewModel:
public class SelectedShippingMethodViewModel
{
public string Name { get; set; }
public decimal Price { get; set; }
}
In my form I create a section with possible options like this:
<h3>Add new product</h3>
<hr />
#using (Html.BeginForm("AddNew", "ProductCreator", null, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<div class="form-group">
<label class="col-sm-2 control-label">Shipping methods</label>
<div class="col-sm-10">
#foreach (ShippingMethod shippingMethod in ViewBag?.ShippingMethods)
{
<div class="row">
<div class="col-md-3">
// I don't know what should be here
#Html.CheckBox("SelectedShippingMethods", false)
#shippingMethod.Name
</div>
<div class="col-md-2">
// I don't know what should be here
#Html.TextBox("SelectedShippingMethods.Price")
</div>
</div>
}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">Add product</button>
</div>
</div>
}
I have a database table with every possible shipping method that I acquire like this:
[HttpGet]
public async Task<ActionResult> AddNew()
{
ViewBag.ShippingMethods = await _shippingService.GetAllShippingMethodsAsync();
return View();
}
The problem is if checkbox is selected I have to bind Price and Name for each individual SelectedShippingMethodViewModel and I have no idea how to make it work.
Your view models are incorrect. To allow users to select the shipping methods they want and add a price, that view model needs to be
public class ShippingMethodViewModel
{
public string Name { get; set; }
public decimal Price { get; set; }
public bool IsSelected { get; set; } // your checkbox binds to this property
}
and the ProductViewModel should be
public class ProductViewModel
{
public int Id { get; set; }
....
public List<ShippingMethodViewModel> ShippingMethods { get; set; }
}
Then in the GET method, initialize your ProductViewModel and populate the ShippingMethods based on all available ShippingMethods, for example
var shippingMethods = await _shippingService.GetAllShippingMethodsAsync()
ProductViewModel model = new ProductViewModel
{
....
ShippingMethods = shippingMethods.Select(x => new ShippingMethodViewModel
{
Name = x.Name
}).ToList()
};
return View(model);
and in the view, use a for loop or EditorTemplate for typeof ShippingMethodViewModel to correctly generate your form controls
#for (int i = 0; i < Model.ShippingMethods.Count; i++)
{
#Html.LabelFor(m => m.ShippingMethods[i].IsSelected, Model[0].ShippingMethods.Name)
#Html.CheckBoxFor(m => m.ShippingMethods[i].IsSelected)
#Html.LabelFor(m => m.ShippingMethods[i].Price)
#Html.TextBoxFor(m => m.ShippingMethods[i].Price)
#Html.HiddenFor(m => m.ShippingMethods[i].Name) // if you want this to be submitted as well
}
Then in the POST method
public ActionResult AddNew(ProductViewModel model)
{
// Get the selected Shipping Methods and the associated price
var selectedMethods = model.ShippingMethods.Where(x => x.Selected);
For whatever reason I'm unable to Create and Edit using the ViewModel called CreateEmployeeViewModel that I created. I can however Create and Edit fine without using the CreateEmployeeViewModel but was told it was bad practive to use the main Models for CRUD. I am however able to retrieve values to my 2 DropDownList tags fine using the CreateEmployeeViewModel, just not Create or Edit. Below are my current Models, ViewModels, Controllers and Views.
I just figure out why I cannot Create using the public IActionResult Create(Employee employee) Active Method.
Employee Model: (located in Models folder)
public class Employee
{
[Key]
public int EmpId { get; set; }
[Required]
public string EmpFirstName { get; set; }
[Required]
public string EmpLastName { get; set; }
public int DeptId { get; set; }
public Department Department { get; set; }
public int BldgId { get; set; }
public Building Building { get; set; }
}
EmployeeController: (located in Controllers folder)
public class EmployeeController : Controller
{
private DataEntryContext _context;
public EmployeeController(DataEntryContext context)
{
_context = context;
}
public IActionResult Index()
{
return View(_context.Employees.ToList());
}
// Populate Department values to DropDownList
private IEnumerable<SelectListItem> GetDeptList()
{
var dept = _context.Departments
.Select(s => new SelectListItem
{
Value = s.DeptId.ToString(),
Text = s.DeptTitle
})
.ToList();
return (dept);
}
// Populate Building values to DropDownList
private IEnumerable<SelectListItem> GetBldgList()
{
var bldg = _context.Buildings
.Select(b => new SelectListItem
{
Value = b.BldgId.ToString(),
Text = b.BldgName
})
.ToList();
return (bldg);
}
public IActionResult Create()
{
CreateEmployeeViewModel model = new CreateEmployeeViewModel();
model.DeptList = GetDeptList();
model.BldgList = GetBldgList();
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(Employee employee)
{
if (ModelState.IsValid)
{
_context.Employees.Add(employee);
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(employee);
}
public IActionResult Edit(int? id)
{
if (id == null)
{
return View("Error");
//return NotFound();
}
var employee = _context.Employees
.Where(e => e.EmpId == id)
.Single();
if (employee == null)
{
return View("Error");
//return NotFound();
}
return View(employee);
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(Employee employee)
{
if (ModelState.IsValid)
{
_context.Employees.Update(employee);
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(employee);
}
}
CreateEmployeeViewModel: (located in ViewModels Folder)
public class CreateEmployeeViewModel
{
public int EmpId { get; set; }
public string EmpFirstName { get; set; }
public string EmpLastName { get; set; }
public int DeptId { get; set; }
public IEnumerable<SelectListItem> DeptList { get; set; }
public int BldgId { get; set; }
public IEnumerable<SelectListItem> BldgList { get; set; }
}
Employee Create View:
<form asp-controller="employee" asp-action="Create" method="post" class="form-horizontal" role="form">
<div class="form-horizontal">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="EmpFirstName" class="col-md-2 control-label">First Name</label>
<div class="col-md-10">
<input asp-for="EmpFirstName" class="form-control" />
<span asp-validation-for="EmpFirstName" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="EmpLastName" class="col-md-2 control-label">Last Name</label>
<div class="col-md-10">
<input asp-for="EmpLastName" class="form-control" />
<span asp-validation-for="EmpLastName" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="DeptId" class="col-md-2 control-label">Department</label>
<div class="col-md-10">
<select asp-for="DeptId" asp-items="#Model.DeptList" class="form-control">
<option>Select Department</option>
</select>
<span asp-validation-for="DeptId" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="BldgId" class="col-md-2 control-label">Building Location</label>
<div class="col-md-10">
<select asp-for="BldgId" asp-items="#Model.BldgList" class="form-control">
<option>Select Building</option>
</select>
<span asp-validation-for="BldgId" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
In your Create method, you are sending to the view the CreateEmployeeViewModel but in your HttpPost Create method you are accepting back the Employee model instead of the CreateEmployeeViewModel. So once you change the post methods signature to accept the correct CreateEmployeeViewModel, you can simply map it back to the Employee model.
Get Action Method:
public IActionResult Create(Employee employee)
{
return View(employee);
}
Just change in your Post Action Method:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(CreateEmployeeViewModel vm)
{
if (ModelState.IsValid)
{
var model = new Employee{
//your logic here for example
employeename = vm.employeename,
employeepassword = vm.employeepassword
}
_context.Employees.Add(model);
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(employee);
}
and donĀ“t forget to cal View Model in your .cshtml
I have the following ViewModel:
public class ActivityReportViewModel
{
public Dictionary<int, List<string>> Periods { get; set; }
public List<Project> Projects { get; set; }
public List<Templates> Templates { get; set; }
public DateTime TimePeriod { get; set; }
}
public class Project
{
public string Customer { get; set; }
public string ProjectNumber { get; set; }
public string ProjectDescription { get; set; }
public bool IsSelected { get; set; }
public int TemplateId { get; set; }
public bool XLSX { get; set; }
public bool PDF { get; set; }
}
I fill this ViewModel in my controller and then send it to my Create view, which works fine and the values of the Projects property are all there. However, when I postback the data to the server, the values are gone. I tried supplying HiddenFields to all properties of each Project to no avail. Here's my relevant view markup:
<div>
#Html.LabelFor(model => model.Projects, htmlAttributes: new { #class = "ms-Label" })
<ul class="ms-List" style="list-style:none;">
#for (int x = 0; x < Model.Projects.Count; x++)
{
<li class="ms-ListItem">
<span class="ms-ListItem-primaryText">#Model.Projects[x].ProjectDescription</span>
<span class="ms-ListItem-secondaryText">#Model.Projects[x].Customer</span>
<span class="ms-ListItem-tertiaryText">#Model.Projects[x].ProjectNumber</span>
#*<div class="ms-ListItem-selectionTarget js-toggleSelection"></div>*#
#Html.HiddenFor(m => Model.Projects[x].IsSelected)
#Html.HiddenFor(m => Model.Projects[x].ProjectDescription)
#Html.HiddenFor(m => Model.Projects[x].Customer)
#Html.HiddenFor(m => Model.Projects[x].ProjectNumber)
#Html.HiddenFor(m => Model.Projects[x].XLSX)
#Html.HiddenFor(m => Model.Projects[x].PDF)
<div class="ms-Dropdown">
<i class="ms-Dropdown-caretDown ms-Icon ms-Icon--caretDown"></i>
#Html.DropDownListFor(m => m.Projects[x].TemplateId, new SelectList(Model.Templates, "Id", "Name"), new { #class = "ms-Dropdown-select" })
</div>
<div class="ms-ChoiceField">
<input id="excel+#Model.Projects[x].ProjectNumber" class="ms-ChoiceField-input" value="#Model.Projects[x].XLSX" type="checkbox">
<label for="excel+#Model.Projects[x].ProjectNumber" class="ms-ChoiceField-field"><span class="ms-Label is-required">Excel</span></label>
</div>
<div class="ms-ChoiceField">
<input id="pdf+#Model.Projects[x].ProjectNumber" class="ms-ChoiceField-input" value="#Model.Projects[x].PDF" type="checkbox">
<label for="pdf+#Model.Projects[x].ProjectNumber" class="ms-ChoiceField-field"><span class="ms-Label is-required">PDF</span></label>
</div>
</li>
}
</ul>
<div>
#Html.ValidationMessageFor(model => model.Projects, "", new { #class = "text-danger" })
</div>
</div>
EDIT:
Here's my POST action method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ActivityReportViewModel report)
{
using (AppContainer _db = new AppContainer())
{
if (ModelState.IsValid)
{
_db.SaveChanges();
return RedirectToAction("Index");
}
return PartialView(report);
}
}
The DefaultViewModel Binder uses the HTML attribute name to determine which property to bind back to on the server. I cannot see name attribute specified on your input element. Please specify name attribute on the elements you wish to post back to the server with the property of the view model.
Specifiy name attribute as below. Notice I have added name attribute with value as the property of your view model
<input id="excel+#Model.Projects[x].ProjectNumber" class="ms-ChoiceField-input" name="#Model.Projects[x].ProjectNumber" value="#Model.Projects[x].XLSX" type="checkbox">