I am working on a Master Detail CRUD operations in an ASP.NET Core MVC project. I am trying to perform the Create operation of both Master and Detail models at once, i want to save an order and its order lines (the number of the order lines is the value of the input quantite):
public class Order
{
[Key]
public int OrderId { get; set; }
[Required]
public DateTime OrderDate { get; set; }
[Required]
public int Quantity { get; set; }
public virtual List<OrderLine> OrderLines { get; set; } = new List<OrderLine>();
}
public class OrderLine
{
[Key]
public int OrderLineId { get; set; }
[Required]
public string Designation{ get; set; }
[Required]
public string Serial{ get; set; }
[Required]
public string State { get; set; }
[ForeignKey("OrderId")]
public int OrderId { get; set; }
public virtual Order Order { get; set; }
}
OrderController:
public IActionResult Create()
{
Order order= new Order();
order.OrderLines.Add(new OrderLine() { OrderLineId = 1 });
return PartialView("_AddOrderPartialView", order);
}
[HttpPost]
public IActionResult Create(Order order)
{
if (order != null)
{
_dbcontext.Order.Add(order);
_dbcontext.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
_AddOrderPartialView.cshtml:
#model Order
#{
ViewData["Title"] = "_AddOrderPartialView";
}
<div class="modal fade" role="dialog" tabindex="-1" id="addOrder" aria-labelledby="addOrderLabel" aria-hidden="true">
<div class="modal-dialog role="document">
<div class="modal-content">
<div class="modal-header">
<h3>Order</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="Quantity" class="control-label">Quantity</label>
<input asp-for="Quantity" class="form-control" id="quantite" />
<span asp-validation-for="Quantity" class="text-danger"></span>
</div>
.......
<h3>Order Lines</h3>
<table class="table table-bordered" id="orderLinesTable">
<thead>
<tr>
<th>Designation</th>
<th>Serial</th>
<th>State</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.OrderLines.Count; i++)
{
<tr>
<td>
#Html.EditorFor(x => x.OrderLines[i].Designation, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
#Html.EditorFor(x => x.OrderLines[i].Serial, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
#Html.EditorFor(x => x.OrderLines[i].State, new { htmlAttributes = new { #class = "form-control" } })
</td>
</tr>
}
</tbody>
</table>
<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>
<script>
$("#quantite").on('change', function () {
var RowNumber = document.getElementById('quantite').value;
var table = document.getElementById('orderLinesTable');
var rows = table.getElementsByTagName('tr');
var rowOuterHtml = rows[rows.length - 1].outerHTML;
$("#orderLinesTable tbody").find("tr:not(:first)").remove();
for (var i = 0; i < RowNumber-1; i++) {
var newRow = table.insertRow();
newRow.innerHTML = rowOuterHtml;
}
});
</script>
The problem is that this code saves only the first order line in the database. any help?
Related
I have two Entities that have many To many relationship, Book and Tags asp Created automatically a table for the two entities, Using the code first approach
I am trying to add a collection of tags in the book creation, but the tag items are null also there is (select asp-for="Tags" ) but it shows me null in [httppost]create.
I tried to add it in through context as it catches the values of tags I add, but there is an error
cannot convert ......dbset<> to Models.tags
Code:
public class Book
{
public int BookID { get; set; }
[Required]
public string Name { get; set; } = null!;
//Navigation property
public virtual ICollection<Tags>? Tags { get; set; }
}
public class Tags
{
public int TagsID { get; set; }
public string TagName { get; set; } = null!;
//Navigation property
public virtual ICollection<Book>? Book { get; set; }
}
//DB Context
public class BLabContext: DbContext
{
public DbSet<Book> Book { get; set; }
public DbSet<Tags> Tags { get; set; }
}
// Book Controller
public class BooksController : Controller
{
private readonly BLabContext _context;
public BooksController(BLabContext context)
{
_context = context;
}
// Tags objects
// public ICollection<Tags> Tags { get; set; }
// GET: Books
public async Task<IActionResult> Index()
{
return View(await _context.Book.ToListAsync());
}
// GET: Books/Create
(on get )
public IActionResult Create()
{
///πππππππ
var tags = _context.Tags.ToList();
ViewData["tags"] = tags;
//ViewBag.tags = tags;
return View();
}
// POST: Books/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("BookID,Name,Description,PublishedOn,Publisher,Price,ImageURL,1)πTagsπ")] Book book)
{
if (ModelState.IsValid)
{
2)π
var tags = _context.Tags;
_context.Add(book);
await _context.SaveChangesAsync();
2)π(cannot convert ......dbset<> to Models.tags
_context.Book.FirstOrDefault(b => b.BookID == book.BookID).Tags.Add(tags);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(book);
}
The create view:
#using Book_Library.Models;
#model Book_Library.Models.Book
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Book</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<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">
<label asp-for="Tags" class="control-label"></label>
#*name="Tags"*#
<select asp-for="Tags" multiple>
#foreach (var tag in #ViewData["tags"] as IList<Tags>)
{
<option value="#tag.TagName">#tag.TagName </option>
}
</select>
</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");}
}
I expect to add a collection of tags to the book on creation, and also view those tags on the details view
You can create a ViewModel to achieve it, Please refer to this demo:
ViewModel
public class CreateBookViewModel
{
[Required]
public string BookName { get; set; }
public List<int> TagId { get; set; }
}
Controller
public IActionResult Create()
{
var tags = _context.Tags.ToList();
List<SelectListItem> dropdown = new List<SelectListItem>();
foreach (var item in tags)
{
var listItem = new SelectListItem();
listItem.Text = item.TagName;
listItem.Value = item.TagsID.ToString();
dropdown.Add(listItem);
}
ViewBag.tags = dropdown;
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(CreateBookViewModel book)
{
if (ModelState.IsValid)
{
Book b = new Book();
b.Name = book.BookName;
if (book.TagId.Count>0)
{
foreach (var item in book.TagId)
{
var tag = _context.Tags.Where(x => x.TagsID == item).FirstOrDefault();
b.Tags.Add(tag);
}
}
_context.Book.Add(b);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(book);
}
View
#model CreateBookViewModel
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Book</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="BookName" class="control-label"></label>
<input asp-for="BookName" class="form-control" />
<span asp-validation-for="BookName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#Model.TagId" class="control-label"></label>
<select asp-for="#Model.TagId" asp-items="#ViewBag.tags" multiple></select>
</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");
}
}
Now, You can add collection of Tags to Book successfully.
Note: You need to instantiate Tags in your Book class, Otherwise the above code will report a nullreferenceexception.
public class Book
{
//.........
//Navigation property
public virtual ICollection<Tags>? Tags { get; set; } = new List<Tags>();
}
I am using ASP.NET Core 5 with a complex model and I am having a heck of a time getting the model to post correctly.
Here is my model class:
public class Project
{
[Key]
public int ProjectId { get; set; }
[Required]
public string Name { get; set; }
public virtual List<ResponsibleParty> ResponsibleParties { get; set; }
public virtual List<ProjectManager> ProjectManagers { get; set; }
}
In my razor page I am doing the following:
[BindProperty]
public Project Project { get; set; }
[BindProperty]
public ResponsibleParty ResponsibleParty { get; set; }
I am using a bootstrap model to popup a window that allows the user to enter the information for ResponibleParty and an "Add" button that posts back where I add the ResponsibleParty to the Project.ResponsibleParty list. On the page returns I can see the ResponsibleParty was added and displayed.
Now.. when I try to add another and the page posts the project model no longer contains the ResponsibleParty.
Any ideas here on what I'm doing wrong?
I am using a bootstrap model to popup a window that allows the user to
enter the information for ResponibleParty and an Add button that posts
back where I add the ResponsibleParty to the Project.ResponsibleParty
List. One the page returns I can see the ResponsibleParty was added
and displayed. Now.. When I try to add another and the Page Posts the
Project model no longer contains the ResponsibleParty. Any Ideas here
on what I'm doing wrong?
Please check your Post method, whether the new item is inserted successfully, and in the get method, whether you are getting the latest data.
Based on the model and your description, I create a sample and use session to store the Project data, you can refer to them:
Configure the application to use session, and add the SessionExtensions class to store object. Refer this article: Configure session state
[Note] Please note the session expired time. Besides, if you don't want to use the session, you can also store the data into database.
Create model:
public class Project
{
[Key]
public int ProjectId { get; set; }
[Required]
public string Name { get; set; }
public virtual List<ResponsibleParty> ResponsibleParties { get; set; }
public virtual List<ProjectManager> ProjectManagers { get; set; }
}
public class ResponsibleParty
{
public int ResponsiblePartyId { get; set; }
public string Name { get; set; }
}
public class ProjectManager {
public int PMid { get; set; }
public string PMName { get; set; }
}
Create a ProjectIndex Razor Page:
ProjectIndex.cshtml:
#page "/projectIndex"
#model RazorWebApplication.Pages.ProjectIndexModel
#{
ViewData["Title"] = "Home page";
}
<h2>Project Index</h2>
<div class="container">
#if (Model.Project != null && Model.Project.ResponsibleParties.Count > 0)
{
<table class="table">
<thead>
<tr>
<th>
ProjectId
</th>
<th>
Name
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Project.ResponsibleParties)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.ResponsiblePartyId)
</td>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
<a asp-action="Edit" asp-route-id="#item.ResponsiblePartyId">Edit</a> |
<a asp-action="Details" asp-route-id="#item.ResponsiblePartyId">Details</a> |
<a asp-action="Delete" asp-route-id="#item.ResponsiblePartyId">Delete</a>
</td>
</tr>
}
</tbody>
</table>
}
</div>
<!-- Button to Open the Modal -->
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#myModal">
Create New ResponsibleParty
</button>
<!-- The Modal -->
<div class="modal" id="myModal">
<div class="modal-dialog">
<div class="modal-content">
<form method="post" asp-page-handler="AddResponsibleParty">
<div class="container-fluid">
<div class="row p-5 border pt-4 my-3 rounded">
<div class="input-group row mb-3">
<div class="col-md-4 col-sm-12 d-sm-block">
<label asp-for="ResponsibleParty.ResponsiblePartyId"></label>
</div>
<div class="col-md-8 col-sm-12">
<textarea class="form-control" asp-for="ResponsibleParty.ResponsiblePartyId"></textarea>
<span class="text-danger" asp-validation-for="ResponsibleParty.ResponsiblePartyId"></span>
</div>
</div>
<div class="input-group row mb-3">
<div class="col-md-4 col-sm-12 d-sm-block">
<label asp-for="ResponsibleParty.Name"></label>
</div>
<div class="col-md-8 col-sm-12">
<textarea class="form-control" asp-for="ResponsibleParty.Name"></textarea>
<span class="text-danger" asp-validation-for="ResponsibleParty.Name"></span>
</div>
</div>
<div class="input-group row mb-3">
<input type="submit" value="Submit" />
</div>
</div>
</div>
</form>
</div>
</div>
</div>
ProjectIndex.cshtml.cs:
public class ProjectIndexModel : PageModel
{
[BindProperty]
public Project Project { get; set; }
[BindProperty]
public ResponsibleParty ResponsibleParty { get; set; }
private readonly string sessionkeyproject = "project";
public void OnGet()
{
//check if the seesion is null.
if (HttpContext.Session.Get<Project>(sessionkeyproject) == default)
{
//if session is null, set the initial data.
Project = new Project()
{
ProjectId = 101,
Name = "A",
ResponsibleParties = new List<ResponsibleParty>()
{
new ResponsibleParty(){ ResponsiblePartyId=1001, Name="RP1"},
new ResponsibleParty(){ ResponsiblePartyId=1002, Name="RP2"},
}
};
//store the data using session
HttpContext.Session.Set<Project>(sessionkeyproject, Project);
}
else
{
// get the latest data from the session.
Project = HttpContext.Session.Get<Project>(sessionkeyproject);
}
}
public async Task<IActionResult> OnPostAddResponsibleParty()
{
var newitem = ResponsibleParty;
if (HttpContext.Session.Get<Project>(sessionkeyproject) == default)
{
return RedirectToPage();
}
else
{
//get the project from the session
Project = HttpContext.Session.Get<Project>(sessionkeyproject);
}
//add the new responsibleparty into the project
Project.ResponsibleParties.Add(new ResponsibleParty()
{
ResponsiblePartyId = newitem.ResponsiblePartyId,
Name = newitem.Name
});
//update the seesion to store the latest data.
HttpContext.Session.Set<Project>(sessionkeyproject, Project);
return RedirectToPage();//still to decide where it should go
}
}
The result as below:
I've been working on my version of the app made in this tutorial (https://learn.microsoft.com/pl-pl/aspnet/core/data/ef-rp/complex-data-model?view=aspnetcore-5.0&tabs=visual-studio). And I've got question about a connection between two objects. My idea is to add more than one Material to Paczka. I've manage to connect them together but I can add only one Material. So my question is what should I do to be able to connect more than one?
Object Material
public class Material
{
[Key]
public int PaczkaID { get; set; }
public string Name { get; set; }
public string PN { get; set; }
public string Cert { get; set; }
public string Qty { get; set; }
public Paczka Paczka { get; set; }
}
And object Paczka
public class Paczka
{
public int PaczkaID { get; set; }
public string CRS { get; set; }
public string WO { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public Material Material { get; set; }
}
Here is how I can add Material to Paczka
public class MaterialModel : PageModel
{
private readonly Pasto.Data.PastoContext _context;
public MaterialModel(Pasto.Data.PastoContext context)
{
_context = context;
}
[BindProperty]
public Paczka Paczka { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Paczka = await _context.Paczkas
.Include(i => i.Material)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.PaczkaID == id);
if (Paczka == null)
{
return NotFound();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int? id)
{
if (id == null)
{
return NotFound();
}
var paczkaToUpdate = await _context.Paczkas
.Include(i => i.Material)
.FirstOrDefaultAsync(s => s.PaczkaID == id);
if (paczkaToUpdate == null)
{
return NotFound();
}
if (await TryUpdateModelAsync<Paczka>(
paczkaToUpdate,
"Paczka",
i => i.Material))
{
if (String.IsNullOrWhiteSpace(
paczkaToUpdate.Material?.Name))
{
paczkaToUpdate.Material = null;
}
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}
}
And HTML
a<form method="post">
<table class="table">
<thead>
<tr>
<th>
<strong>Name</strong>
</th>
<th>
<strong>P/N</strong>
</th>
<th>
<strong>Certificate</strong>
</th>
<th>
<strong>Quantity</strong>
</th>
</tr>
</thead>
<div class="form-group">
<tbody>
<tr>
<td>
<input asp-for="Paczka.Material.Name" class="form-control" />
</td>
<td>
<input asp-for="Paczka.Material.PN" class="form-control" />
</td>
<td>
<input asp-for="Paczka.Material.Cert" class="form-control" />
</td>
<td>
<input asp-for="Paczka.Material.Qty" class="form-control" />
</td>
</tr>
</tbody>
</div>
</table>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
<a asp-page="./Index" class="btn btn-danger">Back to List</a>
</div>
I want Paczka to have many Materials. The material should belong only
to one Paczka because I want to create them while creating Paczka
From your description, the Paczka and Materials should be configured one-to-many relationship. In the Paczka class, use List or ICollection to define the navigation property (Material), code like this:
public class Paczka
{
public int PaczkaID { get; set; }
public string CRS { get; set; }
public string WO { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public List<Material> Material { get; set; }
}
public class Material
{
[Key]
public int PaczkaID { get; set; }
public string Name { get; set; }
public string PN { get; set; }
public string Cert { get; set; }
public string Qty { get; set; }
//the foreign key
public int PaczkaForeignKey { get; set; }
[ForeignKey("PaczkaForeignKey")]
public Paczka Paczka { get; set; }
}
Then, in the View page, you could use #for statement to loop through the Material and display the related data, code like this:
#page
#model RazorPageSample.Pages.MaterialModel
#{
}
<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="Paczka.PaczkaID" />
<div class="form-group">
<label asp-for="Paczka.CRS" class="control-label"></label>
<input asp-for="Paczka.CRS" class="form-control" />
<span asp-validation-for="Paczka.CRS" class="text-danger"></span>
</div>
#for (var i = 0; i < Model.Paczka.Material.Count; i++)
{
<div class="form-group">
<label asp-for="Paczka.Material[i].PaczkaID" class="control-label"></label>
<input asp-for="Paczka.Material[i].PaczkaID" class="form-control" />
<span asp-validation-for="Paczka.Material[i].PaczkaID" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Paczka.Material[i].Name" class="control-label"></label>
<input asp-for="Paczka.Material[i].Name" class="form-control" />
<span asp-validation-for="Paczka.Material[i].Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Paczka.Material[i].Cert" class="control-label"></label>
<input asp-for="Paczka.Material[i].Cert" class="form-control" />
<span asp-validation-for="Paczka.Material[i].Cert" class="text-danger"></span>
</div>
}
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
Then, in the Post method, get the update value and insert or update the data to database, screenshot like this:
More detail information about relationships and update related entity, refer the following articles:
Relationships
Configuring One To Many Relationships in Entity Framework Core
Update related data - ASP.NET MVC with EF Core
I current have parent page that properly navigates to a different child page. The Child page has the stand grid of child records- AND it also has 'Create' link to add a new child record.
Under standard Entity Framework Scaffolding the 'Create' page has standard html'dropdown' lists for the fields that are linked to 'lookup' values.
Below are some screen shots:
The Position Details link navigates to the child records:
The Create New Link brings up the standard scaffolded create page:
The PositionId field shows the full list of choices for parent lookup -rather than bringing in parent key default.
Here is code for create page:
For the create page my question is- how can I default that value of PositionID/PositionNbr to be what it would be for all the existing child records on the previous grid page? This should be a standard child record create scenario- where parent key is prepopulated
I am not finding a whole lot of good examples on a google search for how to prepopulate the parent foreign key in the child records -particularly when the creation is on a separate create page.
Hope my question is making sense- thanks in advance ...
Modified- here is code as code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using ThePositionerRazor2.Models;
namespace ThePositionerRazor2.Pages.PositionDetail
{
public class CreateModel : PageModel
{
private readonly ThePositionerRazor2.Models.WorkManagerV4Context _context;
public CreateModel(ThePositionerRazor2.Models.WorkManagerV4Context context)
{
_context = context;
}
public IActionResult OnGet()
{
ViewData["ImportanceId"] = new SelectList(_context.Imp, "ImportanceId", "ImportanceId");
ViewData["Knowdepth"] = new SelectList(_context.Knowdep, "DepthId", "DepthId");
ViewData["PositionId"] = new SelectList(_context.Possummary, "PositionId", "PositionNbr");
ViewData["TimeSpent"] = new SelectList(_context.Timescale, "TimevalId", "TimevalId");
ViewData["Workitem"] = new SelectList(_context.Workhier, "Workitemid", "Workitemid");
return Page();
}
[BindProperty]
public Posdetail Posdetail { get; set; }
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Posdetail.Add(Posdetail);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
and
#page
#model ThePositionerRazor2.Pages.PositionDetail.CreateModel
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Posdetail</h4>
<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="Posdetail.PositionId" class="control-label"></label>
<select asp-for="Posdetail.PositionId" class ="form-control" asp-items="ViewBag.PositionId"></select>
</div>
<div class="form-group">
<label asp-for="Posdetail.Workitem" class="control-label"></label>
<select asp-for="Posdetail.Workitem" class ="form-control" asp-items="ViewBag.Workitem"></select>
</div>
<div class="form-group">
<label asp-for="Posdetail.TimeSpent" class="control-label"></label>
<select asp-for="Posdetail.TimeSpent" class ="form-control" asp-items="ViewBag.TimeSpent"></select>
</div>
<div class="form-group">
<label asp-for="Posdetail.ImportanceId" class="control-label"></label>
<select asp-for="Posdetail.ImportanceId" class ="form-control" asp-items="ViewBag.ImportanceId"></select>
</div>
<div class="form-group">
<label asp-for="Posdetail.TimeNrmz" class="control-label"></label>
<input asp-for="Posdetail.TimeNrmz" class="form-control" />
<span asp-validation-for="Posdetail.TimeNrmz" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Posdetail.Knowdepth" class="control-label"></label>
<select asp-for="Posdetail.Knowdepth" class ="form-control" asp-items="ViewBag.Knowdepth"></select>
</div>
<div class="form-group">
<label asp-for="Posdetail.Need" class="control-label"></label>
<input asp-for="Posdetail.Need" class="form-control" />
<span asp-validation-for="Posdetail.Need" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Posdetail.TimeCalc" class="control-label"></label>
<input asp-for="Posdetail.TimeCalc" class="form-control" />
<span asp-validation-for="Posdetail.TimeCalc" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Posdetail.Postskval" class="control-label"></label>
<input asp-for="Posdetail.Postskval" class="form-control" />
<span asp-validation-for="Posdetail.Postskval" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Posdetail.Ftetime" class="control-label"></label>
<input asp-for="Posdetail.Ftetime" class="form-control" />
<span asp-validation-for="Posdetail.Ftetime" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Posdetail.Ftesal" class="control-label"></label>
<input asp-for="Posdetail.Ftesal" class="form-control" />
<span asp-validation-for="Posdetail.Ftesal" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Posdetail.Lastupdated" class="control-label"></label>
<input asp-for="Posdetail.Lastupdated" class="form-control" />
<span asp-validation-for="Posdetail.Lastupdated" 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");}
}
addtional info:
The relevant models
using System;
using System.Collections.Generic;
namespace ThePositionerRazor2.Models
{
public partial class Possummary
{
public Possummary()
{
Posdetail = new HashSet<Posdetail>();
}
public int PositionId { get; set; }
public string PositionNbr { get; set; }
public string WorkTitle { get; set; }
public string Purpose { get; set; }
public double? JobValue { get; set; }
public double? TimeTotal { get; set; }
public double? Fte { get; set; }
public double? Salary { get; set; }
public DateTime? Lastupdated { get; set; }
public string JobFamily { get; set; }
public int? DescriptionTypeId { get; set; }
public virtual Descriptiontype DescriptionType { get; set; }
public virtual ICollection<Posdetail> Posdetail { get; set; }
}
}
and
using System;
using System.Collections.Generic;
namespace ThePositionerRazor2.Models
{
public partial class Posdetail
{
public int PosdetailId { get; set; }
public int PositionId { get; set; }
public int Workitem { get; set; }
public int? TimeSpent { get; set; }
public int? ImportanceId { get; set; }
public double? TimeNrmz { get; set; }
public int? Knowdepth { get; set; }
public int? Need { get; set; }
public int? TimeCalc { get; set; }
public double? Postskval { get; set; }
public double? Ftetime { get; set; }
public double? Ftesal { get; set; }
public DateTime Lastupdated { get; set; }
public virtual Imp Importance { get; set; }
public virtual Knowdep KnowdepthNavigation { get; set; }
public virtual Possummary Position { get; set; }
public virtual Timescale TimeSpentNavigation { get; set; }
public virtual Workhier WorkitemNavigation { get; set; }
}
}
Confirmation of your requirement:
In your main Index page,you have a List Possummary.When you click the Details link,it will display the details of list Posdetail which belongs to the Possummary you choose in Details page.Then you click the Create link, it will display the Possummary's PositionId which you choose at the begining in Create page.
To meet your requirement:
You could pass the PositionId in the Create link.Then set the default selected value for selectlist by this PositionId:
public IActionResult OnGet(int PositionID)
{
ViewData["ImportanceId"] = new SelectList(_context.Imp, "ImportanceId", "ImportanceId");
ViewData["Knowdepth"] = new SelectList(_context.Knowdep, "DepthId", "DepthId");
//change here....
ViewData["PositionId"] = new SelectList(_context.Possummary, "PositionId", "PositionNbr", PositionID);
ViewData["TimeSpent"] = new SelectList(_context.Timescale, "TimevalId", "TimevalId");
ViewData["Workitem"] = new SelectList(_context.Workhier, "Workitemid", "Workitemid");
return Page();
}
The whole working demo:
Model:
public class Possummary
{
public Possummary()
{
Posdetail = new HashSet<Posdetail>();
}
[Key]
public int PositionId { get; set; }
public string PositionNbr { get; set; }
public string WorkTitle { get; set; }
public string Purpose { get; set; }
public double? JobValue { get; set; }
public double? TimeTotal { get; set; }
public double? Fte { get; set; }
public double? Salary { get; set; }
public DateTime? Lastupdated { get; set; }
public string JobFamily { get; set; }
public int? DescriptionTypeId { get; set; }
public virtual Descriptiontype DescriptionType { get; set; }
public virtual ICollection<Posdetail> Posdetail { get; set; }
}
public class Descriptiontype
{
public int Id { get; set; }
public string Name { get; set; }
}
public partial class Posdetail
{
public int PosdetailId { get; set; }
public int PositionId { get; set; }
public int Workitem { get; set; }
public int? TimeSpent { get; set; }
public int? ImportanceId { get; set; }
public double? TimeNrmz { get; set; }
public int? Knowdepth { get; set; }
public int? Need { get; set; }
public int? TimeCalc { get; set; }
public double? Postskval { get; set; }
public double? Ftetime { get; set; }
public double? Ftesal { get; set; }
public DateTime Lastupdated { get; set; }
public virtual Imp Importance { get; set; }
public virtual Knowdep KnowdepthNavigation { get; set; }
public virtual Possummary Position { get; set; }
public virtual Timescale TimeSpentNavigation { get; set; }
public virtual Workhier WorkitemNavigation { get; set; }
}
public class Imp
{
[Key]
public int ImportanceId { get; set; }
public string Name { get; set; }
}
public class Knowdep
{
[Key]
public int DepthId { get; set; }
public string Name { get; set; }
}
public class Timescale
{
[Key]
public int TimevalId { get; set; }
public string Name { get; set; }
}
public class Workhier
{
[Key]
public int Workitemid { get; set; }
public string Name { get; set; }
}
Possummarys/Index.cshtml:
#page
#model IndexModel
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Possummary[0].PositionNbr)
</th>
<th>
#Html.DisplayNameFor(model => model.Possummary[0].WorkTitle)
</th>
<th>
#Html.DisplayNameFor(model => model.Possummary[0].Purpose)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Possummary) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.PositionNbr)
</td>
<td>
#Html.DisplayFor(modelItem => item.WorkTitle)
</td>
<td>
#Html.DisplayFor(modelItem => item.Purpose)
</td>
<td>
<a asp-page="./Edit" asp-route-id="#item.PositionId">Edit</a> |
<a asp-page="./Details" asp-route-id="#item.PositionId">Details</a> |
<a asp-page="./Delete" asp-route-id="#item.PositionId">Delete</a>
</td>
</tr>
}
</tbody>
Possummarys/Index.cshtml.cs:
namespace RazorProj3_1.Pages.Possummarys
{
public class IndexModel : PageModel
{
private readonly RazorProj3_1Context _context;
public IndexModel(RazorProj3_1Context context)
{
_context = context;
}
public IList<Possummary> Possummary { get;set; }
public async Task OnGetAsync()
{
Possummary = await _context.Possummary.ToListAsync();
}
}
}
Possummarys/Details.cshtml:
#page
#model DetailsModel
//focus here...
<a asp-page="/PositionDetail/Create" asp-route-PositionID="#Model.Posdetail[0].PositionId" >Create New</a>
<h1>Postition Detail</h1>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Posdetail[0].Position)
</th>
<th>
#Html.DisplayNameFor(model => model.Posdetail[0].WorkitemNavigation)
</th>
<th>
#Html.DisplayNameFor(model => model.Posdetail[0].TimeNrmz)
</th>
<th>
#Html.DisplayNameFor(model => model.Posdetail[0].Importance)
</th>
<th>
#Html.DisplayNameFor(model => model.Posdetail[0].Lastupdated)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Posdetail)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Position.PositionNbr)
</td>
<td>
#Html.DisplayFor(modelItem => item.WorkitemNavigation.Workitemid)
</td>
<td>
#Html.DisplayFor(modelItem => item.TimeNrmz)
</td>
<td>
#Html.DisplayFor(modelItem => item.ImportanceId)
</td>
<td>
#Html.DisplayFor(modelItem => item.Lastupdated)
</td>
<td>
<a asp-page="./Edit" asp-route-id="#item.PosdetailId">Edit</a> |
<a asp-page="./Details" asp-route-id="#item.PosdetailId">Details</a> |
<a asp-page="./Delete" asp-route-id="#item.PosdetailId">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Possummarys/Details.cshtml.cs:
public class DetailsModel : PageModel
{
private readonly RazorProj3_1Context _context;
public DetailsModel(.RazorProj3_1Context context)
{
_context = context;
}
public IList<Posdetail> Posdetail { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
Posdetail = await _context.Posdetail.Include(p=>p.Position)
.Include(p=>p.WorkitemNavigation)
.Where(a=>a.PositionId==id).ToListAsync();
return Page();
}
}
PositionDetail/Create.cshtml: (the same as what you provided)
#page
#model CreateModel
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Posdetail</h4>
<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="Posdetail.PositionId" class="control-label"></label>
<select asp-for="Posdetail.PositionId" class="form-control" asp-items="ViewBag.PositionId"></select>
</div>
<div class="form-group">
<label asp-for="Posdetail.Workitem" class="control-label"></label>
<select asp-for="Posdetail.Workitem" class="form-control" asp-items="ViewBag.Workitem"></select>
</div>
<div class="form-group">
<label asp-for="Posdetail.TimeSpent" class="control-label"></label>
<select asp-for="Posdetail.TimeSpent" class="form-control" asp-items="ViewBag.TimeSpent"></select>
</div>
<div class="form-group">
<label asp-for="Posdetail.ImportanceId" class="control-label"></label>
<select asp-for="Posdetail.ImportanceId" class="form-control" asp-items="ViewBag.ImportanceId"></select>
</div>
<div class="form-group">
<label asp-for="Posdetail.TimeNrmz" class="control-label"></label>
<input asp-for="Posdetail.TimeNrmz" class="form-control" />
<span asp-validation-for="Posdetail.TimeNrmz" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Posdetail.Knowdepth" class="control-label"></label>
<select asp-for="Posdetail.Knowdepth" class="form-control" asp-items="ViewBag.Knowdepth"></select>
</div>
<div class="form-group">
<label asp-for="Posdetail.Need" class="control-label"></label>
<input asp-for="Posdetail.Need" class="form-control" />
<span asp-validation-for="Posdetail.Need" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Posdetail.TimeCalc" class="control-label"></label>
<input asp-for="Posdetail.TimeCalc" class="form-control" />
<span asp-validation-for="Posdetail.TimeCalc" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Posdetail.Postskval" class="control-label"></label>
<input asp-for="Posdetail.Postskval" class="form-control" />
<span asp-validation-for="Posdetail.Postskval" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Posdetail.Ftetime" class="control-label"></label>
<input asp-for="Posdetail.Ftetime" class="form-control" />
<span asp-validation-for="Posdetail.Ftetime" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Posdetail.Ftesal" class="control-label"></label>
<input asp-for="Posdetail.Ftesal" class="form-control" />
<span asp-validation-for="Posdetail.Ftesal" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Posdetail.Lastupdated" class="control-label"></label>
<input asp-for="Posdetail.Lastupdated" class="form-control" />
<span asp-validation-for="Posdetail.Lastupdated" 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");}
}
PositionDetail/Create.cshtml.cs:
public class CreateModel : PageModel
{
private readonly RazorProj3_1Context _context;
public CreateModel(RazorProj3_1Context context)
{
_context = context;
}
public IActionResult OnGet(int PositionID)
{
ViewData["ImportanceId"] = new SelectList(_context.Imp, "ImportanceId", "ImportanceId");
ViewData["Knowdepth"] = new SelectList(_context.Knowdep, "DepthId", "DepthId");
//change here....
ViewData["PositionId"] = new SelectList(_context.Possummary, "PositionId", "PositionNbr", PositionID);
ViewData["TimeSpent"] = new SelectList(_context.Timescale, "TimevalId", "TimevalId");
ViewData["Workitem"] = new SelectList(_context.Workhier, "Workitemid", "Workitemid");
return Page();
}
[BindProperty]
public Posdetail Posdetail { get; set; }
public async Task<IActionResult> OnPostAsync()
{
//the same as yours...
}
}
Result:
Is there any way I can have multiple drop-down menu displaying the data from the same database entity on the same razor page without creating several classes with new IDs (e.g. ResourceID; ResourceID1; ResourceID2)
I am able to display the drop down with the appropriate data from the MS SQL database in the 'Create.cshtml' and 'Edit.cshtml' razor pages, but the chosen data when saved displays the ID of the chosen resource instead of the name of the resource in the 'Index.cshtml', 'Detail.cshtml' and 'Delete.cshtml' views.
The Resource model:
namespace ProjectReporting.Models
{
public class Resource
{
public int ID { get; set; }
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Long Name")]
public string LongName { get; set; }
[DefaultValue(true)]
[Display(Name = "Active")]
public bool IsActive { get; set; } = true;
[Display(Name = "Is Manager")]
public bool IsManager { get; set; }
[Display(Name = "Is Forecast Owner")]
public bool IsForecastOwner { get; set; }
public ICollection<Project> Projects { get; set; }
}
}
The Project model:
namespace ProjectReporting.Models
{
public class Project
{
public int ID { get; set; }
[Display(Name = "ID")]
public int PID { get; set; }
[Display(Name = "Project Name")]
public string ProjectName { get; set; }
[Display(Name = "Forecast Owner")]
public int ResourceID { get; set; }
public Resource Resource { get; set; }
[Display(Name = "DSM")]
public int? ResourceID1 { get; set; }
public ICollection<ProjectComment> ProjectComments { get; set; }
}
}
The Create.cshtml page:
#page
#model ProjectReporting.Pages.Projects.CreateModel
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Project</h4>
<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="Project.PID" class="control-label"></label>
<input asp-for="Project.PID" class="form-control" />
<span asp-validation-for="Project.PID" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Project.ProjectName" class="control-label"></label>
<input asp-for="Project.ProjectName" class="form-control" />
<span asp-validation-for="Project.ProjectName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Project.ResourceID" class="control-label"></label>
<select asp-for="Project.ResourceID" class="form-control" asp-items="ViewBag.ResourceID"><option value="" default="" selected="">-- Select --</option></select>
</div>
<div class="form-group">
<label asp-for="Project.ResourceID1" class="control-label"></label>
<select asp-for="Project.ResourceID1" class="form-control" asp-items="ViewBag.ResourceID1"><option value="" default="" selected="">-- Select --</option></select>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="Project.IsArchived" /> #Html.DisplayNameFor(model => model.Project.IsArchived)
</label>
</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 Create.cshtml page:
namespace ProjectReporting.Pages.Projects
{
public class CreateModel : PageModel
{
private readonly ProjectReporting.Data.ApplicationDbContext _context;
public CreateModel(ProjectReporting.Data.ApplicationDbContext context)
{
_context = context;
}
public IActionResult OnGet()
{
ViewData["OrganisationID"] = new SelectList(_context.ProjectType.Where(a => a.IsActive == true), "ID", "TypeName");
ViewData["ResourceID"] = new SelectList(_context.Resource.Where(a => a.IsActive & a.IsForecastOwner == true), "ID", "LongName");
ViewData["ResourceID1"] = new SelectList(_context.Resource.Where(a => a.IsActive == true), "ID", "LongName");
return Page();
}
[BindProperty]
public Project Project { get; set; }
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Project.Add(Project);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
The Index.cshtml page:
#page
#model ProjectReporting.Pages.Projects.IndexModel
#{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Project[0].PID)
</th>
<th>
#Html.DisplayNameFor(model => model.Project[0].Organisation)
</th>
<th>
#Html.DisplayNameFor(model => model.Project[0].ProjectName)
</th>
<th>
#Html.DisplayNameFor(model => model.Project[0].Resource)
</th>
<th>
#Html.DisplayNameFor(model => model.Project[0].ResourceID1)
</th>
<th>
#Html.DisplayNameFor(model => model.Project[0].IsArchived)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Project) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.PID)
</td>
<td>
#Html.DisplayFor(modelItem => item.Organisation.OrgName)
</td>
<td>
#Html.DisplayFor(modelItem => item.ProjectName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Resource.LongName)
</td>
<td>
#Html.DisplayFor(modelItem => c)
</td>
<td>
#Html.DisplayFor(modelItem => item.IsArchived)
</td>
<td>
<a asp-page="./Edit" asp-route-id="#item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="#item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="#item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
The item.Resource.LongName works fine for the first resource, but I would like the same to happen with the item.Resource.LongName.
The Index.cshtml.cs
namespace ProjectReporting.Pages.Projects
{
public class IndexModel : PageModel
{
private readonly ProjectReporting.Data.ApplicationDbContext _context;
public IndexModel(ProjectReporting.Data.ApplicationDbContext context)
{
_context = context;
}
public IList<Project> Project { get;set; }
public async Task OnGetAsync()
{
Project = await _context.Project
.Include(p => p.Resource).ToListAsync();
}
}
}
I have set the FK in the migration file to create the DB to be able to retrieve the data and would like to avoid having to create one class file by resource.
table.ForeignKey(
name: "FK_Project_Resource_ResourceID",
column: x => x.ResourceID,
principalTable: "Resource",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Project_Resource_ResourceID1",
column: x => x.ResourceID1,
principalTable: "Resource",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
The result shows the right data in the drop-down and the correct Resource ID selected when saved. However, the index, details and delete page only display the ResourceID instead of the LongName. If I use Resource.LongName for the second ResourceID1, it rightly displays the same LongName than for ResourceID.
How can I have multiple resource drop-down on the page that point to the same entity and display the LongName on the Index, Detail and Delete pages?
It is not clear about your relationships between Resource and Project, and you could not use both ResourceID and ResourceID1 for a Resource property for restoring different resources.
For many-to-many relationships, you could refer to
https://learn.microsoft.com/en-us/ef/core/modeling/relationships#many-to-many
A workaround is that you just get all resources in handler and retrieve it in view:
Index.cshtml.cs:
public class IndexModel : PageModel
{
private readonly ProjectReporting.Data.ApplicationDbContext _context;
public IndexModel(ProjectReporting.Data.ApplicationDbContext context)
{
_context = context;
}
public IList<Project> Project { get;set; }
public IList<Resource> Resources { get; set; }
public async Task OnGetAsync()
{
Resources = await _context.Resource.ToListAsync();
Project = await _context.Projects
.Include(p => p.Resource).ToListAsync();
}
}
Index.cshtml:
<td>
#Html.DisplayFor(modelItem => item.Resource.LongName)
</td>
<td>
#{
var resource = Model.Resources.FirstOrDefault(r => r.ID == item.ResourceID1);
}
#resource.LongName
</td>