基于ASP.NET AJAX技术开发在线RSS阅读器(下篇)

五、逻辑层设计

(一)添加RSS频道

在展开真正的逻辑层设计之前,先让我们简单地浏览一下下面的草图4。图4展示了我对于两个重要ASP.NET AJAX客户端控件—ListView和DataSource以及MS AJAX官方资料中建议的实现客户端数据绑定架构的理解。

图4:ASP.NET AJAX框架中建议的典型的客户端数据绑定架构

从上图中,我们可以得出如下结论:在实战环境(本例中也是如此)下,当添加一个新的RSS频道时,我们并不需要立即把这些数据存储到服务器端的SQL Server数据库中,而是暂时存储到与客户端控件ListView相关联的数据源控件中。因此,你可以已经猜出,该DataSource控件是支持批更新操作的—支持对于客户端修改数据的临时存储并最终以批处理方式实现更新服务器端数据库。上面草图中的两个重要方法—“Save”和“Load”在此实现逻辑中起着重要作用。

在这一节中,我们仅需要在客户端临时存储用户新输入的RSS频道信息。在此,我在操作ListView控件的过程中遇到了第一个难题。下面先让我们来看一下相应的源码。

列表1

<script language="javascript" type="text/javascript">

var g_RSSNameList;

function pageLoad(sender, args) {

g_RSSNameList=$find('RSSNameList');

…………

}

function Add_onclick() {

g_RSSNameList.addItem();

var  index=datatable.get_length()-1;

datatable.getItem(index).setProperty('Rss_ID', index);

datatable.getItem(index).setProperty('Rss_Name', $find('txtRssName').get_text());

datatable.getItem(index).setProperty('Rss_URL', $find('txtRssUrl').get_text());

}

当用户点击客户端HTML Input按钮“Add the RSS Info”时将调用上面的JavaScript函数Add_onclick(即click事件处理器)。在此,g_RSSNameList是一个全局JavaScript变量,它引用了ListView“RSSNameList”(即负责存储和显示与RSS频道信息的控件)。在这个函数中,我们首先调用ListView的addItem方法;此方法能够把一条空记录添加在与DataSource控件关联的DataTable控件的最后,同时还能够使数据集变脏(这一点对于后面我们将讨论的“保存”操作至关重要)。然后,我们得到此空记录的记录号。然后,我们调用方法getItem(这个方法与方法getRow完全一致)取回空记录并调用方法setProperty来填充相应的数据库表格字段。注意,在此,我们必须使用客户端全局方法$find而不是$get;有关其间的差异请参考框架在线资料。最终,我们实现了在客户端数据源中插入(即‘添加’)一条记录。

注意,在此,我使用了最简单的ASP.NET AJAX客户端校验器组件—requiredFieldValidator来防止用户不在文本框内输入内容。下面是相应的xml-script代码声明。

列表2

            

<textBox id="txtRssName">

<validators>

<requiredFieldValidator errorMessage="You must name the RSS!" />

</validators>

</textBox>

<validationErrorLabel id="validator1" associatedControl="txtRssName" />

<textBox id="txtRssUrl">

<validators>

<requiredFieldValidator errorMessage="You must name the Url of the RSS!" />

</validators>

</textBox>

<validationErrorLabel id="validator2" associatedControl="txtRssUrl" />

在此,在第二个文本框架控件的校验中,你可以选用更好一些的校验器控件regExValidator(它能够利用正规表达式对这里的url进行格式校验)。如果你让两个控件空着(例如仅输入几个空格字符),那么,系统将显示一个红色的“*”号—示意你应该输入一些数据。在调试此程序时,当我保持此两个文本框为空(不输入任何内容),系统看上去毫无反映。这怀疑这是客户端校验器Validator中存在的一个小小的BUG,有兴趣的读者可作进一步分析。

【作者注】在本文演示程序中,我没有实现“删除”和“修改”功能。在此,我强烈建议读者试着加入这两个功能。通过这些操作,你的ASP.NET AJAX客户端编程功力将得到极大提升。

接下来,让我们探讨如何显示存储于服务器端的RSS隧道的问题。

(二)显示RSS频道

在这一部分中,要显示RSS频道,存在至少两种情形:

1)当页面初次加载时,所有存储于服务器端SQL Server数据库中的RSS频道都将显示于前面我们提到的客户端控件ListView中;

