我正在尝试更新一个旧的Web Forms应用程序,使其使用4.5中添加的新模型绑定特性,类似于MVC绑定特性。
我在制作一个可编辑的FormView时遇到了麻烦,它呈现一个包含简单成员的单一模型,以及一个其他模型的集合的成员。我需要用户能够编辑父对象的简单属性和子集合的属性。
问题是,当代码试图更新模型时,在模型绑定之后,子集合(ProductChoice.Extras)始终为空。
以下是我的模型:
[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; }
}和我背后的用户控制代码:
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)
{
}
}
}我的控件标记:
<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方法中。
发布于 2017-09-01 13:23:26
深入研究System.Web.ModelBinding会发现,CollectionModelBinder期望传递给FormValueProvider的值的格式与MVC的格式相同,即: MyCollectioni
public static string CreateIndexModelName(string parentName, string index)
{
if (parentName.Length != 0)
{
return (parentName + "[" + index + "]");
}
return ("[" + index + "]");
}不幸的是,您的中继器的元素名称将不匹配该条件。
虽然这肯定是非正统的,但您仍然可以通过编写非服务器文本框来实现这一点,然后为它们命名,以datalist命名容器开头,然后是索引。多亏了"Request.Unvalidated“(也是在4.5中引入的),您可以将数据绑定到此数据,即使它不是由服务器端控件表示的。
发布于 2016-05-11 07:27:55
不幸的是,我不知道Web表单是如何做到这一点的,所以我不确定如何用中继器重现,但在MVC中,模型绑定器需要一个索引来重建列表。如果我不得不猜测这是如何在web表单中完成的,它将类似于:
<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表单的其余部分一起发布,这允许模型绑定器重建对象的有序列表。
我希望这是一些帮助,并原谅我的任何错误,因为我没有一个网络表单项目来测试这一点。
发布于 2021-06-28 12:30:07
我找到了一种在UpdateMethod中绑定object的集合属性的解决方法:
我的模型:
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; }
}控件:
<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>后面的代码:
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**:** 的集合我找到了什么
UpdateMethod中的自动模型绑定只适用于(1) 服务器控件;(2)具有简单类型的属性,如string或int...,而不是复杂类型,如collection或类对象。MVC对于这些复杂类型,你可以随心所欲地使用方法。但是,我认为这种方法可能需要以下条件:
- 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.在我的研究过程中,
BindItem、Eval或Container.ItemIndex (of Repeater)只适用于Textbox等Webform控件,或适用于runat=server的HTML元素。两者都将更改name属性以适应Webform模型绑定样式,因此您不能将它们用于MVC方法。i不能放在<%# %>中。它必须放在<%=%>中。正因为如此,我尝试了很多次,但都无法将Text构建为TextBox的BindItem.Enrollments[i].Id。很遗憾,我对此的希望破灭了。如果我在asp:button或<button>的onClick事件中绑定集合,那么MVC
collection放在HTML元素中,而不是服务器控件中,并将其命名为MVC style。这就是我对这个问题的发现。我在这上面花了不少时间,所以我想在这里发布这篇文章,以防有人想要尝试这种MVC方法。
感谢您阅读这篇长文=))。
https://stackoverflow.com/questions/21667583
复制相似问题