[原创]翻译:Dropthings.com的Web Portal开发学习笔记

最近一直在研究Dropthings的架构以及源码,先暂且写个日志,后续内容不断更新
准备翻译Dropthings项目架构的作者写的这本书,不过翻译书比看书难度大多了,看书只需要领会精神就行了,但是翻译书就需要措词和表达能力同时需要让读者能够看懂就不是简单的英语水平能力了,希望不要误人子弟,呵呵…..:

数据库文件下载:
点击下载此文件
目录:
1.简介:
2.Web Portal和Widgets(部件)架构
整个应用程序有清晰的三层架构组成,分别是UI层、业务逻辑层和数据访问层:

Web表示层:
包含Web页面、Web服务、资源(图片,CSS,JavaScript和resx文件)和配置文件。
业务逻辑层:
提供实体类、业务逻辑和中间层数据缓存
数据访问层:
对数据库和数据源的数据库访问和连接封装成了一个接口。同时实现了对实体类与数据库行的映射的工厂类。
Dropthings使用了.NET3.0和.NET3.5的新功能,Web层使用ASP.NET AJAX的RIA技术、业务逻辑层使用新的WF(WorkFlow)工作流实现复杂的业务逻辑的处理。整体架构使用Linq进行数据持久操作。
C#3.0语言的新特性和Linq使得操作集合、数据库行和XML变得容易。WF使得操作赋值的业务流程变得简单。Linq to SQL 应用在业务逻辑层和数据访问层。尽管简单的增、删、改功能在数据访问层进行了封装,更多的请求要求业务逻辑层的响应速度快。这就是为什么Linq to SQL贯彻整个业务层和数据访问层的原因。
2.Web Portal和Widgets(部件)架构
任何Web Protal的核心是它支持Widgets(部件)并且支持用户自定义起始页面,意思就是用户可以提供自定制的服务,不管是公司的一个部门还是第三方。
书中所说的Defaut.asp就是我们所说的一个ASP.NET实现的用于显示widgets和允许用户添加、删除、移动、自定义并且这些操作不会刷新的主页。应用程序记录下用户自定义的页面的样式。Web Protals允许非注册用户使用添加widgets、编辑、删除、创建页面和设置参数。
一个Dropthings的Widgets就是一个ASP.NET的Web Control。可以是一个用户控件也可以是服务器端控件并且符合ASP.net的生命周期规则。Widgets支持PostBacks、ViewState、Sessions和Caches。唯一不同的地方是所有Widgets都实现了IWidget接口来调用核心框架提供的各种服务。一个自定义的AJAX Control实现了drag-and-drop功能。
每个Widget都包含在框架和容器中,容器(container)提供具有标题、编辑链接、最大、最小化和关闭按钮的标题栏。Widget被加载在标题栏下方的主体部分。事件包括改变标题、编辑链接的click事件、最大、最小和关闭按钮事件调用IWidget接口。
Web Portal实现了异步的请求和异步数据处理,使得用户很少刷新页面。Widgets是一种支持ASP.NET请求机制的控件。因此Dropthings的核心就是Widget,不久你就会感觉到所有Widget都是包含在UpdatePanel中并且全部支持异步请求。虽然你可以不用注册就可以使用自定义Widget,哪怕你在不同的计算机上再次登录也会显示你先前自定义的页面布局。ASP.NET的membership(会员管理)和profile(配置)允许匿名用户有持久状态知道注册用户后进行转换。页面和Widget状态存储在数据库表中。
Object Model 对象模型
ASP.NET的membership包含User和Role。如果用户创建了一个或多个页面,每个页面包含一个或多个Widget实例。Widget实例和Widget不同之处类似于面向对象中的类和对象,例如,Flickr图片Widget是一个用于加载Flickr图片的Widget,当一个用户添加它到一个页面后,也就是实例化了一个Widget成为了一个实例。本书中所说的widget实际上都是widget实例。
如图所示:

Application Components 应用程序组件
Dropthings使用门面模式来提供一个统一的业务逻辑接口。实现对子系统的访问处理用户、页面、widgets和etc。门面命名为DashboardFacade,结构如图:

