返回首页

{A}更新:有一个新的文章,这也解释了这个项目的最新发展。 。简介
,我会告诉你我是如何建立一个起始页类似谷歌中空玻璃在使用的。NET 3.0,LINQ,ASP.NET AJAX的的DLinq和XLINQ晚7。我有我一天一天的发展经验记录在这篇文章中,并记录了所有的技术挑战,有趣的发现和重要的设计和建筑的决定。你会发现相当接近实际,您可以使用日常实施。欢迎您来参与开发,使该项目的部件。
{S0}更新2007年1月6日:斯科特格思里向我展示了如何提高ASP.NET AJAX客户端的性能,切换到调试= quot; falsequot;在web.config中。它可以提高性能显著。 。2007年1月5:部署问题进行了讨论。 。2007年1月4日:Visual Studio 2005中NET框架3.0(Windows Workflow Foundation的)作为先决条件所需的扩展。 。2007年1月4:有人问我,如果我选择一个与谷歌的斗争。我不是。我很尊重谷歌,因为他们率先在这方面,我只是一个追随者。起始页是一个非常好的项目,显示所有这些新技术。什么是Web 2.0 AJAX起始页
一个起始页,允许你建立你自己的主页,在页面上拖放部件。您可以完全控制你想看到什么,你希望看到的地方,的,以及如何你想看到的。部件都是独立的应用程序,为您提供一组功能,喜欢做列表,地址簿,联系人列表,RSS提要,等开始页也被广泛称为RSS聚合,或笼统地说,quot;内容aggregatorsquot从各种网络资源。但你不能只阅读RSS提要使用您的起始网页也组织您的数字生活。 AJAX起始页一步提前给您最先进的国家有很多JavaScript效果的UI老同学开始像页。他们给你像桌面应用程序的外观和感觉,利用AJAX和大量先进的JavaScript和DHTML技术。
一些流行的AJAX开始页面。特点
建立您的网页,通过拖放小部件。你完全可以把你想要什么,你想要的个性化页面。您可以添加,删除页面上的小部件。您可以拖放你喜欢的地方。关闭浏览器并再次回来,你会看到你离开它的确切的设置。您可以使用它无需注册,只要你喜欢。

一旦你把你的页面上的内容很多,你会发现一个页面是远远不够的。您可以选择使用多个页面。你喜欢,你可以创建多页。小部件
小部件提供一个有趣的建筑,在那里你可以专注于提供相关的部件的功能,再也不必担心身份验证,授权,个人资料,个性化,存储,框架等,这些都是一些小部件得到从他们理所当然主机。此外,您可以建立独立的主机项目的部件。您不必整个主机的Web应用程序的源代码在您的本地开发计算机,以建立部件。只需创建一个普通的ASP.NET 2.0 Web站点,创建一个用户控件,让它做什么它应该做定期回发模型,而不必担心有关JavaScript,实现一个小接口,和你做!我已经尽我所能,创建一个架构,你不必担心在所有有关Ajax和JavaScript。此外,该架构允许您使用定期ASP.NET 2.0控件,控制,和任何在ASP.NET AJAX的扩展。您还可以得到完整的服务器端编程的支持,并可以利用的。NET 2.0或3.0。您可以使用普通的ViewState和存储临时状态。您还可以使用ASP.NET缓存以Widget的缓存数据。这是远远比你发现在当前的启动页,你必须建立整个部件使用JavaScript,你需要遵守特定的API准则和严格quot; postbackquot;模型。那些专为目前的起始页部件必须知道,一个创伤经验widget开发真正对他们来说是。技术
客户端是使用ASP.NET AJAX RC和AJAX Control Toolkit中。几个自定义扩展是用来提供专业的拖放功能。
中间层是使用Windows Workflow Foundation,和数据访问层使用的DLinq和SQL Server 2005。Web层
基本上只有一个页面,在Default.aspx。你看到的所有客户端功能,可在Default.aspx和构件的形式。我们不能做回发或过多的页面之间的导航,因为这会杀网络2.0ness。因此,必须提供所有的功能在一个页面中从来没有职位,不重定向到其他页面。
{S2}
的标签只是简单的LT; ULgt;和LT; LIGT;在UpdatePanel。当您更改网页的标题,或添加了新的一页,它不回整个页面,因为只有在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;

}

