我正在尝试更新旧的Web窗体应用程序,以使用4.5中添加的新模型绑定功能,类似于MVC绑定功能。
我无法制作一个可编辑的FormView,它提供了一个包含简单成员的模型以及一个其他模型集合的成员。我需要用户能够编辑父对象的简单属性和子集合的属性。
问题是当代码尝试更新模型时,子模型绑定后,子集合(ProductChoice.Extras
)始终为null。
以下是我的模特:
[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
方法。
答案 0 :(得分:0)
不幸的是我不确切知道如何使用Web Forms完成这一操作,因此我不确定如何使用转发器重现这一点,但在MVC中,模型绑定器需要索引来重建列表。如果我不得不猜测这是如何在网络表单中完成的,那将是类似的:
<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循环替换了转发器,该循环使用用于访问每个Extra的索引遍历集合。这类似于我需要在ASP.NET MVC中执行您想要的操作。提交表单时,索引将与Web表单的其余部分一起发布,这允许模型绑定器重建有序的对象列表。
我希望这是一些帮助并原谅我的任何错误,因为我目前没有一个Web表单项目来测试它。
答案 1 :(得分:0)
深入研究System.Web.ModelBinding,发现CollectionModelBinder期望传递给FormValueProvider的值与MVC的格式相同,即:MyCollection [i]
public static string CreateIndexModelName(string parentName, string index)
{
if (parentName.Length != 0)
{
return (parentName + "[" + index + "]");
}
return ("[" + index + "]");
}
不幸的是,您的转发器的元素名称与该条件不符。
虽然肯定是非正统的,但你仍然可以通过编写非服务器文本框来实现这一点,然后从你的datalist命名容器开始给它们一个名称,然后是索引。并且感谢&#34; Request.Unvalidated&#34; (也在4.5中介绍),即使服务器端控件没有表示数据,你也可以对这些数据进行数据绑定。
答案 2 :(得分:0)
我找到了一种解决方法来绑定 UpdateMethod 中对象的集合属性:
我的模型:
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 之前,Webforms 4.5 模型绑定将绑定到 student
对象。请注意,Enrollments
为空。
创建IValueProvider provider
并将其传递给TryUpdateMethod
后,Enrollments
的计数为3
(等于表单中提交的数据)。
更多详情:
关于绑定到 edit
的集合的发现:
在您的问题中,您无法将数据绑定为 MVC 的原因是 UpdateMethod
中的自动模型绑定仅适用于 (1) ASP 服务器控件; (2) 具有简单类型的属性,如 string
或 int
...,而不是复杂类型,如 collection
或类对象。
对于这些复杂类型,您可以按照自己的意愿使用 MVC 方法。但是,我认为这种方法可能需要以下条件:
runat=server
),因为需要 name
属性(例如 <input>
或 <select>
)。你明白我的意思吗? Webform Server Controls 有自己的 name
属性设计,旨在适应它自己的模型绑定,MVC 不接受这种绑定。这就是您无法设置 name
属性的原因,如果您尝试使用 Jquery/JavaScript/Code-Behind 更改它,Webform Server 控件的模型绑定将不起作用。name
进行绑定。所以它不会绑定 ASP 服务器控件样式 name
(比如 $Maincontent$...
的东西)。我研究期间的其他不太相关的事情:
BindItem
或 Eval
或 Container.ItemIndex
(Repeater
)仅适用于像 Textbox
这样的网络表单控件,或带有 {{ 1}}。两者都会更改 runat=server
属性以适应 Webform 模型绑定样式,因此您不能将这些用于 MVC 方法。
在@JonathanWalter 示例中,计数器 name
不能放置在 i
内。它必须放在 <%# %>
内。因此,我尝试了很多次,但无法将 <%=%>
构造为 Text
的 BindItem.Enrollments[i].Id
。所以很遗憾,我对此的希望落空了。
如果我将集合绑定在 TextBox
或 onClick
的 asp:button
事件中,MVC 方法也有效。只需确保将 <button>
放在 HTML 元素中,而不是服务器控件中,并将其命名为 MVC 样式。
这是我对这个问题的发现。我花了很多时间在这上面,所以我想把这个贴在这里,以防有人想尝试这种 MVC 方法。
感谢您阅读这篇长篇文章 =))。
答案 3 :(得分:-1)
[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 :(得分:-1)
您应该在内部数据列表中指定编辑项模板,因为项目模板中的属性可能不会在自动构造并传递给Update方法的模型中返回。 我没有时间尝试,但这应该有用......
<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 :(得分:-2)
在查询字符串中传递extras值,在update方法中添加参数值。使用HTML 5上的数据属性将这3个值存储在“表单视图”元素
中例如
UpdateMethod="UpdateProduct/104/coke/2"
或
UpdateMethod="UpdateProduct/?ExtraProductId=104&ExtraName=coke&ExtraQuantity=2"
对于第一种方法,您必须在Route Config中编写路由规则。
在用户控件内部,如下所示
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
});
}
}