ASP.NET Web Forms 4.5 model binding where the model contains a collection

妖精的绣舞 提交于 2019-11-30 13:13:00

问题


I'm trying to update an old Web Forms application to use the new model binding features added in 4.5, similar to the MVC binding features.

I'm having trouble making an editable FormView that presents a single model that contains simple members plus a member that is a collection of other models. I need the user to be able to edit the simple properties of the parent object and the properties of the child collection.

The problem is that the child collection (ProductChoice.Extras) is always null after model binding when the code is trying to update the model.

Here are my models:

[Serializable]
public class ProductChoice
{
    public ProductChoice()
    {
        Extras = new List<ProductChoiceExtra>();
    }

    public int Quantity { get; set; }
    public int ProductId { get; set; }
    public List<ProductChoiceExtra> Extras { get; set; }
}

[Serializable]
public class ProductChoiceExtra
{
    public int ExtraProductId { get; set; }
    public string ExtraName { get; set; }
    public int ExtraQuantity { get; set; }
}

And my user control code behind:

public partial class ProductDetails : System.Web.UI.UserControl
{
    private Models.ProductChoice _productChoice;

    protected void Page_Load(object sender, EventArgs e)
    {
        _productChoice = new Models.ProductChoice()
        {
            Quantity = 1,
            ProductId = 1
        };
        _productChoice.Extras.Add(new Models.ProductChoiceExtra()
        {
            ExtraProductId = 101,
            ExtraName = "coke",
            ExtraQuantity = 1
        });
        _productChoice.Extras.Add(new Models.ProductChoiceExtra()
        {
            ExtraProductId = 104,
            ExtraName = "sprite",
            ExtraQuantity = 2
        });

    }

    public Models.ProductChoice GetProduct()
    {
        return _productChoice;
    }

    public void UpdateProduct(Models.ProductChoice model)
    {
        /* model.Extras is always null here, it should contain two ProductChoiceExtra objects */

        if (TryUpdateModel(_productChoice) == true)
        {
        }
    }
}

My control markup:

<div id="selectOptions">
    <asp:FormView runat="server" ID="fvProductSelection" DefaultMode="Edit"
        ItemType="Models.ProductChoice"
        SelectMethod="GetProduct"
        UpdateMethod="UpdateProduct" >

        <EditItemTemplate>
            <asp:linkbutton id="UpdateButton" text="Update" commandname="Update" runat="server"/>
            <asp:HiddenField runat="server" ID="ProductId" Value="<%# BindItem.ProductId %>" />
            <asp:TextBox Text ="<%# BindItem.Quantity %>" ID="Quantity" runat="server" />

            <asp:Repeater ID="Extras" ItemType="Models.ProductChoiceExtra" DataSource="<%# BindItem.Extras %>" runat="server">
                <ItemTemplate>
                    <asp:HiddenField Value="<%# BindItem.ExtraProductId %>" ID="ExtraProductId" runat="server"  />
                    <asp:Label Text="<%# BindItem.ExtraName %>" ID="Name" runat="server" />
                    <asp:TextBox Text="<%# BindItem.ExtraQuantity %>" ID="Quantity"  runat="server" />
                </ItemTemplate>
            </asp:Repeater>
        </EditItemTemplate>
    </asp:FormView>
</div>

I have tried making the Extras property a BindingList rather than a List but it didn't make any difference, the Extras collection isn't bound in the UpdateProduct method.


回答1:


Unfortunately I do not know exactly how this is done with Web Forms so I am unsure how to reproduce this with a repeater, but in MVC the model binder requires an index to reconstruct the list. If I would have to guess how this is done in web forms, it would be something similar to this:

<div id="selectOptions">
    <asp:FormView runat="server" ID="fvProductSelection" DefaultMode="Edit"
        ItemType="Models.ProductChoice"
        SelectMethod="GetProduct"
        UpdateMethod="UpdateProduct" >
        <EditItemTemplate>
            <asp:linkbutton id="UpdateButton" text="Update" commandname="Update" runat="server"/>
            <asp:HiddenField runat="server" ID="ProductId" Value="<%# BindItem.ProductId %>" />
            <asp:TextBox Text ="<%# BindItem.Quantity %>" ID="Quantity" runat="server" />

            <% for (int i = 0; i < BindItem.Extras.Count; i++)
            { %>
                    <asp:HiddenField Value="<%# BindItem.Extras[i].ExtraProductId %>" ID="ExtraProductId" runat="server"  />
                    <asp:Label Text="<%# BindItem.Extras[i].ExtraName %>" ID="Name" runat="server" />
                    <asp:TextBox Text="<%# BindItem.Extras[i].ExtraQuantity %>" ID="Quantity"  runat="server" />
            <% } %> 
        </EditItemTemplate>
    </asp:FormView>
