ASP.NET Core MVC Master Detail in partial view - asp.net-core

I am trying to do CRUD operations on a Master Detail model Department and Employee using modal popup. This is the code I used:
DepartmentController:
[HttpGet]
public IActionResult Create()
{
Department department= new Department();
department.Employees.Add(new Employee() { EmployeeId = 1 });
department.Employees.Add(new Employee() { EmployeeId = 2 });
department.Employees.Add(new Article() { EmployeeId = 3 });
return PartialView("_AddDepartmentPartialView",department);
}
[HttpPost]
public IActionResult Create(Department department)
{
if (department != null)
{
_dbcontext.Department.Add(department);
_dbcontext.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
Index.cshtml:
#model IEnumerable<Department>
#{
ViewData["Title"] = "Index";
Layout = "~/Views/Shared/_Theme.cshtml";
}
<div>
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Department</h3>
<div class="card-tools">
<button type="button" class="btn btn-info" data-toggle="modal" data-target="#addDepartment">
<i class="fa fa-plus"></i>
Ajouter
</button>
</div>
</div>
<div class="card-body">
.....
</div>
</div>
</div>
</div>
</div>
#await Html.PartialAsync("_AddDepartmentPartialView", new Department())
_AddDepartmentPartialView.cshtml:
#model Department
#{
ViewData["Title"] = "_AddDepartmentPartialView";
}
<div class="modal fade " role="dialog" tabindex="-1" id="addDepartment" aria-labelledby="addDepartmentLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
.....
</div>
<div class="modal-body" >
.......
<form asp-action="Create" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<table class="table">
<thead>
<tr>
<th>Employee name</th>
<th>Profession</th>
<th>Email</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Employees.Count; i++)
{
<tr>
<td>
#Html.EditorFor(x => x.Employees[i].EmployeeName, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
#Html.EditorFor(x => x.Employees[i].Profession, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
#Html.EditorFor(x => x.Employees[i].Email, new { htmlAttributes = new { #class = "form-control" } })
</td>
</tr>
}
</tbody>
</table>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-primary" >Sauvegarder</button>
</div>
</form>
</div>
</div>
</div>
</div>
But this PartialView displays only the inputs of the Department model and it doesn't displays the rows of the table to insert employees records (it displays only the head of the table).
So, how to pass both Department and Employee to the partial view?
UPDATE
I tried the solution of #Xinran Shen, the modal popup finally appears with both models Department and Employee. But on Ajouter click, the Index view behind the modal popup changed (a table of all departments is supposed to appear but the nothing is displayed), also i have a Dropdownlist in the modal popup it appears empty and it doesn't populated. I think because i am using a custom Layout page but i couldn't find where is the problem exactly. Any help??

In Index.cshtml, You use:
#await Html.PartialAsync("_AddDepartmentPartialView", new Department())
to load the partial View, It will not create partial view by Create get method. And you just pass new Department() into partial View, The Department passed into view is null, So it doesn't display the rows of table.
You can set an onclick event on Ajouter button, When user click the button, It will access Create get method and Initialize Department with the initial value, Then return partial view to the index view.
.............
<button type="button" class="btn btn-info" data-bs-toggle="modal" data-bs-target="#addDepartment" onclick="GetDetails()">
<i class="fa fa-plus"></i>
Ajouter
</button>
..........
<div class="card-body" id="Body">
</div>
...............
<script>
function GetDetails() {
$.ajax({
type: "Get",
url: "/Home/Create",
success: function (res) {
$("#Body").html(res);
$("#addDepartment").modal('show');
}
});
}
</script>
Then when you click button, It will show the full table.

Related

How do I load a partial View (modal popup) in MVC Areas I'm using Visual Studio 2022 .NET 7

