ASP.NET WebForms 4.8 FormView绑定含自定义列表模型的问题
ASP.NET WebForms 4.8 FormView列表属性绑定问题及解决方案
问题描述
在ASP.NET WebForms 4.8项目中使用FormView双向模型绑定,标量属性和枚举类型绑定正常,但包含自定义类型列表的属性存在三类问题:
- 问题1:SelectMethod返回的模型包含
List<自定义类>属性,Repeater可在EditView中正常渲染(自定义类已标记[Serializable]),但UpdateMethod中该属性始终为null; - 问题2:已创建添加列表项的按钮,但后台方法无法引用当前编辑的模型实例来添加新项;
- 问题3:添加项后,无法重新渲染Repeater显示新项,且希望保留模型其他未保存的更改。
示例代码
ASPX页面
<%@ Page Title="Home Page" Language="C#" AutoEventWireup="true" Inherits="ModelExample._Default" CodeBehind="~/Default.aspx.cs" %> <html> <head></head> <body> <form runat="server" id="MainForm"> <asp:FormView ID="frmVw" runat="server" DefaultMode="Edit" EnableModelValidation="true" ItemType="Models.PetModel" DataKeyNames="PetId" SelectMethod="frmVw_GetItem" UpdateMethod="frmVw_UpdateItem"> <EmptyDataTemplate> No pet found, try adding ?PetId=11 as a query string parameter. </EmptyDataTemplate> <EditItemTemplate> <div id="EditPetDetails"> <div><h2>Pet Details:</h2></div> <div> <asp:Label runat="server" ID="lblPetName" Text="Name"></asp:Label> <asp:TextBox runat="server" ID="tbPetName" Text="<%# BindItem.Name %>"></asp:TextBox> </div> <div> <asp:Label runat="server" ID="lblDob" Text="Date of Birth"></asp:Label> <asp:TextBox runat="server" ID="tbDob" Text='<%# BindItem.DateOfBirth %>' TextMode="Date"></asp:TextBox> </div> <div> <asp:Label runat="server" ID="lblSpecies" Text="Species"></asp:Label> <asp:DropDownList runat="server" ID="ddlSpecies" SelectMethod="ddlSpecies_Get" SelectedValue='<%# BindItem.Species %>'></asp:DropDownList> </div> <div> <asp:Label runat="server" ID="lblWeight" Text="Weight (kg)"></asp:Label> <asp:TextBox runat="server" ID="tbWeight" Text="<%# BindItem.Weight %>" TextMode="Number"></asp:TextBox> </div> <div> <asp:Button runat="server" ID="btnSave" Text="Save" CommandName="Update" /> </div> </div> <hr /> <div id="AddNotes"> <div> <h2>Add Note:</h2> </div> <div> <asp:Label runat="server" ID="lblNewNoteType" Text="Type"></asp:Label> <asp:DropDownList runat="server" ID="ddlNewNoteType" SelectMethod="NoteType_Get"></asp:DropDownList> </div> <div> <asp:TextBox runat="server" ID="tbNewNoteBody" TextMode="MultiLine" Rows="5" Columns="50"></asp:TextBox> </div> <div> <asp:Button runat="server" ID="btnAddNote" Text="Add Note" OnClick="btnAddNote_Click" /> </div> </div> <hr /> <div id="NotesHistory"> <h2>Notes Histroy:</h2> <asp:Repeater runat="server" ID="rptNotes" ItemType="Models.VisitNote" DataSource="<%# BindItem.VisitNotes %>"> <ItemTemplate> <div> <asp:Label runat="server" ID="lblNoteCreatedOn" Text="Date:"></asp:Label> <asp:TextBox runat="server" ID="tbNoteCreatedOne" Text="<%# Item.CreatedOn %>" TextMode="Date" ReadOnly="true"></asp:TextBox> </div> <div> <asp:Label runat="server" ID="lblNoteCreatedBy" Text="Author:"></asp:Label> <asp:Label runat="server" ID="tbNoteCreatedBy" Text="<%# Item.CreatedBy %>"></asp:Label> </div> <div> <asp:Label runat="server" ID="lblNoteType" Text="Type:"></asp:Label> <asp:Label runat="server" ID="tbNoteType" Text="<%# Item.NoteType %>"></asp:Label> </div> <div> <asp:TextBox runat="server" ID="tbNoteBody" Text="<%# Item.NoteBody %>" ReadOnly="true" TextMode="MultiLine" Rows="5" Columns="50"></asp:TextBox> </div> </ItemTemplate> </asp:Repeater> </div> </EditItemTemplate> </asp:FormView> </form> </body> </html>
后台代码
using Models; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.ModelBinding; using System.Web.UI; using System.Web.UI.WebControls; namespace ModelExample { public partial class _Default : Page { private static List<PetModel> _pets = new List<PetModel>(); protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack && !_pets.Any()) { var notesCollection = new List<Models.VisitNote>() { new Models.VisitNote() { NoteId = 11, CreatedBy = "Jeanann Sutherland", CreatedOn = "2021-10-31", NoteType = Models.NoteTypes.Intake, NoteBody = "Pekoe has presented with sneezing, enflamed nose, nasal drainage and abnormaly clingy demeanor." } }; _pets.Add(new Models.PetModel() { PetId = 11, DateOfBirth = "2014-07-24", Name = "Pekoe", Species = Models.PetSpecies.Cat, Weight = 12.3f, VisitNotes = notesCollection }); } } public IEnumerable<string> ddlSpecies_Get() { return Enum.GetNames(typeof(Models.PetSpecies)).ToList<string>(); } public IEnumerable<string> NoteType_Get() { return Enum.GetNames(typeof(Models.NoteTypes)).ToList<string>(); } public Models.PetModel frmVw_GetItem([QueryString] int? PetId) { return _pets.FirstOrDefault(p=> p.PetId == PetId); } public void frmVw_UpdateItem(int? PetId) { Models.PetModel item = _pets.FirstOrDefault(p=> p.PetId == PetId); if (item == null) { ModelState.AddModelError("", String.Format("Item with id {0} was not found", PetId)); return; } var existingNotes = item.VisitNotes; TryUpdateModel(item); item.VisitNotes = existingNotes; var notesCount = item.VisitNotes?.Count() ?? 0; } protected void btnAddNote_Click(object sender, EventArgs e) { int petId = Convert.ToInt32(frmVw.DataKeys[0].Value); var pet = _pets.FirstOrDefault(p => p.PetId == petId); if (pet == null) return; var ddlNewNoteType = frmVw.FindControl("ddlNewNoteType") as DropDownList; var tbNewNoteBody = frmVw.FindControl("tbNewNoteBody") as TextBox; if (ddlNewNoteType == null || tbNewNoteBody == null) return; var author = "Dr P Sorthes"; NoteTypes noteType = Enum.Parse<NoteTypes>(ddlNewNoteType.SelectedValue); string body = tbNewNoteBody.Text.Trim(); pet.VisitNotes.Add(new VisitNote() { CreatedBy = author, NoteType = noteType, CreatedOn = DateTime.UtcNow.ToString("yyyy-MM-dd"), NoteBody = body }); UpdateModel(pet, new FormValueProvider(ModelBindingExecutionContext)); frmVw.DataBind(); } } } namespace Models { public class PetModel { private DateTime? dateOfBirth; public PetModel() { VisitNotes = new List<VisitNote>(); } public int PetId { get; set; } public string Name { get; set; } public string DateOfBirth { get { return dateOfBirth?.ToString("yyyy-MM-dd") ?? ""; } set { dateOfBirth = DateTime.Parse(value); } } public PetSpecies Species { get; set; } public Single Weight { get; set; } public List<VisitNote> VisitNotes { get; set; } } [Serializable] public class VisitNote { private DateTime? createdOn; public int NoteId { get; set; } public String CreatedOn { get { return createdOn?.ToString("yyyy-MM-dd") ?? ""; } set { createdOn = DateTime.Parse(value); } } public String CreatedBy { get; set; } public NoteTypes NoteType { get; set; } public String NoteBody { get; set; } } [Serializable] public enum NoteTypes { Intake, Diagnosis, Prescription, Followup } [Serializable] public enum PetSpecies { Dog, Cat, Bird, Fish, Snake } }
解决方案说明
问题1:UpdateMethod中List属性为null
- 初始化集合:在
PetModel构造函数中初始化VisitNotes集合,避免默认null值; - 保留原有集合:在Update方法中先保存原有集合,执行
TryUpdateModel后再恢复集合,防止被模型绑定覆盖为null。
问题2:获取当前模型实例及控件值
- 通过DataKeys获取当前ID:利用FormView的
DataKeys属性获取当前编辑的PetId,再从数据源中获取对应模型; - FindControl获取控件值:通过FormView的
FindControl方法定位新增笔记的表单控件,读取输入值。
问题3:重新渲染Repeater并保留未保存更改
- 同步表单值到模型:调用
UpdateModel将当前表单的未保存输入同步到模型; - 重新绑定FormView:执行
frmVw.DataBind()刷新整个FormView,Repeater会自动加载更新后的列表数据; - 避免重复初始化数据:修改
Page_Load,仅在首次加载时初始化测试数据,回发时跳过,防止覆盖已修改的数据。
内容的提问来源于stack exchange,提问作者DCAggie
相关产品推荐
相关产品推荐

