First steps in Razor Pages with VS2022. Simple CRUD SQL based.
Everything OK with the "main" edit view. I can edit and change all using also the PageRemote.
I created a second EditNote page to change only one field but i'm unable to reach the goal.
My files:
Models\Customer.cs
namespace Test.Model
{
public class Customer
{
public int CustomerID { get; set; }
[BindProperty(SupportsGet = true)]
[PageRemote(PageHandler = "CheckCode", HttpMethod = "get", ErrorMessage = "Code already exist")]
public int Code { get; set; }
[Required(ErrorMessage ="Mandatory")]
[MaxLength(100)]
public string Description { get; set; }
[Display(Name = "Notes")]
[MaxLength(300)]
public string? Note { get; set; }
Pages\Customers\EditNote.cshtml
<form method="post">
<div asp-validation-summary="ModelOnly" ></div>
<input type="hidden" asp-for="Customer.CustomerID" />
<h1> @Model.Customer.Code</h1>
<div >
<label asp-for="Customer.Note" ></label>
<input asp-for="Customer.Note" />
<span asp-validation-for="Customer.Note" ></span>
</div>
Pages\Customers\EditNote.cshtml.cs
public class EditModel : PageModel
{
private readonly Test.Data.TestContext _context;
public EditModel(Test.Data.TestContext context)
{
_context = context;
}
[BindProperty]
public Customer Customer { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Customer = await _context.Customer.FirstOrDefaultAsync(m => m.CustomerID == id);
if (Customer == null)
{
return NotFound();
}
return Page();
}
// 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.Attach(Customer).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CustomerExists(Customer.CustomerID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool CustomerExists(int id)
{
return _context.Customer.Any(e => e.CustomerID == id);
}
//Only for test
public JsonResult OnGetCheckCode(Customer Customer) { return new JsonResult(true); }
}
}
Now, when i edit the record with this EditNote page, i see only Code as label and Notes as field (as expected) but when i change something in Note and try to Save, Model is Invalid e "looking inside" i see that the reasons is that Description is empty but mandatory as defined in the model. For sure i would avoid to send hidden value in the user page.
What i'm doing wrong and how i can fix this?
CodePudding user response:
In the EditModel
page, you need to create a model that's better aligned with the form you're designing. That model would be a subset of the Customer
model.
Like this:
public class CustomerNote
{
public int CustomerID { get; set; }
[Display(Name = "Notes")]
[MaxLength(300)]
public string? Note { get; set; }
}
In OnGetAsync
, build the model using a Projection (Select
):
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return this.NotFound();
}
this.Customer = await this._context.Customer
.Select(m => new CustomerNote
{
CustomerID = m.CustomerID,
Note = m.Note
})
.FirstOrDefaultAsync(m => m.CustomerID == id);
if (this.Customer == null)
{
return this.NotFound();
}
return this.Page();
}
Finally, in OnPostAsync
, load the Customer entity by Id and update its properties with the CustomerNote model.
Something like this:
public async Task<IActionResult> OnPostAsync()
{
if (!this.ModelState.IsValid)
{
return this.Page();
}
var customer = await this._context.Customer.SingleAsync(m => m.CustomerID == this.Customer.CustomerID);
customer.Note = this.Customer.Note;
try
{
await this._context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!this.CustomerExists(Customer.CustomerID))
{
return this.NotFound();
}
else
{
throw;
}
}
return this.RedirectToPage("./Index");
}