I try make a library Sistem where in category to book has a relation many to many i need put in my book edit view a partial view frow the entity, could someone help-me?
my edit view:
#model MVC_Library.Models.Book
#{
ViewData["Title"] = "Edit";
BookCategory teste = ViewBag.testeview;
}
<h1>Edit</h1>
<h4>Book</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="BookId" />
<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="Author" class="control-label"></label>
<input asp-for="Author" class="form-control" />
<span asp-validation-for="Author" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PublishDate" class="control-label"></label>
<input asp-for="PublishDate" class="form-control" />
<span asp-validation-for="PublishDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="BasePrice" class="control-label"></label>
<input asp-for="BasePrice" class="form-control" />
<span asp-validation-for="BasePrice" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Quantity" class="control-label"></label>
<input asp-for="Quantity" class="form-control" />
<span asp-validation-for="Quantity" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PublishingCompanyId" class="control-label"></label>
<select asp-for="PublishingCompanyId" class="form-control" asp-items="ViewBag.PublishingCompanyId"></select>
<span asp-validation-for="PublishingCompanyId" class="text-danger"></span>
</div>
<div>
#Html.Partial("_EditBookCategory", teste)
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
my partial view that work (but in this way just pre-select one category item)
#model MVC_Library.Models.BookCategory
<div class="row">
<div class="col-md-12">
<label asp-for="CategoryId" class="control-label"></label>
<select multiple asp-for="CategoryId" class="form-control" name = "BookCategory[]" asp-items="ViewBag.SelectedCategory"></select>
</div>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
and my controller edit part
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var book = await _context.Books.FindAsync(id);
if (book == null)
{
return NotFound();
}
List<BookCategory> bookCategory = _context.BookCategories.Where(e => e.BookId == id).ToList();
ViewData["PublishingCompanyId"] = new SelectList(_context.PublishingCompanies, "PublishingCompanyId", "PublishingCompanyName", book.PublishingCompanyId);
//this is what i try for try return mutiple category select (don't work with this viewbag)
/* ViewBag.SelectedCategory = new MultiSelectList(_context.Categories.ToList(), "CategoryId", "CategoryName", bookCategory); */
//this viewbag return only one category selected but work
ViewBag.testeview = bookCategory.First();
return View(book);
}
Just once selected (the first), but a want to return all. Need help if this could possible.
If you want to display all the related categories selected:
Be sure asp-for="CategoryId" here CategoryId should be type of int[] or string[]. Or just remove asp-for tag helper, because you have added name="BookCategory[]" here, it will override asp-for generated name attribute. When you form submit, your action always need contain a parameter named BookCategory[], no matter you add asp-for or not.
Besides BookCategory[] is not a correct name with no index in array, it should be BookCategory or any other name and the parameter name in action should be type of int[] or string[]. Guessing you may want to match the selected value to List model BookCategory's CategoryId. It is not acceptable for multiple select.
Be sure the last parameter in MultiSelectList should be int[] or string[]. From your code, the dataValueField in MultiSelectList matches CategoryId property, so your last parameter should be something like bookCategory.Select(a=>a.CategoryId).ToList().
ViewBag.SelectedCategory = new MultiSelectList(_context.Categories.ToList(), "CategoryId", "CategoryName",
bookCategory.Select(a=>a.CategoryId).ToList());
No need use ViewBag.testeview, just change like below:
#Html.Partial("_EditBookCategory", new BookCategory())
Whole working demo should be like:
Model:
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public DateTime PublishDate { get; set; }
public int BasePrice { get; set; }
public int Quantity { get; set; }
public int PublishingCompanyId { get; set; }
public List<BookCategory> BookCategory { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public List<BookCategory> BookCategory { get; set; }
}
public class BookCategory
{
public int CategoryId { get; set; }
public int BookId { get; set; }
public Category Category { get; set; }
public Book Book { get; set; }
}
View:
#model Book
#{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Book</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="BookId" />
<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="Author" class="control-label"></label>
<input asp-for="Author" class="form-control" />
<span asp-validation-for="Author" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PublishDate" class="control-label"></label>
<input asp-for="PublishDate" class="form-control" />
<span asp-validation-for="PublishDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="BasePrice" class="control-label"></label>
<input asp-for="BasePrice" class="form-control" />
<span asp-validation-for="BasePrice" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Quantity" class="control-label"></label>
<input asp-for="Quantity" class="form-control" />
<span asp-validation-for="Quantity" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PublishingCompanyId" class="control-label"></label>
<select asp-for="PublishingCompanyId" class="form-control" asp-items="ViewBag.PublishingCompanyId"></select>
<span asp-validation-for="PublishingCompanyId" class="text-danger"></span>
</div>
<div>
//change here....
#Html.Partial("_EditBookCategory", new BookCategory())
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Partial View:
#model BookCategory
<div class="row">
<div class="col-md-12">
<label asp-for="CategoryId" class="control-label"></label>
<select multiple class="form-control" name="CategoryId" asp-items="ViewBag.SelectedCategory"></select>
</div>
</div>
Controller:
Note: I just hard-coded the data due to easy testing, it is the same with your get data from database. You just need change like what my comment said, other code is no need change like what I did.
public IActionResult Edit(int? id)
{
var book = new Book()
{
BookId = 1,
PublishDate = DateTime.Now,
Author = "a1",
BasePrice = 34,
PublishingCompanyId = 1,
Quantity = 23,
Title = "aa"
};
List<BookCategory> bookCategory = new List<BookCategory>()
{
new BookCategory(){BookId=1,CategoryId=1},
new BookCategory(){BookId=2,CategoryId=1},
new BookCategory(){BookId=2,CategoryId=3}
};
ViewData["PublishingCompanyId"] = new List<SelectListItem>() {
new SelectListItem() { Value = "1", Text = "Elementos1" },
new SelectListItem() { Value = "2", Text = "Elementos2" },
new SelectListItem() { Value = "3", Text = "Elementos3" }
};
var c = new List<Category>()
{
new Category(){CategoryId=1,Name="aa"},
new Category(){CategoryId=2,Name="bb"},
new Category(){CategoryId=3,Name="cc"}
};
//change here....
ViewBag.SelectedCategory = new MultiSelectList(c, "CategoryId", "Name", bookCategory.Select(a=>a.CategoryId).ToList());
//ViewBag.testeview = bookCategory.First();
return View(book);
}
[HttpPost]
public IActionResult Edit(int? id,Book book, int[] CategoryId)
{
//do your stuff......
return View();
}
Result:
For how to choose multiple option, you just need press Ctrl key meanwhile click the option.
I have a view inside Admin area and I want to populate it with the following codes but
when I run the program my view shows nothing except empty controls. It returns string without any problems but have trouble with viewmodels
My viewmodel is as follow:
public class AddQuestionsViewModel
{
[Required]
[MinLength(20)]
public string Question { get; set; }
[Required]
[MinLength(50)]
public string Answer { get; set; }
public string Category { get; set; }
public int CategoryId { get; set; }
public SelectList Categories { get; set; }
public DateTime DateTime { get; set; }
public bool IsDisplayed { get; set; }
public int UserId { get; set; }
}
My view:
#model MosahebeClient.Areas.Admin.Models.AddQuestionsViewModel
#{
ViewData["Title"] = "Create";
Layout = "~/Areas/Admin/Views/Shared/_AdminLayout.cshtml";
}
<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="Question" class="control-label"></label>
<input asp-for="Question" class="form-control" />
<span asp-validation-for="Question" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Answer" class="control-label"></label>
<input asp-for="Answer" class="form-control" />
<span asp-validation-for="Answer" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Category" class="control-label"></label>
<input asp-for="Category" class="form-control" />
<span asp-validation-for="Category" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="CategoryId" class="control-label"></label>
<input asp-for="CategoryId" class="form-control" />
<span asp-validation-for="CategoryId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="DateTime" class="control-label"></label>
<input asp-for="DateTime" class="form-control" />
<span asp-validation-for="DateTime" class="text-danger"></span>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="IsDisplayed" /> #Html.DisplayNameFor(model => model.IsDisplayed)
</label>
</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">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
My Controller:
[Area("Admin")]
public ActionResult Create()
{
AddQuestionsViewModel model = new AddQuestionsViewModel();
model.Categories = new SelectList(_categoryRepository.GetAll(), "Id", "Name");
return View(model);
}
Do you want to add a drop-down list to your view? If so, you can add the following code in your view.
//...
<select asp-items="#Model.Categories">
<option>Please select a option</option>
</select>
//...
I send a request to the web service and the response that I get is a list of names and values. I loop them in the view and render a form, to allow users to fill the fields and submit it. But the viewmodel always returns null in post action.
this is my View:
<form method="post">
<div class="row register-form">
#foreach (var item in Model.FlowOutputModel)
{
<div class="col-md-6">
<div class="form-group">
<label for="#item.CharacteristicValue">
#item.CharacteristicName:
</label>
<input asp-for="#item.CharacteristicValue" type="text"
class="form-control"
placeholder="#item.CharacteristicName *" value="" required/>
</div>
</div>
}
<div class="col-md-12">
<input type="submit" class="btnRegister" value="Submit"/>
</div>
</div>
</form>
my model as followin:
public class DecisionInputModel
{
public string FlowName { get; set; }
public List<FlowOutputModel> FlowOutputModel { get; set; }
}
public class FlowOutputModel
{
public string CharacteristicName { get; set; }
public string CharacteristicValue { get; set; }
}
My Controller
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(DecisionInputModel decisionInput)
{
if (ModelState.IsValid)
{
_service.GetResults(decisionInput);
}
}
Any suggestions?
replace loop foreach by for and add action to form
<form method="post" action="#Url.Action("Index", "Home")">
<input asp-for="#Model.FlowName" type="text" class="form-control" />
// or maybe even better
#Html.TextBoxFor(model => model.FlowName, new { #class = "form-control" })
<div class="row register-form">
#for(int i=0; i < Model.FlowOutputModel.Count; i++)
{
<div class="col-md-6">
<div class="form-group">
<label for="#Model.FlowOutputModel[i].CharacteristicValue">
#Model.FlowOutputModel[i].CharacteristicName:
</label>
<input asp-for="#Model.FlowOutputModel[i].CharacteristicValue" type="text"
class="form-control"
placeholder="#Model.FlowOutputModel[i].CharacteristicName *" value="" required/>
</div>
</div>
}
<div class="col-md-12">
<input type="submit" class="btnRegister" value="Submit"/>
</div>
</div>
</form>
You have to add the "name" attribute to the input:
<input name="FlowOutputModel[#Model.FlowOutputModel.IndexOf(item)].CharacteristicValue" type="text" class="form-control" placeholder="#item.CharacteristicName *" value="" required />
I'm not understanding how to properly bind a single item from a collection. In my project, a student can have multiple plans with these models:
public class Student
{
public Student()
{
Plans = new HashSet<Plan>();
}
public int StudentId { get; set; }
public string Designee { get; set; }
public string DesigneePhone { get; set; }
public virtual ICollection<Plan> Plans { get; set; }
}
public partial class Plan
{
public int PlanId { get; set; }
public int StudentId { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public virtual Student Student { get; set; }
}
I'm listing each plan with its own Save button on the page. I thought I was using the asp-for helper correctly according to this Learn Razor Pages post.
#page "{studentId:int}"
#model CollectionTest.PlansModel
<form asp-page-handler="SaveMain" method="post">
<div class="row mt-3">
<input type="hidden" asp-for="Student.StudentId" />
<div class="col">
<label asp-for="Student.Designee"></label>
<input asp-for="Student.Designee" class="form-control" />
</div>
<div class="col">
<label asp-for="Student.DesigneePhone"></label>
<input asp-for="Student.DesigneePhone" class="form-control" />
</div>
<div class="col-auto align-self-end">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</div>
</form>
<hr />
#for (int i = 0; i < Model.Student.Plans.Count(); i++)
{
var planId = Model.Student.Plans.ToList()[i].PlanId.ToString();
<div id="#("card_Plan" + planId)" class="card">
<div class="card-header">
<h5>Plan ##planId</h5>
</div>
<div class="card-body">
<form id="#("form_Plan" + planId)" asp-page-handler="SavePlan" method="post">
<input type="hidden" asp-for="Student.Plans.ToList()[i].PlanId" />
<div class="row">
<div class="col">
<label asp-for="Student.Plans.ToList()[i].StartDate"></label>
<input asp-for="Student.Plans.ToList()[i].StartDate" class="form-control" />
<span asp-validation-for="Student.Plans.ToList()[i].StartDate"></span>
</div>
<div class="col">
<label asp-for="Student.Plans.ToList()[i].EndDate"></label>
<input asp-for="Student.Plans.ToList()[i].EndDate" class="form-control" />
<span asp-validation-for="Student.Plans.ToList()[i].EndDate"></span>
</div>
</div>
</form>
</div>
<div class="card-footer">
<div class="float-right">
<button type="submit" class="btn btn-primary" form="#("form_Plan" + planId)">Save</button>
</div>
</div>
</div>
}
But it seems like nothing is being bound in the SavePlan page handler:
[BindProperty]
public Student Student { get; set; }
.
.
.
public async Task<IActionResult> OnPostSavePlan(int planId)
{
if (!ModelState.IsValid)
{
return Page();
}
/******************************
*
* Student.Plans is empty and planId is zero
*
*******************************/
Plan plan = await _planContext.Plan.FirstAsync(p => p.PlanId == planId);
_planContext.Attach(plan).State = EntityState.Modified;
try
{
await _planContext.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!PlanExists(plan.PlanId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Plans");
}
What am I doing wrong?
According to your code, because what you pass in the razor page is [0].PlanId, but what you receive in the OnPostSavePlan method is planId. If you only need to receive planId, you need to change the form.
<div class="card-body">
<form id="#("form_Plan" + planId)" asp-page-handler="SavePlan" method="post">
<input type="hidden" asp-for="#planId" />
From your requirement,it seems you want to pass a list of plans. So you need to modify your code in the following steps:
Plans.cshtml:
<form id="form_Plan" asp-page-handler="SavePlan" method="post">
#for (int i = 0; i < Model.Student.Plans.Count(); i++)
{
var planId = Model.Student.Plans.ToList()[i].PlanId.ToString();
<div id="#("card_Plan" + planId)" class="card">
<div class="card-header">
<h5>Plan ##planId</h5>
</div>
<div class="card-body">
<input type="hidden" asp-for="Student.Plans.ToList()[i].PlanId" />
<div class="row">
<div class="col">
<label asp-for="Student.Plans.ToList()[i].StartDate"></label>
<input asp-for="Student.Plans.ToList()[i].StartDate" class="form-control" />
<span asp-validation-for="Student.Plans.ToList()[i].StartDate"></span>
</div>
<div class="col">
<label asp-for="Student.Plans.ToList()[i].EndDate"></label>
<input asp-for="Student.Plans.ToList()[i].EndDate" class="form-control" />
<span asp-validation-for="Student.Plans.ToList()[i].EndDate"></span>
</div>
</div>
</div>
</div>
}
<div class="card-footer">
<div class="float-right">
<button type="submit" class="btn btn-primary" form="form_Plan">Save</button>
</div>
</div>
</form>
Plans.cshtml.cs:
public class PlansModel : PageModel
{
public IActionResult OnGet()
{
//for easy testing,I add the data manually
Student = new Student()
{
Plans = new List<Plan>()
{
new Plan(){ PlanId=1, StartDate=DateTime.Parse("2019-8-7")},
new Plan(){ PlanId=2, StartDate=DateTime.Parse("2019-4-7")},
new Plan(){ PlanId=3, StartDate=DateTime.Parse("2019-6-7")}
}
};
return Page();
}
[BindProperty]
public Student Student { get; set; }
public async Task<IActionResult> OnPostSavePlan(List<Plan> plans)
{
//...
}
}
Result:
Update:
Plans.cshtml:
#foreach (var plan in Model.Student.Plans)
{
var planId = plan.PlanId.ToString();
<div id="#("card_Plan" + planId)" class="card">
<div class="card-header">
<h5>Plan ##planId</h5>
</div>
<div class="card-body">
<form id="#("form_Plan" + planId)" asp-page-handler="SavePlan" method="post">
<input type="hidden" asp-for="#plan.PlanId" />
<div class="row">
<div class="col">
<label asp-for="#plan.StartDate"></label>
<input asp-for="#plan.StartDate" class="form-control" />
<span asp-validation-for="#plan.StartDate"></span>
</div>
<div class="col">
<label asp-for="#plan.EndDate"></label>
<input asp-for="#plan.EndDate" class="form-control" />
<span asp-validation-for="#plan.EndDate"></span>
</div>
</div>
</form>
</div>
<div class="card-footer">
<div class="float-right">
<button type="submit" class="btn btn-primary" form="#("form_Plan" + planId)">Save</button>
</div>
</div>
</div>
}
Plans.cshtml.cs:
public async Task<IActionResult> OnPostSavePlan(Plan plan)
{
//...
}
Result:
My current view model:
public class CustomerVM
{
public string fname;
public virtual IList<DocumentVM>? Documents { get; set; } = null!;
}
public class DocumentVM
{
public string UniqueNo;
...some code ommitted
}
Below are Chtml design. one customer can fill 2 d documents.
...some code ommited
<div class="form-group col-md-3">
<label asp-for="customer.FirstName" class="col-form-label"></label>
<input asp-for="customer.FirstName" class="form-control" />
<span asp-validation-for="customer.FirstName" type="text" class="text-danger"></span>
</div>
<div class="form-group col-md-4">
<label asp-for="customer.Documents[0].DocTypeId" class="col-form-label">Document Type</label>
<select asp-for="customer.Documents[0].DocTypeId" asp-items="#Model.customer.DocumentTypeList" class="form-control">
<option>Choose</option>
</select>
</div>
<div class="form-group col-md-3">
<label asp-for="customer.Documents[0].UniqueNo" class="col-form-label"></label>
<input asp-for="customer.Documents[0].UniqueNo" type="text" class="form-control" />
<span asp-validation-for="customer.Documents[0].UniqueNo" class="text-danger"></span>
</div>
<div class="form-group col-md-4">
<label asp-for="customer.Documents[1].DocTypeId" class="col-form-label">Document Type</label>
<select asp-for="customer.Documents[1].DocTypeId" asp-items="#Model.customer.DocumentTypeList" class="form-control">
<option>Choose</option>
</select>
</div>
<div class="form-group col-md-3">
<label asp-for="customer.Documents[1].UniqueNo" class="col-form-label"></label>
<input asp-for="customer.Documents[1].UniqueNo" type="text" class="form-control" />
<span asp-validation-for="customer.Documents[1].UniqueNo" class="text-danger"></span>
</div>
This code works perfectly. But I need to change my view model Customer
public virtual IList<DocumentVM>? Documents { get; set; } = null!;
to
public virtual ICollection<DocumentVM>? Documents { get; set; } = null!;
I can't directly access index from Razor view. Is it possible to change my view design change IList to ICollection object in ASP.NET Core 3.1?
Thanks in advance
You want to save multiple documents on form submit, so on form rendering use for loop which will populate the strongly typed controls according to collection and can be submit accordingly.
for (int i = 0; i < Model.Count; i++)
{
#Html.LabelFor(m => m[i].UniqueNo)
#Html.TextBoxFor(m => m[i].UniqueNo)....
}