2)当用户点击按钮“Refresh”时,所有原始的存储于服务器端SQL Server数据库中的RSS频道信息都被显示于客户端控件ListView中。

现在,首先让我们来分析第一种情形。下面的列表显示了相应的xml-script声明性代码。

列表3

            

<script type="text/xml-script">

<page xmlns:script="http://schemas.microsoft.com/xml-script/2005">

<components>

<dataSource id="RSSInfoDataSource" serviceURL="MyDataService.asmx" >

</dataSource>

<button id="Save">

<click>

<invokeMethodAction target="RSSInfoDataSource" method="save" />

</click>

<bindings>

<binding dataContext="RSSInfoDataSource" dataPath="isDirtyAndReady"

property="element" propertyKey="disabled" transform="Invert" />

</bindings>

</button>

<button id="Refresh">

<click>

<invokeMethodAction target="RSSInfoDataSource" method="load" />

</click>

<bindings>

<binding dataContext="RSSInfoDataSource" dataPath="isReady" 

property="element" propertyKey="disabled" transform="Invert" />

</bindings>

</button>

<application>

<load>

<invokeMethodAction target="RSSInfoDataSource" method="load"/>

</load>

</application>

</components>

</page>

</script>

在此,我们首先指定DataSource控件“RSSInfoDataSource”。这个控件将被ASP.NET AJAX框架以异步方式绑定到服务器端DataService“MyDataService”。其次,在最后面的<application>节中,当该Web应用程序启动时,它相应的load事件方法被调用。然后,在子节<invokeMethodAction>中,数据源“RSSInfoDataSource”的load方法被调用,这将触发一次异步回寄—调用数据服务“MyDataService”的Web方法GetAllRecords。下面的列表展示了这个方法的实现代码。

列表4

[WebMethod]

[DataObjectMethod(DataObjectMethodType.Select)]

public List<RssInfo> GetAllRecords()

{

return new SqlTaskProvider().GetAllRecords();

}

有关于此方法之前的修改属性,在此不多赘述,请参考ASP.NET AJAX在线文档。现在,一旦这个方法执行结束,客户端ListView控件“RSSNameList”即被以存储于服务端SQL Server数据库表格RssStore中的记录所填写。

注意,在此我们略微施了点小技:为了取得较好的用户体验感,我们隐藏了两个字段Rss_ID和Rss_URL(这两个字段中的内容是不需要显示的)。下面给出的是相应的HTML代码片断。

列表5

<div id="searchResults_itemTemplate" >

<span id="searchResults_Rss_ID" style="display: none; visibility: hidden;"></span>

<span id="searchResults_Rss_Name"></span>

<span id="searchResults_Rss_URL" style="display: none; visibility: hidden;"></span>

</div>

不必我们担心,ASP.NET AJAX框架会自动区分这一情形,并仍然能够以数据库记录数据填充ListView控件,只不过是隐藏了其中两个字段的显示而已。

现在,让我们来分析第二种情形—当用户点击按钮“Refresh”时。尽管从字面上看,我们使用的是“Refresh”(即“刷新”),但是这个按钮的真正作用是把服务器端的数据库数据加载到当前ListView控件中。这一过程与客户端的数据控件DataSource存在着直接的联系。下面的xml-script代码向你展示了相应的编程。

列表6

            
<button id="Refresh">

<click>

<invokeMethodAction target="RSSInfoDataSource" method="load" />

</click>

<bindings>

<binding dataContext="RSSInfoDataSource" dataPath="isReady" 
     property="element" propertyKey="disabled" transform="Invert" />

</bindings>

</button>

从上面的代码可知,当用户点击按钮“Refresh”时,客户端DataSource控件“RSSInfoDataSource”的方法load被调用。然后,Web方法GetAllRecords将被异步调用;最后此Web方法返回的数据被填充到ListView控件中。

为了完整起见,在本示例程序中,我们还引入了一个“Save”按钮,其功能与按钮“Refresh”恰恰相反。当用户点击按钮“Save”时,所有位于ListView控件中的最新的RSS频道数据都将被以AJAX方式(即“异步”方式)永久性存储到服务器端的SQL Server数据库中。要想进一步探讨这个按钮的作用机制,请参考我发表在51CTO上的另一篇文章“”;在此文章中,我较为全面地分析了ASP.NET AJAX客户端ListView和ItemList控件等与以Web服务包装下的服务端SQL Server数据库的交互。