在这里,我们通过用户名(这基本上是一个GUID为一个新的用户),并且,我们得到一个UserPageSetup对象,它包含了用户设置和页面和部件是在屏幕上显示的第一页。
同样,第二次访问,它只是执行UserVisitWorkflow加载用户的设置。{C}
,但对性能如何?我做了一些分析工作流执行的开销,这是非常快速同步执行。这里的证明你在Visual Studio输出窗口的日志:
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

前四个项目是在数据访问的个人活动所花费的时间。这里的时间条目都在几秒钟内,前四个条目内活动的数据库操作的持续时间。最后一个是5活动和一些额外的代码的工作流运行的总时间。如果总结所有的个人活动数据库操作的执行时间,它是0.25,这是比总执行时间仅为0.015秒。这意味着,执行工作流本身需要大约0.015秒,这几乎是没有。数据访问使用的DLinq
的DLinq是这么多的乐趣。它是如此令人惊讶的简单写产生真正优化的SQL数据访问层。如果你还没有使用的DLinq之前,振奋的影响!
当你使用的DLinq,你刚才设计的数据库,然后使用SqlMetal.exe(LINQ五月份CTP版),以生成一个数据访问类,其中包含所有的数据访问代码和实体类。对黑暗的时代,认为当你不得不手工代码下面的数据库设计和手工编写数据访问类的所有实体类。每当你的数据库设计改变了,你不得不修改实体类和修改的插入,更新,删除,在数据访问层的方法。当然,你可以使用第三方ORM工具,或者使用某种代码生成从数据库架构生成实体类,并生成数据访问层代码。但是,做没有更多的DLinq不为你所有!
有关的DLinq的最好的事情是,它可以产生一种叫做投影机,其中包含必要的字段,而不是整个对象。有没有ORM工具或面向对象的数据库库可以做到这一点,因为它现在真的需要一个自定义的编译器为了支持这一。投影的好处是纯粹的性能。您不选择你不需要的字段,也没有你建造一个巨型的对象,它有的所有字段。的DLinq仅选择所需的字段,其中只包含选定的字段创建对象。
让我们来看看它是多么容易创建一个名为quot数据库中的新对象; Pagequot;
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生成的类。
说,你想改变页面的名称:
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();

,这里的投影,我说的是:
var users = from u in db.AspnetUsers

select { UserId = u.UserId, UserName = u.LoweredUserName };



foreach( var user in users )

{

    Debug.WriteLine( user.UserName );

}

如果你想不喜欢选择20行,100行部分传呼:
var users = (from u in db.AspnetUsers

select { UserId = u.UserId, UserName = u.LoweredUserName }).Skip(100).Take(20);



foreach( var user in users )

{

    Debug.WriteLine( user.UserName );

}

如果您正在寻找交易,是多么简单:
using( TransactionScope ts = new TransactionScope() )

{

    List<Page> pages = db.Pages.Where( p => p.UserId == oldGuid ).ToList();

    foreach( Page page in pages )

    page.UserId = newGuid;



    // Change setting ownership

    UserSetting setting = db.UserSettings.Single( u => u.UserId == oldGuid );

    db.UserSettings.Remove(setting);



    setting.UserId = newGuid;

    db.UserSettings.Add(setting);

    db.SubmitChanges();



    ts.Complete();

}

