How to save data from multiple selection dropdown - c#

I started learning about the entity framework and asp.net and I wanted to use all CRUD operations.
I have Task and Category models and they are in many-to-many relationship. In my database there are 3 tables: 1.Task (it contains Id, Title, Description..), 2.Category (it contains categoryId and categoryName) and 3.TaskCategory (contains Id (taskId) and categoryId).
I managed to do everything I wanted except to add multiple categories to task using dropdown. I created dropdown, and loaded categories and I know how to add one category to task (when relation is 1:N) (asp-for="CategoryId"). For multiple selection I tried with selectedCategories (list od integers - ids) instead of CategoryId but I dont know what to do with that list. How to join category and task in TaskCategory while saving task in controller (in other words: how to save categories to task)?
AddTask.cshtml
<div class="form-group">
<label class="control-label">Category</label>
<select class="select-picker" asp-for="selectedCategories"
asp-items="#(new SelectList(Model.Categories, "CategoryId", "CategoryName"))" multiple>
</select>
</div>
<script>
...
$('.select-picker').selectpicker('toggle');
</script>
HomeController.cs
public IActionResult AddTask()
{
var categories = _categoryRepository.GetAllCategories().OrderBy(c => c.CategoryName);
var taskCategories = _taskCategoryRepository.GetAllTaskCategories().OrderBy(tc => tc.Id);
var homeViewModel = new HomeViewModel()
{
Task = null,
TaskCategory = null,
Categories = categories.ToList(),
TaskCategories = taskCategories.ToList(),
selectedCategories = new List<int>()
};
return View(homeViewModel);
}
[HttpPost]
public IActionResult AddTask(Task task, List<int> selected)
{
// foreach (var selectedCategoryId in selected)
// {
//
// }
_taskRepository.AddTask(task);
return RedirectToAction("Index");
return View();
}
This way I get Task in database, but ofcourse without any category saved.

Try to use ViewModel to handle relationships , the following is the working demo I made , you could refer to and make the modification as per your need
Many-to Many relationship between Task and Category :
public class Task
{
public int Id { get; set; }
public string Title { get; set; }
public List<TaskCategory> TaskCategories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public List<TaskCategory> TaskCategories { get; set; }
}
public class TaskCategory
{
public int CategoryId { get; set; }
public Category Category { get; set; }
public int TaskId { get; set; }
public Task Task { get; set; }
}
//DbContext
public DbSet<Task> Task { get; set; }
public DbSet<Category> Category { get; set; }
public DbSet<TaskCategory> TaskCategory { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TaskCategory>()
.HasKey(t => new { t.CategoryId, t.TaskId });
modelBuilder.Entity<TaskCategory>()
.HasOne(pt => pt.Category)
.WithMany(p => p.TaskCategories)
.HasForeignKey(pt => pt.CategoryId);
modelBuilder.Entity<TaskCategory>()
.HasOne(pt => pt.Task)
.WithMany(t => t.TaskCategories)
.HasForeignKey(pt => pt.TaskId);
}
Create a TaskViewModel , a element posts back an array of its selected option values (which in your case will be an array of the Categories Id values). Your model needs a property to bind to. Add the following property:
public class TaskViewModel
{
public Task Task { get; set; }
public SelectList CategoryList { get; set; }
public List<int> selectedCategories { get; set; }
}
AddTask.cshtml:
#model WebApplication1.Models.Tasks.TaskViewModel
<div class="row">
<div class="col-md-4">
<form asp-action="AddTask">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Task.Title" class="control-label"></label>
<input asp-for="Task.Title" class="form-control" />
<span asp-validation-for="Task.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="selectedCategories" class="control-label"></label>
<select asp-for="selectedCategories" class="form-control" asp-items="Model.CategoryList" multiple></select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
Then in Post method , the value of selectedCategories will contain an array of CategoryId values that you selected in the view.
[HttpPost]
public IActionResult AddTask(TaskViewModel taskVM)
{
Models.Tasks.Task task = taskVM.Task;
_context.Add(task);
_context.SaveChanges();
foreach(var selectedId in taskVM.selectedCategories)
{
_context.TaskCategory.Add(new TaskCategory
{
TaskId = task.Id,
CategoryId = selectedId,
});
}
_context.SaveChanges();
return RedirectToAction("Index");
}