(三)显示指定RSS频道相应内容

现在,问题变得越来越有趣了。为了显示一个RSS频道的内容,为仅需要点击ListView控件中一项,然后让右边的Accordion控件展示相应的细节信息,不就行了?不错,但这仅是从用户角度得到的结论。但作为开发人员,为了解决这个问题,我花费了大量时间才找到一种实现方案。下面,让我来逐一向各位介绍。

通过网络取得RSS频道内容

1、关于ListView控件

有关ASP.NET AJAX框架提供的“高级”客户端ListView控件,我想先简单地讨论几句。从研究框架相应的源码文件PreviewScript.js,我们发现在这个控件的descriptor块定义(注:只在位于此块中的内容才可能在xml-script声明性编程方式中使用)中仅提供了极少数目的属性,方法与事件定义。下列我们干脆列出这个控件的descriptor块的定义。

列表7

Sys.Preview.UI.Data.ListView.descriptor = {

properties: [ { name: 'alternatingItemCssClass', type: String },

{ name: 'layoutTemplate', type: Sys.Preview.UI.ITemplate },

{ name: 'itemCssClass', type: String },

{ name: 'itemTemplate', type: Sys.Preview.UI.ITemplate },

{ name: 'itemTemplateParentElementId', type: String },

{ name: 'selectedItemCssClass', type: String },

{ name: 'separatorCssClass', type: String },

{ name: 'separatorTemplate', type: Sys.Preview.UI.ITemplate },

{ name: 'emptyTemplate', type: Sys.Preview.UI.ITemplate } ],

events: [ {name: 'renderComplete'} ]

}

在上面的descriptor块定义中,仅有几个有限的属性和一个事件。后面我们将使用的方法,还要求助于控件的父类DataControl。为此,为能够控制用户点击的ListView控件中的每一项(对应一条记录数据),我们不得不进一步研究ListView控件定义中的prototype块,并进而求助于JavaScript编程。

2、通过JavaScript编程为ListView添加事件

首先,让我们列出与上面所有相应的源码部分。

列表8

            

<script language="javascript" type="text/javascript">

var g_RSSNameList;

function pageLoad(sender, args) {

g_RSSNameList=$find('RSSNameList');

$addHandler($get('RSSNameList'), 'click', clickRowHandler);

}

function clickRowHandler(ev)

{

var s = ev.target;

while (s && (typeof(s.dataIndex) === 'undefined')) {

s = s.parentNode;

}

if (s) {

var idx = s.dataIndex;

//调用与“哑元”控件dummySrvBtn相应的服务器端方法

var btn ='<%=dummySrvBtn.ClientID%>';

var txt='<%=txtRssUrl2.ClientID%>';

document.getElementById(txt).value=s.lastChild.innerText;

__doPostBack(btn,'');

}

}

……(省略)

function pageUnload() {

//释放事件处理器

if (clickRowHandler)

$removeHandler($get('RSSNameList'), "click",clickRowHandler);

clickRowHandler=null;

}

在此,我们首先使用全局方法$addHandler来把事件处理器clickRowHandler关闻到ListView控件RSSNameList的click事件上。接下来,我们来分析这个事件处理器函数的编程。一开始,属性ev.target指向ListView RSSNameList。当程序执行到if条件语句时,变量s指向searchResults_itemTemplate(数据库记录正是在其中显示)。现在,我们可以使用变量idx来取得记录索引的当前值并且可以获取我们想取回的任何字段值(在此,我们仅对最后一个字段Rss_URL感兴趣)。

情况到现在变得越来越复杂了—我们怎样才能为RssDataSource控件(注意这是一个第三方服务器端数据控件)动态地指定RSS url呢?在此,我的方案是求助于方法“__doPostBack”。根据本文分析,一般情况下,我们无法在一个Web服务方法内实现把RssDataSource动态地绑定到Acccordion控件。这样以来,我们只取另寻出路—CodeFile文件AjaxRssReader.aspx.cs。试验证明,在这个后台文件中,我们可以实现上面的目标—把RssDataSource动态地绑定到Acccordion(注意,我们无法在客户端JavaScript脚本中实现这一绑定,因为这是一个服务器端数据源控件)。这也正是我们求助于__doPostBack方法的原因,借助于这个方法,我们可以间接地实现客户端控件与服务器端控件的通讯。

