首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >模型包含集合的ASP.NET Web Forms 4.5模型绑定

模型包含集合的ASP.NET Web Forms 4.5模型绑定
EN

Stack Overflow用户
提问于 2014-02-10 09:21:53
回答 6查看 3.6K关注 0票数 18

我正在尝试更新一个旧的Web Forms应用程序,使其使用4.5中添加的新模型绑定特性,类似于MVC绑定特性。

我在制作一个可编辑的FormView时遇到了麻烦,它呈现一个包含简单成员的单一模型,以及一个其他模型的集合的成员。我需要用户能够编辑父对象的简单属性和子集合的属性。

问题是,当代码试图更新模型时,在模型绑定之后,子集合(ProductChoice.Extras)始终为空。

以下是我的模型:

代码语言:javascript
复制
[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; }
}

和我背后的用户控制代码:

代码语言:javascript
复制
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)
        {
        }
    }
}

我的控件标记:

代码语言:javascript
复制
<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>

我曾尝试将Extras属性设置为BindingList而不是List,但这没有任何区别,因为Extras集合没有绑定在UpdateProduct方法中。

EN

回答 6

Stack Overflow用户

发布于 2017-09-01 13:23:26

深入研究System.Web.ModelBinding会发现,CollectionModelBinder期望传递给FormValueProvider的值的格式与MVC的格式相同,即: MyCollectioni

代码语言:javascript
复制
public static string CreateIndexModelName(string parentName, string index)
{
    if (parentName.Length != 0)
    {
        return (parentName + "[" + index + "]");
    }
    return ("[" + index + "]");
}

不幸的是,您的中继器的元素名称将不匹配该条件。

虽然这肯定是非正统的,但您仍然可以通过编写非服务器文本框来实现这一点,然后为它们命名,以datalist命名容器开头,然后是索引。多亏了"Request.Unvalidated“(也是在4.5中引入的),您可以将数据绑定到此数据,即使它不是由服务器端控件表示的。

票数 1
EN

Stack Overflow用户

发布于 2016-05-11 07:27:55

不幸的是,我不知道Web表单是如何做到这一点的,所以我不确定如何用中继器重现,但在MVC中,模型绑定器需要一个索引来重建列表。如果我不得不猜测这是如何在web表单中完成的,它将类似于:

代码语言:javascript
复制
<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>

请注意,我用一个for循环替换了中继器,该循环使用用于访问每个额外数据的索引遍历集合。这类似于我被要求在ASP.NET MVC中做你想做的事情。提交表单时,索引与web表单的其余部分一起发布,这允许模型绑定器重建对象的有序列表。

我希望这是一些帮助,并原谅我的任何错误,因为我没有一个网络表单项目来测试这一点。

票数 0
EN

Stack Overflow用户

发布于 2021-06-28 12:30:07

我找到了一种在UpdateMethod中绑定object的集合属性的解决方法:

我的模型:

