上周我们说,不使用绑定控件,依然可以更加简单直观的用代码的方式展示数据。这周我们继续这个话题,看看在不使用控件,尤其是不使用控件的回发事件的情况下如何向服务器提交数据。所谓向服务器提交数据,就是通常所说的增加、删除和修改数据的功能。
通过分析微软绑定控件的方法属性等不难发现其实现机制,概括的说,就是利用隐藏域传递了一些信息给服务器,服务器再根据这些信息产生回发事件。回发事件本身及其参数、以及其他的页面状态数据都是通过隐藏域的方式来传递的。
显然,微软的这些实现手段本身没有什么不好,我们不使用WebForm的回发事件(尤其是以Button的Command事件为基础的事件)的原因,也并不是我们有不使用隐藏域的更好的手段,实际上我们的实现手段和微软是一样的!那么,为什么要自己来实现呢?
原因是,我们使用微软的封装,完全不能带来微软广告中所宣称的那些好处!不能减少代码的书写,不能从根本上屏蔽Html的相关技术点,反而还要学习控件的种种大量的所谓技巧,而且很多时候这些技巧还解决不了问题,这时还是得了解Html的底层机制并且在控件的基础上扩展所需要的功能,事情到了这一步,已经不仅仅是掌握html的技术,我们还被迫去研究微软这些控件的实现层面的东东,才能“玩转”这些控件!更可悲的是,我们种种学习上的付出没有什么好的结果:如果你不是天天用这些控件,你始终面临学了忘、忘了学、用的时候还不免时时刻刻查查文档的问题,最后做出来的由于控件自身的技术局限,性能还必然很糟糕(既浪费CPU,又浪费带宽)!我还可说更多的不足之处,没那么多时间敲键盘,算了,不说了!下面从理论上概括一句,说明导致这种局面的原因:
第一是:WebApplication(包括WebBrowser的html、js和WebServer)的逻辑模型GUI Application的逻辑模型之间不存在合适的映射,第二是:从根本上讲也不存在要把WebApplication映射成GUI编程模型的需求!这两句话展开来讲,足够写一本书了!大家可以琢磨琢磨。
上面这段文字是对本周日讲座内容的解释。具体来说,本周的代码案例是“纯手工打造”增删改数据的apx页面!
1. 使用Command事件
从最早aspx1.0的DataGrid,一直到2.0的GridView,再到3.0才出的的ListView,以及各自同期推出的数据绑定控件,在实现增删改的内置功能时,都是直接或间接地用了基于Command事件的技术。Command事件是ICommandControl的成员,在WebControls中Button、LinkButton和ImageButton都实现了这个接口。Command事件和Click事件之间的不同之处在于,Command事件带了两个事件参数,分别是CommandName和CommandArgument,作为CommandEvents 对象的两个属性传递给事件处理程序。而这两个事件参数对应Button的两个属性。
代码示例:
1: <script runat="server">
2: string actionMessage = "";
3: void Button1_Command(object sender, CommandEventArgs e)
4: {
5: actionMessage = string.Format(
6: "CommandName is {0} and CommandAgrument is {1}.",
7: e.CommandName, e.CommandArgument);
8: }
9: </script>
10:
11: <html xmlns="http://www.w3.org/1999/xhtml">
12: <head runat="server">
13: <title></title>
14: </head>
15: <body>
16: <form id="form1" runat="server">
17: <div>
18: <asp:Button ID="Button1" runat="server" Text="CommandButton"
19: CommandName="Action"
20: CommandArgument="ActionArgument"
21: OnCommand="Button1_Command" />
22: <%= actionMessage %>
23: </div>
24: </form>
25: </body>
26: </html>
在这个代码中,
1)18~21行演示了Command事件的使用方法,19行指定行为(即命令名称),20行指定和行为相关参数,21行注册事件处理程序。
2)2~8行为事件处理程序,作为示例,该处理程序只是简单把命令名称和参数赋值给页面的actionMessage变量。
3)22行的作用是在页面Render的时候输出actionMessage字符串。
运行结果为:
2. Comand的实现原理和特点
观察页面的源代码,我们会发现其中有如下的内容:
1: <div class="aspNetHidden">
2: <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTgxNDY3ODMzMmRkp+mt437A/3jfOxcBtvvBdZO1mILJP5z/8kw9J+kdLxc=" />
3: </div>
4: <div class="aspNetHidden">
5: <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWAgLRmsTzBwKM54rGBkfMpIUfIPHTTCSwcEfsHmw4T5H1Qg0aGd5Koe0sfZMX" />
6: </div>
7: <div>
8: <input type="submit" name="Button1" value="CommandButton" id="Button1" />
9: CommandName is Action and CommandAgrument is ActionArgument.
10: </div>
3. 手工实现数据的增删改功能
我们将要实现的程序功能是显示并并编辑学员通讯录数据,截图如下:
点击删除可以删除一行地址数据,点击添加可以增加在文本框中输入的地址数据,点击编辑,则界面切换为编辑状态,在此状态下没有添加功能,但是具有对已有功能修改的功能,录入数据后按修改生效,按取消则返回上一层。
页面的html如下:
1: <form id="form1" runat="server">
2: <input type="hidden" id="POST_ACTION_NAME" name="POST_ACTION_NAME" />
3: <input type="hidden" id="POST_ACTION_ARGUMENT" name="POST_ACTION_ARGUMENT" />
4: <div>
5: <table class="address">
6: <tr>
7: <th>学号</th>
8: <th>姓名</th>
9: <th>电话</th>
10: <th>邮件</th>
11: <th></th>
12: </tr>
13: <% foreach (Student s in list.Values){ %>
14: <%if (s.Code == editCode){ %>
15: <tr>
16: <td><input id="code" name="code" /></td>
17: <td>姓:<input id="lastName" name="lastName" /><br />名:<input id="firstName" name="firstName" /></td>
18: <td><input id="mobile" name="mobile" /></td>
19: <td><input id="email" name="email" class="email" /></td>
20: <td><a href="#" onclick='actionPost("update","<%= s.Code %>");' >修改</a> | <a href="#" onclick='actionPost("cancel","");' >取消</a></td>
21: </tr>
22: <%}else{ %>
23: <tr>
24: <td><%= s.Code %></td>
25: <td><%= s.LastName + " " + s.FirstName %></td>
26: <td><%= s.Mobile %></td>
27: <td><%= s.Email %></td>
28: <td><a href="#" onclick='actionPost("edit","<%= s.Code %>");' >编辑</a> | <a href="#" onclick='actionPost("delete","<%= s.Code %>");' >删除</a></td>
29: </tr>
30: <%} %>
31: <%} %>
32: <%if (string.IsNullOrWhiteSpace(editCode)){ %>
33: <tr>
34: <td><input id="code" name="code" /></td>
35: <td>姓:<input id="lastName" name="lastName" /><br />名:<input id="firstName" name="firstName" /></td>
36: <td><input id="mobile" name="mobile" /></td>
37: <td><input id="email" name="email" class="email" /></td>
38: <td><a href="#" onclick='actionPost("insert","");' >添加</a></td>
39: </tr>
40: <%} %>
41: </table>
42: </div>
43: </form>
在此代码中,20、28和38行中使用名为actionPost的脚本函数发出编辑数据的请求,actionPost函数具有两个参数,第一个相当于CommandName,第二个相当于CommandArgument。actionPost的实现方法是把这两个参数的值分别赋值给POST_ACTION_NAME和POST_ACTION_ARGUMENT这两个隐藏域。actionPost的代码如下:
1: function actionPost(action, arg) {
2: var frm = document.forms["form1"];
3: if (!frm) {
4: frm = document.form1;
5: }
6: frm.POST_ACTION_NAME.value = action;
7: frm.POST_ACTION_ARGUMENT.value = arg;
8: frm.submit();
Student类模拟后台操作数据库数据,其中用静态方法提供了增删改数据的功能。在aspx页面的Load中,只需要根据请求的POST_ACTION_NAME,分别执行相应的数据处理就能完成我们所需要的功能。这部分的代码不在这里一一列出,大家可以下载源代码来阅读。
4. 技术选择的优缺比较
完成本例中这样简单的功能,如果使用GridView之类的控件,大家可能会觉得更简单,甚至可以什么都不用知道,在集成环境中拖拖控件、再在属性框中设设属性就能搞定了。而使用本例中展示的方法需要写一些包括javascript在内的代码,似乎很复杂了许多。
问题的关键是,实际的软件开发中,功能需求往往没有这么简单,在碰到复杂的需求时,使用“数据源控件+数据绑定控件”的技术线路,必然要进一步专研控件的更多功能细节,另外在使用比如ObjectDataSource时,还必须按约定编写特定的业务方法,而且默认的数据传递机制往往不能奏效,还得写相关的事件程序维护参数。
而自己直接使用actionPost的方法,非常直接了当,不管要完成的业务功能复杂还是简单,非业务所需要的结构性代码就这么多了。在实际的应用中可以根据需要把actionPost改写成更通用的函数,放到公共js库中中使用。
5. 使用Ajax的技术实现增删改的功能
我倾向于增删改这样的功能应该使用Ajax技术来实现,ServerPage技术(包括Asp, Asp.Net以及JSP等)不适合做这样的工作,ServerPage更适合于数据的展示。
6. 关于下周我们要讨论的主题
在今天的实例中的aspx文件中的html源代码使用了循环语句来直接产生输出,这不是必须的,我们也可以使用Repeater来完成同样的功能,参见源代码文件RepeaterDemo.aspx,和前面的实现相比,显然要复杂一些,而且由于引入了绑定表达式,必须对DataBind()、Eval()方法有所了解才行。部分代码如下:
1: <asp:Repeater ID="Repeater1" runat="server" >
2: <ItemTemplate>
3: <tr visible='<%# Eval("Code","{0}") == editCode? true:false %>' runat="server">
4: <td><input id="code" name="code" value='<%# Eval("Code") %>' /></td>
5: <td>姓:<input id="lastName" name="lastName" value='<%# Eval("LastName")%>' /><br />名:<input id="firstName" name="firstName" value='<%# Eval("FirstName")%>'/></td>
6: <td><input id="mobile" name="mobile" value='<%# Eval("Mobile") %>' /></td>
7: <td><input id="email" name="email" class="email" value='<%# Eval("Email") %>' /></td>
8: <td><a href="#" onclick='actionPost("update","<%# Eval("Code","{0}") %>");' >修改</a> | <a href="#" onclick='actionPost("cancel","");' >取消</a></td>
9: </tr>
10: <tr visible='<%# Eval("Code","{0}") == editCode? false:true %>' runat="server">
11: <td><%# Eval("Code") %></td>
12: <td><%# Eval("LastName") + " " + Eval("FirstName")%></td>
13: <td><%# Eval("Mobile") %></td>
14: <td><%# Eval("Email") %></td>
15: <td><a href="#" onclick='actionPost("edit","<%# Eval("Code","{0}") %>");' >编辑</a> | <a href="#" onclick='actionPost("delete","<%# Eval("Code","{0}") %>");' >删除</a></td>
16: </tr>
17: </ItemTemplate>
18: </asp:Repeater>
学着写一两个具有自定义数据绑定功能的控件,就不难明白DataBind()的实质是构建控件,由于回发事件的存在、以及绑定中的控件不是在Load之前就能构建好的、要在事件处理程序中使用控件对象就得保证控件已经构建并还原状态,等等这些导致了WebForm模型的复杂性。我们如果不使用绑定,那么就不用过多关心页面的生命周期模型,也避免了许多程序出错的机会,比如,Load的顺序是页在前子控件在后(而Init正好相反),所以在页中使用this.DataBind()而导致的错误等等,对于熟手可能这不算什么技术,但我见过非常多的程序员在这些问题的调试上大半天大半天的耗费了时间!这时,我所想到的就是为什么没有更好的技术手段,让我们程序员能轻松一些!效率高一些。
我始终坚信简单的力量,我也始终提倡的直观编码,用最简单直接的方法来编写程序。
然而要进一步理清WebForm的技术脉络,还是得关注一下WebForm的页面生命周期模型,这就是我们下周要讨论的的主题。