Related

How to use IEnumerable in form razor page

I have a form for create page in razor pages to fill organization details.
The organization has a field ParentId(it might belong to another organisation).
I want to iterate list of parentIds (and show Title in options) in Select.
I am getting this error: The following sample generates CS1579 because the MyCollection class doesn't contain the public GetEnumerator method:
//Model
public class Organisation : EntityBase
{
[Key]
public int Id { get; set; }
public Organisation Parent { get; set; }
[Required]
public string Title { get; set; }
}
//Controller
public IActionResult Create()
{
IEnumerable<Organisation> objList = _db.Organisations;
return View(objList);
}
//View
#model MindNavigatorDB.Entities.Organisation;
<form asp-action="Create" method="post">
<div class="form-group">
<label asp-for="Parent" class="control-label"></label>
<select id="country"
class="form-select form-control"
asp-for="Parent"
aria-label="Select">
#foreach (Organisation item in Model)
{
<option selected="selected" value="">Please select</option>
}
</select>
<span asp-validation-for="Parent" class="text-danger"></span>
</div>
</form>
you have to add ParentId to Orgainzation
public class Organisation : EntityBase
{
[Key]
public int Id { get; set; }
public int? ParentId { get; set; }
public virtual Organisation Parent { get; set; }
[Required]
public string Title { get; set; }
}
Create view model
public class OrganizationViewModel
{
public Organization {get; set;}
public List<SelectListItem> ParentSelectList {get; set;}
}
action
public IActionResult Create()
{
var viewModel= new OrganizationViewModel
{
Organization=new Organization(),
ParentSelectList = _db.Organisations.Select( i=> new SelectListItem
{
Value=i.Id.ToString(),
Text=i.Title
}).ToList()
}
return View(viewModel);
}
view
#model OrganisationViewModel;
<form asp-action="Create" method="post">
<div class="form-group">
<label class="control-label"> Parent </label>
<select class="form-control" asp-for="#Model.Organization.ParentId" asp-items="#Model.ParentSelectList" ></select>
<span asp-validation-for="#Model.Organization.ParentId" class="text-danger"></span>
</div>
</form>

How to bind view model for Razor Pages (.NET Core)?

Let's say I have this view model. Bear in mind, this is a view model. Not the domain/entity model.
public class Cart
{
public string Name { get; set; }
public int Qty { get; set; }
public decimal Price { get; set; }
public decimal TotalPrice { get; set; }
}
How do I scaffold to create CRUD Razor Page ?
Here is a demo ,you could refer to :
OrderItem Entity model and Cart View model, the View Model is related to the presentation layer of our application. They are defined based on how the data is presented to the user rather than how they are stored.
public class OrderItem
{
public int Id { get; set; }
public int Qty { get; set; }
public decimal Price { get; set; }
public decimal TotalPrice { get; set; }
public Product Product { get; set; }
}
public class Product
{
public int Id { get; set; }
public string ProductName { get; set; }
public decimal Price { get; set; }
}
public class Cart
{
public string Name { get; set; }
public int Qty { get; set; }
public decimal Price { get; set; }
public decimal TotalPrice { get; set; }
}
public class RazorPagesDbContext:DbContext
{
public RazorPagesDbContext(DbContextOptions<RazorPagesDbContext> options):base(options)
{ }
public DbSet<Product> Product { get; set; }
public DbSet<OrderItem> OrderItem { get; set; }
}
The CreateOrder Razor Page
#page
#model RazorPages2_2.Pages.Carts.CreateOrderModel
#{
ViewData["Title"] = "CreateOrder";
}
<h1>CreateOrder</h1>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Cart.Name" class="control-label"></label>
<input asp-for="Cart.Name" class="form-control" />
<span asp-validation-for="Cart.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Cart.Price" class="control-label"></label>
<input asp-for="Cart.Price" class="form-control" />
<span asp-validation-for="Cart.Price" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Cart.Qty" class="control-label"></label>
<input asp-for="Cart.Qty" class="form-control" />
<span asp-validation-for="Cart.Qty" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Cart.TotalPrice" class="control-label"></label>
<input asp-for="Cart.TotalPrice" class="form-control" />
<span asp-validation-for="Cart.TotalPrice" 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-page="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
The CreateOrder page model, the Cartproperty uses the [BindProperty] attribute to opt-in to model binding. When the Create form posts the form values, the ASP.NET Core runtime binds the posted values to the Cart model then put the values into the entity model.
public class CreateOrderModel : PageModel
{
private readonly RazorPagesDbContext _context;
public CreateOrderModel(RazorPagesDbContext context)
{
_context = context;
}
public IActionResult OnGet()
{
var product = _context.Product.FirstOrDefault();
Cart = new Cart
{
Name = product.ProductName,
Price = product.Price,
Qty = 2,
TotalPrice = product.Price * 2
};
return Page();
}
[BindProperty]
public Cart Cart { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var product = _context.Product.SingleOrDefault(p => p.ProductName == Cart.Name);
OrderItem orderItem = new OrderItem
{
Price = Cart.Price,
Qty = Cart.Qty,
TotalPrice = Cart.TotalPrice,
Product = product
};
_context.OrderItem.Add(orderItem);
await _context.SaveChangesAsync();
return RedirectToPage("../Index");
}
}
Result:
You could refer to the offocial doc about the Razor pages to create the page you want .
CODE BEHIND :
public class IndexModel : PageModel
{
private readonly ApplicationDbContext _db;
public IndexModel(ApplicationDbContext db)
{
_db = db;
}
public IEnumerable<Cart> Carts { get; set; }
public async Task OnGet()
{
Books = await _db.Carts.ToListAsync();
}
}
You need :
public class ApplicationDbContext:DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options):base(options)
{
}
public DbSet<Cart> carts { get; set; }
}
for the View :
#model CardList.IndexModel

