WebSnap 控件组简介
Jimmy Tharpe(翻译:天津财经学院->叶明)
WebSnap提供的丰富接口使得常用功能的实现变得易如反掌,例如,WebSnap提供的IwebUserList接口不但可以跟踪用户名、口令、权限和显示名称,而且可以对内存中的数据进行基本的跟踪。如果我们需要开发数据库应用呢?通过再实现(re-implementing)IwebUserList接口同样可以实现,例如我在例子中使用的IwebUserList组件,它同WebSnap 组中的其他控件具有很好的集成性。
下面,我将和大家一同感受WebSnap的强大功能,我们将再实现一些接口,以获得一些还未经公开的功能特性。
TDBWebUserList
为创建一个具备数据库功能的万维网用户列表,必须实现IwebUserList接口。
IWebUserList = interface
['{0877DEAF-AB5D-11D4-A503-00C04F6BB853}']
function ValidateUser(Strings: TStrings): Variant;
function CheckAccessRights(UserID: Variant; Rights: string): Boolean;
end;
如果我们仔细研究WebSnap对这个接口的实现,我们就会发现存在一个不足之处:无法取得用户的显示名称!唉!我们就此放弃了这一功能吗?当然不会!我们可以通过简单的创建一个继承接口IudWebUserList达到目的:
IudWebUserList = interface(IWebUserList)
['{0C7E6E80-3F82-47C6-B37E-04BEA4FAEE4A}']
function UserDisplayName(AUserID: variant): string;
end;
呵呵,现在我们有了一个实现的接口,正如WebSnap所要求的,它仍然可以保证良好的兼容性。我们每当需要放置一个接口,只需要对它进行实现,这在WebSnap中通过接口很容易做到。
增加缓冲区
我曾经在社区中征求大家对TDBWebUserList的意见,Graham Colwell建议我为登录的用户增加缓冲区以便改善系统的运行速度,这样,每当有人请求关于用户的信息(比如权限信息),系统就没有必要频繁的访问和更新数据库了。这个解决方案看起来很简单:只需要创建一个登录用户的列表,然后改为从列表中读取数据就可以了。但是,这又带来的另一个问题:我的组件怎样才能获知用户从网络中退出以便从缓冲区中删除他的记录呢?
我仔细研究了博览的文档(也包括博览的源码),却没有找到相关的解决方案,于是我开始浏览WebSnap的源码,在那里我找到了答案:InotifyWebActivate!InotifyWebActivate接口在激活和停用WebSnap应用的时候通知它的调用者。InotifyWebActivate接口是这样定义的:
INotifyWebActivate = interface
['{CE18BE42-1358-11D4-ABF4-F18FFAD12B3C}']
procedure NotifyActivate;
procedure NotifyDeactivate;
end;
在实现了这个未公开的接口之后,我的组件就可以获知WebSnap应用的激活和停用了。好,在找到这个接口之后,剩下的工作只是一个检查用户是否退出系统的例行程序,如果用户已经退出系统,就从缓冲区中删除他的记录。
TudEndUserSessionAdapter
将TudEndUserSessionAdapter组件加入WebSnap是为了获得TDBWebUserList的IudWebUserList接口并修正EndUser.DisplayName错误(TendUserSessionAdapter显示的是用户名而不是显示名称)。
为此,我研究了TendUserSessionAdapter,发现用户名是存储在一个Session对象中,而显示名称却没有。为获得显示名称,只需做如下修改:为显示名称设置一个新的Session变量,当脚本请求显示名称的时候,读取这个session对象并返回这个值。
function TudCustomEndUserSessionAdapter.GetUserName: string;
begin
if WebContext <> nil then
Result := WebContext.Session.Values[sUserName];
if Result = '' then
Result := UserID;
end;
很简单,不是吗?所以WebSnap并不难学。
TudLoginFormAdapter
TudLoginFormAdapter是对WebSnap部分最简单的扩充,它只是统计存在的TloginFormAdapter并输出NextPage属性,这就是它的全部源代码:
type
TudLoginFormAdapter = class(TLoginFormAdapter)
published
property NextPage;
end;
NextPage 属性允许你设置一旦用户登录将转向哪里,例如,你也许想把管理员转向管理页面,而将客户转向技术支持页面,转向哪里取决于用户的权限。下面是一个例子:
procedure TLoginPage.udLoginFormAdapterLogin(Sender: TObject; UserID: Variant);
begin
if HomePage.DBWebUserList.CheckAccessRights(UserID, 'admin') then
udLoginFormAdapter.NextPage := AdminPage.Name
else if HomePage.DBWebUserList.CheckAccessRights(UserID, 'user') then
udLoginFormAdapter.NextPage := UserPage.Name
else
udLoginFormAdapter.NextPage := VisitorPage.Name;
end;
当然,因为NextPage属性是输出的,你可以在设计程序的时候为它赋值。
TudStringsValuesList
介绍TudStringsValuesList有两个理由:修正博览的TstringsValuesList错误(OnPrepareStrings事件从未调用)和在Delphi中提供对在迭代脚本中当前字符串的访问。即使这意味着完全的重写,也并不是十分困难——只需要简单的复制/粘贴,对代码做几处很小的改动。
修正错误 修正OnPrepareStrings错误很简单,先让我们来看看博览的TstringsValuesList源码摘录:
procedure TCustomStringsValuesList.SetStrings(const Value: TStrings);
begin
FStrings.Assign(Value);
end;
function TCustomStringsValuesList.GetStrings: TStrings;
begin
if not (csLoading in ComponentState) and not StringsPrepared then
PrepareStrings;
Result := FStrings;
end;
procedure TCustomStringsValuesList.PrepareStrings;
begin
StringsPrepared := True;
if Assigned(OnPrepareStrings) then
OnPrepareStrings(Self);
end;
SetStrings和GetStrings方法是为了控制TstringsValuesList的Strings属性。如果你研究一下GetStrings方法,你将发现它只有当strings没有准备好并且组件没有被加载的时候才调用PrepareStrings方法
理想的情况是这:当首次需要strings的时候,PrepareStrings应该被调用,当请求被停用的时候,ImplNotifyDeactivate应该将StringsPrepared设回为否,所以当再次请求时PrepareStrings将再次被调用,然而,事实却不是这样,理由如下:
function TCustomStringsValuesList.ImplGetListName: string;
begin
if FIndex < FStrings.Count then
Result := FStrings.Names[FIndex]
else
Result := '';
end;
function TCustomStringsValuesList.ImplGetListValue: Variant;
var
S: string;
begin
if FIndex < FStrings.Count then
begin
S := FStrings.Names[FIndex];
if S <> '' then
Result := FStrings.Values[S]
else
Result := FStrings[FIndex]
end
else
Result := Unassigned;
end;
function TCustomStringsValuesList.ImplNextIteration(
var OwnerData: Pointer): Boolean;
begin
Inc(FIndex);
Result := FIndex < FStrings.Count;
end;
当脚本需要使用 strings的时候,代码访问的是内部Fstrings变量,从而“骗过”了GetStrings方法!唉!为了解决这个问题,只需将所有涉及Strings的方法中对Fstrings的首次调用由Fstrings改为Strings就可以了。
增加CurrentIteration属性CurrentIteration属性的作用也许你也能猜出来,在Delphi的代码中,你的页面脚本所在位置就是它的current iteration,这个功能很容易实现,在迭代的实现方法中,分配返回的字符串:
function TudStringsValuesList.ImplStartIterator(var OwnerData: Pointer):Boolean;
begin
FIndex := 0;
Result := FIndex < Strings.Count;
if Result then
FCurrentIteration := Strings[FIndex];
end;
function TudStringsValuesList.ImplNextIteration(var OwnerData:Pointer): Boolean;
begin
Inc(FIndex);
Result := FIndex < Strings.Count;
if Result then
FCurrentIteration := Strings[FIndex];
end;
如果迭代成功,结果为真,如果结果为真,我们将CurrentIteration属性的值设为迭代的值。
结束语
WebSnap就象一个万维网上的机器,如果你有机会,请打开它的引擎盖,看看博览的做工是多么出众,如果你看到需要改进的地方,请动手完善我们的WebSnap,我想这也正是设立这个社区的目的吧!