Web层Default.aspx调用DashboardFacade执行添加Tab或Widget、存储Widget状态。DashboardFacade调用具体工作流的操作进行处理。工作流利用Window Workflow Foundation进行实际的处理。每个工作流都包含多个活动(Activities)。每个活动类似于只实现一个功能的类。活动使用DatabaseHelper和DashboardDataContext类操作数据库。DatabaseHelper用于执行通用的数据库操作。DashboardDataContext是用于Linq to SQL的类到数据库表之间的映射。
Data Model 数据模型

数据模型中应用了ASP.NET的Membership的数据库表aspnet_Users包含所有用户帐户。
具体各表的内容如下:

  • aspnet_Users:是ASP.NET的membership默认的表。不过这个表只包含未注册用户信息,注册用户信息存储在aspnet_membership表。图中没有显示,因为它和其他表没什么关系。
  • Page:通过UserId与aspnet_users建立主外键关系。
  • Widget:存储Widget的详细内容和信息。存储每个widget的标题和是否动态加载。也存储用户第一次访问时创建的默认设置。
  • WidgetInstance:通过WidgetId和PageID分别与Page和Widget表建立关系。
  • UserSetting:同过UserId与aspnet_Users表建立关系。

具体表的描述如下:

Table 索引类型 描述
Page UserID Nondusted 用户页面利用Where UserID=<ID>进行加载
Page ID Clusted 页面的主键,编辑页面用于查询
 Widget ID Clusted 部件的主键,当一个部件实例创建后关联到部件ID
 Widget IsDefault Nondusted 第一次访问,默认部件被创建到页面上。IsDefault设定哪些部件被创建。
 WidgetInstance ID Clusted 部件实例的主键,用于更新删除部件实例标示实例。
 WidgetInstance UserID Nondusted 用户ID,用于按用户读取部件实例。

备注:

  • 簇集索引使用的是自增加的整形字段。因为SQL Server本身的数据库文件管理就是基于簇集索引的。如果不选择自动增加的主键当Insert和Delete时候比较麻烦。
  • 外键值不是子增加的字段是因为他们不需要增加。

文件结构:
Dropthings是基于C#语言的ASP.NET项目。源码可以从http://www.codeplex.com/dropthings处下载。

Default.aspx:
    控制所有Widget的起始页面。
WidgetService.asmx
   声明通过起始页面对Widget所有操作的Web Service。
Proxy.asmx
   允许Widget引用其他第三方的扩展资源和Web service的接口。
WidgetContainer.ascx
   所有Widget的容器,起到核心框架和实际Widget之间的桥梁作用。
所有Widget在Widget文件夹下,每个Widget都是一个Web Control,用到的图片、css、js文件都保存在Widget中的子文件夹下。
Update Panels
UpdatePanel允许整个站点的任何部分都可以进行AJAX异步请求操作。不过,UpdatePanel最重要的是支持拖拽。使用多个UpdatePanel时,多个异步请求同时更新页面会很慢。当你嵌套使用UpdatePanel会更加复杂。所以设计时一定要注意布局。


Dropthings中,所有Widget包含在一个UpdatePanel(ID=UpdatePanelLayout)中,因为当用户切换Tab时需要整体加载页面。并且每个Tab创建和修改后的页面都包含在另一个UpdatePanel(ID=TabUpdatePanel),因为Tab可以动态增加。从Widget列表中添加Widget也是一个UpdatePanel(ID=AddContentUpdatePanel)。如图所示。
代码如下:
       <!–添加WidgetUpdatePanel–>
        <asp:UpdatePanel ID="AddContentUpdatePanel" runat="server" UpdateMode="conditional">
        <ContentTemplate>
                ….略
        </ContentTemplate>
        </asp:UpdatePanel>
        <!–Tab和内容UpdatePanel–>
        <asp:UpdatePanel ID="UpdatePanelTabAndLayout" runat="server" UpdateMode="conditional">
        <ContentTemplate>
             <!–Tab面板 –>
            <asp:UpdatePanel ID="TabUpdatePanel" runat="server" UpdateMode="conditional">
            <ContentTemplate>          
                <div id="tabs">
                    <!– 组件容器UpdatePanel–>
                    <asp:UpdatePanel ID="UpdatePanelLayout" runat="server" UpdateMode="conditional">
                        <ContentTemplate>
                            <!–自定义的三列UpdatePanel –>
                            <uc3:WidgetPanels ID="WidgetPanelsLayout" runat="server" />
                        </ContentTemplate>
                    </asp:UpdatePanel>
                    </li>
                </ul>           
                </div>             
            </ContentTemplate>
        </asp:UpdatePanel>
   
         </ContentTemplate>
    </asp:UpdatePanel>