</div>

Notice I replaced the repeater with a for loop that iterates through the collection with the index used to access each Extra. This is similar to how I am required to do what you want in ASP.NET MVC. The index is posted along with the rest of the web form when the form is submitted which allows the model binder to reconstruct the ordered list of objects.

I hope this is some amount of help and forgive me for any errors, as I do not have a web forms project to test this with at the moment.




回答2:


Digging into System.Web.ModelBinding reveals that the CollectionModelBinder expects that values passed in to the FormValueProvider would be in the same format as they would be for MVC, that is: MyCollection[i]

public static string CreateIndexModelName(string parentName, string index)
{
    if (parentName.Length != 0)
    {
        return (parentName + "[" + index + "]");
    }
    return ("[" + index + "]");
}

Unfortunately, your repeater's element names will not match that criteria.

While certainly unorthodox, you could still achieve this by writing non-server textboxes, and then giving them a name starting with your datalist naming container, followed by the index. And thanks to "Request.Unvalidated" (also introduced in 4.5), you have the ability to databind to this data even though it's not represented by server-side controls.




回答3:


[Serializable]
public class ProductChoice
{
public int Quantity { get; set; }
    public int ProductId { get; set; }
    public ProductChoiceExtra Extras { get; set; }
}

[Serializable]
public class ProductChoiceExtra
{
    public int ExtraProductId { get; set; }
    public string ExtraName { get; set; }
    public int ExtraQuantity { get; set; }
    public List<ProductChoiceExtra> listProducts{get;set;}
}



回答4:


You should specify an Edit Item Template in an inner datalist as the Properties in the Item Template may not be returned in the model that is constructed automatically and passed to the Update method. I've not had time to try it but this should work...

<div id="selectOptions">
    <asp:FormView runat="server" ID="fvProductSelection" DefaultMode="Edit"
        ItemType="Models.ProductChoice"
        SelectMethod="GetProduct"
        UpdateMethod="UpdateProduct" >

        <EditItemTemplate>
            <asp:linkbutton id="UpdateButton" text="Update" commandname="Update" runat="server"/>
            <asp:HiddenField runat="server" ID="ProductId" Value="<%# BindItem.ProductId %>" />
            <asp:TextBox Text ="<%# BindItem.Quantity %>" ID="Quantity" runat="server" />

            <asp:DataList ID="Extras" DataSource="<%# DataBinder.Eval(Container.DataItem, "Extras") %>" runat="server">
                <EditItemTemplate>
                    <asp:HiddenField Value="<%# BindItem.ExtraProductId %>" ID="ExtraProductId" runat="server"  />
                    <asp:Label Text="<%# BindItem.ExtraName %>" ID="Name" runat="server" />
                    <asp:TextBox Text="<%# BindItem.ExtraQuantity %>" ID="TextBox1"  runat="server" />
                </EditItemTemplate>
                <ItemTemplate>
                    <asp:HiddenField Value="<%# BindItem.ExtraProductId %>" ID="ExtraProductId" runat="server"  />
                    <asp:Label Text="<%# BindItem.ExtraName %>" ID="Name" runat="server" />
                    <asp:TextBox Text="<%# BindItem.ExtraQuantity %>" ID="Quantity"  runat="server" />
                </ItemTemplate>
            </asp:Repeater>
        </EditItemTemplate>
    </asp:FormView>
</div>



回答5:


Pass the extras values in query string, add parameter values in the update method. Use data attributes on HTML 5 to store those 3 values in the Form View element

example

UpdateMethod="UpdateProduct/104/coke/2"

or

UpdateMethod="UpdateProduct/?ExtraProductId=104&ExtraName=coke&ExtraQuantity=2"

for the first approach you have to write routing rule in Route Config.

Inside the user control like as below

public void UpdateProduct(Models.ProductChoice model, int ExtraProductId, string ExtraName, int ExtraQuantity)
{
    /* model.Extras is always null here, it should contain two ProductChoiceExtra objects */

    if (TryUpdateModel(_productChoice) == true)
    {
      model.Extras.Add(new Models.ProductChoiceExtra()
      {
        ExtraProductId = ExtraProductId,
        ExtraName = ExtraName,
        ExtraQuantity = ExtraQuantity
      });
    }
}


来源:https://stackoverflow.com/questions/21667583/asp-net-web-forms-4-5-model-binding-where-the-model-contains-a-collection

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!