难以置信?相信这一点。
您可能有关的DLinq性能有些感慨。相信我,它生成的SQL,我想它做的完全正确的。使用SqlProfiler和看到它发送给数据库的查询。您可能还认为所有这些quot; varquot;东西听起来像在旧的COM时代晚期绑定。它不会被强类型的代码或书面超级优化的代码,这不正是你想要的自己的手一样快。你会惊讶地知道,这一切的DLinq代码实际上得到的LINQ编译器转换成纯粹和简单。NET 2.0中的IL。没有什么神奇的东西或没有额外的库才能运行此代码在您现有的。NET 2.0项目。的DLinq与许多ORM工具,也没有严重依赖于反思。第1天:大厦的widget容器,使用UpdatePanel
有两个概念,一个是widget容器和其他的Widget。 widget容器提供了框架,其中有一个头和主体区。实际部件是装在身体方面。 WidgetContainer是一个服务器控件,这是在页面上动态创建的每一个部件的实例。实际的Widget也是一个服务器控件,这是动态加载widget容器内。
每个部件都包含几个UpdatePanel的,这有助于更小的部分没有整个页面刷新或整个构件刷新更新的部件。例如,装载容器内的实际部件是装在UpdatePanel。所以,不管如何实际的控件回发多次,整个部件不回传或整列。
,找到正确的UpdatePanel组合和位于UpdatePanel的HTML元素的分布是困难的。例如,我第一次把整个部件之一的UpdatePanel内。它工作得很好,只有一个每个部件的UpdatePanel,所以开销小。但问题是用的UpdatePanel里面的HTML元素附加扩展。当的UpdatePanel刷新,它消除了现有的HTML元素,并创造了新的的。因此,所有连接到以前的HTML元素的扩展迷路,除非扩展在UpdatePanel。把里面的UpdatePanel的扩展手段的UpdatePanel刷新时,扩展新的实例的创建和初始化。这使得UI体验非常缓慢。实际上,你可以看到进展缓慢,视觉,当你在部件上,这使得它回传本身的东西。
所以,最终的想法是单独的标题区和身体之间的多个UpdatePanel的面积。一个UpdatePanel中的主机头的面积和其他UpdatePanel主机的实际部件。这样,如果你的widget,widget的身体刷新的东西,它不刷新标题区,并连接头扩展不会丢失。 CustomFloatingBehavior扩展连接头。因此,扩展本身也需要在UpdatePanel。但是,把里面的UpdatePanel的扩展,意味着所有的UpdatePanel刷新,再次扩展是创建和初始化的时间。这给表现不佳。
{S3}
因此,最佳的解决方案迄今有两个UpdatePanel的每WidgetContainer,包含的头,而不是整个头本身的内容。所以,当头的UpdatePanel刷新,其中包含整个头部的DIV没有得到重建,因为它是在UpdatePanel之外。这样,我们可以把外面的UpdatePanel CustomFloatingBehavior扩展。因此,扩展可以连接头容器DIV。
{S4}的
WidgetContainer是相当简单的。它的标题和展开/折叠/关闭按钮,标题区和实际Widget是托管的身体部位。在解决方案中,该文件"; WidgetContainer.ascxquot;是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" />

在页面加载时,每个部件的实例,第一个widget容器被创建,然后widget容器,主机内的实际部件。 WidgetContainer工程的核心框架和实际部件之间的网关,并提供了一​​个方便的API,用于存储状态,或改变部件的状态,像展开/折叠等WidgetContainer也传达重要的信息像时的实际部件倒塌或当它是封闭的,等等。
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);

}

在这里,widget容器第一次加载Widget的定义中提供的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功能是很容易实现,除了关闭一个。被称为关闭时,我们需要从页面中删除的部件。这意味着,在页面上和数据库中的WidgetInstance行WidgetContainer需要拆除。现在,这是WidgetContainer本身无法做到的东西。它需要列容器,其中包含的WidgetContainer。在Default.aspx是对所有WidgetContainers的容器。所以,每当被称为关闭,WidgetContainer引发一个事件到Default.aspx,和Default.aspx的实际工作中移除的部件和刷新列。第2天:建立一个自定义拖放扩展和多列放置区
AJAX控件工具包带有一个DragPanel扩展,您可以使用提供支持拖放到面板。它也有一个ReorderList控制,您可以使用它提供在一个列表中的项目重新排序。我们的部件基本上都是一个头作为拖动手柄和垂直流在每一列的面板。因此,它是可能的,我们可以创建一个在每一列的重新排序清单和使用DragPanel拖动的部件。但是,我不能使用ReorderList因为:ReorderList严格使用一个HTML表格来呈现其项目。ReorderList需要拖动控制模板为每个项目创建一个拖动手柄。我们已经有了一个Widget内创建一个拖动手柄,所以我们不能让ReorderList创建另一个拖动手柄。我需要一个客户端的回调,拖拽和重新排序的项目,这样我可以让AJAX调用,并坚持小部件的位置。
接下来的麻烦与DragPanel扩展。默认实现拖放AJAX Control Toolkit中存在一些问题:当你开始拖动,该项目成为绝对定位,但是当你删除它,它不会成为静态定位。有一个小黑客需要恢复原来的位置quot; staticquot;它不把所有项目上拖动项目。因此,当你开始拖动,您会看到该项目被拖动下面的其他项目,这使得拖动有时卡住,尤其是当有一个IFRAME。
所以,我已经做了CustomDragDropExtender一个CustomFloatingExtender。 CustomDragDropExtender是列部件都放在容器。它提供了重新排序的支持。根据订购的容器,这是一个特定的类的名称标记,它允许任何项目。它是如何工作的:
<asp:Panel ID="LeftPanel" runat="server"  class="widget_holder" columnNo="0">

        <div id="DropCue1" class="widget_dropcue">

        </div>