这样将所有的Widget都放在一个UpdatePanel中的结果是更新或添加Widget时需要更新整体UpdatePanel。当以不更新时要产生大量的异步请求HTML和JavaScript。比较好的解决办法是没列包含一个UpdatePanel,不是整个Widget区域。当你拖拽一个widget跨列时,只是JavaScript进行页面间的处理,不需要更新updatePanel,只需要告诉服务器哪个widget被移动了。服务器端可以像客户端一样计算widget的坐标,这样除了增加和删除修改新的widget需要进行异步请求,其他时候都不要发送请求。

拖拽操作
有两种方法实现拖拽:Free Form 和Columnwise,Protopage 用FreeForm实现的拖拽,这样的应用依赖绝对坐标,widget可以拖拽到任意位置。iGoogleLive.com 是使用Column-wise实现的拖拽,允许你在列间进行widget的拖拽。Column-wise方式清晰布局widget,大部分web protal都使用这种方式。

实现多列间的拖拽操作,整个页面被分成三列,每列包含一个ASP.NET的Panel控件。Widget可以添加到任意panel中。拖拽功能是自定义的功能。拖动存在两种方式:一种是本列中的排列顺序拖动,一种是列间的拖动。
如果我们在ASP.NET的AJAX框架中实现IDropTarget接口实现每列的拖拽区,每个widget作为一个IDrogSource在列间拖动。更有意思的是能够记录下widget的坐标值。例如,拖拽一个widget下移,widget会自动向上填充空出的地方。同样你移动一个widget到另一个上方,widget会自动下移到移动的widget下方。这些功能都是类似Extenders的实现的,所以你可以简单的实现一个panel的extender,它将实现类似IDropTarget的功能,并记录坐标。
当拖拽完成我们怎么实现异步存储坐标到服务器呢?当我们拖拽完毕,UI界面进行了更新,但是服务器不知道。任何请求操作都会触发服务器端响应因为需要刷新页面或列。服务器需要异步获得坐标值并存储,但是客户端拖拽完毕后根本体会不到坐标已经发送到服务器。另外一个问题是整个form实现拖拽只基于一个extender。Extenders需要依附于Column Panel并作为一个拖拽目标,也就是widget的拖拽句柄允许widget被拖拽到任何目的。
下面的内容我们将分析如何处理添加widget和起始页面的容器。
使用Widget框架

Dropthings使用一个weidget框架提供了widget相关的功能:验证、权限、配置、存储。Widget从这个框架获得这些功能。如图所示。
而且,你不要依赖任何对象就能创建widget。在你本地开发的计算机上不需要整个web portal的源代码就能创建widget。你只需要创建一个ASP.NET2.0的普通站点,创建一个用户控件,让它实现一个标准的请求响应处理,实现一个接口,仅次而已。
不必要担心基于Dropthings框架中的AJAX和javascript。这个框架允许你使用任何ASP.NET2.0的AJAX组件和Ajax Control Toolkit控件。服务器端支持.NET2.0,3.0,3.5利用View state存储临时状态信息。ASP.NET缓存用来缓存widget数据。这种方式比用javascript进行缓存要好得多。
Dropthings框架中使用了ASP.net的membership实现了验证和权限管理。允许widgets加载时获得当前用的配置信息。核心持久化了保存了用户的状态也就是用户的操作,例如对widget的展开、关闭装提以及增加、删除、修改操作。Widget和核心通过一个widget容器进行交互。widget container包含一个widget起到中介的作用。widget container对widget实例提供类似持久化服务和事件响应。page 包含多个wiget container,但是每个widget container只包含一个widget实例。如图

