制作类似于googleIG的页面(一)
2007-08-27 16:13 DQ 阅读(417) 评论(0) 收藏 举报
一、技术
客户端采用ASP.NET Ajax RC和Ajax Control Toolkit.几个特殊的扩展用来提供特殊的拖拽状态。
中间层用工作流(wwf),数据访问层采用Dlinq技术,数据库使用SQL Server 2005。
二、WEB层
在此事例中基本上只有一个Default.aspx页面。你看到的客户端所有内容都是在Default.aspx和小窗口技术展现出来的。我们不能在页面中过多的使用回传或者链接,这样会忽视主要的技术。
在UpdatePanel中只简单的使用<ul>和<li>标签。当你更改页签标题或者添加一个新页面的时候,它不需要提交整个页面,因为它只提交了updatepanel中包含的那部分的更新。其它页面部分如下代码所示:
public UserPageSetup NewUserVisit( )
{
var properties = new Dictionary<string,object>();
properties.Add("UserName", this._UserName);
var userSetup = new UserPageSetup();
properties.Add("UserPageSetup", userSetup);
WorkflowHelper.ExecuteWorkflow( typeof( NewUserSetupWorkflow ),
properties );
return userSetup;
}
以上的代码中我们传送了变量UserName(唯一的标识一个新用户的Guid),我们得到了一个回传的UserPageSetup对象。它记录了用户的初始设置、页面以及拖拽窗口在屏幕上的显示方式。
类似的当用户第二次访问时,它通过执行工作流UserVisitWorkflow加载初始设置。
public UserPageSetup LoadUserSetup( )
{
var properties = new Dictionary<string,object>();
properties.Add("UserName", this._UserName);
var userSetup = new UserPageSetup();
properties.Add("UserPageSetup", userSetup);
WorkflowHelper.ExecuteWorkflow( typeof( UserVisitWorkflow ),
properties );
return userSetup;
}
但是性能如何呢?我在工作流执行的过程中做了一些试验,得出它能高效的执行同步程序。这里有一些在Visual Studio output 窗口中得到的日志:
334ec662-0e45-4f1c-bf2c-cd3a27014691 Activity: Get User Guid 0.078125
b030692b-5181-41f9-a0c3-69ce309d9806 Activity: Get User Pages 0.0625
b030692b-5181-41f9-a0c3-69ce309d9806 Activity: Get User Setting 0.046875
b030692b-5181-41f9-a0c3-69ce309d9806 Activity: Get Widgets in page: 189 0.0625
334ec662-0e45-4f1c-bf2c-cd3a27014691 Total: Existing user visit 0.265625
前四行是一个用户的独立四个数据访问活动,时间单位是秒。最后一行是运行一个包括五个活动和其它一些额外代码的工作流所用的时间。如果你把这些单独操作数据库的活动加起来,你会发现时0.25,这样只有0.015秒的时间花费在了工作流上(workflow)。
三、用Dlinq进行数据访问
Dlinq可以非常简单的进行数据访问。当你使用Dlinq时,你仅仅需要设计数据库,然后用工具产生数据访问类代码和数据实体类代码。Dlinq最好的地方在于可以生成一个叫做工程的项目,它仅仅包含哪些有用的领域而不是整个对象。如果你不需要你可以不使用SELECT语句,也不用建立含有所有字段的对象。Dlinq仅仅为你需要的字段建立对象。
下面的代码可以看到在数据库建立一个对象是多么简单的事情。对象名叫做”page”
var db = new DashboardData(ConnectionString);
var newPage = new Page();
newPage.UserId = UserId;
newPage.Title = Title;
newPage.CreatedDate = DateTime.Now;
newPage.LastUpdate = DateTime.Now;
db.Pages.Add(newPage);
db.SubmitChanges();
NewPageId = newPage.ID;
在这里DashboardData是SqlMetal.exe生成的一个类。
如果说你想改变Pagede 名字可以如下做:
var page = db.Pages.Single( p => p.ID == PageId );
page.Title = PageName;
db.SubmitChanges();
在这里仅仅有一行被选择。
你也可以如下方式只选择一个值:
var UserGuid = (from u in db.AspnetUsers
where u.LoweredUserName == UserName &&
u.ApplicationId == DatabaseHelper.ApplicationGuid
select u.UserId).Single();
下面是我所讲的工程:
<PRE lang=cs>var users = from u in db.AspnetUsers
select { UserId = u.UserId, UserName = u.LoweredUserName };
foreach( var user in users )
{
Debug.WriteLine( user.UserName );
}
第一天教程:建立一个小窗口程序包含UpdatePanel
在这里需要解释两个概念,一个是小窗体容器另一个是小窗体。小窗体容器提供了具有页面标题头和页面内容的区域。小窗体则加载在页面内容区域。WidgetContainer小窗体容器是一个在页面中每个小窗体实例化时都太创建的服务器控件。小窗体也是一个动态加载的服务器控件。
每一个小窗体包含了若干个UpdatePanel,这样可以在不提交整个窗体的情况下更新窗体内的内容。举例来说,在窗体容器中加载的小窗体都包含在UpdatePanel中。所以,不论有多少次小窗体提交表单,整个窗体是不会有任何提交的。
把UpdatePanel和Html元素来年良好的融合在一起是非常困难的事情。举例说,我起初把整个窗体放在一个UpdatePanel中。但是产生的问题是那些放在UpdatePanel中的Html元素,在更新时,它们被删除并创建新的元素。,这样的结果是原来的值全丢失了,每当UpdatePanel更新时,所有的元素也被初始化。这样导致整个页面的加载非常慢。
因此最终的解决办法是用多个UpdatePanel分隔头和内容的区域。一个UpdatePanel放在头的区域,其他的UpdatePanel放在活动窗体的区域。这样当你更新每个活动窗体时不会更新头部,而且头部记录的内容不会丢失。但是如下图的结构仍然后导致整个头部的更新。
因此,我们得到了最优化的解决方案。在每一个容器内放两个UpdatePanel。一个包含了头部内容而不是整个的头部区域。因此,当头部的UpdatePanel更新时,包含整个头部的DIV不会被重新创建。如下图所示:
由此活动窗体容器的代码如下所示:
<asp:Panel ID="Widget" CssClass="widget" runat="server">
<asp:Panel id="WidgetHeader" CssClass="widget_header" runat="server">
<asp:UpdatePanel ID="WidgetHeaderUpdatePanel" runat="server"
UpdateMode="Conditional">
<ContentTemplate>
<table class="widget_header_table" cellspacing="0"
cellpadding="0">
<tbody>
<tr>
<td class="widget_title"><asp:LinkButton ID="WidgetTitle"
runat="Server" Text="Widget Title" /></td>
<td class="widget_edit"><asp:LinkButton ID="EditWidget"
runat="Server" Text="edit" OnClick="EditWidget_Click" /></td>
<td class="widget_button"><asp:LinkButton ID="CollapseWidget"
runat="Server" Text="" OnClick="CollapseWidget_Click"
CssClass="widget_min widget_box" />
<asp:LinkButton ID="ExpandWidget" runat="Server" Text=""
CssClass="widget_max widget_box" OnClick="ExpandWidget_Click"/>
</td>
<td class="widget_button"><asp:LinkButton ID="CloseWidget"
runat="Server" Text="" CssClass="widget_close widget_box"
OnClick="CloseWidget_Click" /></td>
</tr>
</tbody>
</table>
</ContentTemplate>
</asp:UpdatePanel>
</asp:Panel>
<asp:UpdatePanel ID="WidgetBodyUpdatePanel" runat="server"
UpdateMode="Conditional" >
<ContentTemplate><asp:Panel ID="WidgetBodyPanel" runat="Server">
</asp:Panel>
</ContentTemplate>
</asp:UpdatePanel>
</asp:Panel>
<cdd:CustomFloatingBehaviorExtender ID="WidgetFloatingBehavior"
DragHandleID="WidgetHeader" TargetControlID="Widget" runat="server" />
当页面加载的时候,为每一个窗体实例,首先会创建一个窗体容器然后把活动的窗体放到窗体容器中。WidgetContainer工作原理就像一个网关,介于核心的框架和活动窗体以及存储页面状态的API之间。或者改变一个小窗体的收缩与展开。
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
var widget = LoadControl(this.WidgetInstance.Widget.Url);
widget.ID = "Widget" + this.WidgetInstance.Id.ToString();
WidgetBodyPanel.Controls.Add(widget);
this._WidgetRef = widget as IWidget;
this._WidgetRef.Init(this);
}
以上是窗体容器第一次从窗体定义中加载活动窗体URL的代码。然后它把小窗体放在内容页中。然后通过对IWidgetHost的引用。
WidgetContainer通过实现了IWidgetHost接口,实现了活动窗体与与页面框架和容器的通信。
public interface IWidgetHost
{
void SaveState(string state);
string GetState();
void Maximize();
void Minimize();
void Close();
bool IsFirstLoad { get; }
}
执行的过程是如此的简单,如,IWidgetHost.Minimize收缩小窗体内容部分的代码如下:
void IWidgetHost.Minimize()
{
DatabaseHelper.Update<WidgetInstance>(this.WidgetInstance,
delegate(WidgetInstance i)
{
i.Expanded = false;
});
this.SetExpandCollapseButtons();
this._WidgetRef.Minimized();
WidgetBodyUpdatePanel.Update();
}
首先我们个呢新了WidgetInstance行然后更新了UI。活动小窗体回调IWidget接口。
除了关闭按钮以外,其他的功能都是在接口IWidgetHost中好实现的。当调用关闭时间。我们需要把小窗体移出页面。这意味着,WidgetContainer在页面上而WidgetInstance那行需要被移出。现在这是WidgetContainer它自己所不能完成的。这项工作需要包含了WidgetContainer的列容器来完成。Default.aspx就是所有WidgetContainers的容器。所以当关闭按钮被调用时,WidgetContainer发送一个时间到Default.aspx,然后由它来更新和移出。
我的新征途
浙公网安备 33010602011771号