为了实现上面的目标,现在,我们要创建两个“哑元”ASP.NET控件(这仅是我的思路)—一个Button控件和一个TextBox控件。由于它们在运行时刻必须是不显示的,所以我们把它们的尺寸设置为最小值(宽高均为1个像素X),并相应地把其背景设置为浅灰色(这正好相应于按钮区域“buttonarea”的背景色)。

【作者注】在试验中,我发现我们无法把两个“哑元”控件的Visible属性设置为false;否则,将出现运行时刻错误。

接下来,通过调用方法“__doPostBack”我们间接地调用了服务器端click事件处理器—dummySrvBtn_Click(object sender,EventArgs e)(此函数位于CodeFile文件AjaxRssReader.aspx.cs内)。下面是这个函数相应的源码。

列表9

protected void dummySrvBtn_Click(object sender, EventArgs e)

{

RssDataSource1.Url = txtRssUrl2.Text.Trim();

Accordion1.DataSourceID = "RssDataSource1";

Accordion1.DataBind();

}

现在,我们先略过对这段代码的分析。

然后,通过把字段Rss_URL的值赋为相关联的HTML Input元素txtRssUrl2.ClientID(对应ASP.NET服务器端Id—txtRssUrl2)的attribute值(对应于对应服务器端的Text属性),我们就成功地实现了把数据从客户端传输到服务器端。

最后,调用方法“__doPostBack”将必然导致一次整个页面的刷新—这与AJAX基本思想是根本相违背的。这正是我们引入ASP.NET AJAX服务器控件UpdatePanel(它把Accordion控件相应的区域包围起来)的原因。注意,在此我们使用UpdatePanel的AsyncPostBackTrigger触发器来实现使按钮“dummySrvBtn”的click事件触发UpdatePanel的刷新操作。下面是相关的HTML代码部分。

列表10

<asp:UpdatePanel ID="UpdatePanel1" runat="server">

<Triggers>

<asp:AsyncPostBackTrigger ControlID="dummySrvBtn" EventName="Click" />

</Triggers>

<ContentTemplate>

<ajaxToolkit:Accordion ID="Accordion1" CssClass="myAccordion" HeaderCssClass="header"

…………(省略)

也因此,我们才引入了服务器控件UpdateProgress来配合UpdatePanel控件取得更友好些的用户体验。

也许我上面的方案太“丑陋”了;因此,非常希望热心读者能够进一步改进之。

显示频道内容

到目前为止,这一步已经变得相当简单了。事实上,在上面的源码9中我们已经实现了这一目标。通过对于控件RssDataSource1的属性Url的动态赋值,然后把它绑定到控件Accordion1,进而调用Accordion1的方法DataBind,我们最终实现根据用户点击的频道标题信息显示该频道的具体网页内容。

【作者注】第一,通过Google搜索因特网,我竟然没有发现实现RssDataSource控件动态赋值的相关示例。第二,由于某些RSS内容中可能不包含author字段,所以我干脆在控件Accordion的<ContentTemplate>块中注释掉此字段。

最后,让我们来看一下我们的最终成果吧,如下图5所示。

图5:在用户点击RSS频道“Scott Guthrie”后的示例程序运行时刻快照

六、总结

在本文中我们使用微软ASP.NET AJAX框架开发了一个简单的RSS阅读器程序。作为一个演示程序,我们仅为了探讨这个框架的基本使用思路;所以,此软件还存在许多地方有待改进:

1)仅使用ASP.NET AJAX客户端技术开发

2)进一步探讨实现客户端与服务器通讯的其它方式

3)使用客户端控件XSLTView控件来取代本文所使用的ASP.NET AJAX Control Toolkit控件Accordion来显示RSS频道内容

4)使用客户端控件Validator—regExValidator来更为严格地校验用户输入的RSS频道地址

5)进一步支持用户在运行时刻实现对RSS频道信息的删除/修改

6)增加另外的类型RSS并且为用户提供分类管理

………

有兴趣的朋友可以根据上面这些提示进一步改进本文示例。

原文地址:http://developer.51cto.com/art/200708/53158_3.htm

posted @ 2007-08-11 09:41  oec2003  阅读(1164)  评论(0编辑  收藏