Widget很简单,就是一个标准的web Control,你可以在page_load事件中编写代码。也可以从ASP.net用户控件定义事件。Widgets类似与SharePoint Web Parts,但是比Web Part好的地方是你可以使用ASP.net控件到自定义控件。用户控件用Visual Studio编辑,你不用自定义控件。你也可以创建widget到.ascx文件中,不要编辑成dll或者发布dll到服务器,只需要拷贝ascx文件到web文件夹中。
比如你想显示fickr的照片。可以创建一个web control作为widget来实现。下面的代码实现了当页面加载时调用fickr的service功能:
protected void Page_Load(object sender, EventArgs e)
{
if( !base.IsPostBack )
this.ShowPictures(0);
else
this.ShowPictures(PageIndex);
}
在这个widget中加入linkbutton来用于切换图片,编写linkbutton的onclick时间用来浏览图片,代码如下:
protected void LinkButton1_Click(object sender, EventArgs e)
{
if( this.PageIndex > 0 ) this.PageIndex –;
this.ShowPictures(this.PageIndex);
}
protected void LinkButton2_Click(object sender, EventArgs e)
{
this.PageIndex ++;
this.ShowPictures(this.PageIndex);
}
ASP.NET的页面生命周期类似于普通页面处理。在widget中你可以使用ASP.NET的组件和编写这些组件的事件响应代码。
Container提供了widget容器和框架并且定义了header和body区域。实际上widget是运行时被widget container加载到body区域的。页面上的每个widget都是首先创建一个widget container然后widget container在动态加载widget实例到它的body区域。widget container是框架的一部分,你只需要编写一次。widget开发者不需要编写container因为他们只需要编写widget就行了,container已经定义好了。

widget container是web control当加载页面时候用于动态创建每个widget实例。widget也是一个web control在widget container的Page.LoadControl(.,.)事件中动态加载。实际上widget是被加载到UpdatePanel控件中的。因此,不管什么时候都是widget实例都是发送请求的,widget container没有进行任何请求处理操作。
设计Widget Container
设计好Container的关键问题是如何准确获得UpdatePanels。如何布局ASP.NET到UpatePanel中是个问题。将每个widget container放在一个UpdatePanel中足够处理。但是有个问题就是UpdatePanel中需要动态添加HTML代码。当UpdatePanel更新时将删除现有的HTML代码重新读取ASP.NET组件并重新创建。结果,所有extenders的之前生成的HTML内容将被清除,除非extenders一直在UpatePanel。将extenders放在UpdatePanel中就意味着每次刷新UpdatePanel将初始化并创建一个extenders实例。每次请求处理后UI界面将刷新慢,特别是在页面上使用widget时候。
需要将Header和Body分别放在两个Updatepanel中,这样修改Widget时只刷新Body区域不必刷新Header区域,但是所有的Extender需要加载在Header区域,这样就不必每次都加载Extender来减少处理时间提高响应速度。如图所示。

不过,最好是将UpdatePanel包含在HeaderPanel中,只将标题和LinkButton放在UpdatePanel中,这样不用刷新UpdatePanel时(比如单击LinkButton后)重新创建HeaderPanel区域,仅仅需要更新title和LinkButton就可以了.下面的图显示了将HeaderPanel放在UpdatePanel外的设计:

WidgetContainer 实现很简单。Header区只包含标题、最小化、最大化、隐藏按钮和一个body区域用于动
态加载widget。图2.4所示的Dropthings文件结构中所示的WidgetContainer.ascx定义了WidgetContainer
,代码如下:
Example 2-1. The .ascx content for the WidgetContainer
<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定义在命名为:Widget的Panel中。首先headerPanel包含Header区域和
WidgetHeaderUpdatePanel的UpdatePanel。它里面包含标题和最小化、最大化的LinkButton用来改变编辑
区域。 WidgetBodyUpdatePanel的UpdatePanel包含一个运行时创建的Widget,并且包含在一个Panel中,
加载widget 是在Page.LoadControl(..)事件中处理的,并添加到Body panel中。
CustomFloatingBehaviorExtender 用于添加widget的Header并且是整个widget支持拖拽。
3.使用ASP.NET AJAX 构建表示层
4.使用.Net3.5构建持久层和业务逻辑层
5.构建客户端Widgets(部件)
6.优化ASP.NET AJAX
7.创建异步,事务和WebService缓存
8.调试和优化服务器端性能
9.优化客户端性能
10.一般部署、发布问题解答

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