Here is what I have tried
The code runs perfectly until I try to implement it in my ÄdminPortal MVC Area, then the Partial View doesn't popup at all. I'm sure I've missed something simply but being new to this I have been unable to find the answer. TIA for your help
My Controller
using Microsoft.AspNetCore.Mvc;
using Quotemaster.Areas.AdminPortal.Models;
using Quotemaster.data;
namespace Quotemaster.Areas.AdminPortal.Controllers
{
[Area("AdminPortal")]
public class MakesController : Controller
{
private readonly QMContext _context;
public MakesController(QMContext context)
{
_context = context;
}
public IActionResult Index()
{
var makesList = _context.Makes.ToList();
return View(makesList);
}
[HttpGet]
public IActionResult Create()
{
Makes makes = new Makes();
return PartialView("_MakesPartial", makes);
}
[HttpPost]
public IActionResult Create(Makes makes)
{
_context.Makes.Add(makes);
_context.SaveChanges();
var makesList = _context.Makes.ToList();
return View(makesList);
}
}
}
My Index page
#model IEnumerable<Quotemaster.Areas.AdminPortal.Models.Makes>
#{
ViewData["Title"] = "Index";
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
<div id="AddMake"></div>
<button type="button" class="btn btn-warning" data-bs-toggle="ajax-modal" data-bs-target="#AddMakes"
data-url="#Url.Action("Create")">
Add Make
</button>
<table class="table table-hover">
<thead style="background-color:orange">
<tr>
<th>
Make
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Make)
</td>
<td>
<a asp-action="Edit" asp-route-id="#item.MakeId">Edit</a> |
<a asp-action="Details" asp-route-id="#item.MakeId">Details</a> |
<a asp-action="Delete" asp-route-id="#item.MakeId">Delete</a>
</td>
</tr>
}
</tbody>
</table>
<script type="text/javascript">
//Show Add Make Modal
$(function () {
var AddMakesElement = $('#AddMakes');
$('button[data-bs-toggle="ajax-modal"]').click(function (event) {
var url = $(this).data('url');
$.get(url).done(function (data) {
AddMakesElement.html(data);
AddMakesElement.find('.modal').modal('show');
})
})
//Save Add Makeform data
AddMakesElement.on('click', '[data-bs-save="modal"]', function (event) {
var form = $(this).parents('.modal').find('form');
var actionUrl = form.attr("action");
var sendData = form.serialize();
$.post(actionUrl, sendData).done(function (data) {
AddMakesElement.find('.modal').modal('hide');
location.reload(true);
})
})
})
</script>
My partial View
#model Quotemaster.Areas.AdminPortal.Models.Makes
//partial View
<!DOCTYPE html>
<html lang="en">
<head>
<title>Add Makes</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<!-- The Modal -->
<div class="modal fade" id="AddMakes" name="AddMakes">
<div class="modal-dialog">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header" style="background-color:orange">
<h4 class="modal-title">Add Makes</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<!-- Modal body -->
<div class="modal-body">
<form asp-action="Create" enctype="multipart/form-data" method="post">
<div class="form-group">
<label asp-for="Make"></label>
<input asp-for="Make" class="form-control form-control-sm">
<span asp-validation-for="Make" class="text-danger"></span>
</div>
</form>
</div>
<!-- Modal footer -->
<div class="modal-footer">
<button class="btn btn-warning">Add Make</button>
<button type="button" class="btn btn-danger" data-bs-dismiss="modal">Close</button>
#*<div input typeof="hidden" id="urlMakesData"value="#Url.Action("AddMakes","Ajax")"></div>*#
</div>
</div>
</div>
</div>
</body>
</html>
1.You want to call the Create action and render the html to the following div:
<div id="AddMake"></div>
But you get the wrong element in js herevar AddMakesElement = $('#AddMakes');, it should be AddMake instead of AddMakes.Change to:
var AddMakesElement = $('#AddMake');
2.Be sure your partial view located in one of the following location:
/Areas/AdminPortal/Views/Makes/_MakesPartial.cshtml
/Areas/AdminPortal/Views/Shared/_MakesPartial.cshtml
/Views/Shared/_MakesPartial.cshtml

Bootstrap Modal Popup background view