Unable to add the value from dropdownlist to database

I created a form to store information about the customers and his membership type. For that I am using the drop down list to hold values for membership types. But on submitting the form, the value(Id) for membership type isnt added to database
//Model Membership Types
public int Id { get; set; }
public string Name { get; set; }
//ViewModel NewCustomerviewModel
public IEnumerable<MembershipTypes> MembershipTypes { get; set; }
public Customers Customers{ get; set; }
//Controler CustomerController
public IActionResult Index()
{
var customers = _context.Customers.Include(c => c.MembershipTypes).ToList();
return View(customers);
}
[HttpPost]// Create is the aciton for Submit Button
public IActionResult Create(Customers customers)
{
_context.Customers.Add(customers);
_context.SaveChanges();
return RedirectToAction("Index", "Customers");
}
//View Model
#model Wes.ViewModels.NewCustomerviewModel;
#Html.DropDownListFor(m => m.Customers.MembershipTypes, new SelectList(Model.MembershipTypes, "Id", "Name"),"Select Membership Type", new { #class = "form-control" })
When the Form is Submitted, it should add all the values to the database including the value of Drop Down List Membership Types
You could try doing it this way:
//model
public int Id { get; set; }
public string Name { get; set; }
public enum MembershipTypes
{
Type1,
Type2,
Type3
}
public MembershipTypes _membershipTypes {get; set; }
//controller
[HttpPost]
public IActionResult Create([Bind("Id","Name","_membershipTypes")] Customers customers)
{
if (ModelState.IsValid)
{
_context.Add(customers);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
Return View(customers);
}
//view
<div class="row">
<div class="col-md-6">
<form asp-action="Create">
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
#Html.DropDownList("_membershipTypes",
new SelectList(Enum.GetValues(typeof(MembershipTypes))),
"Select membership type",
new { #class = "form-control" })
</div>
<input type="submit" value="Submit!" />
</form>
</div>
</div>
You need to show more about the relationships(one-to-one,one-to-many) of your models.
The parameters of your post action need to correspond with the model of your view,use NewCustomerviewModel instead of Customers.
The dropdownlist shows the type of name and pass id as value to action, so your asp-for of dropdown list needs to be set for an id or id list.
Refer to my demo which pass id list of MembershipTypes to action using multiple select.
1.My ViewModel NewCustomerviewModel,
public class MembershipTypes
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class NewCustomerviewModel
{
public int[] SelectMembershipTypesId { get; set; }
public Customers Customers { get; set; }
}
public class Customers
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<MembershipTypes> MembershipTypes { get; set; }
}
2.Create GET action
public IActionResult Create()
{
var model = new NewCustomerviewModel()
{
Customers = new Customers()
{
MembershipTypes = _context.MembershipTypes.ToList()
},
};
return View(model);
}
3.Create POST action
[HttpPost]
public async Task<IActionResult> Create(NewCustomerviewModel viewmodel)
{
if (ModelState.IsValid)
{
viewmodel.Customers.MembershipTypes= _context.MembershipTypes
.Where(m =>viewmodel.SelectMembershipTypesId.Contains(m.Id))
.ToList();
_context.Add(viewmodel.Customers);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(viewmodel);
}
4. Create View
#Html.DropDownListFor(m => m.SelectMembershipTypesId,
new SelectList(Model.Customers.MembershipTypes, "Id", "Name"), "Select Membership Type",
new { #class = "form-control", #multiple = "multiple" })

Build a checkbox list in razor page from many-to-many EF Core entity

My question is: How to build html markup in razor pages and the LINQ queries (in the backend) to bring a checkbox list of all my SubCategoies in the EDIT and CREATE views.
Allowing me to create a product with multiple subcategories and also updating them at any time in the EDIT view.
Using .Net EF Core 2.2, Razor Pages.
Main class (Product):
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Category Category { get; set; }
public List<ProductSubcategory> SubCategories { get; set; }
}
Product has a many-to-many relationship with Subcategory:
public class SubCategory
{
public int Id { get; set; }
public string Name { get; set; }
public List<ProductSubcategory> SubCategories { get; set; }
}
So the join table (entity) is ProductSubcategory:
public class ProductSubcategory
{
public int ProductId { get; set; }
public Product Product { get; set; }
public int SubCategoryId { get; set; }
public SubCategory SubCategory { get; set; }
}
The Edit (and create) Product view:
<h2>Editar: #Model.Product.Name</h2>
<form method="post">
<input type="hidden" asp-for="Product.Id" />
<div class="form-group">
<label asp-for="Product.Name"></label>
<input asp-for="Product.Name" class="form-control" />
<span class="text-danger" asp-validation-for="Product.Name"></span>
</div>
<div class="form-group">
<label asp-for="Product.Description"></label>
<textarea asp-for="Product.Description" class="form-control"></textarea>
<span class="text-danger" asp-validation-for="Product.Description"></span>
</div>
<div class="form-group">
<label asp-for="Product.Category"></label>
<select class="form-control" asp-for="Product.Category" asp-items="Model.Categories"></select>
<span class="text-danger" asp-validation-for="Product.Category"></span>
</div>
<div class="form-group">
//Code to allow the subcategory selection.
//preferable as checkboxes
//() subcat1 (x)subcat2 ()subcat3
//() subcat4 ()subcat5 (x)subcat6
</div>
<button type="submit" class="btn btn-primary">Salvar</button>
</form>
The Edit.cshtml.cs PageModel
public class EditModel : PageModel
{
private readonly IProductData _ProductData;
private readonly IHtmlHelper _HtmlHelper;
[BindProperty]
public Product Product { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
public string MessageCreate { get; set; }
public EditModel(IProductData _productData, IHtmlHelper _htmlHelper)
{
_ProductData = _productData;
_HtmlHelper = _htmlHelper;
}
public IActionResult OnGet(int? productId)
{
Categories = _HtmlHelper.GetEnumSelectList<Category>();
if (productId.HasValue)
{
Product = _ProductData.GetById(productId.Value);
}
else
{
MessageCreate = "Criar novo Produto";
Product = new Product();
}
if (Product == null)
{
return RedirectToPage("./NotFound");
}
return Page();
}
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
Categories = _HtmlHelper.GetEnumSelectList<Category>();
return Page();
}
if (Product.Id > 0)
{
_ProductData.Update(Product);
}
else
{
_ProductData.Create(Product);
}
_ProductData.Commit();
TempData["Message"] = "Produto salvo!!!";
//PRG POST-REDIRECT-GET
return RedirectToPage("./Detail", new { productId = Product.Id });
}
}
The checkbox is used to represent a boolean property. I see you don't have a bool property so I suppose you need to add a Boolean property in SubCategories class like:
public bool IsChecked { get; set; } // added this property
Then you need to add a property to your PageModel(Edit or Create) to represent the data and ensured that posted values will be bound to it:
[BindProperty]
public List<Subcategory> SubCategories { get; set; } = new List<Subcategory>();
At the end all you need is to get the model binder to associate each checkbox with a specific Subcategory. The following code shows my example in .cshtml file:
#for (var i = 0; i < Model.SubCategories.Count(); i++)
{
<input asp-for="SubCategories[i].IsChecked" />
}

Correct way to do Create and Update actions for one to many objects in .Net Core Ef Core

I'm working on my first .Net Core application and I was able to put together the CRUD actions for my first table.
The second table has a foreign key to the first table and I think I've gone off on the wrong path in trying to put together the Create and Update actions. The Update action needs to pass in all items from the foreign key table for the user to select from. But as an update Action, the View needs to have the actual objects foreign key selected from the drop-down list.
What's the correct way to do this? I can't seem to find a consistent answer on Google.
Table 1 Model:
public class HTMLElement
{
public int Id { get; set; }
public string Element { get; set; }
public string ElementName { get; set; }
public virtual ICollection<CustomizedElement> CustomizeHTMLElements { get; set; }
}
Table 2 Model:
public class CustomizedElement
{
public int Id { get; set; }
public string Name { get; set; }
public int? HTMLElementId { get; set; }
public HTMLElement HTMLElement { get; set; }
}
ViewModel for Table Two:
public class CustomizedHTMLElementViewModel
{
[Display(Name = "Name")]
public string Name { get; set; }
[Display(Name = "HTML Element")]
public int HTMLElement { get; set; }
[Display(Name = "HTML Elements")]
public ICollection<HTMLElement> HTMLElements { get; set; }
}
Create Controller:
I want to pass in all of the items from Table 1 into the view for a drop-down list that the user can select from and this works but not sure if it's the correct way to do this.
[HttpGet]
public IActionResult AddCustomizedHTMLElement()
{
var elements = new CustomizedHTMLElementViewModel
{
HTMLElements = db.HTMLElements.ToList()
};
return View(elements);
}
The Post action is working but not sure if it's the most efficient.
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult AddCustomizedHTMLElement(CustomizedHTMLElementViewModel CHTMLEV)
{
if (ModelState.IsValid)
{
var element = db.HTMLElements.Single(e => e.Id == CHTMLEV.HTMLElement);
CustomizedElement cElement = new CustomizedElement()
{
Name = CHTMLEV.Name,
HTMLElement = element,
};
db.CustomizedElements.Add(cElement);
db.SaveChanges();
return RedirectToAction("CustomizedHTMLElements");
}
return View(CHTMLEV);
}
I'm having a really hard time with the Update action. I want to pass in all foreign keys but also have the objects foreign key selected in the drop-down list in the View
[HttpGet]
public IActionResult UpdateCustomizedHTMLElement(int Id)
{
var mElement = db.CustomizedElements.Include(e => e.HTMLElement).FirstOrDefault(e => e.Id == Id);
ViewBag.ElementsList = db.HTMLElements.ToList();
return View(mElement);
}
I haven't worked on the Update actions [HttpPost] yet until I get the View right.
Update View:
<form method="post" asp-controller="Templates" asp-action="UpdateCustomizedHTMLElement">
<div class="form-group">
<label asp-for="Name"></label>:
<input class="form-control form-control-sm" type="text" asp-for="Name" />
<span class="text-danger" asp-validation-for="Name"></span>
</div>
<div class="form-group">
<label asp-for="HTMLElement"></label>:
#Html.DropDownListFor(m => m.HTMLElement, new SelectList(ViewBag.ElementsList, "Id", "Element"), new { #class = "form-control" })
<span class="text-danger" asp-validation-for="HTMLElement"></span>
</div>
<div>
<button class="btn btn-primary" type="submit" value="Submit">Save</button>
<button class="btn btn-secondary" type="button" value="Cancel" onclick="location.href='#Url.Action("CustomizedHTMLElements", "Templates")'">Cancel</button>
</div>
</form>
public class UpdateCustomizedHTMLViewModel
{
public int SelectedHtmlElementId { get; set;}
public CustomizedElement ElementToUpdate { get; set;}
public ICollection<HTMLElement> HTMLElements { get; set;}
}
Then fill ElementToUpdate and HTMLElements in your get and when you select the HTMLElement you want to update with in the dropdown you assign that Id to SelectedHtmlElementId

Categories

Resources