I am using ASP.NET Core 8.0 with Razor Pages (not MVC). Webpage has a table where the prices of services offered can be modified as shown here:
I have created a model class ServProdModel
and a bound property collection called Servs
:
[BindProperty]
public List<ServProdModel>? Servs { get; set; }
The values get bound properly, as can be seen. But I am facing issues in the POST
handler.
I have bound it into the HTML Table
as follows (using explicit index) like this:
@foreach (var itm in Model.Servs)
{
<tr>
<td class="visually-hidden">
<input type="hidden" name='Servs.Index' value='@itm.ServID' />
<input type="hidden" name='Servs[@itm.ServID].ServID' value='@itm.ServID' />
</td>
<td>
<input type="text" name='Servs[@itm.ServID].Price' value='@itm.Price' />
</td>
...
</tr>
}
I get the collection in the Request.Form
as shown below:
I also see the Servs.Count = 5
, which is correct since there are 5 services.
HOWEVER, all the items in the Servs List
are null
. And I get an error:
System.NullReferenceException: Object reference not set to an instance of an object.
What is missing? Please help!
I also tried using for
loop but the same thing happens.
[EDIT]
Image shows Collection with null objects:
Following image shows Request.Form
collection after using Sequential Index instead of Explicit Index:
[UPDATE]: As promised, attaching a minimal code to reproduce the defect.
1. TestPg.cshtml
@page
@model NewCOS.Pages.Admins.TestPgModel
@{
}
@if (Model.Servs != null)
{
<form method="post">
<div class="row g-2 mb-1">
<div class="col-md-8">
<button type="submit" asp-page-handler="AddServs" class="btn btn-sm btn-success">Add Selected Services To Cart</button>
</div>
<div class="col-md-4">
</div>
</div>
<div class="table-responsive scrollbar" style="max-height:300px">
<table id="tblItems" class="table table-bordered table-striped fs-10">
<thead class="clsDummy">
<tr>
<th class="visually-hidden"></th>
<th class="bg-200 text-center">Select</th>
<th class="bg-200 text-center">Services</th>
<th class="bg-200 text-center">Quantity</th>
<th class="bg-200 text-center">Price</th>
<th class="bg-200 text-center cos-pre">Promotion</th>
<th class="bg-200 text-center">Discount</th>
<th class="bg-200 text-center">Discount<br />Amount</th>
</tr>
</thead>
<tbody class="clsDummyBody">
@{
//int i = 0;
@foreach (var itm in Model.Servs)
{
<tr>
<td class="visually-hidden">
<input type="hidden" name='Servs.Index' value='@itm.ServID' />
<input type="hidden" name='Servs[@itm.ServID].ServID' value='@itm.ServID' />
</td>
<td class="text-center">
<input type="checkbox" id='@("S" + itm.ProdID)' class="form-check-input" onchange="changeQty(this);" />
</td>
<td><span>@itm.Service</span></td>
<td>
<div class="input-group input-group-sm flex-nowrap" data-quantity="data-quantity">
<button class="btn btn-sm btn-outline-secondary border-300 px-2 shadow-none" data-type="minus">-</button>
<input type="number" id='@("QS" + itm.ProdID)' name='Servs[@itm.ServID].Qty' value='@itm.Qty' min="0" aria-label="Quantity" style="width: 50px" class="form-control text-center px-2 input-spin-none" />
<button class="btn btn-sm btn-outline-secondary border-300 px-2 shadow-none" data-type="plus">+</button>
</div>
</td>
<td>
<div class="d-flex flex-row">
<div class="d-inline-flex pt-1">$ </div>
<div class="d-inline-flex">
<input type="text" name='Servs[@itm.ServID].Price' value='@itm.Price' class="form-control form-control-sm" placeholder="Enter Price" maxlength="10" />
</div>
</div>
</td>
<td>
<select class="form-select form-select-sm" name='Servs[@itm.ServID].PromoID' asp-for="@itm.PromoID">
<option value="0">-- Select --</option>
<option value="1">Senior Discount</option>
<option value="2">Complementary</option>
<option value="3">Student Discount</option>
</select>
</td>
<td>
<input class="form-control form-control-sm text-center px-2 input-spin-none" type="number" min="0" max="1" step="0.01" value="0" aria-label="Discount between 0 and 1" style="width: 50px" />
</td>
<td>$ <span>0.00</span></td>
</tr>
//i++;
}
}
</tbody>
</table>
</div>
</form>
}
2. TestPg.cshtml.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace NewCOS.Pages.Admins
{
public class TestPgModel : PageModel
{
[BindProperty]
public List<ServModel>? Servs { get; set; }
public void OnGet()
{
ServModel ServProd = new ServModel();
Servs = ServProd.Fetch(ServModel.ItemType.Service);
}
public void OnPostAddServs()
{
var chk = Request.Form;
if (Servs != null)
{
foreach (var itm in Servs)
{
int Qty = itm.Qty;
decimal prc = itm.Price;
}
}
}
}
public class ServModel
{
private int _uid;
public int SchoolUID;
public int CompID;
public string? UPC;
public string ProductName = "";
public string Service = "";
public decimal RetailPrice = 0.00M;
public decimal Price = 0.00M;
public int Qty = 0;
public int PromoID = 1;
public decimal DiscAmount = 0.00M;
public decimal Discount = 0.00M;
public decimal TaxAmount = 0.00M;
public decimal TotalAmount = 0.00M;
public ItemType TransCode;
public int ChkServ;
public int ChkProd;
public int ProdID { get { return _uid; } }
public int ServID { get { return _uid; } }
public enum ItemType
{
Product, Service
}
public List<ServModel>? Fetch(ItemType type)
{
List<ServModel>? Services = new List<ServModel>();
if (type == ItemType.Service)
{
Services.Add(new ServModel { _uid = 1, TransCode = ItemType.Service, Service = "Acrylic/Gel Tips/Overlay", Price = 20.00M, CompID = 1 });
Services.Add(new ServModel { _uid = 2, TransCode = ItemType.Service, Service = "Acrylic/Natural Nails", Price = 25.00M, CompID = 1 });
Services.Add(new ServModel { _uid = 10, TransCode = ItemType.Service, Service = "Additional Colors Each", Price = 15.00M, CompID = 1 });
Services.Add(new ServModel { _uid = 11, TransCode = ItemType.Service, Service = "Airstyle w/brush", Price = 5.00M, CompID = 1 });
Services.Add(new ServModel { _uid = 17, TransCode = ItemType.Service, Service = "Beard Trim", Price = 3.00M, CompID = 1 });
return Services;
}
else { return null; }
}
}
}
IMP: This code implements explicit index. If you would like to test with Sequential Index then
int i = 0;
i++;
<input type="hidden" name='Servs.Index' value='@itm.ServID' />
Servs[@itm.ServID]
with Servs[@i]
For model binding, you have to work with the properties (getter/setter) instead of field.
Change for those fields that you are passing the value from the view to properties in your ServModel
class.
public class ServModel
{
public decimal Price { get; set; } = 0.00M;
public int Qty { get; set; } = 0;
public int PromoID { get; set; } = 1;
...
public int ServID
{
get { return _uid; }
set { _uid = value; }
}
...
}