0

UPDATE: I changed the form a little bit and added some code to try and get the script to sync with the form. I also checked the payload and saw that only 2 out of 4 fields are being submitted to the form, which is probably what is causing the bad request.

I am trying to make a full stack ASP.NET Core application using Razor pages and am trying to make a form submit data properly.

Here is my view page with the form:

@page
@model RogersPizza.Pages.OrderModel
@{
 Layout = "Shared/_Layout.cshtml";
}
@section title {
 <title>Order</title> 
}
@section scripts {
 
}
@section siteMenu {
 <div name="Site Menu">
 <table width="100%">
 <tr>
 <th><a href="Index">Rogers Pizza</a></th>
 <th><a href="Menu">Menu</a></th>
 <th>Order</th>
 <th><a href="About">About</a></th>
 <th><a href="Contact">Contact</a></th>
 <th><a href="Employees">Employees</a></th>
 </tr>
 </table>
 </div>
 
}
@section body {
 <div name="Order UI">
 <form method="post">
 <p>Choose your pizza</p>
 <select asp-for="Order.Pizza" id="pizzas" required>
 @foreach(var item in Model?.Pizzas)
 {
 <!-- <option>@Html.DisplayFor(modelItem => item.Name)</option> -->
 <option value="@item.Name">@item.Name</option>
 }
 </select>
 <div id= "payment-options"></div>
 <script src="~/js/paymentOptionsBundled.js"></script>
 <input type="hidden" asp-for="Order.PaymentOption" id="selectedPaymentOption" />
 <input type="hidden" asp-for="Order.GiftCardNumber" id="giftCardNumber" value="9999999999999999"/>
 <input type="hidden" asp-for="Order.ID" name="Order.ID" value="999">
 <input type="submit" value="Place Order" />
 </form> 
 </div>
}

Here is the react script inside the form:

import React from 'react';
import { useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
export default function PaymentOptions() {
 const [paymentOption, setPaymentOption] = useState("cash");
 const [giftCard, setGiftCard] = useState("");
 useEffect(() => {
 const form = document.querySelector('form');
 const syncHiddenInputs = () => {
 const paymentInput = document.getElementById('selectedPaymentOption');
 const giftCardInput = document.getElementById('giftCardNumber');
 if (paymentInput) {
 paymentInput.value = paymentOption;
 } 
 if (giftCardInput) {
 // if(paymentOption === "cash")
 // {
 // giftCardInput.value = "9999999999999999";
 // }
 // else if(paymentOption === "giftCard")
 // {
 // giftCardInput.value = giftCard || "";
 // }
 giftCardInput.value = paymentOption === "cash" ? "9999999999999999" : giftCard; 
 }
 };
 form?.addEventListener('submit', syncHiddenInputs);
 return () => form?.removeEventListener('submit', syncHiddenInputs);
 }, [paymentOption, giftCard]);
 const handlePaymentMethodChange = (e) => {
 setPaymentOption(e.target.value);
 };
 const handleGiftCardChange = (e) => {
 setGiftCard(e.target.value);
 };
 return (
 <div>
 <div name="selectPaymentMethod">
 <p>Please select a payment method</p>
 </div>
 <select name="paymentOptions" value={paymentOption} onChange={handlePaymentMethodChange}>
 <option value="cash">Cash</option>
 <option value="giftCard">Gift Card</option>
 </select>
 {paymentOption === "cash" &&
 <div name="cash">
 <p>Please have your cash ready when receiving your order.</p>
 </div>
 }
 {paymentOption === "giftCard" &&
 <div name="giftCard">
 <p>Please enter your gift card number below.</p>
 <input
 type="text"
 id="giftCardInput"
 inputmode="numeric"
 placeholder="Enter gift card number"
 min="0"
 minlength="16"
 maxlength="16"
 pattern="[0-9]{16}"
 value={giftCard}
 onChange={handleGiftCardChange}
 required
 />
 </div>
 }
 </div>
 );
}
const domNode = document.getElementById('payment-options');
if (domNode) {
 const root = createRoot(domNode);
 root.render(<PaymentOptions />);
}

Here is the model page:

using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RogersPizza.Models;
namespace RogersPizza.Pages
{
 public class OrderModel : PageModel
 {
 private readonly RogersPizza.Data.StoreContext _context;
 private readonly ILogger<OrderModel> _logger;
 [BindProperty] public Order? Order { get; set; }
 public OrderModel(RogersPizza.Data.StoreContext context, ILogger<OrderModel> logger)
 {
 _context = context;
 _logger = logger;
 }
 public IList<Pizza>? Pizzas { get; set; }
 public async Task OnGetAsync()
 {
 Pizzas = await _context.Pizzas.ToListAsync();
 }
 public IActionResult OnPost()
 {
 _logger.LogInformation("Received order: Pizza={Pizza}, Payment={PaymentOption}, GiftCard={GiftCardNumber}",
 Order?.Pizza, Order?.PaymentOption, Order?.GiftCardNumber);
 return RedirectToPage("/Index");
 }
 // private bool ValidateOrder(Order order)
 // {
 // return false;
 // }
 }
}

Here is the Order model used when binding the form:

namespace RogersPizza.Models
{
 public class Order
 {
 public int? ID { get; set; }
 public required string Pizza { get; set; }
 public required string PaymentOption { get; set; }
 public string? GiftCardNumber { get; set; }
 }
}

Currently, my form bonding fails when submitting the form on the view page and I get a 400 error message page when using Chrome with the following message in the console:

Failed to load resource: the server responded with a status of 400 ()

Here is the payload. Notice how only 2 out of the 4 form fields are included. Pizza and GiftCardNumber should also be included:

paymentOptions=giftCard&Order.ID=999

How can I get my form to submit properly?

asked Aug 5 at 22:02
8
  • 2
    Use your browser's dev-tools Network panel to inspect the request and response. ASP.NET is actually pretty good at providing details in the response body, it's far from silent Commented Aug 5 at 23:33
  • 2
    HTML input - name vs. id Commented Aug 6 at 6:05
  • 1
    I am not clear about what is making the request - is it razor page or react? In React code i don't see any form being used. Commented Aug 6 at 7:10
  • @MichałTurczyn Razor is submitting the form. React is working with the form to only render certain fields depending on what payment method is selected. Commented Aug 7 at 20:34
  • 1
    Citing the most crucial info: Name Attribute: Used on form elements to submit information. Only input tags with a name attribute are submitted to the server Commented Aug 8 at 6:30

1 Answer 1

1

This is not a model binding issue. It's a Request Verification issue. Request Verification is a mechanism designed to prevent possible Cross Site Request Forgery attacks and is enabled by default in Razor Pages.

The most common cause of a 400 status code in Razor Pages is a failure to include the request verification token in a POST request initiated from client code, or where you doctor the payload using client code. You need to ensure that the hidden field __RequestVerificationToken is included as part of the payload, either as a form value or a header. The hidden field should be generated by the form tag helper given that it has its method set to POST. If not, you can generate one in the page using @Html.AntiForgeryToken()

More information here: https://www.learnrazorpages.com/security/request-verification

answered Aug 8 at 12:24
Sign up to request clarification or add additional context in comments.

1 Comment

You are correct. Though adding a name attribute to all form fields instead of an asp-for attribute helped to include all the fields in the request payload, I still had the 400 error after that change. After enabling more logging in my Program.cs file, I found logs in my terminal saying the required antiforgery cookie was not present.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.