</asp:Panel>



<cdd:CustomDragDropExtender ID="CustomDragDropExtender1" runat="server"

      TargetControlID="LeftPanel" DragItemClass="widget"

      DragItemHandleClass="widget_header"

      DropCueID="DropCue1" DropCallbackFunction="WidgetDropped" />

LeftPanel成为一个widget容器允许的部件被丢弃它,并重新排序。 DragItemClass上的扩展属性定义的,可以责令项目。这可以防止非小部件的HTML的div越来越下令。 widgetquot;只有类quot DIV的是有序的。所以,说有5 DIV的类名为quot; widgetquot。它将使只有这五个div的重新排序:
<div id="LeftPanel" class="widget_holder" >

    <div id="WidgetContainer1_Widget" class="widget"> ... </div>

    <div id="WidgetContainer2_Widget" class="widget"> ... </div>



    <div id="WidgetContainer3_Widget" class="widget"> ... </div>

    <div id="WidgetContainer4_Widget" class="widget"> ... </div>

    <div id="WidgetContainer5_Widget" class="widget"> ... </div>



    <div>This DIV will not move</div>

    <div id="DropCue1" class="widget_dropcue"></div>

</div>

也需要它调用时下降容器上的一个部件是一个DropCallbackFunction。
function WidgetDropped( container, item, position )

{

    var instanceId = parseInt(item.getAttribute("InstanceId"));

    var columnNo = parseInt(container.getAttribute("columnNo"));

    var row = position;



    WidgetService.MoveWidgetInstance( instanceId, columnNo, row );

}

这使我得到的部件被删除或重新排序,列,和位置。然后,我可以调用Web Sservice和异步通知服务器刚刚发生了什么。服务器的更新部件的位置,根据新的安置。
注:我不是做了回传,而不是调用一个拖放式Web服务。如果我不回传,说回传列的UpdatePanel,然后将刷新整个列,给出了一个贫穷的拖放经验。这是为什么拖放不刷新页面的任何部分,并默默地在后台调用的Web服务,以保存被丢弃的小部件的位置。
HTML输出作为一个属性列格内包含的列数,以及每个部件的DIV包含部件的实例ID。这两个ID帮助服务器标识列是什么,哪个部件已被移动。
<div id="LeftPanel" class="widget_holder" columnNo="0">

        <div InstanceId="151" id="WidgetContainer151_Widget" class="widget">

