[英]ASP.NET Web Forms 4.5 model binding where the model contains a collection
我正在嘗試更新舊的 Web 窗體應用程序以使用 4.5 中添加的新模型綁定功能,類似於 MVC 綁定功能。
我在制作一個可編輯的 FormView 時遇到了麻煩,該 FormView 呈現一個包含簡單成員的單個模型以及一個作為其他模型集合的成員。 我需要用戶能夠編輯父對象的簡單屬性和子集合的屬性。
問題在於,當代碼嘗試更新模型時,模型綁定后ProductChoice.Extras
( 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
但沒有任何區別,在UpdateProduct
方法中未綁定Extras
集合。
深入研究 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 + "]");
}
不幸的是,您的中繼器的元素名稱不符合該標准。
雖然肯定是非正統的,但您仍然可以通過編寫非服務器文本框來實現這一點,然后給它們一個名稱,以您的數據列表命名容器開頭,后跟索引。 並且感謝“Request.Unvalidated”(也在 4.5 中引入),即使它沒有由服務器端控件表示,您也可以數據綁定到此數據。
不幸的是,我不知道如何使用 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 循環替換了轉發器,該循環使用用於訪問每個 Extra 的索引遍歷集合。 這類似於我在 ASP.NET MVC 中需要做的事情。 提交表單時,索引與 Web 表單的其余部分一起發布,這允許模型綁定器重建對象的有序列表。
我希望這是一些幫助並原諒我的任何錯誤,因為我目前沒有一個網絡表單項目來測試這個。
我找到了一種解決方法來綁定 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 控件有自己的name
屬性設計,旨在適應其自己的模型綁定,MVC 不接受該模型綁定。 這就是您不能設置name
屬性的原因,如果您嘗試使用 Jquery/JavaScript/Code-Behind 更改它,Webform Server 控件的模型綁定將不起作用。name
來綁定。 因此它不會綁定 ASP 服務器控件樣式name
(如$Maincontent$...
東西)。在我的研究中其他不太相關的事情:
BindItem
或Eval
或Container.ItemIndex
(來自Repeater
)僅適用於 Webform 控件,如Textbox
或帶有runat=server
HTML 元素。 兩者都會更改name
屬性以適應 Webform 模型綁定樣式,因此您不能將這些用於 MVC 方法。
在@JonathanWalter 示例中,計數器i
不能放在<%# %>
。 它必須放在<%=%>
。 因此,我嘗試了很多次,但無法將Text
構造為BindItem.Enrollments[i].Id
for TextBox
。 所以我對此的希望落空了,很遺憾。
如果我將集合綁定在asp:button
或<button>
onClick
事件中,MVC 方法也有效。 只需確保將collection
放在 HTML 元素中,而不是服務器控件中,並將其命名為 MVC 樣式。
所以這是我對這個問題的發現。 我花了很多時間在這上面,所以我想把這個貼在這里,以防有人想嘗試這種 MVC 方法。
感謝您閱讀這篇長篇文章 =))。
[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;}
}
您應該在內部數據列表中指定一個編輯項目模板,因為項目模板中的屬性可能不會在自動構造並傳遞給 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>
在查詢字符串中傳遞附加值,在更新方法中添加參數值。 使用 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
});
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.