I am working on a ASP.NET Core CRUD applciation using modal popup i have a master detail models Stock and Article. i used this code to display the modal popup:
StockController:
public IActionResult Index()
{
List<Category> categories = _dbcontext.Category.ToList();
ViewBag.ListCategories = new SelectList(categories, "CategoryId", "CategoryName");
List<Stock> AllStocks = _dbcontext.Stock.ToList();
return View(AllStocks);
}
[HttpGet]
public IActionResult Create()
{
Stock stock = new Stock();
stock.Articles.Add(new Article() { ArticleId = 1 });
return View("_AddStockPartialView", stock);
}
[HttpPost]
public IActionResult Create(Stock stock)
{
if (stock != null)
{
_dbcontext.Stock.Add(stock);
_dbcontext.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
Index.cshtml:
#model IEnumerable<Stock>
#{
ViewData["Title"] = "Index";
Layout = "~/Views/Shared/_Theme.cshtml";
}
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Stock</h3>
<div class="card-tools">
<button type="button" class="btn btn-info" data-toggle="modal" data-target="#addStock" onclick="GetDetails()">
<i class="fa fa-plus"></i>
Ajouter
</button>
</div>
</div>
<div class="card-body" id="display">
<table class="table table-bordered table-hover">
.....
</table>
</div>
</div>
</div>
</div>
<script>
function GetDetails() {
$.ajax({
type: "Get",
url: "/Stock/Create",
success: function (res) {
$("#display").html(res);
$("#addStock").modal('show');
}
});
}
</script>
_AddStockPartialView.cshtml:
#model GestionStock.Models.Stock
#{
ViewData["Title"] = "_AddStockPartialView";
}
<div class="modal fade " role="dialog" tabindex="-1" id="addStock" aria-labelledby="addStockLabel" aria-hidden="true" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h5>Stock</h5>
</div>
<div class="modal-body">
<form asp-action="Create" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="CategoryId" class="control-label"></label>
<select asp-for="CategoryId" class="form-control" asp-items="ViewBag.ListCategories"></select>
<span asp-validation-for="CategoryId" class="text-danger"></span>
</div>
.......
<table class="table table-striped" id="articleTable">
<thead>
<tr>
<th>Numero serie</th>
<th>Marque</th>
<th>etat</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Articles.Count; i++)
{
<tr>
<td>
#Html.EditorFor(x => x.Articles[i].NumeroSerie, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
#Html.EditorFor(x => x.Articles[i].Marque, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
#Html.EditorFor(x => x.Articles[i].Etat, new { htmlAttributes = new { #class = "form-control" } })
</td>
</tr>
}
</tbody>
</table>
<input type="hidden" id="hdnLastIndex" value="0" />
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" #*onclick="javascript:window.location.reload()"*#>Annuler</button>
<button type="submit" class="btn btn-primary">Sauvegarder</button>
</div>
</form>
</div>
</div>
</div>
</div>
Everything works fine and the modal popup is displaying with master detail models. But when i click te button to display the modal popup, the background view (Index.cshtml) is changed like the picture below and the CategoryId SelectList isn't populating:
Although this is the index view which is supposed to display in the background of the modal popup:
So why is Index view chaning when displaying the modal popup?
Below is a work demo, you can refer to it.
In controller, make some change in create action.
[HttpGet]
public IActionResult Create()
{
List<Category> categories = _dbcontext.Category.ToList();
ViewBag.ListCategories = new SelectList(categories, "CategoryId", "CategoryName");
Stock stock = new Stock();
stock.Articles.Add(new Article() { ArticleId = 1 });
return PartialView("_AddStockPartialView", stock);
}
2.In the Index view:
remove table (<table>) before <div class="card-body" id="display">
3.In the _Theme.cshtml, check and make sure you already have below code:
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/GestionStock.styles.css" asp-append-version="true" />
...
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
4.result(I use my model data to show the table) :

Add Delete details in Master Detail model - ASP.NET Core