产生的附加属性是从服务器端。
,使得第一个扩展是真的很辛苦。我一般不公开承认如果事情是很难的,对我来说,这么信任我,当我说很难,这是"HAR Dquot。该架构是当你开始只是十分热烈。但渐渐地,你会把握的想法,你一定会努力欣赏的OOP风格的超慢的​​JavaScript对象模型,ASP.NET AJAX提供。第3天:构建数据访问层和站点的负载
,它是那么容易建立数据访问层使用的DLinq。首先,我设计的数据库:
{五}
用户包含一个网页的集合。每个页面都包含一个WidgetInstances集合。一个WidgetInstance代表一个部件。 Widget的表中包含的部件,例如,部件的名称和用户控件文件名,部件的代码定义。 WidgetInstance代表的列和行页面上的一个部件的一个实例。 UserSetting存储一些用户级别设置。
设计数据库后,我用SqlMetal.exe生成数据访问类名为DashboardData其中包含了所有的实体类和数据库的工作的DLinq实现。 DashboardData从DataContext类,这是一个在System.Data.Dlinq命名空间中所有的数据访问类的基类继承。它的所有方法插入,更新,删除,选择,交易管理,连接管理,等等。
我还创建了一个方便的DatabaseHelper类,它包含插入,更新和删除的方便的方法。的DLinq的问题之一是,如果你的实体,通过多层次的旅游,那么他们得到从他们最初加载的DataContext脱节。所以,当您尝试更新再次使用不同的DataContext的实体,你首先需要附加数据上下文的实体的实例,然后进行更改,并调用SubmitChanges。现在的问题是,从业务层,你没有访问数据访问层将更新实体对象而创建的DataContext。业务层将只发送实体对象的数据访问组件和数据访问层,然后通过创建一个新的DataContext的更新。不过的DLinq需要重视实体对象quot; beforequot;对它们进行更改。但定期业务层将使第一次修改,然后发送到数据访问组件,以更新的对象。所以,像这样的传统的尝试将失败:
不知怎的,你需要这样做:
Page p = DashboardData.GetSomePage();

...

...

// Long time later may be after a page postback



DashboardData.AttachPage( p );

p.Title = "New Title";

DashboardData.UpdatePage( p );

但是这是不可能的,因为这意味着你不能让DashboardData无国籍。您需要创建内部方法的DataContext,不知何故,你需要存储的DataContext之间的函数调用参考。这可能是为单个用户的情况下确定的,但不是一个多用户的网站可以接受的解决办法。
所以,我没有这种方法:
Page p = DashboardData.GetSomePage();

...

...

// Long time later may be after a page postback



DashboardData.Update<Page>( p, delegate( Page p1 )

{

  p1.Title = "New Title";

});

在这里,Updatelt; GT;方法首先重视与DataContext的页面对象,然后调用的委托,通过引用附加的对象。现在,您可以修改传递的对象,如果你修改原始对象内委托。委托完成后,将更新使用DataContext.SubmitChanges();
实施Updatelt; GT;方法是这样的:
public static void Update<T>(T obj, Action<T> update)

{

    var db = GetDashboardData();

    db.GetTable<T>().Attach(obj);

    update(obj);

    db.SubmitChanges();

}

在这里的用法的例子:
委托给了我们一个好处,你在业务层或来电。所以,你可以访问UI元素或其他功能/你需要的属性,以更新的实体的属性。
为方便起见,我已Insertlt; GT,Deletelt; GT也。但他们并不需要,因为他们没有这样一个quot;附加首先,修改laterquot;要求。
public static void Delete<T>(Action<T> makeTemplate) where T:new()

{

    var db = GetDashboardData();

    T template = new T();

    makeTemplate(template);

    db.GetTable<T>().Remove(template);

    db.SubmitChanges();

}
第4天:建立一个Flickr照片和RSS部件使用XLINQ
我们将建立的第一个部件是一个很好的Flickr部件。
{中六}
下载XML饲料从Flickr网站Flickr的照片,然后呈现一个3X3的网格与图片。
第一步是下载并解析XML使用XLINQ。下面一个简单的方法,准备从URL一个XElement:
var xroot = XElement.Load(url);

Nown我们每个照片里面的XML节点转换为方便加工Pho​​toInfo类的对象:
var photos = (from photo in xroot.Element("photos").Elements("photo")

select new PhotoInfo

{

    Id = (string)photo.Attribute("id"),

    Owner = (string)photo.Attribute("owner"),

    Title = (string)photo.Attribute("title"),

    Secret = (string)photo.Attribute("secret"),

    Server = (string)photo.Attribute("server"),

    Farm = (string)photo.Attribute("Farm")

})

但是从截图中,你看,你可以浏览之间的照片,因为Flickr的实际回报超过9张照片。因此,我们需要准备PhotoInfo类的对象,只有那些属于当前分页索引XML节点。
下面是如何对XML进行分页:
var photos = (from photo in xroot.Element("photos").Elements("photo")

select new PhotoInfo

{

    Id = (string)photo.Attribute("id"),

    Owner = (string)photo.Attribute("owner"),

    Title = (string)photo.Attribute("title"),

    Secret = (string)photo.Attribute("secret"),

    Server = (string)photo.Attribute("server"),

    Farm = (string)photo.Attribute("Farm")

}).Skip(pageIndex*Columns*Rows).Take(Columns*Rows);

