I'll only include the relevant properties of the model and the related lines for simplicity:
Model: Department.cs
Property: public virtual Staff HOD { get; set; }
ControllerMethod:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Add([Bind] Department newDepartment)
{
await _departmentRepository.AddDepartmentAsync(newDepartment);
await _departmentRepository.SaveTransactionAsync(true);
return Redirect("Details" + "/" + newDepartment.Id);
}
View:
#model Department
#inject IStaffRepository staffRepository
#{
// select list
var listOfEmployees = new List<Staff>();
foreach (var staff in await staffRepository.GetAllStaffAsync())
{
listOfEmployees.Add(staff);
}
var selectListHOD = new SelectList(listOfEmployees, "EmployeeId", "Name"); //populates the option element with the correct Employee Id
}
<form class="form-group" method="post">
<div class="d-flex flex-column">
<div class="input-group" style="margin-bottom: 1%">
#Html.DropDownListFor(department => department.HOD, selectListHOD, htmlAttributes: new { #class="form-control" })
</div>
<div class="input-group">
<input class="btn btn-primary" type="submit" value="Save" />
</div>
</div>
</form>
Result:
(I have not included the other properties seen here in the question, but trust me, the properties of the parameter to the controller get correctly populated with them, except for the HOD property)
Source:
<form class="form-group" method="post">
<div class="d-flex flex-column">
<div class="input-group" style="margin-bottom: 1%">
<input class="form-control" id="Id" name="Id" placeholder="Id" type="text" value="" /> </div>
<div class="input-group" style="margin-bottom: 1%">
<input class="form-control" id="Name" name="Name" placeholder="Department" type="text" value="" /> </div>
<div class="input-group" style="margin-bottom: 1%">
<input class="form-control" id="Description" name="Description" placeholder="Description" type="text" value="" /> </div>
<div class="input-group" style="margin-bottom: 1%">
<select class="form-control" id="HOD" name="HOD">
<option value="EMP01">Employee 1</option>
<option value="EMP02">Employee 2</option>
</select>
</div>
<div class="input-group">
<input class="btn btn-primary" type="submit" value="Save" /> </div>
</div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8AIwSWkSO0hJqMxy-oQKoeG35LTDmI2N1XHDZL9qeaxRxc17TyZbT2z9Iq0GMPkRyE7HnaX1r4ZSIs0bQATYB7w_A_HZDBXGETMmpdSqlMXZCmf7cH9ECzrNGz0Wuu9zHkE50yI92vPY-GxNPG-pRhs" />
</form>
My guess is that the value being supplied to the controller seems to be wrong. How would I pass the actual object instead of the ID?
I tried using this instead of EmployeeId, but it throws Object Reference not found exception.
You cannot bind an object with <select></select>.You can try to bind Hod.EmployeeId with <select></select>.And add a hidden input to bind Hod.Name.When selected value changes,change the value of hidden input.Here is a demo:
<form class="form-group" method="post">
<div class="d-flex flex-column">
<div class="input-group" style="margin-bottom: 1%">
#Html.DropDownListFor(department => department.HOD.EmployeeId, selectListHOD, htmlAttributes: new { #class = "form-control",#onChange= "AddName()" })
<input hidden asp-for="HOD.Name" />
</div>
<div class="input-group">
<input class="btn btn-primary" type="submit" value="Save" />
</div>
</div>
</form>
#section scripts
{
<script>
$(function () {
AddName();
})
function AddName() {
$("#HOD_Name").val($("#HOD_EmployeeId option:selected").text());
}
</script>
}
result:
Related
because I need to create my user info in many different tables with many different Models and views I used this code in the address page but as shown in my remarks I lost the user info in the post function My question is why and what to do????? by the way when I copied the User1 difenation from my OnGet function to the OnPost function this code work perfectly as explained in my comment but I still want to understand why a public property lose the information please read my comments
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using RazorPagesUI.Models;
namespace RazorPagesUI.Pages.Forms
{
partial class AddAddressModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
public AddAddressModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
[BindProperty(SupportsGet = true)]
public string Mail { get; set; }
public IEnumerable<SelectListItem>? Country { get; set; }
[BindProperty]
public AddressModel? Address { get; set; }
public string SelectedString { get; set; }
public UserModel User1 { get; set; }=new UserModel();
public void OnGet()
{
List<string> TagIds = Mail.Split(',').ToList();
Int32.TryParse(TagIds[0], out int j);
User1.Id = j;
User1.Email = TagIds[1];
User1.FirstName = TagIds[2];
User1.LastName = TagIds[3];
User1.Password = TagIds[4]
Country = new SelectListItem[]
{
new SelectListItem ("Canada", "Canada"),
new SelectListItem ("Egypt", "Egypt"),
new SelectListItem ( "Usa", "Usa")
};
}
public IActionResult OnPost()
{
//when I get to here User1 is null
Address.Country = Request.Form["country"];
if (ModelState.IsValid == false)
{
return Page();
}
//I need to insert my user info to my user table but User1 is null
//here I insert Address info
return RedirectToPage("/index", new{ Name = User1.Firstname);//User1
becomes Null
}
}
}
cshtml file As asked to include in my post
#page
#using RazorPagesUI.Models
#model RazorPagesUI.Pages.Forms.AddAddressModel
#{
ViewData["Title"] = "Add Address";
}
<b>Adderres for: #Model.User1.FirstName #Model.User1.LastName</b>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
<div class="text-center">
<h1>Add Address</h1>
</div>
<form method="post">
<div class="container-fluid">
<div class="p-1">
<div class="text-center">
<select name = "country" asp-items="#Model.Country">
</select>
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.State" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.City"
/>
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.StreetNumber"
placeholder="Street #" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.StreetName"
placeholder="Street Name" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.AppNumber"
placeholder="App#" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.ZipCode" />
</div>
</div>
<div class="p-1">
<div class="text-center">
<input type="tel" asp-for="Address.Phone" />
</div>
</div>
<div class="p-1">
<div class="text-center">
<input type="tel" asp-for="Address.CellPhone" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<button type="submit">Submit</button>
</div>
</div>
</div>
</form>
Firstly,you need to pass User1.FirstName when form post,so that you can get User1.FirstNamein OnPost handler.
form(add hidden input with User1.FirstName):
<form method="post">
<div class="container-fluid">
<div class="p-1">
<div class="text-center">
<select name = "country" asp-items="#Model.Country">
</select>
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.State" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.City"
/>
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.StreetNumber"
placeholder="Street #" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.StreetName"
placeholder="Street Name" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.AppNumber"
placeholder="App#" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.ZipCode" />
</div>
</div>
<div class="p-1">
<div class="text-center">
<input type="tel" asp-for="Address.Phone" />
</div>
</div>
<div class="p-1">
<div class="text-center">
<input type="tel" asp-for="Address.CellPhone" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="hidden" asp-for="User1.FirstName" />
<button type="submit">Submit</button>
</div>
</div>
</div>
</form>
cshtml.cs(If you want to bind the data to User1,you need to use [BindProperty],so that you can use User1.Firstname in OnPost handler):
[BindProperty]
public UserModel User1 { get; set; } = new UserModel();
You have to show your cshtml file i.e. the front end of the Razor page for a more clear description of your problem. But in general, I'm seeing that you are trying to bind a property called Country of a complex object called Address of type AddressModel In this case the name of the input/select in your cshtml file should reflect the complex path to the target Country property of the Address object. It should be something like this <select name="Address.Country" asp-items="Model.Country"></select> Notice the name of the select element Address.Country i.e. it reflects the full path to the target property. More information on complex model binding in razor pages here https://www.learnrazorpages.com/razor-pages/model-binding If you manage to bind the property of the complex object correctly this line of code Address.Country = Request.Form["country"]; becomes redundant. The value of Address.Country should be populated automatically.
I have an ASP.NET Core 3.1 Razor Pages application.
It is quite simple when it is only one form on a page, but how correctly show the errors when there are few forms?
Example:
/ManageUser.cshtml
<div>
<form method="post">
<div class="validation-summary-valid text-danger" asp-validation-summary="All"></div>
<div class="form-group">
<label asp-for="OldPassword"></label>
<input asp-for="OldPassword" class="form-control" />
</div>
<div class="form-group">
<label asp-for="NewPassword1"></label>
<input asp-for="NewPassword1" class="form-control" />
</div>
<div class="form-group">
<label asp-for="NewPassword2"></label>
<input asp-for="NewPassword2" class="form-control" />
</div>
<button type="submit" class="btn btn-primary" asp-page-handler="ChangePassword">Change password</button>
</form>
</div>
<!-- let's say it is the other "tab" -->
<div>
<form method="post">
<div class="validation-summary-valid text-danger" asp-validation-summary="All"></div>
<div class="form-group">
<label asp-for="ChangeEmail"></label>
<input asp-for="ChangeEmail" class="form-control" />
</div>
<button type="submit" class="btn btn-primary" asp-page-handler="ChangeEmail">Change email</button>
</form>
</div>
And, if on the backend side any error is found:
public async Task<IActionResult> OnPostChangeEmailAsync([FromService] UserManager<User> userManager)
{
//... skipped for brevity
ModelState.AddModelError("", "User not found or deleted");
return Page();
}
then this error is shown for both forms. Any way to show it for corresponding form only?
PS: Please do not propose to make the errors field-bound: it is not the case and the fields in the example is just for the simplicity.
Taking field validation as an example, you can try the following code.
View:
<div>
<form method="post">
<span class="text-danger">#Html.ValidationMessage("PasswordError")</span>
<div class="form-group">
<label asp-for="UserModel.OldPassword"></label>
<input asp-for="UserModel.OldPassword" class="form-control" />
<span asp-validation-for="UserModel.OldPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="UserModel.NewPassword1"></label>
<input asp-for="UserModel.NewPassword1" class="form-control" />
<span asp-validation-for="UserModel.NewPassword1" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="UserModel.NewPassword2"></label>
<input asp-for="UserModel.NewPassword2" class="form-control" />
<span asp-validation-for="UserModel.NewPassword2" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary" asp-page-handler="ChangePassword">Change password</button>
</form>
</div>
<div>
<form method="post">
<span class="text-danger">#Html.ValidationMessage("EmailError")</span>
<div class="form-group">
<label asp-for="UserModel.ChangeEmail"></label>
<input asp-for="UserModel.ChangeEmail" class="form-control" />
<span asp-validation-for="UserModel.ChangeEmail" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary" asp-page-handler="ChangeEmail">Change email</button>
</form>
</div>
The backend side(you can remove the attributes):
public async Task<IActionResult> OnPostChangeEmailAsync(UserModel userModel)
{
if (!ModelState.IsValid)
{
ModelState.Remove("UserModel.OldPassword");
ModelState.Remove("UserModel.NewPassword1");
ModelState.Remove("UserModel.NewPassword2");
ModelState.AddModelError("EmailError", "User not found or deleted");
}
return Page();
}
public async Task<IActionResult> OnPostChangePassword(UserModel userModel)
{
if (!ModelState.IsValid)
{
ModelState.Remove("UserModel.ChangeEmail");
ModelState.AddModelError("PasswordError", "Password is inconsistent ");
}
return Page();
}
Result:
Here's my problem, I have a Book that contains List<Page> that contains List<Line> and I'm trying to create a view that will edit my book. This view contains all the lines from the book. I've made an MVVM for List<Page> that calls smaller MVVM for List<Line>. The problem is that MVVM is not counting all Line as one single form, so this happens:
<form>
<h1>Page one</h1>
<input type="hidden" name="[0].lineContent" value=""/>
<input type="hidden" name="[1].lineContent" value=""/>
<h1>Page one</h1>
<input type="hidden" name="[0].lineContent" value=""/>
<input type="hidden" name="[1].lineContent" value=""/>
<input type="hidden" name="[2].lineContent" value=""/>
</form>
Each time my for loop for page iterates, my for loops in lines get reset to 0, this creates duplicate name entries.
There are multiple ways to correct this, the easiest:
Build input by hand with unique name id instead of using HTML helpers
My question is the following: how can I achieve a clean razor that can be reused, and that will not duplicate name entries in my form?
UPDATE:
Context:
Just like the book exemple up, my models have the same structure.
in my case I have a Submission that contains a list of Section that contains a list of SubSection that contains a list of SubmissionLine.
Here's my main view:
#model
QuotingPlus.Models.Submission
#{
ViewData["Title"] = "Edit";
}
<form id="submission-form" asp-action="Edit">
<div>
<p class="d-inline-block">
<a class="btn btn-primary" data-toggle="collapse"
href="#multiCollapseExample1" role="button" aria-expanded="false" aria-controls="multiCollapseExample1">Edit submission details</a>
</p>
<nav aria-label="breadcrumb" class="d-inline-block">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="/Clients/Details/#(Model.IdProjectNavigation.IdClientNavigation.IdClient)">#Model.IdProjectNavigation.IdClientNavigation.FirstName
#Model.IdProjectNavigation.IdClientNavigation.LastName</a>
</li>
<li class="breadcrumb-item">
#Model.IdProjectNavigation.Name
</li>
<li class="breadcrumb-item active" aria-current="page">#Model.Number</li>
</ol>
</nav>
</div>
<div class="row">
<div class="col mb-3">
<div class="collapse multi-collapse" id="multiCollapseExample1">
<div class="card card-body">
<h1>Submission details</h1>
<hr/>
<div class="row">
<div class="col-md-4">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="IdSubmission"/>
<div class="form-group">
<label asp-for="IdTypeSubmission" class="control-label"></label>
<select asp-for="IdTypeSubmission" class="form-control" asp-items="ViewBag.IdTypeSubmission"></select>
<span asp-validation-for="IdTypeSubmission" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="IdProject" class="control-label"></label>
<select asp-for="IdProject" class="form-control" asp-items="ViewBag.IdProject"></select>
<span asp-validation-for="IdProject" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Number" class="control-label"></label>
<input asp-for="Number" class="form-control"/>
<span asp-validation-for="Number" class="text-danger"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="save-warning" style="display: none;" class="alert alert-info alert-dismissible fade show" role="alert">
<strong>WARNING!</strong> Make sure you save before leaving.
<button type="button" class="close" data-dismiss="alert" aria-label="Close" onclick="SetWarningDisplayPreferenceCookie();">
<span aria-hidden="true">×</span>
</button>
</div>
<div id="carouselIndicators" style="height: 100% !important;" class="carousel slide pb-5 mb-5" data-interval="false">
<div class="carousel-inner">
#{
Html.RenderPartial("SubmissionSectionEditor", Model.SubmissionSection.ToList());
}
</div>
<nav class="navbar navbar-light bg-secondary mb-0 pt-2 fixed-bottom">
<div>
<button type="submit" value="Save" class="btn btn-default text-white">
<i class="material-icons" style="font-size: 2em;">
save
</i>
</button>
</div>
<a class="col text-center mh-100 pt-2 pb-2" href="#carouselIndicators" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="false"></span>
</a>
<a class="col text-center mh-100 pt-2" href="#carouselIndicators" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="false"></span>
</a>
<div>
<a asp-action="Index" class="btn text-white">
<i class="material-icons" style="font-size: 2em;">
cancel
</i>
</a>
</div>
</nav>
</div>
</form>
Here's SubmissionSectionEditor.cshtml
#model List<SubmissionSection>
#{
var isFirstCarouselItem = true;
}
#for (var indexSection = 0; indexSection < Model.Count(); indexSection++)
{
<div class="carousel-item #((isFirstCarouselItem) ? "active" : "")">
#{
isFirstCarouselItem = false;
}
<h1>#Model[indexSection].IdSectionNavigation.Name</h1>
<div id="#(Model[indexSection].IdSection + "accordion")">
#{
var submissionSubSections = M odel[indexSection].SubmissionSubSection;
}
#if (submissionSubSections != null)
{
Html.RenderPartial("SubmissionSubSectionEditor", submissionSubSections.ToList());
}
</div>
</div>
}
Here's SubmissionSubSectionEditor.cshtml
#model List<SubmissionSubSection>
#for (var indexSubSection = 0; indexSubSection < Model.Count; indexSubSection++)
{
<div class="card">
<div class="card-header" id="#(Model[indexSubSection].IdSubSection + "SubSectionHeader")">
<h2 class="mb-0">
<button type="button" class="btn btn-link collapsed" data-toggle="collapse" data-target="#("#" + Model[indexSubSection].IdSubSection + "SubSectionCollapse")" aria-
expanded="true" aria-controls="#(Model[indexSubSection].IdSubSection + "SubSectionCollapse")">
#Model[indexSubSection].IdSubSectionNavigation.Name
</button>
</h2>
</div>
<div id="#(Model[indexSubSection].IdSubSection + "SubSectionCollapse")" class="collapse" aria-labelledby="#(Model[indexSubSection].IdSubSection +
"SubSectionHeader")" data-parent="#("#" + Model[indexSubSection].IdSubmissionSectionNavigation.IdSection + "accordion")">
<div class="card-body">
<table class="table w-100">
<thead class="thead-dark">
<tr>
<th>Quantity</th>
<th>Article</th>
<th>Total Material</th>
<th>Unit Price Material</th>
<th>Total Sub Contractor</th>
<th>Unit Price Sub Contractor</th>
<th>Total Workforce</th>
<th>Unit Price Workforce</th>
<th>Display</th>
</tr>
</thead>
<tbody>
#{
Html.RenderPartial("SubmissionLineEditor", Model[indexSubSection].SubmissionLine.ToList());
}
</tbody>
</table>
</div>
</div>
</div>
}
Here's SubmissionLineEditor.cshtml
#model List<SubmissionLine>
#for (var indexLine = 0; indexLine < Model.Count; indexLine++)
{
<tr>
#Html.HiddenFor(x => Model[indexLine].IdSubmissionLine)
#Html.HiddenFor(x => Model[indexLine].IdArticle)
#Html.HiddenFor(x => Model[indexLine].IdSubmissionSubSection)
<td>#Html.TextBoxFor(x => Model[indexLine].Quantity, new {#type = "number", #step = "0.5", #min="0"})</td>
<td>#Model[indexLine].IdArticleNavigation.Designation</td>
<td>#Model[indexLine].TotalMaterial</td>
<td>#Model[indexLine].IdArticleNavigation.UnitPriceMaterial</td>
<td>#Model[indexLine].TotalSubContractor</td>
<td>#Model[indexLine].IdArticleNavigation.UnitPriceSubContractor</td>
<td>#Model[indexLine].TotalWorkforce</td>
<td>#Model[indexLine].IdArticleNavigation.UnitPriceWorkforce</td>
<td>#Html.CheckBoxFor(x => Model[indexLine].IsDisplayed, new {#class = "checkbox"}).
</td>
</tr>
}
I only want to know how I can keep this structure with MVVM and avoid the input name duplicate issue?
RE-UPDATE:
I'm not quite sure why this would be useful since I get name duplicate input, but here's my save action:
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Admin, SuperAdmin, Employe")]
public ActionResult Edit(int id, Submission submission, [FromForm] List<SubmissionLine> lines)
{
var test = Request.Form;
if (id != submission.IdSubmission)
{
return NotFound();
}
if (ModelState.IsValid)
{
SubmissionUpdateHelper.SaveSubmissionModifications(_context, submission, lines);
return RedirectToAction(nameof(Index));
}
ViewData["IdProject"] = new SelectList(_context.Project, "IdProject", "Name", submission.IdProject);
ViewData["IdTypeSubmission"] = new SelectList(_context.TypeSubmission, "IdTypeSubmission",
"TypeSubmission1", submission.IdTypeSubmission);
return View(submission);
}
For showing sub-list properties in View, Try code below:
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<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">
#for (int i = 0; i < Model.Pages.Count; i++)
{
<div class="form-group">
<label asp-for="Pages[i].PageName" class="control-label"></label>
<input asp-for="Pages[i].PageName" class="form-control" />
<span asp-validation-for="Pages[i].PageName" class="text-danger"></span>
</div>
<div class="form-group">
#for (int j = 0; j < Model.Pages[i].Lines.Count; j++)
{
<div class="form-group">
<label asp-for="Pages[i].Lines[j].LineContent" class="control-label"></label>
<input asp-for="Pages[i].Lines[j].LineContent" class="form-control" />
<span asp-validation-for="Pages[i].Lines[j].LineContent" class="text-danger"></span>
</div>
}
</div>
}
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
Finally, I've just added the input by hand so I could keep my MVVM pattern. I've also used an increment variable in TempData so I could keep track of the input
in Edit.cshtml
#{
TempData["submissionLineCount"] = 0;
}
here's the new version of SubmissionLineEditor.cshtml
#model List<SubmissionLine>
#for (var indexLine = 0; indexLine < Model.Count; indexLine++)
{
<tr>
<input name="[#(TempData["submissionLineCount"])].IdSubmissionLine" value="#(Model[indexLine].IdSubmissionLine)" type="hidden"/>
<input name="[#(TempData["submissionLineCount"])].IdArticle" value="#(Model[indexLine].IdArticle)" type="hidden"/>
<input name="[#(TempData["submissionLineCount"])].IdSubmissionSubSection" value="#(Model[indexLine].IdSubmissionSubSection)" type="hidden"/>
<td>#TempData["submissionLineCount"]</td>
<td>
<input name="[#(TempData["submissionLineCount"])].Quantity" value="#(Model[indexLine].Quantity)" type="number" step="0.5" min="0"/>
</td>
<td>#Model[indexLine].IdArticleNavigation.Designation</td>
<td>#Model[indexLine].TotalMaterial</td>
<td>#Model[indexLine].IdArticleNavigation.UnitPriceMaterial</td>
<td>#Model[indexLine].TotalSubContractor</td>
<td>#Model[indexLine].IdArticleNavigation.UnitPriceSubContractor</td>
<td>#Model[indexLine].TotalWorkforce</td>
<td>#Model[indexLine].IdArticleNavigation.UnitPriceWorkforce</td>
<td>
<input type="checkbox" name="[#(TempData["submissionLineCount"])].IsDisplayed" class="checkbox" value="#(Model[indexLine].IsDisplayed)"/>
</td>
</tr>
{
TempData["submissionLineCount"] = (Convert.ToInt32(TempData["submissionLineCount"]) + 1);
}
}
This is clearly not the best way, I hope there will be more documentation on how to apply MVVM in AspNet.Core in the future...
If this answer gets deprecated, make sure to post your own!
I am trying to make a system where the user can click on an item in a list, and then edit that item while still remaining in the Index-view.
My attempt is just a mix between Index.cshtml and Edit.cshtml:
#model IEnumerable<MyStore.Models.ProductIdentifier>
#{int primary_id = (this.ViewContext.RouteData.Values["primary_id"] != null
? int.Parse(this.ViewContext.RouteData.Values["primary_id"].ToString())
: 0);
}
#foreach (var item in Model)
{
if (item.Id == primary_id)
{
// This list-item is editable (copied from Edit.cshtml):
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="#item.Id" />
<div class="form-group col-lg-4">
<input asp-for="#item.Label" class="form-control" />
<span asp-validation-for="#item.Label" class="text-danger"></span>
</div>
<div class="form-group col-lg-6">
<input asp-for="#item.Description" class="form-control" />
<span asp-validation-for="#item.Description" class="text-danger"></span>
</div>
<div class="form-group col-lg-1">
<input asp-for="#item.SortOrder" class="form-control" />
<span asp-validation-for="#item.SortOrder" class="text-danger"></span>
</div>
<div class="form-group col-lg-1">
<button type="submit" value="Save" class="btn btn-primary">
<span class="glyphicon glyphicon-floppy-disk"></span> Save
</button>
</div>
</form>
}
else
{
// This list-item is just a plain list-item:
<div class="row table">
<div class="col-lg-4">
<a asp-action="Index" asp-route-primary_id="#item.Id">
#Html.DisplayFor(modelItem => item.Label)
</a>
</div>
<div class="col-lg-6">
#Html.DisplayFor(modelItem => item.Description)
</div>
<div class="col-lg-1">
#Html.DisplayFor(modelItem => item.SortOrder)
</div>
<div class="col-lg-1">
<a asp-action="Delete" asp-route-id="#item.Id" class="btn btn-xs btn-danger">
<span class="glyphicon glyphicon-trash"></span>
</a>
</div>
</div>
}
}
The form data is supposed to be posted to the Edit-method in the controller:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Label,Description,SortOrder")] ProductIdentifier productIdentifier)
{
if (id != productIdentifier.Id) { return NotFound(); }
if (ModelState.IsValid)
{
try
{
_context.Update(productIdentifier);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductIdentifierExists(productIdentifier.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(productIdentifier);
}
... but because I had to add #item. in front of the elements in the form (because the model is an IEnumerable, and I only want to post a single object), the model binding no longer works, and a null object is being posted.
How can I get it to work?
I made it work!
First, I created a ViewModel which contains both an ICollection of identifiers and a single instance of an identifier:
public class ViewModelEditIdentifierInIndexView
{
public ViewModelProductIdentifier SingleItem { get; set; }
public ICollection<ViewModelProductIdentifier> ListOfItems { get; set; }
}
I had to make some changes in the Index method in the controller, to cater for the viewmodel:
public async Task<IActionResult> Index(int? primary_id)
{
ProductIdentifier pi = await _context.ProductIdentifiers
.Where(i => i.Id == primary_id)
.SingleOrDefaultAsync();
ViewModelEditIdentifierInIndexView ViewModel = new ViewModelEditIdentifierInIndexView
{
SingleItem = _mapper.Map<ViewModelProductIdentifier>(pi),
ListOfItems = _mapper.Map<ICollection<ViewModelProductIdentifier>>(await _context.ProductIdentifiers.ToListAsync())
};
return View(ViewModel);
}
Then, I changed the model in the Index-view:
#model MyStore.Models.ViewModels.ViewModelEditIdentifierInIndexView
Then, I changed the edit form. The most important change is the addition of name-tags on each input-field:
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="SingleItem.Id" name="Id" />
<div class="form-group col-lg-4" style="padding-left:0px;">
<input asp-for="SingleItem.Label" name="Label" class="form-control" />
<span asp-validation-for="SingleItem.Label" class="text-danger"></span>
</div>
<div class="form-group col-lg-6" style="padding-left:0px;">
<input asp-for="SingleItem.Description" name="Description" class="form-control" />
<span asp-validation-for="SingleItem.Description" class="text-danger"></span>
</div>
<div class="form-group col-lg-1" style="padding-left:0px;">
<input asp-for="SingleItem.SortOrder" name="SortOrder" class="form-control" />
<span asp-validation-for="SingleItem.SortOrder" class="text-danger"></span>
</div>
<div class="form-group col-lg-1" style="padding-left:0px;">
<button type="submit" value="Save" class="btn btn-xs btn-success">
<span class="glyphicon glyphicon-floppy-disk"></span>
</button>
<a href="/Admin/ProductIdentifiers" class="btn btn-xs btn-warning">
<span class="glyphicon glyphicon-chevron-left"></span>
</a>
</div>
</form>
I didn't have to make any changes to the Edit method in the controller.
I am a programmer-to-be, currently working on a project at work.
My problem is this: I have 3 submit buttons (on the same form), that each call a different action. These buttons HAVE to be disabled, if the HTML Selector (dropdown) does not have any options to choose from.
My form currently looks like this:
<form asp-action="Index" method="post" onkeypress="return event.keyCode != 13;">
<div class="col-md-9">
<div class="form-group form-inline">
<label asp-for="OrderNumber">Order number: </label>
<input asp-for="OrderNumber" class="form-control" type="text" placeholder="Insert order number" autofocus />
</div>
<div>
<label>Delete</label>
<input asp-for="Deletion" type="checkbox" />
</div>
<div>
<div class="col-md-4">
<input class="btn btn-block" type="submit" name="action" value="Receive" />
</div>
<div class="col-md-4">
<input class="btn btn-block" type="submit" name="action" value="Start" />
</div>
<div class="col-md-4">
<input class="btn btn-block" type="submit" name="action" value="Finish" />
</div>
</div>
</div>
<div class="col-md-3">
<div class="form-group form-inline">
<label asp-for="ProfileId">Profile: </label>
<select asp-for="ProfileId" class="form-control">
#foreach (var p in Model.Profiles)
{
<option value="#p.ProfileId">#p.Name</option>
}
</select>
</div>
</div>
</form>
Thanks for any help, and if you need more information, or if this question is poorly described, do not hesitate to ask! :)
Give your buttons an id. Set them to be disabled (enabled="false").
Call a javascript function when the dropdownlist is changed.
Check the selectedIndex of the dropdownlist in the function that is called onchange and set the button accordingly.
I could write a function that would do it, but you wouldn't learn anything. If you do a bit of research, have a stab at writing the function, I'll fix it if it doesn't work.
Simply disable the form fields that need be depending on if the others have the same conditional requirements. You can give them a custom class and use some javascript code and toggle the class with some JS like so.
toggleAttribute = function(className,index){
var eLs = [].slice.call(document.getElementsByClassName(className),0);
for(var i=0; i<eLs.length; i++){
eLs[i].disabled=(index==0 ? false : true); //index 0 is for 'Yes'
}
}
I came up with a solution of my own using Razor.
My form now looks like this:
<form asp-action="Index" method="post" onkeypress="return event.keyCode != 13;">
<div class="col-md-9">
<div class="form-group form-inline">
<label asp-for="OrderNumber">Order number: </label>
<input asp-for="OrderNumber" class="form-control" type="text" placeholder="Insert order number" autofocus />
</div>
<div>
<label>Delete</label>
<input asp-for="Deletion" type="checkbox" />
</div>
<div>
<div class="col-md-4">
<input class="btn btn-block" type="submit" name="action" value="Receive"
#{ if (Model.Profiles.Count() == 0) { #: disabled="disabled"
} } />
</div>
<div class="col-md-4">
<input class="btn btn-block" type="submit" name="action" value="Start"
#{ if (Model.Profiles.Count() == 0) { #: disabled="disabled"
} } />
</div>
<div class="col-md-4">
<input class="btn btn-block" type="submit" name="action" value="Finish"
#{ if (Model.Profiles.Count() == 0) { #: disabled="disabled"
} } />
</div>
</div>
</div>
<div class="col-md-3">
<div class="form-group form-inline">
<label asp-for="ProfileId">Profile: </label>
<select asp-for="ProfileId" class="form-control">
#foreach (var p in Model.Profiles)
{
<option value="#p.ProfileId">#p.Name</option>
}
</select>
</div>
</div>
</form>
I added an if-statement, that checks if there is any profiles in my list of profiles. If there is none (0), it adds the disabled="disabled" attribute to my inputs.
Thanks to anyone that tried to assist me in solving this, and providing me with suggestions for a solution :)