I am doing CRUD operations on a Master Detail models Team and TeamMember using modal popup. I followed this video to create Add and Delete buttons in Create action like this:
This is the code i used to perform this:
TeamController:
public IActionResult Create()
{
Team team = new Team();
team.TeamMembers.Add(new TeamMember() { TeamMemberId = 1 });
return PartialView("_AddTeamPartialView", team);
}
[HttpPost]
public IActionResult Create(Team team)
{
if (team != null)
{
_dbcontext.Team.Add(team);
_dbcontext.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
_AddTeamPartialView.cshtml:
#model Team
#{
ViewData["Title"] = "_AddTeamPartialView";
}
<div class="modal fade" role="dialog" tabindex="-1" id="addTeam" aria-labelledby="addTeamLabel" aria-hidden="true" data-backdrop="static" data-keyboard="false" >
<div class="modal-dialog modal-xl modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h3>Team</h3>
</div>
<div class="modal-body">
<form asp-action="Create" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="TeamName" class="control-label">TeamName</label>
<input asp-for="TeamName" class="form-control" />
<span asp-validation-for="TeamName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Coach" class="control-label">Coach</label>
<input asp-for="Coach" class="form-control" />
<span asp-validation-for="Coach" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="NumberTeamMembers" class="control-label">NumberTeamMembers</label>
<input asp-for="NumberTeamMembers" class="form-control" id="NumberOfTeamMembers" />
<span asp-validation-for="NumberTeamMembers" class="text-danger"></span>
</div>
<h3>Members</h3>
<table class="table table-bordered" id="membersTable">
<thead>
<tr>
<th>Name</th>
<th>BirthDate</th>
<th>Phone</th>
<th>
<button id="addbtnMember" type="button" class="btn btn-sm btn-secondary visible"
onclick="AddItem(this)">
Add
</button>
</th>
</tr>
</thead>
<tbody >
#for (int i = 0; i < Model.TeamMembers.Count; i++)
{
<tr>
<td>
#Html.EditorFor(x => x.TeamMembers[i].Name, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
#Html.EditorFor(x => x.TeamMembers[i].BirthDate, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
#Html.EditorFor(x => x.TeamMembers[i].Phone, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
<button id="btnremove-#i" type="button" class="btn btn-sm btn-danger"
onclick="DeleteItem(this)" >
Delete
</button>
</td>
</tr>
}
</tbody>
</table>
<input type="hidden" id="hdnLastIndex" value="0" />
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" onclick="javascript:window.location.reload()">CANCEL</button>
<button type="submit" class="btn btn-primary">SAVE</button>
</div>
</form>
</div>
</div>
</div>
</div>
#*#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}*#
<script>
function DeleteItem(btn) {
$(btn).closest('tr').remove();
document.getElementById('hdnLastIndex').value = document.getElementById('hdnLastIndex').value - 1;
}
function AddItem(btn) {
var table = document.getElementById('membersTable');
var rows = table.getElementsByTagName('tr');
var rowOuterHtml = rows[rows.length - 1].outerHTML;
var lastrowIdx = document.getElementById('hdnLastIndex').value;
var RowNumber = document.getElementById('NumberOfTeamMembers').value;
var nextrowIdx = eval(lastrowIdx) + 1;
document.getElementById('hdnLastIndex').value = nextrowIdx;
rowOuterHtml = rowOuterHtml.replaceAll('_' + lastrowIdx + '_', '_' + nextrowIdx + '_');
rowOuterHtml = rowOuterHtml.replaceAll('[' + lastrowIdx + ']', '[' + nextrowIdx + ']');
rowOuterHtml = rowOuterHtml.replaceAll('-' + lastrowIdx, '-' + nextrowIdx);
if (nextrowIdx < RowNumber) {
var newRow = table.insertRow();
newRow.innerHTML = rowOuterHtml;
}
}
</script>
By using this code i can add team members less or egal the number of team members, but the problem is that when i delete one the team members (for example the first team member) during the create operation of a new team, all the team members inserted after the deleted one won't be saved. where is the problem in this code?

Reusing Razor view component in list context

Say, on a page I have a list of items and a Delete button next to each. Upon clicking, I want to show a pop-up with a confirmation message.
The confirmation dialog and the deletion functionality are put into a view component.
I know I can do like this:
foreach (var item in Model.List)
{
<tr class="row">
<td class="col-12">
#item.Name
<button class="btn btn-danger ml-auto" data-toggle="modal"
data-target="#delete-item-#item.Id">×</button>
<vc:delete-item-dialog id="delete-item-#item.Id" item-id="#item.Id"></vc:delete-item-dialog>
</td>
</tr>
}
But then each delete-item-dialog view component is rendered separately, bloating the size of the generated HTML.
Is it possible to place that view component only in one place, after the end of the list, and provide the item-id parameter more dynamically?
Is it possible to place that view component only in one place, after the end of the list, and provide the item-id parameter more dynamically?
Yeah, you can use ajax to load the view component dynamically. Below is a working demo.
View:
#model List<User>
<table>
#foreach (var item in Model)
{
<tr class="row">
<td class="col-12">
#item.Name
<button class="btn btn-danger ml-auto" onclick="deleteuser(#item.Id)">
×
</button>
</td>
</tr>
}
</table>
<div id="container">
</div>
#section scripts{
<script>
function deleteuser(id){
$.ajax({
type: 'get',
url: 'GetMyViewComponent?id=' + id,
success: function (result) {
$("#container").html(result);
$('.modal').modal('show');
}
})
}
</script>
}
Controller:
public IActionResult UserList()
{
var users = new List<User>
{
new User{ Id = 1, Name = "Mike"},
new User{ Id = 2, Name = "John"},
new User{ Id = 3, Name = "David"},
};
return View(users);
}
public IActionResult GetMyViewComponent(int id)
{
return ViewComponent("PopUp", id);
}
PopUpViewComponent class:
public class PopUpViewComponent : ViewComponent
{
public IViewComponentResult Invoke(int id)
{
return View(id);
}
}
PopUpViewComponent Razor View:
#model int
<div class="modal fade" id="delete-#Model" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitle">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
Are you sure to delete #Model?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary">Ok</button>
</div>
</div>
</div>
</div>
Result:

