Search code examples
c#asp.net-corerazor-pages

Edit only some fields


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" class="text-danger"></div>
            <input type="hidden" asp-for="Customer.CustomerID" />
            <h1> @Model.Customer.Code</h1>
            <div class="form-group">
                <label asp-for="Customer.Note" class="control-label"></label>
                <input asp-for="Customer.Note" class="form-control" />
                <span asp-validation-for="Customer.Note" class="text-danger"></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?


Solution

  • 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");
    }