代码语言:javascript
复制
public partial class Student
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public Student()
    {
        this.Enrollments = new List<Enrollment>();
    }

    public int Id { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public Nullable<int> YearId { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual List<Enrollment> Enrollments { get; set; }
    public virtual AcademicYear AcademicYear { get; set; }
}


public partial class Enrollment
{
    public int Id { get; set; }
    public Nullable<decimal> Grade { get; set; }
    public Nullable<int> CourseId { get; set; }
    public Nullable<int> StudentId { get; set; }

    public virtual Course Course { get; set; }
    public virtual Student Student { get; set; }
}

控件:

代码语言:javascript
复制
<asp:FormView runat="server" ID="addStudentForm"
    DataKeyNames="Id"
    ItemType="WebApplication3.Student" 
    UpdateMethod="addStudentForm_UpdateItem" DefaultMode="Edit"
    RenderOuterTable="false" OnItemUpdated="addStudentForm_ItemUpdated"
    SelectMethod="addStudentForm_GetItem">
    <EditItemTemplate>            
        <fieldset>
        
            <asp:HiddenField ID="Id" Value="<%# BindItem.Id %>" runat="server" />
            <asp:Panel ID="Panel1" runat="server">

                <asp:Label ID="Label1" runat="server" Text="FirstName: "></asp:Label>
                <asp:TextBox ID="FirstName" runat="server" Width="70%" Text="<%# BindItem.FirstName %>" ></asp:TextBox>

            </asp:Panel>

            <asp:Panel ID="Panel2" runat="server">

                <asp:Label ID="Label2" runat="server" Text="LastName: "></asp:Label>
                <asp:TextBox ID="LastName" runat="server" Width="70%" Text="<%# BindItem.LastName %>" ></asp:TextBox>

            </asp:Panel>

            <asp:Panel ID="Panel4" runat="server"  >
                <% for (int i = 0; i < enrollments.Count; i++)
                    { %>

                    // ```name``` is constructed like in MVC
                    <input hidden id="test" type="text" name="Enrollments[<%= i %>].Id" value='<%= enrollments[i].Id %>'/>
                    <input id="test1" type="text" name="Enrollments[<%= i %>].Grade" value='<%= enrollments[i].Grade %>' />
                <%  } %>

            </asp:Panel>
                            
            <asp:Button runat="server" Text="Edit" CommandName="Update" />
            <asp:Button runat="server" Text="Cancel" CausesValidation="false" OnClick="Cancel_Click" />
        </fieldset>
    </EditItemTemplate>
</asp:FormView>

后面的代码:

代码语言:javascript
复制
public List<Enrollment> enrollments = new List<Enrollment>(); // Contain the data after ```SelectMethod``` from db, is for ```for``` loop in Controls page

protected void Page_Load(object sender, EventArgs e)
{
}

public void addStudentForm_UpdateItem(Student student) // At this line, the ```student``` will be bound with data from ASP SerVer Control only
{

    // Add these 3 lines of code to use MVC approach.
    // THis kind of binding uses prefix like [] or ., which ASP server controls are not accepted
    NameValueCollection nameValues = Request.Form;

    IValueProvider provider = new NameValueCollectionValueProvider(nameValues, System.Globalization.CultureInfo.CurrentUICulture);

    TryUpdateModel(student, provider); // After this, the ```student``` will be bound with Form Values that have their ```key``` (```name``` attribute) being constructed like MVC approach (see above Control code).

    // Database update go here
    
}

结果:

在TryUpdateMethod之前,WebForms4.5模型绑定将绑定到student对象。请注意,Enrollments为空。

创建IValueProvider provider并将其传递给TryUpdateMethod后,Enrollments count为3(等于表单中提交的数据)。

更多详细信息:

关于绑定到edit**:** 的集合我找到了什么

  1. 在你的问题中,不能将数据绑定为MVC的原因是,UpdateMethod中的自动模型绑定只适用于(1) 服务器控件;(2)具有简单类型的属性,如stringint...,而不是复杂类型,如collection或类对象。

MVC对于这些复杂类型,你可以随心所欲地使用方法。但是,我认为这种方法可能需要以下条件:

代码语言:javascript
复制
- It only works with HTML element (and without `runat=server`), because of the requirement for `name` attribute (of `<input>` or `<select>` for example). You get what I mean? The Webform Server Controls have its own design for `name` properties, which is designed to fit it own model binding, which not being accepted in MVC. That's why you cannot set `name` properties, and if you try to change it with Jquery/JavaScript/Code-Behind, the model binding for the Webform Server Controls will not work.
- This approach only use "MVC style" `name` to bind. So it will not bind the ASP Server Control style `name` (like `$Maincontent$...` thing).
- The binding for Webform Server Controls approach and MVC approach cant be merged in one. I had to do one after one like in my example.

在我的研究过程中,

  1. 其他不太相关的事情:

  • BindItemEvalContainer.ItemIndex (of Repeater)只适用于Textbox等Webform控件,或适用于runat=server的HTML元素。两者都将更改name属性以适应Webform模型绑定样式,因此您不能将它们用于MVC方法。

  • 在@JonathanWalter示例中,计数器i不能放在<%# %>中。它必须放在<%=%>中。正因为如此,我尝试了很多次,但都无法将Text构建为TextBoxBindItem.Enrollments[i].Id。很遗憾,我对此的希望破灭了。

如果我在asp:button<button>onClick事件中绑定集合,那么MVC

  • 方法也可以工作。只需确保将collection放在HTML元素中,而不是服务器控件中,并将其命名为MVC style。

这就是我对这个问题的发现。我在这上面花了不少时间,所以我想在这里发布这篇文章,以防有人想要尝试这种MVC方法。

感谢您阅读这篇长文=))。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/21667583

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档