我们从当前的PageIndex只有9张照片。当用户点击下一个或上的链接,页面索引改变。 Skip方法跳过在XML项目的数量,和take方法只需要在指定的XML节点。
一旦我们的照片对象来呈现,一个3x3的HTML表呈现的照片:
foreach( var photo in photos )

{

    if( col == 0 )

            table.Rows.Add( new HtmlTableRow() );



    var cell = new HtmlTableCell();



    var img = new HtmlImage();

    img.Src = photo.PhotoUrl(true);

    img.Width = img.Height = 75;

    img.Border = 0;



    var link = new HtmlGenericControl("a");

    link.Attributes["href"] = photo.PhotoPageUrl;

    link.Attributes["Target"] = "_blank";

    link.Attributes["Title"] = photo.Title;

    link.Controls.Add(img);



    cell.Controls.Add(link);

    table.Rows[row].Cells.Add(cell);



    col ++;

    if( col == Columns )

    {

            col = 0; row ++;

    }



    count ++;

}

我之所以来代替HtmlLink HtmlGenericControl,HtmlLink不允许其Controls集合里面添加控件。这是一个限制HtmlLink类。
这是很容易使用XLINQ。然后,我内置的RSS部件显示RSS饲料从饲料来源。首先,我从Widget国饲料的URL,然后下载源的XML:
string url = State.Element("url").Value;

int count = State.Element("count") == null ? 3 :

                           int.Parse( State.Element("count").Value );



var feed = Cache[url] as XElement;

if( feed == null )

{

    feed = XElement.Load(url);

    Cache.Insert(url, feed, null, DateTime.MaxValue, TimeSpan.FromMinutes(15));

}

然后,我绑定到DataList显示一个超链接列表的XML:
FeedList.DataSource = (from item in feed.Element("channel").Elements("item")

                                select new

                                {

                                     title = item.Element("title").Value,

                                     link = item.Element("link").Value

                                }).Take(this.Count);

DataList是很简单:
<asp:DataList ID="FeedList" 

    runat="Server" EnableViewState="False">



<ItemTemplate>

<asp:HyperLink ID="FeedLink" runat="server" Target="_blank"

      CssClass="feed_item_link"

NavigateUrl='<%# Eval("link") %>'>

<%# Eval("title") %>

</asp:HyperLink>

</ItemTemplate>

</asp:DataList>

的所有!
但是有位与状态的调整。每个RSS构件存储在其国家的URL。 Widget的表有一个DefaultState列,其中包含预定义的URL的RSS小部件。当在页面上创建一个RSS widget是,默认状态是复制部件的实例的状态。 XLINQ使得它很容易处理的简单的XML片段。例如,下面是我阅读的网址:
public string Url

{

    get { return State.Element("url").Value; }

    set { State.Element("url").Value = value; }

}

状态的XML是这样的:
国家财产解析XML并返回它作为一个XElement根节点LT指; stategt;
private XElement State

{

    get

    {

       if( _State == null ) _State = XElement.Parse(this._Host.GetState());

                return _State;

    }

}
第5天:在业务层大厦的工作流
在这里的工作流显示用户访问该网站时会发生什么:
{七}
首先,我们得到的UserGuid从用户的名称。然后,我们使用GUID加载在当前页的页面,用户设置的部件。最后,我们准备的一个UserPageSetup对象,它包含了所有的渲染页面所需的信息。
现在,发生了什么,当用户首次访问该网站?我们需要创建一个匿名用户,并创建一个用户的默认页面设置,然后再次加载用户的页面设置。这是内进行新的用户访问的工作流程,这是这样的:
{S8}
最后一项活动,名为"CallWorkflowquot;调用再次以加载这是刚刚创建的用户设置用户访问流程。所以,在这里我们可以看到一些工作流程的重用。
的活动做的工作量非常小。例如,创建新的一页活动,创建一个新的页面,并返回的ID:
protected override ActivityExecutionStatus Execute(

                   ActivityExecutionContext executionContext)