Reloading of layout causes Model to go empty (MVC4)

I'm trying to show a table of results in my view by use of HttpPost, but my view always turns up empty.
I have found out by now that the httpPost action works and returns a result, but before completing the action, the layout is called again, where the RenderBody is called again. Once the RenderBody is done, the view contains an empty model again and results in the page showing up empty.
Looking for advice how to solve this issue! I broke my head on this for hours and don't seem to see the light...
The View code:
#using MovieRental.DAL.Entities; #model List
<Movie>
#{ ViewBag.Title = "MyRentedMovies"; }
<h2>MyRentedMovies</h2>
<div class="container">
<div class="col-md-12 column">
<form id="frmmymovies" class="col-md-12">
<input id="currentuserid" type="hidden" name="userid" value="#ViewBag.user.UserId">
</form>
<div id="mymovies" class="btn btn-primary">Show my rented movies</div>
</div>
</div>
<div>
#ViewBag.cost;
</div>
#if (Model != null) {
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<table id="" class="table table-striped table-bordered">
<thead>
<tr>
<th>Action</th>
<th>Movie Title</th>
<th>Release Date</th>
<th>Storyline</th>
<th>Location</th>
<th>Movie Genre</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Action</th>
<th>Movie Title</th>
<th>Release Date</th>
<th>Storyline</th>
<th>Location</th>
<th>Movie Genre</th>
</tr>
</tfoot>
<tbody>
#foreach (var item in Model) {
<tr>
<td><i class="glyphicon glyphicon-plus" movieId="#item.MovieId"></i>
</td>
<td>#item.MovieName</td>
<td>#item.ReleaseDate</td>
<td>#item.Storyline</td>
<td>#item.Location.LocationName.ToString()</td>
<td>#item.MovieGenre.GenreName.ToString()</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
} else {
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<div class="alert alert-info" role="alert">
You currenty don't have any rented movies in your possession.
</div>
</div>
</div>
</div>
}
The Controller code:
public ActionResult MyRentedMovies()
{
return View();
}
[HttpPost]
public ActionResult MyRentedMovies(int userid)
{
var myrentdetails = rentrep.GetAll().Where(e => e.UserId == userid);
List<Movie> mymovies = new List<Movie>();
foreach (var item in myrentdetails)
{
Movie movie = movierep.FindById(item.MovieId);
mymovies.Add(movie);
}
return View(mymovies);
}
The JS script:
$(function () {
$('#mymovies').click(function () {
//var user = $('#currentuserid').val();
console.log('gethere');
$.post('/useronly/myrentedmovies', $('#frmmymovies').serialize(), function (data) {
if (data) {
console.log(data);
document.location = '/useronly/myrentedmovies';
}
else {
console.log('error');
}
});
});
});