ASP.NET WebForms:GridView 里的 DropDownList 绑定到不存在的值时,如何统一显示空白
Copyright Notice: This article is an original work licensed under the CC 4.0 BY-NC-ND license.
If you wish to repost this article, please include the original source link and this copyright notice.
Source link: https://v2know.com/article/1315
最近在处理 ASP.NET WebForms 的 GridView 时,遇到了一个很典型的问题:
GridView 里的 DropDownList 是通过 SqlDataSource 绑定选项的,但数据库里传进来的值,未必一定存在于当前 DropDownList.Items 里面。
比如:
数据库值:999
DropDownList 选项:001, 002, 003
这时候如果直接写:
SelectedValue='<%# Bind("XXX") %>'
WebForms 很容易直接报错:
'ddlXXX' has a SelectedValue which is invalid because it does not exist in the list of items.
WinForms 和 WebForms 的差异
在 WinForms 里,如果绑定到了不存在的选项,很多情况下画面上会自动显示为空白。
但 ASP.NET WebForms 的 DropDownList 没有这种自动兜底机制。
WebForms 的逻辑比较严格:
SelectedValue 必须存在于 Items 中
如果不存在,就会抛异常。
所以不能期待它像 WinForms 一样“找不到就自动显示空白”。
不推荐的写法
下面这种写法虽然方便,但风险比较高:
<asp:DropDownList ID="ddlXXX" runat="server"
DataSourceID="sdsXXX"
DataTextField="Name"
DataValueField="Code"
SelectedValue='<%# Bind("XXX") %>'>
</asp:DropDownList>
问题在于,Bind("XXX") 会在绑定过程中直接设置 SelectedValue。
如果这个值不存在于 Items 里,就可能在你做任何补救处理之前先报错。
推荐做法:统一在 DataBound 里安全设置
比较稳的做法是:
不要在 aspx 里直接写 SelectedValue='<%# Bind(...) %>'
而是在 DropDownList 的 DataBound 事件里统一判断
也就是先让 DropDownList 完成自己的选项绑定,然后再判断数据库里的值是否存在。
如果存在,就正常选中。
如果不存在,就选中空白项。
共通方法
可以先准备一个共通方法:
Protected Sub SafeSetDropDownValue(ddl As DropDownList, value As Object)
Dim v As String = If(value Is Nothing OrElse value Is DBNull.Value, "", value.ToString())
' 值为空时,显示空白
If String.IsNullOrEmpty(v) Then
If ddl.Items.FindByValue("") Is Nothing Then
ddl.Items.Insert(0, New ListItem("", ""))
End If
ddl.SelectedValue = ""
Return
End If
' 值存在于 DropDownList 中时,正常选中
If ddl.Items.FindByValue(v) IsNot Nothing Then
ddl.SelectedValue = v
Else
' 值不存在时,显示空白
If ddl.Items.FindByValue("") Is Nothing Then
ddl.Items.Insert(0, New ListItem("", ""))
End If
ddl.SelectedValue = ""
End If
End Sub
然后在各个 DropDownList 的 DataBound 事件里调用:
Protected Sub ddlXXX_DataBound(sender As Object, e As EventArgs)
Dim ddl As DropDownList = CType(sender, DropDownList)
Dim row As GridViewRow = CType(ddl.NamingContainer, GridViewRow)
Dim value As Object = DataBinder.Eval(row.DataItem, "XXX")
SafeSetDropDownValue(ddl, value)
End Sub
aspx 侧可以这样写:
<asp:DropDownList ID="ddlXXX" runat="server"
DataSourceID="sdsXXX"
DataTextField="Name"
DataValueField="Code"
AppendDataBoundItems="True"
OnDataBound="ddlXXX_DataBound">
<asp:ListItem Text="" Value=""></asp:ListItem>
</asp:DropDownList>
这样处理后的效果
数据库值存在于选项中:
数据库值:001
选项:001, 002, 003
结果:显示 001
数据库值不存在于选项中:
数据库值:999
选项:001, 002, 003
结果:显示空白
数据库值本来就是空:
数据库值:NULL / ""
结果:显示空白
为什么不建议继续用 Bind
如果坚持使用:
SelectedValue='<%# Bind("XXX") %>'
理论上也可以通过 DataBinding 事件提前插入一个项目来避免报错。
例如:
Protected Sub ddlXXX_DataBinding(sender As Object, e As EventArgs)
Dim ddl As DropDownList = CType(sender, DropDownList)
Dim row As GridViewRow = CType(ddl.NamingContainer, GridViewRow)
Dim value As String = Convert.ToString(DataBinder.Eval(row.DataItem, "XXX"))
If Not String.IsNullOrEmpty(value) Then
If ddl.Items.FindByValue(value) Is Nothing Then
ddl.Items.Insert(0, New ListItem("", value))
End If
End If
End Sub
但这个做法有一个问题:
Text = ""
Value = "999"
也就是说,画面上虽然显示空白,但实际选中的值仍然是 999。
如果用户不修改这个下拉框,更新时这个旧值可能又会被提交回去。
所以如果目标是“值不存在时显示空白,并且视为未选择”,那么更推荐取消 SelectedValue='<%# Bind(...) %>',改成在 DataBound 里自己控制。
总结
ASP.NET WebForms 的 DropDownList 不像 WinForms 那样会自动容错。
在 GridView 里使用 DropDownList 时,如果数据库值不一定存在于选项列表中,最稳的处理方式是:
取消 aspx 里的 SelectedValue='<%# Bind(...) %>'
在 DropDownList 的 DataBound 事件中使用 Items.FindByValue 判断
存在就选中,不存在就显示空白
这样可以一次性解决 GridView 内多个 DropDownList 绑定到不存在选项时报错的问题,也能避免因为旧数据、权限过滤、选项变更等原因导致页面直接崩掉。
This article was last edited at