{

    DashboardData db = DatabaseHelper.GetDashboardData();



    var newPage = new Page();

    newPage.UserId = UserId;

    newPage.Title = Title;

    newPage.CreatedDate = DateTime.Now;

    newPage.LastUpdate = DateTime.Now;



    db.Pages.Add(newPage);

    db.SubmitChanges(ConflictMode.FailOnFirstConflict);

    NewPageId = newPage.ID;



    return ActivityExecutionStatus.Closed;

}

DashboardFacade,这是业务层的切入点,是相当简单的。它知道哪些工作流程上调用哪些操作。它只是需要的参数,并调用正确的工作流程操作。例如,它具有NewUserVisit的功能它什么也不做,但执行NewUserVisitWorkflow的。
public class DashboardFacade

{

  private string _UserName;



  public DashboardFacade( string userName )

  {

    this._UserName = userName;

  }



  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;

  }

有三个主要的头痛,我已经解决,同时实现业务层,使用工作流和的DLinq。在ASP.NET工作流的同步执行获取对象的工作流程执行后完成从另一个同步调用一个工作流在ASP.NET工作流的同步执行
工作流程一般是由异步执行。 WorflowRuntime通常只创建一次每个应用程序域,并运行相同的实例是在同一个应用程序域使用无处不在。在ASP.NET中,可以确保一个WorkflowRuntime的实例,并随处可见的唯一途径是通过存储在HttpApplication的。此外,您不能使用缺省的调度服务,异步执行的工作流程。您需要使用ManualWorkflowSchedulerService,这是专为同步工作流程执行。
有一个方便的类,称为WorkflowHelper工作流的创建和执行。其ExecuteWorkflow功能同步执行工作流。
public static void ExecuteWorkflow( Type workflowType,

        Dictionary<string,object> properties)

{

   WorkflowRuntime workflowRuntime =

        HttpContext.Current.Application["WorkflowRuntime"] as

        WorkflowRuntime;



   ManualWorkflowSchedulerService manualScheduler =

               workflowRuntime.GetService

               <ManualWorkflowSchedulerService>();



   WorkflowInstance instance =

        workflowRuntime.CreateWorkflow(workflowType, properties);



   instance.Start();

   manualScheduler.RunWorkflow(instance.InstanceId);

}

需要执行的工作流程和数据字典的类型传递给工作流。
在执行任何工作流之前,首先,在WorkflowRuntime需要初始化一次且仅一次。这是在Application_Start事件的Global.asax。
void Application_Start(object sender, EventArgs e)

{

    // Code that runs on application startup

    DashboardBusiness.WorkflowHelper.Init();

}

WorkflowHelper.Init的初始化工作:
public static WorkflowRuntime Init()

{

    var workflowRuntime = new WorkflowRuntime();



    var manualService = new ManualWorkflowSchedulerService();

    workflowRuntime.AddService(manualService);



    var syncCallService = new Activities.CallWorkflowService();

    workflowRuntime.AddService(syncCallService);



    workflowRuntime.StartRuntime();



    HttpContext.Current.Application["WorkflowRuntime"] = workflowRuntime;



    return workflowRuntime;

}

在这里,你看到两个服务添加到工作流运行时。一个是同步执行,而另一个是从另一个同步执行的一个工作流程。从另一个同步调用一个工作流
,这是一个重大头痛的问题解决。 InvokeWorkflow活动与Workflow Foundation的执行异步工作流。所以,如果你是从ASP.NET调用工作流,这反过来又调用另一个工作流,第二个工作流是要提前终止,而不是完全执行。究其原因,ManualWorkflowSchedulerService将执行第一个工作流同步,然后完成工作流的执行,并返回。如果您使用InvokeWorkflow活动,以从第一个工作流运行的另一个工作流,它会启动另一个线程,它不会得到足够的时间来完全执行父工作流结束前。
{S9}
在这里,你看到只有一个在第二个工作流的活动得到执行的机会。剩下的两个活动没有得到调用。
幸运的是,我发现了一个同步的工作流程执行的执行情况:
的活动作为输入的工作流程和它同步执行。这项活动的实施是非常复杂的。让我们跳过它。获取对象的工作流执行后完成
这是最难的。获取数据的工作流通常的方法是使用CallExternalMethod活动。您可以通过一个接口调用工作流的同时,和工作流内的活动,可以通过接口调用主机。呼叫者可以实现的接口和工作流的数据。
这是该接口必须使用内部数据类型或类型是可序列化的要求。 SERIALIZABLE是一个要求,因为工作流程可以去睡觉或得到坚持和恢复以后。但是,的DLinq实体类不能作出序列化。 SqlMetal生成的类是不作为[Serializable的]标记第一。即使你手动添加属性,它不会工作。我相信,在编译过程中,其他一些没有得到Serializable属性的运行时类的类被编译成。因此,你不能传递的DLinq实体类活动的工作流宿主。
我发现的解决方法是通过在字典中,我们通过工作流的属性的对象引用。由于ManualWorkflowSchedulerService同步运行的工作流程,对象引用保持在一生的工作流程有效。没有跨AppDomain的调用,所以没有必要进行序列化。此外,修改的对象,或利用他们不造成任何性能问题,因为对象是在同一进程中分配。
下面是一个例子:
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;

}

