【ASP.NET Step by Step】之十六至二十三 Inserting, Updating, and Deleting Data
点击GridView的删除,事件触发过程
GridView控件提供了对行编辑和删除的内建的支持。配置一个GridView支持删除需要添加一个删除按钮列。当最终用户点击某一特定行的删除按钮时,引发一次回传并且GridView执行以下步骤:
1. 对ObjectDataSource的DeleteParameters赋值
2. 调用ObjectDataSource的Delete()方法,删除指定的记录
3. 通过调用它的Select()方法GridView重新绑定到ObjectDataSource
赋值到DeleteParameters的值是点击删除按钮这一行的DataKeyNames字段的值。因此正确地设置GridView的DataKeyNames属性是至关重要的。如果缺少了这个,DeleteParameters将在第1步被赋上一个null值,从而在第2步中将不会导致删除任何记录。
点击GridView的编辑,事件触发过程
当最终用户点击特定一行的编辑按钮时,引发一次回传并且GridView执行以下步骤:
2. 通过调用它的Select()方法,GridView重新绑定自己到ObjectDataSource
3. 与EditItemIndex相匹配的行呈现为编辑模式。在此模式下,Edit按钮替换为Update和Cancel按钮,并且那些ReadOnly属性为False的绑定列呈现为
TextBox服务器控件,这些TextBox的Text属性被赋值为相应的数据字段的值。
到这里HTML标记被返回到浏览器,允许最终用户可以修改行数据。当用户点击保存按钮,再次发生一次回传,并且GridView执行以下几个步骤:
1. ObjectDataSource的UpdateParameters的值被赋值为最终用户在GridView的编辑界面输入的值
2. 调用ObjectDataSource的Update()方法,更新指定的记录
3. 通过调用Select()方法,GridView重新绑定自己到ObjectDataSource
GridView的DataKeyNames属性指定的主键的值在第1步中赋值到UpdateParameters,反之非主键的值来自当前编辑行的TextBox服务器控件。如果DataKeyNames遗漏了,那么UpdateParameters主键的值在第1步中将被赋上一个空值,然后转入第2步中将不会导致任何记录的更新。
也可以这样说:基于GridView的DataKeyNames里的主键值和GridView里的值,组成ObjectDataSOurce的UpdateParameters
如图所示,更新数据,触发一连串的Pre-和Post-事件
注意:
我们创建的BLL层UpdateProduct方法是接受所有参数的,所以如果我们的GridView里绑定的列少了一些,
编辑的时候,这些没绑定的非只读列会被ObjectDataSource自做主张的置为NULL传给BLL层;
因此如果GridView少了ProductName,因为它是要求非空的,所以Update的时候就产生了异常。
所以,我们绑定几列,就应该在BLL层重载UpdateProduct(几列)方法。
例如,我们在BLL重载了带三个参数的UpdateProduct方法,并且指定ObjectDataSource控件的Update方法为三个参数的UpdatProduct
TypeName="ProductsBLL" UpdateMethod="UpdateProduct">
<UpdateParameters>
<asp:Parameter Name="productName" Type="String" />
<asp:Parameter Name="unitPrice" Type="Decimal" />
<asp:Parameter Name="productID" Type="Int32" />
</UpdateParameters>
</asp:ObjectDataSource>
这时,如果GridView是所有列,那么ObjectDataSource则会调用能接受这些参数的方法重载,
而不顾ObjectDataSource的声明标记指定只接受3个输入参数的事实。
而如果GridView含有4个列,则会在试图保存时引发一个异常
could not find a non-generic method 'UpdateProduct' that has parameters: ProductName, UnitPrice, ProductID, QuantityPerUnit.
注意Added by GridView
Pre级事件处理UnitPrice格式问题
UnitPrice的显示格式是{0:C}, 如果想让编辑时候也显示这个状态,要设置绑定列的ApplyFormatInEditMode属性为true,
这样设置后产生的新问题是当GridView尝试把用户提供的值赋值到ObjectDataSource的UpdateParameters集合,它无法把UnitPrice字符串“$
解决方法:
protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
if (e.NewValues["UnitPrice"] != null)
e.NewValues["UnitPrice"] = decimal.Parse(e.NewValues["UnitPrice"].ToString(), System.Globalization.NumberStyles.Currency);
else
{
// Show the Label
MustProvideUnitPriceMessage.Visible = true;
// Cancel the update
e.Cancel = true;
}
}
GridView的RowUpdating事件接受的第二个参数是一个GridViewUpdateEventArgs类型的对象,
它包含一个NewValues字典,当中的每一个属性保存着用户输入的值,
e.Cancel=true; 说明当用户忘记输入UnitPrice,则显示提示信息,并Cancel掉更新。
Bind() 和 Eval()
Code
<%# Bind("dataField") %> 双向绑定,
用户点击编辑按钮,Bind()绑定数据到模版(TextBox),
点击Update按钮,通过Bind()指定的数据字段的值被回传到ObjectDataSource的UpdateParameters
<%# Eval("dataField") %> 单向绑定,
仅仅在绑定数据到模版时取得数据字段的值,但并不会在回传时将用户输入的值返回到数据源控件的参数
Post级事件用于异常处理
1. 如果数据库不正常运作,则在试图连接数据库时通过TableAdapter抛出一个SqlException异常。
2. DAL层异常,遗漏了ProductName值则引发抛出一个NoNullAllowedException异常,因为ProdcutsRow类的ProductName属性设置了它的AllowDBNull属性为false(这句话好像有点问题)
3. BLL层异常,
数据库这样的设置 .NET在创建DataSet时做了什么呢?
.NET为我们做了:
private ProductsDataTable tableProducts;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public string ProductName {
get {
return ((string)(this[this.tableProducts.ProductNameColumn]));
}
set {
this[this.tableProducts.ProductNameColumn] = value;
}
}
//
}
public partial class ProductsDataTable : global::System.Data.TypedTableBase<ProductsRow> {
private global::System.Data.DataColumn columnProductName;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public ProductsDataTable() {
this.TableName = "Products";
this.BeginInit();
this.InitClass();
this.EndInit();
}
//
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::System.Data.DataColumn ProductNameColumn {
get {
return this.columnProductName;
}
}
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
private void InitClass() {
this.columnProductName.AllowDBNull = false;
this.columnProductName.MaxLength = 40;
//
}
够复杂吧
还是回到主题,没有任何的动作,这些异常都会从数据访问层冒出到业务逻辑层,然后到ASP.NET页面,最后到ASP.NET运行时
用户是不希望看到这样的东西的。人性化的方法是我们提前处理这些异常,给用户友好的界面。
ObjectDataSource 和数据Web控件的post级事件都提供了发现并不让它出现在ASP.NET运行时的方法
下面的代码实现了上述的目的:
2{
3 if (e.Exception != null)
4 {
5 // Display a user-friendly message
6 ExceptionDetails.Visible = true;
7 ExceptionDetails.Text = "There was a problem updating the product. ";
8
9 if (e.Exception.InnerException != null)
10 {
11 Exception inner = e.Exception.InnerException;
12
13 if (inner is System.Data.Common.DbException)
14 ExceptionDetails.Text += "Our database is currently experiencing problems. Please try again later.";
15 else if (inner is NoNullAllowedException)
16 ExceptionDetails.Text += "There are one or more required fields that are missing.";
17 else if (inner is ArgumentException)
18 {
19 string paramName = ((ArgumentException)inner).ParamName;
20 ExceptionDetails.Text += string.Concat("The ", paramName, " value is illegal.");
21 }
22 else if (inner is ApplicationException)ExceptionDetails.Text += inner.Message;
23 }
24
25 // Indicate that the exception has been handled
26 e.ExceptionHandled = true;
27
28 // Keep the row in edit mode
29 e.KeepInEditMode = true;
30 }
31}
32
可以看出来,如果UnitPrice小于0,产生的异常,在这里被捕获了
// UnitPrice小于0,抛出异常
public partial class Northwind
{
public partial class ProductsDataTable
{
public override void BeginInit()
{
this.ColumnChanging += ValidateColumn;
}
void ValidateColumn(object sender, DataColumnChangeEventArgs e)
{
if (e.Column.Equals(this.UnitPriceColumn))
{
if (!Convert.IsDBNull(e.ProposedValue) && (decimal)e.ProposedValue < 0)
{
throw new ArgumentException("UnitPrice cannot be less than zero", "UnitPrice");
}
}
else if (e.Column.Equals(this.UnitsInStockColumn) ||
e.Column.Equals(this.UnitsOnOrderColumn) ||
e.Column.Equals(this.ReorderLevelColumn))
{
if (!Convert.IsDBNull(e.ProposedValue) && (short)e.ProposedValue < 0)
{
throw new ArgumentException(string.Format("{0} cannot be less than zero", e.Column.ColumnName),e.Column.ColumnName);
}
}
}
}
}
BLL层面的异常也在这里被捕获
我们给UpdateProduct重载增加一个业务规则:禁止把UnitPrice字段的值设置为超过原来的两倍。为了实现这一点,调整UpdateProduct重载以使它可以执行这个检查并且在违反该规则时抛出一个ApplicationException异常。
// Make sure the price has not more than doubled
11 if (unitPrice != null && !product.IsUnitPriceNull())
12 if (unitPrice > product.UnitPrice * 2)
13 throw new ApplicationException("When updating a product price," + " the new price cannot exceed twice the original price.");
BLL引发的ApplicationException异常在GridView的RowUpdated事件处理程序中被侦测并处理。
验证控件
对比上面对UnitPrice<0的处理,抛出异常,在RowUpdated捕获;
一般更常用的是增加验证控件。
例如给ProductName的EditItemTemplate添加RequiredFieldValidator验证控件,
RequiredFieldValidator的ControlToValidate属性为EditProductName。最后,设置ErrorMessage属性为“You must provide the product’s name” 并将Text属性设置为“*”。
例如给UnitPrice 的EditItemTemplate模板增加CompareValidator验证控件
设置CompareValidator控件ControlToValidate属性为TextBoxID,Operator属性为GreaterThanEqual,ValueToCompare属性为 “0”, 并且Type属性为Currency,ErrorMessage属性为“The price must be greater than or equal to zero and cannot include the currency symbol”,
ASP.NET包含了一个总结控件ValidationSummary control,可以显示那些检测到无效数据的验证控件的ErrorMessage。以文本方式在页上某个位置概述错误结果,或者通过一个客户端消息框。
设置其ShowSummary属性为false并设置ShowMessageBox属性为true。不需要设置其他什么属性。这样,所有的验证错误都会显示在一个客户端消息框中。
对验证控件要分组
默认情况下,当postback发生时页面上所有的验证都会生效。显然,当编辑GridView记录时我们不希望DetailsView新增功能的验证起作用,
尴尬局面-当用户在编辑product时输入了有效数据,在点击更新时却由于新增功能中的name和price空白而产生验证错误。
例如将GridView中CommandField的ValidationGroup属性则指定为EditValidationControls(直接输入),GridView EditItemTemplate中可以指定的,ValidationGroup属性统一设置为EditValidationControls,验证控件的ValidationGroup属性设置为EditValidationControls。
验证组中的验证控件只在有相同ValidationGroup属性的按钮产生postback时才会进行有效性检测。
对于ValidationSummary控件也要改变
由于ValidationSummary控件也拥有ValidationGroup属性并且只显示来自于同一验证组中验证控件的信息。因此,我们需要使用两个验证控件,分别作为InsertValidationControls验证组和EditValidationControls验证组:
ShowSummary="False" ValidationGroup="EditValidationControls" />
<asp:ValidationSummary ID="ValidationSummary2" runat="server" ShowMessageBox="True"
ShowSummary="False" ValidationGroup="InsertValidationControls" />
SupplierName列改为DropDownList
只要将其转化为模板,把EditTemplateItem改为DropDownList,设置绑定的数据源,绑定字段。
Product表中的CategoryID 和 SupplierID列允许为NULL,而编辑模板中的下拉列表却没有NULL这一项。所以存在下面两种问题:
1. 用户无法则现在的界面中将某个product非空的category或supplier设置为NULL
2. 如果产品的CategoryID 或 SupplierID为NULL,在点击Edit按钮时程序会抛出异常。这是因为Bind()表达式中CategoryID(或SupplierID)返回NULL值时,SelectedValue无法找到NULL这一列表项因而抛出异常。
为了支持CategoryID 和 SupplierID的NULL值,需要为两个DropDownList增加一个NULL值选项。方法是将DropDownList的AppendDataBoundItems属性设置为true并手动增加一个值为空字符串的列表项。在ASP.NET的数据绑定逻辑中,空字符串将自动转换为NULL,NULL值也可以转为空字符串。因此,先元素标记大致如下:
<asp:ListItem Value="">(None)</asp:ListItem>
</asp:DropDownList>
开放式并发
见此处
删除操作的客户端确认
JavaScript的confirm(string)方法
在一个模式窗口中显示那些作为string参数传进来的文本,这个窗口将会显示两个按钮-确定(OK)和取消(Cancel)。
根据点击不同的按钮来返回一个布尔类型值。(如果点击了OK,返回true;如果点击Cancel,返回false)
ASP.NET 2.0为为Button,LinkButton,ImageButton新引入的OnClientClick这个属性,可以通过它为客户端单击事件添加客户端脚本。
如果onClinetClick=“True"则执行单击,false则放弃。
OnClientClick="return confirm('Are you certain that you want to delete this product?');">
</asp:LinkButton>
GridView或者DetailsView上内置的一些删除按钮,怎么处理呢?
方法1. 把他们转化为Template,就变成了按钮,照上面做
方法2. DataBound事件中进行绑定,而且还可以访问字段,增加ProducName
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
// reference the Delete LinkButton
LinkButton db = (LinkButton)e.Row.Cells[0].Controls[0];
// Get information about the product bound to the row
Northwind.ProductsRow product = (Northwind.ProductsRow) ((System.Data.DataRowView) e.Row.DataItem).Row;
db.OnClientClick = string.Format("return confirm('Are you certain you want to delete the {0} product?');",
product.ProductName.Replace("'", @"\'"));
}
}
几点注意:
1. 如果有Select,Delete,则用e.Row.Cells[0].Contorls[2],
因为在删除按钮前面有两个控件,一个是编辑按钮,另一个是LiteralControl,用来隔离编辑按钮和删除按钮。
2. 这里用Index访问控件,不变之处是 如果有人增加了Select,Edit,或是改变了GridView的按钮类型,编译没问题,点击就会异常;而且修改起来很麻烦;
所以常常把他们转为Template,变为真的按钮。
3. 为什么不用product.ProductName
因为可能有产品名称为Duck's bag,这样程序产生运行时错误: error:Expected')' [拼”return confirm('...‘);"时候产生错误]
所以把所有产品名中的' 转为 \'
所以最好的做法是:
1. 把GridView的Delete转为Template,设置Delete按钮的ID为“DeleteBtn”
2. RowDataBound事件
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
LinkButton db = (LinkButton)e.Row.FindControl("DeleteBtn");
Northwind.ProductsRow product = (Northwind.ProductsRow)((System.Data.DataRowView)e.Row.DataItem).Row;
db.OnClientClick = string.Format("return confirm('Are you certain you want to delete the {0} product?');",
product.ProductName.Replace("'",@"\'"));
}
}
根据登录用户而定制不同的数据修改界面
1. 登陆用户不同,绑定不同的DataSouce实现不同的界面
2. 根据某项Data,设置访问权限,例如被废弃的产品将是不可编辑的,
Northwind.ProductsRow product = (Northwind.ProductsRow)((System.Data.DataRowView)e.Row.DataItem).Row;
if (product.Discontinued)
{ LinkButton editbutton = (LinkButton)e.Row.FindControl("EditLinkButton");
editbutton.Visible = false;}
出处:http://www.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。