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
Related
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:
I created a search bar like this
<form method="post" asp-action="Search">
<input type="text" name="search" placeholder="Enter here the Name " />
<select name="type" id="type" class="form-control">
<option value="Success">Inactive Reservation</option>
<option value="Approved">Active Reservation</option>
<option value="Pending">Pending Reservation</option>
</select>
<input type="submit" value="Search" />
</form>
and the method in controller:
public async Task<IActionResult> Search(string search,string type)
{
var allRsv = from m in _db.Reservation
select m;
var Rsv = allRsv
.Where(x => x.ClientName.ToLower().Contains(search.ToLower()) &&
x.Status.ToLower().Contains(type.ToLower()));
return View(Rsv);
}
What I want: to send in search page something like 'You searched for #search and type: #type.
return View has no option to do this ,neither return to action ..
Can I do it in a simple way ?
My single idea it is to send query string and then request query in search view
What I want: to send in search page something like 'You searched for #search and type: #type.
You can try to pass data to search page via ViewData etc, like below.
In View Page
<form method="post" asp-action="Search">
<input type="text" name="search" placeholder="Enter here the Name " />
<select name="type" id="type" class="form-control">
<option value="Success">Inactive Reservation</option>
<option value="Approved">Active Reservation</option>
<option value="Pending">Pending Reservation</option>
</select>
<input type="submit" value="Search" />
</form>
<h3>You searched for "#ViewData["search"]" and type: #ViewData["type"].</h3>
In Action Method
public async Task<IActionResult> Search(string search, string type)
{
var allRsv = from m in _db.Reservation
select m;
var Rsv = allRsv
.Where(x => x.ClientName.ToLower().Contains(search.ToLower()) &&
x.Status.ToLower().Contains(type.ToLower()));
ViewData["search"] = search;
ViewData["type"] = type;
return View(Rsv);
}
Test Result
Using ViewData to pass data between controllers and views is fine as long as there are not many pieces of data in between. If you have lots of going, it will make everybody else harder to understand what's going on with ViewData because it's a weak-typed and you have no idea what it contains, what's available to get, etc. And then you have to go back to the controller and see what's being passed. What if there are multiple controllers returning this same view...yucky!
In addition, it's not a good practice to display what you have from your database directly from the controller to the view.
Hence you should use one of the alternatives of ViewData, that is ViewModel, which is strongly-typed!
Fake Entity Models
Since I don't have your database, for this demo, I am setting up two fake entity models that represent the data from your persistence storage.
namespace DL.NetCore.EmptySolution.Web.UI.Models.Reservation
{
public class FakeReservationStatusEntity
{
public string StatusId { get; set; }
public string Status { get; set; }
}
public class FakeReservationEntity
{
public int ReservationId { get; set; }
public int ClientId { get; set; }
public string ClientName { get; set; }
public DateTime StartTimeUtc { get; set; }
public FakeReservationStatusEntity ReservationStatus { get; set; }
public int CreatedByUserId { get; set; }
}
}
There is one-to-many relationship between reservation and reservation status I assumed. And please notice I purposely made it so that it has more properties than the view model!
Viewmodels
They're just POCOs (Plain Old CLR Objects) that serve as data containers to travel between controllers and views.
namespace DL.NetCore.EmptySolution.Web.UI.Models.Reservation
{
public class ReservationFiltersViewModel
{
[Display(Name = "Client name")]
public string ClientNameSearchQuery { get; set; }
[Display(Name = "Reservation type")]
public string ReservationTypeSearchQuery { get; set; }
public IDictionary<string, string> AvailableReservationTypes { get; set; }
}
public class ReservationViewModel
{
public int ReservationId { get; set; }
public string ReservationType { get; set; }
public string ClientName { get; set; }
public DateTime StartTime { get; set; }
}
public class ReservationListViewModel
{
public ReservationFiltersViewModel Filters { get; set; }
public IEnumerable<ReservationViewModel> Reservations { get; set; }
}
}
Controller
namespace DL.NetCore.EmptySolution.Web.UI.Controllers
{
public class ReservationController : Controller
{
public IActionResult Index(string c, string t)
{
var vm = new ReservationListViewModel
{
Filters = new ReservationFiltersViewModel
{
ClientNameSearchQuery = c,
ReservationTypeSearchQuery = t,
// You would normally get the list from your database
AvailableReservationTypes = GetFakeReservationStatusesFromDb()
.ToDictionary(x => x.StatusId, x => x.Status)
},
Reservations = Enumerable.Empty<ReservationViewModel>()
};
// You would normally get the list of reservations from your database
var reservationsFromDb = GetFakeReservationsFromDb();
// Filters
if (!String.IsNullOrWhiteSpace(c))
{
reservationsFromDb = reservationsFromDb
.Where(x => x.ClientName.Contains(c, StringComparison.InvariantCultureIgnoreCase));
}
if (!String.IsNullOrWhiteSpace(t))
{
reservationsFromDb = reservationsFromDb
.Where(x => x.ReservationStatus.StatusId.Contains(t, StringComparison.InvariantCultureIgnoreCase));
}
// See you only want to explore what you want on the view
vm.Reservations = reservationsFromDb
.Select(x => new ReservationViewModel
{
ReservationId = x.ReservationId,
ClientName = x.ClientName,
ReservationType = x.ReservationStatus.Status,
StartTime = x.StartTimeUtc.ToLocalTime()
});
return View(vm);
}
[HttpPost]
public IActionResult Search(ReservationFiltersViewModel filters)
{
return RedirectToAction(nameof(Index),
new { c = filters.ClientNameSearchQuery, t = filters.ReservationTypeSearchQuery });
}
...
}
}
Index View
#model DL.NetCore.EmptySolution.Web.UI.Models.Reservation.ReservationListViewModel
#{
ViewBag.Title = "Reservations";
var selectList = new SelectList(Model.Filters.AvailableReservationTypes, "Key", "Value");
}
<h2>Reservations</h2>
<p class="text-muted">
List of reservations you can manage
</p>
<div class="row">
<div class="col-lg-4">
<div class="card">
<div class="card-body">
<form method="post" asp-area="" asp-controller="reservation" asp-action="search">
<div class="form-group">
<label asp-for="Filters.ClientNameSearchQuery"></label>
<input asp-for="Filters.ClientNameSearchQuery" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Filters.ReservationTypeSearchQuery"></label>
<select asp-for="Filters.ReservationTypeSearchQuery" class="form-control"
asp-items="selectList">
<option value="">- select -</option>
</select>
</div>
<button type="submit" class="btn btn-success">Search</button>
</form>
</div>
</div>
</div>
<div class="col-lg-8">
<!-- This could be better optimized, i.e., only display non-empty search -->
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
You searched for <strong>#Model.Filters.ClientNameSearchQuery</strong>
and <strong>#Model.Filters.ReservationTypeSearchQuery</strong>
</div>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Client name</th>
<th>Start from</th>
<th>Type</th>
</tr>
</thead>
<tbody>
#foreach (var reservation in Model.Reservations)
{
<tr>
<td>#reservation.ReservationId</td>
<td>#reservation.ClientName</td>
<td>#reservation.StartTime.ToShortDateString()</td>
<td>#reservation.ReservationType</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
With ViewModel, there is no magic string flowing around like with ViewData. Everything is strongly typed. And the form only posts back the filter model that contains only what we need to the server.
Screenshots
Source code
My demo project's source code is at https://github.com/davidliang2008/DL.NetCore.EmptySolution. The checkin specifically for this demo is at https://github.com/davidliang2008/DL.NetCore.EmptySolution/commit/32087b989de06e316cf747ad49da6ad4b24b61b8
So i am in the process of creating my 3rd crud service inside my webapp. the webapp is running .net core 3.0. my 2 previous crud apps where project and employee. so a user/employee does some work on a project.
so for my 'work' crud service/application in my creation view I need to have a dropdown of all projects and a dropdown for all employees and a view inputs. for employee and project the id is a guid because its unique. But i am seem to having an issue that my modelstateis always invalid.
ps: also my data model so to say it doesn't make any foreign keys for example project id with work id or employee id."
model for work:
public class Work
{
public int Id { get; set; }
[Required]
[StringLength(512, ErrorMessage = "Description cannot exceed 512 characters!")]
public string Description { get; set; }
[Required]
[Display(Name = "Working hours")]
public double WorkingHours { get; set; }
[Required]
[Display(Name = "Employee")]
public Guid EmployeeId { get; set; }
public Employee Employee { get; set; }
[Required]
[Display(Name = "Project")]
public Guid ProjectId { get; set; }
public Projects Project { get; set; }
[Required]
public DateTime dateAdded { get; set; }
}
viewmodel:
public class WorkViewModel
{
public IEnumerable<Projects> projects { get; set; }
public IEnumerable<Employee> employees { get; set; }
public Guid employeeId { get; set; }
public Guid projectId { get; set; }
public Work work { get; set; }
}
create view:
#model TimeSheetTool.ViewModels.WorkViewModel
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<div class="row">
<div class="col-sm-4">
<form asp-action="Save">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<select asp-for="projectId" asp-items="#(new SelectList(Model.projects, "Id", "Name"))">
<option>Please select one!</option>
</select>
<span asp-validation-for="projectId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="work.Description" class="control-labels"></label>
<input asp-for="work.Description" class="form-control" />
<span asp-validation-for="work.Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="work.WorkingHours" class="control-labels"></label>
<input asp-for="work.WorkingHours" class="form-control" />
<span asp-validation-for="work.WorkingHours"></span>
</div>
<div class="form-group">
<select asp-for="employeeId" asp-items="#(new SelectList(Model.employees, "Id", "Name"))">
<option>Please select one!</option>
</select>
<span asp-validation-for="employeeId" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-outline-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
What am I doing wrong with this? as you can see in the create view for select list I have got Id i assume it would actually need guid since i'm using guid but then the app won't work at all.
It is because of the Work property in the view model.
public Work work { get; set; }
It has validation, which if not satisfied will cause the invalid model state issue
Add the necessary properties to the view model
public class WorkViewModel {
public IEnumerable<Projects> projects { get; set; }
public IEnumerable<Employee> employees { get; set; }
public Guid employeeId { get; set; }
public Guid projectId { get; set; }
[StringLength(512, ErrorMessage = "Description cannot exceed 512 characters!")]
public string Description { get; set; }
[Display(Name = "Working hours")]
public double WorkingHours { get; set; }
}
and populate them as needed when invoking the action to bind the view
<div class="form-group">
<label asp-for="Description" class="control-labels"></label>
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="WorkingHours" class="control-labels"></label>
<input asp-for="WorkingHours" class="form-control" />
<span asp-validation-for="WorkingHours"></span>
</div>
The values can be copied over to the model on post
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>
So i have a view in my ASP.net Core application that i Add a new Car Repair and i want also to add Parts in this model. The Database has 3 tables : Repair - Parts - RepairParts.
I have managed to create the new repair but i have no idea how to pass a list of parts from this view to the controller and insert them in the RepairParts Table.
Repair Class
public class Repair
{
[Key]
public int RepairId { get; set; }
public int VehicleId { get; set; }
public string Notes { get; set; }
public string Mileage { get; set; }
public DateTime RepairDate { get; set; }
public string uuid { get; set; }
[ForeignKey("VehicleId")]
public Vehicle Vehicle { get; set; }
public virtual ICollection<RepairParts> Parts { get; set; }
}
Part Class
public class Part
{
public int PartId { get; set; }
public int Code { get; set; }
public string PartCode { get; set; }
public string Type { get; set; }
public string Name { get; set; }
public string Descr { get; set; }
public string Manufacturer { get; set; }
public string uuid { get; set; }
public virtual ICollection<RepairParts> Repairs { get; set; }
}
RepairPart Class
public class RepairParts
{
public int RepairId { get; set; }
public virtual Repair Repair { get; set; }
public int PartId { get; set; }
public virtual Part Part { get; set; }
}
Repair Controller
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult
Create([Bind("VehicleId,Mileage,RepairDate,Notes")] RepairCreateModel repair)
{
try
{
Repair rep = new Repair();
var NewRepair = _mapper.Map(repair, rep);
_repairService.Add(NewRepair);
}
catch (Exception)
{
throw;
}
return Ok();
}
Repair Service
public void Add(Repair repair)
{
string userid = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
repair.uuid = userid;
_context.Repairs.Add(repair);
_context.SaveChanges();
}
Repair Create View
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input asp-for="VehicleId" value="#ViewBag.VehicleId" type="hidden" />
<div class="form-group row">
<label class="col-sm-1 col-form-label">Owner</label>
<div class="col-sm-2">
<input type="text" class="form-control" value="#ViewBag.Owner"
readonly>
</div>
</div>
<div class="form-group row">
<label class="col-sm-1 col-form-label">Vehicle</label>
<div class="col-sm-2">
<input type="text" class="form-control" value="#ViewBag.Vehicle"
readonly>
</div>
<label class="col-sm-2 col-form-label">Licence Plates</label>
<div class="col-sm-2">
<input type="text" class="form-control"
value="#ViewBag.LicencePlates" readonly>
</div>
</div>
<div class="form-group row">
<label class="col-sm-1 col-form-label">Mileage</label>
<div class="col-sm-2">
<input asp-for="Mileage" type="text" class="form-control">
</div>
<label class="col-sm-2 col-form-label">Date:</label>
<div class="col-sm-2">
<input asp-for="RepairDate" type="text" class="form-control">
</div>
</div>
<div class="form-group row">
<label class="col-sm-1 col-form-label">Notes</label>
<div class="col-sm-8 row">
<textarea asp-for="Notes" rows="5" class="form-control"></textarea>
</div>
</div>
<div class="form-group row">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
Anyone has any idea how can i achieve that?