到目前为止,一切都很好。但是,你怎么写的DLinq代码在WinFX的项目?如果您创建一个WinFX的项目,并开始编写LINQ代码,它将无法编译。 LINQ需要一个特殊的编译器以生成C#2.0中白细胞介素,LINQ代码。有一个专门的C#编译器在quot; C:\ PROGRAM FILES \ LINQ预览\的binquot;文件夹其中的MSBuild编译的LINQ代码,以便使用。经过长期斗争和一个LINQ项目文件和WinFX的项目文件之间的比较,我发现,WinFX的项目已在最后的一个节点:
<Import

  Project="$(MSBuildExtensionsPath)\Microsoft\Windows Workflow Foundation\

           v3.0\Workflow.Targets" />

和LINQ项目的节点:
<Import Project="$(ProgramFiles)\LINQ Preview\Misc\Linq.targets" />

这些说明建设项目选择正确的MSBuild脚本。但是,如果你只是把LINQ的节点在WinFX的项目,这是行不通的。你必须注释掉第一个节点:
<!--<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" />-->

在此之后,它内置的代码,一切都运行成功。
但是,条件和规则的工作流没有运行。在运行时,工作流程扔quot;工作流验证Exceptionquo​​t;当我在规则中使用的代码,它的工作原理。但如果我使用声明性规则条件,然后,它不工作。声明性规则的工作流程或活动,它包含了所有的XML格式的定义的规则下增加一条,作为嵌入的资源。它出现的规则。文件不正确嵌入式工作流运行时无法找到它,在执行工作流。
{S10}
现在,这对我是一个死胡同。如果我创建一个定期的WinFX的项目,那么它工作得很好。但话又说回来,我可以不写在一个普通的WinFX的项目LINQ代码。所以,我要创建一个LINQ和WinFX的项目组合,并没有使用声明性规则。但我拼命想编写工作流和活动规则。我在这个问题上纠缠了整整一夜,但没有发现任何解决方案。它是如此令人沮丧。然后在黎明时,到处是绝对的沉默和太阳上升,我有我从天上神的启示:汝等带来的苦难,你以上的源。
所以,我没有。我从一个级别。CS文件下的规则。文件(苦难之源)项目一级向上。然后,它看起来像这样:

对于这一点,我不得不用记事本打开项目文件(csproj)和删除的LT; D​​ependentUpongt; LT下的节点; EmbeddedResourcegt;节点:
<ItemGroup>

 <EmbeddedResource Include="Activities\CreateDeafultWidgetsOnPageActivity.rules">

<!-- <DependentNode>CreateDeafultWidgetsOnPageActivity.cs</DependentNode> -->



 </EmbeddedResource>

和它的工作!但绝对没有,在世界上,我可以有已知的,有权的方式呢?第6天:开关问题
小部件需要知道它的首次负荷的部件,或者如果它是一个回传。通常情况下,当它第一次负载,部件负载从他们的持久性状态的所有设置和首次呈现的UI。 具体方法如下:
下一步结论







{A19}







奥马尔Zabir


的问候,


的问候,
奥马尔Zabir




感谢
至于,


{S16}




干杯

回答