skip to Main Content

I have a Razor Pages page, and want to submit an array of objects and automatically bind them.

However, these objects will be edited (added, edited and deleted) on the client side. So rather than specify the index of each array item (name=Ship.Products[1].ProductId), it would be much easier to omit the index (name=Ship.Products[].ProductId).

However, Razor Pages does not appear to recognize the data without the array index.

Models
public class EditShipViewModel
{
    public int Id { get; set; }

    [Required]
    [StringLength(80)]
    public string Name { get; set; }

    [StringLength(80)]
    public string Shipper { get; set; }

    [StringLength(80)]
    [DisplayName("Port of Origin")]
    public string PortOfOrigin { get; set; }

    [DisplayName("Is Empty")]
    public bool IsEmpty { get; set; }

    [DisplayName("ETA")]
    public DateTime? Eta { get; set; }
    public DateTime? Arrival { get; set; }
    public DateTime? Departure { get; set; }

    public IEnumerable<EditShipProductViewModel> Products { get; set; }
}

public class EditShipProductViewModel
{
    public int Id { get; set; }
    [DisplayName("Product")]
    public int ProductId { get; set; }
    [DisplayName("Inbound Quantity")]
    public double InboundQuantity { get; set; }
}
Property
[BindProperty]
public EditShipViewModel Ship { get; set; }
Markup
<table class="table table-striped">
    <tbody id="ship-products">
        <tr>
            <td>
                <input name="Ship.Products[].Id" type="hidden" value="1">
                <input name="Ship.Products[].ProductId" type="hidden" value="1">
            </td>
            <td>
                <input name="Ship.Products[].InboundQuantity" type="hidden" value="1000">
            </td>
            <td class="text-end">[Edit][Delete]</td>
        </tr>
        <tr>
            <td>
                <input name="Ship.Products[].Id" type="hidden" value="2">
                <input name="Ship.Products[].ProductId" type="hidden" value="2">
            </td>
            <td>
                <input name="Ship.Products[].InboundQuantity" type="hidden" value="2000">
            </td>
            <td class="text-end">[Edit][Delete]</td>
        </tr>
        <tr>
            <td>
                <input name="Ship.Products[].Id" type="hidden" value="3">
                <input name="Ship.Products[].ProductId" type="hidden" value="3">
            </td>
            <td>
                <input name="Ship.Products[].InboundQuantity" type="hidden" value="3000">
            </td>
            <td class="text-end">[Edit][Delete]</td>
        </tr>
    </body>
</table>

Is there any way to have Razor Pages binding recognize this markup as an array of objects?

Update

Here’s the actual data posted to my page.

enter image description here

2

Answers


  1. Short answer; No.

    You can bind a collection of simple values like name=1&name=2&name=3 to int[] name. But there’s no way this can work with complex objects, as the server would have no logical way to tell when one item stops and another begins.

    If you don’t want to worry about posting sequential values, then you can bind a Dictionary<int,T> instead of a List<T>. That way the client can generate new dictionary keys, but can also remove them without needing to renumber everything else.

    public IDictionary<int, EditShipProductViewModel> Products { get; set; }
    
            <tr>
                <td>
                    <input name="Ship.Products[23].Id" type="hidden" value="1">
                    <input name="Ship.Products[23].ProductId" type="hidden" value="1">
                </td>
                <td>
                    <input name="Ship.Products[23].InboundQuantity" type="hidden" value="1000">
                </td>
                <td class="text-end">[Edit][Delete]</td>
            </tr>
            <tr>
                <td>
                    <input name="Ship.Products[42].Id" type="hidden" value="2">
                    <input name="Ship.Products[42].ProductId" type="hidden" value="2">
                </td>
                <td>
                    <input name="Ship.Products[42].InboundQuantity" type="hidden" value="2000">
                </td>
                <td class="text-end">[Edit][Delete]</td>
            </tr>
    

    As a side effect, this approach works well with preserving user supplied values in ModelState when re-rendering the same template, even if the user supplied data failed to parse.

    Login or Signup to reply.
  2. However, these objects will be edited (added, edited and deleted) on
    the client side

    If you want to avoid failing model binding due to the index of array item not numbered sequentially starting at zero

    You may try replace the index of array and add a input with the name Ship.Products.index as below:

    <tr>
                <td>
                    <input name="Ship.Products[a].Id" type="hidden" value="1">
                    <input name="Ship.Products[a].ProductId" type="hidden" value="1">
                    <input name="Ship.Products.index" type="hidden" value="a">
                </td>
                <td>
                    <input name="Ship.Products[a].InboundQuantity" type="hidden" value="1000">
                </td>
                <td class="text-end">[Edit][Delete]</td>
            </tr>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search