四月 19, 2007

第一天:用UpdatePanel建造Widget的容器

这里有两个概念,一个是Widget容器一个是Widget. Widget容器只是提供一个头和内容的区域.Widget加载在Widget容器的内容区域.在这个页面里每个Widget初始化是通过WidgetContainer 服务器端控件动态创建的.实际上Widget也是服务器控件在Widget容器内部动态创建.

在整个页面刷新或者单个Widget更新的时候每个Widget都是通过里面包含的更新控制板来获取更新的.例如.Widget实际是在容器内的UpdatePanel里加载.因此,不论怎么样Widget都不会回调数据.

正确的组合UpdatePanel和UpdatePanel里的Html Elements是比较困难的.比如,第一次我在Widget里面放了一个UpdatePanel.当每个widget里面只有一个UpdatePanel时,它工作的很好.但是UpdatePanel里面的HTml附加的扩展是个问题.当UpdatePanel刷新的时候,他会创建一个新的,Html扩展就不存在了.由于所有的扩展附属与先前的Html上,要想不丢失必须这些扩展也在UpdatePanel里面.把扩展放到UpdatePanel的意思是,不论UpdatePanel什么时候刷新,新的扩展实例都要创建和初始化.创建这个UI是非常慢的.你可以看到当widget回调的时候很迟缓.

因此.最终的方案是把标题区域和内容区域分开到多个UpdatePanel里面.一个UpdatePaenl负责标题区域一个负责widget.当Widget做一些事情的时候需要widget刷新就不需要标题刷新了,并且扩展可以附加到标题那么就不会丢失啦.CustomFloatingBehavior 就是附加到标题的扩展程序.同样的这个扩展程序需要放到UpdatePanel里面.但是把扩展程序放到UpdatePanel里面就需要每次UpdatePanel刷新就要创建和初始化一次.这样的性能不是很好.
800)this.style.width=800;” src=”http://www.codeproject.com/Ajax/MakingGoogleIG/WidgetContainer1.png” alt=”Widget Container first idea” onclick=”window.open(this.src)” style=”cursor: pointer” />
因此,到目前为止,最好的方案是两个UpdatePanel在每个WidgetContainer里面,一个只是包含标题内容,但不是真个标题部分.当这个标题UpdatePanel刷新的时候.DIV包含的标题部分在UpanelPanel外面而不需要刷新.这样我们也可以放一个CustomFloatingBehavior 扩展程序在UpdatePanel外面.这样这个扩展就可以附加到标题容器的DIV上了.
800)this.style.width=800;” src=”http://www.codeproject.com/Ajax/MakingGoogleIG/WidgetContainer2.png” alt=”Widget Container final idea” onclick=”window.open(this.src)” style=”cursor: pointer” />
这个WidgetContainer 十分简单.他有一个包含标题名称和展开/折叠/关闭按钮的标题部分和一个包含Widgegt的内容区域.在这个方案里”WidgetContainer.ascx” 文件是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容器创建接着widget在容器内部创建.WidgetContainer是核心框架和Widget之间通道,通过他提供的API来存储状态或者改变状态,像展开/折叠等等.
当Widget折叠或者关闭等等信息也是通过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地址加载widget.然后把widget放到内容panel里.It also passes its own reference as IWidgetHost to the actual widget

WidgetContainer实现了IWidgetHost接口使目前的widget与框架和容器来通信.
public interface IWidgetHost
{
        void SaveState(string state);
        string GetState();
        void Maximize();
        void Minimize();
        void Close();   
        bool IsFirstLoad { get; }
}

执行起来非常的简单.比如.可以用IWidgetHost.Minimize 来折叠Widget内容区域.

首先我们更新WidgetInstance 然后刷新UI.这个Widget也通过IWidget接口获得一个回调.

除了Close以外IWidgetHost都能容易的实现所有的功能.当发起Close信号的时候,我们需要在这个页面移除这个Widget.就是说,WidgetContainer还在这个页面而这个WidgetInstance要从数据库移除.WidgetContainer 自己是不能做到的.需要通过包含WidgetContainer的所在列来实现.Default.aspx包含所有的WidgetContainer.因为,当Close事件触发的时候,WidgetContainer给Default.aspx产生一个事件,并且default.aspx移除这个Widget和刷新这列.

void IWidgetHost.Minimize()
{
    DatabaseHelper.Update<WidgetInstance>(this.WidgetInstance,
                                         delegate(WidgetInstance i)
    {
        i.Expanded = false;
    });
       
        this.SetExpandCollapseButtons();
        this._WidgetRef.Minimized();

        WidgetBodyUpdatePanel.Update();       
}

四月 10, 2007

用Dlinq来存储数据


Dlinq非常有趣.它写数据储存层是如此的简单并且能生成最佳的Sql.如果你以前没有用过Dlinq那就来感受一下他所带来的冲击吧.
当你用Dlinq的时候,你只用设计数据库然后用SqlMetal.exe创建包含所有数据储存代码和实体类的数据存储类.按照数据库设计手工写这些实体类和数据访问类的时候是件很痛苦的事情.不管什么时候你要重新设计数据库你必须修改实体类和修改数据层的”insert update delete get”方法.当然你可以用第三方的ORM工具或者自己的代码生成工具来产生实体类和数据层代码.Dlinq就可以完成这些所有的事情!
Dlinq最好的事情是他可以生成只包含必要的关键而不需要生成全部的对象.目前ORM工具或者Object Oriented Database类库想要支持必须需要特定的编译器.为了表现优雅的性能.你可以不选择自己不需要的字段,也不需要建造一个包含所有字段的巨大的类.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生成的类.
你想修改页面的名字:
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查询他发送给数据库的数据.你可能还觉得这些”变种”老东西好像回到了组件时代.它可能不像输入代码或者手写代码那样又快有强大的准确表达你的意思. 你会惊奇的发现Linq编译器可以把Dlinq代码转化为这么优雅简单的.net 2.0 IL.这不是魔术也不是额外的类库.也不想大多数ORM工具需要靠大量的反射来实现.

四月 8, 2007

Widgets
Widgets是一个比较有趣的架构,你只需要专注于widget相关特性而不需要担心像认证,授权,个性化,框架等等问题.而这所有的事情继承在主项目.并且你可以建立一个独立与主项目的部件.并且不需要主项目的源码.尽尽建立一个正常的ASP.Net2.0的web站点,创建一个用户控件,使这个控件具有一般的回调模式实现一些接口就可以了,而不需要考虑javascript.你不需要再担心AJAX和javascript啦,我已经尽力创建了一个好的架构.同样这个架构允许你用一般的ASP.NET2.0控件,Ajax控件或者任何其他的ASP.NET Ajax扩展控件.你也可以完全使用.NET2.0 或者3.0的服务器端程序.你可以用ViewState来储存状态.你也可以使用ASP.NET缓存来储存数据.它比当前的那种必须用JAVASCRIPT和必须遵循一些特殊的API规则严格的”不回调”模式的start pages的Widget更好.开发那些Widget是一件很痛苦的经历

技术
客户端使用ASP.NET AJAX RC 和AJAX控件.几个自定义扩展用来支持专门的拖拽特性
中间层利用Windows WorkFlow foundation,数据层用Dlinq 和sql server2005.

web层
他根本上只是一个default.aspx页面.你能看到的所有客户端特性都在DEFAULT.ASPX页面和Widget里面.我们不能使页面回调和太多导航在页面之间,因为这样就不是web2.0了.因此所以的特性必须在一个页面提供并且不能重定向到其他页面.

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和返回一个在第一个页面里包含用户设置,页面和部件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;
}

但是性能怎么样呢?在同步执行时workflow的执行是非常快的.这是从VisualStudio输出窗口得到的记录.

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

四月 6, 2007

原文:Build Google IG like Ajax Start Page in 7 days using ASP.NET Ajax and .NET 3.0

第一次翻译文章,有点乱而且还不太通顺.希望作者看到后没有被气死.

文章很长,先翻译个介绍出来!

介绍
我将用7晚给你展示用asp.net ajax,.net3.0,Linq,DLing和XLinq 制作一个类似与Google IG的开始页面.我将记录我每天的开发经历,技术难题,有意思的发现和重要的计划在这篇文章里.你会发现相当接近实际的Google IG.它有可拖拽的部件,个性化页面,多页面等等属性.它不仅仅是一个原型或者一个简单的项目.他是一个真实的开源start page 运行在http://www.dropthings.com 你可以每天用他.欢迎你加入到开发行列为这个项目制作部件.

Screenshot

更新日志
2007年1月6日:Scott Guthrie告诉我如何在web.config里面debug设置为”false”的时候改善asp.net ajax客户端的性能.
2007年1月5日:探讨了部署的问题

什么是Web2.0 ajax Start page
Start page 允许您通过拖拽部件建立一个属于你自己的主页.这些部件可以给你提供像任务表,地址簿,联络表,rss订阅等等功能的独立程序.Start Page 广泛被认为是Rss聚合或者是内容聚合器.但你可以用他来组织你的数字生活而不仅仅是阅读rss. Ajax start page 依靠艺术性的UI和更多的javascript特效已经领先于像My Yahoo这样的旧式start pages.通过利用Ajax和更多高级的Javascript和DHTML技术让你像使用桌面应用程序一样.
Pageflakes, Live, Google IG, Netvibes, Protopage, Webwag等等是一些流行的Ajax Start Pages.在这些Start Pages中Google IG是最简单的一个.在这里我制作一个介于Google IG和飞鸽之间的Ajax和富客户端方面的Start Pages.Google IG大部分是web1.0的那种回调模式不是真正Ajax的.例如你在换页,添加模块,改变部件属性的时候可以看到他在回调.但我已建立的这个Start Pages使用更多的ajax来提供丰富的客户端体验接近于你在飞鸽上看到的那样.

特征

用拖拽部件创建你的页面.你可以完全自定义这个页面.你在这个页面可以添加删除部件.你可以拖拽他们.关闭你的浏览器并重新打开他,你会看到跟离开他一样正确格局.不用注册就可以使用只要你喜欢.

Drag & Drop

一旦你放了一些内容到你的页面里,你会发现一个页面根本不够.你可以选择使用多个页面.你可以创建很多页面只要你喜欢.

七月 17, 2006

SELECT *
FROM sysprocesses
WHERE (dbid IN
          (SELECT dbid
         FROM sysdatabases
         WHERE name = ‘oa_db’))

七月 7, 2006

作为所谓的Blog三大特征之一的Trackback Ping,在网络上并没有一个真正的规范,它实际上最早是在Moveable发明并开始运用的一种技术,与另外两项被广泛认为是Blog最主要特征的RSS和Permalink相比,RSS本质上是是一种遵循W3C RDF规范的XML格式,Permalink是一种非常通用的静态地址技术,而Trackback Ping目前为止仅仅是在blog程序中得到运用,除了Moveable的一份技术规范文档外,也没有什么权威的标准,尤其在国内,众多blog程序有的支持有的不支持,有的有限支持,甚至还有因标准不一致而不能互相通信的(参见《blog is dead(blog已死)》http://blog.igooi.com/archive/2005/10/28/6716.aspx)。

  但是作为开发人员,我们不需要去讨论或者争论Trackback是不是死了、它会不会带来恶意Spam这些问题,就像垃圾邮件的存在,并不影响电子邮件的技术进步和使用一样。对于开发人员来讲,既然Trackback被认为是Blog的三大技术之一,而且blog程序的用户有这种需求,那我们在开发blog的时候,就应该包含这项技术。而且,我们应该尽可能地使我们开发的技术符合标准,或者规范,至少让它发挥作用。

  Trackback Ping是由Moveable Type发明的规范,那么在没有更权威的标准之前,我们使用这项技术,当然应该以他们的规范为准,这里是他们的Trackback技术规范文档:http://www.movabletype.org/docs/mttrackback.html

  Trackback的完整实现至少包含两个方面,其一:客户端发送Trackback Ping;其二:服务器端接收和处理Trackback Ping,并向客户端返回处理结果。然后,根据需要我们可以考虑在客户端接收或者不接收,处理或者不处理返回的信息。下面是具体的代码:

         /// 作 用:向指定的URL发送Trackback Ping,并根据服务器端返回的信息,提示用户处理情况。参数必须Server.URLEncode
        ///目标URL,也即所引用的blog所提供的引用地址
        ///我的Blog的URL
        ///我的blog站点名称
        ///当前这篇blog的标题
        ///当前这篇blog的摘要
        /// 返回结果:字符串,以“|”分隔,第一部分为数字,0表示成功,1表示有错;第二部分是具体信息。
        public static int Send(string RemoteUrl, string MyBlogURL, string MyBlogName, string MyBlogTitle, string MyBlogExcerpt)
        {
            //’构造要发送的请求内容
            try
            {
                string strPostInfo = “title=” + MyBlogTitle;
                strPostInfo += “&url=” + MyBlogURL;
                strPostInfo += “&excerpt=” + MyBlogExcerpt;
                strPostInfo += “&blog_name=” + MyBlogName;

                byte[] strs = System.Text.Encoding.Default.GetBytes(strPostInfo);

                HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(RemoteUrl);

                myRequest.Method = “POST”;
                myRequest.ContentType = “application/x-www-form-urlencoded”;
                myRequest.ContentLength = strs.Length;
                Stream newStream = myRequest.GetRequestStream();
                // 发送数据
                newStream.Write(strs, 0, strs.Length);
                newStream.Close();
                return 1;
            }
            catch (System.Exception es)
            {
                return 0;
            }
        }

六月 27, 2006

注意:此属性在 .NET Framework 2.0 版中是新增的。

获取或设置一个值,该值指示回发后是否将用户返回到客户端浏览器中的同一位置。

命名空间:System.Web.UI
程序集:System.Web(在 system.web.dll 中)

属性值

如果应保持客户端位置,则为 true;否则为 false

Collapse 图像备注

当网页回发到服务器时,用户将返回到该页的页首。在较长的网页中,这意味着用户必须将页滚动到该页的上一位置。当
MaintainScrollPositionOnPostback 属性设置为 true 时,用户将返回到该页的上一位置。 

很方便的一个功能,这点微软也考虑到了,越来越喜欢.net 了

六月 5, 2006

  .net 2.0 framework 中新增了 System.Transactions 命名空间,其中提供的一系列接口和类使得在 .net 2.0 中使用事务比起从前要方便了许多。有关在 .net 2.0 下操作数据库事务的文章已经有了很多,这里只提一下如何设计自定义事务操作。

  一、事务使用基础

  先看一段使用事务的代码:

1using (TransactionScope ts= new TransactionScope())
2{
3    //自定义操作
4    ts.Complete();
5}

  这里使用 using 语句定义了一段隐性事务。如果我们在该语句块中加入一段对 SQL Server 操作的代码,那么它们将会自动加入这个事务。可以看出,这种事务的使用方式是极其方便的。

  那么,有没有可能在该语句块中加入我们自己定义的事务操作,并且该操作能够随着整个事务块的成功而提交,随其失败而回滚呢?答案当然是可以的,否则我就不会写这篇随笔了。

  二、实现自定义事务操作

  根据事务的特性,我们可以推想:这个操作必须有实现提交和回滚之类动作的方法。没错,这就是 System.Transactions 命名空间中的 IEnlistmentNotification 接口。我们先写一个最简单的实现:

 1class SampleEnlistment1 : IEnlistmentNotification
 2{
 3    void IEnlistmentNotification.Commit(Enlistment enlistment)
 4    {
 5        Console.WriteLine(”提交!”);
 6        enlistment.Done();
 7    }
 8
 9    void IEnlistmentNotification.InDoubt(Enlistment enlistment)
10    {
11        throw new Exception(”The method or operation is not implemented.”);
12    }
13
14    void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
15    {
16        Console.WriteLine(”准备!”);
17        preparingEnlistment.Prepared();
18    }
19
20    void IEnlistmentNotification.Rollback(Enlistment enlistment)
21    {
22        Console.WriteLine(”回滚!”);
23        enlistment.Done();
24    }
25}
26
27

  好,定义完之后,还需要向事务管理器进行注册,把它加入到当前事务中去:

1using (TransactionScope ts= new TransactionScope())
2{
3    SampleEnlistment1 myEnlistment1 = new SampleEnlistment1();
4    Transaction.Current.EnlistVolatile(myEnlistment1, EnlistmentOptions.None);
5    ts.Complete();
6}

  执行这一段代码,我们可以得到以下的输出:

准备!
提交!

  先解释一下,当调用 ts.Complete() 方法的时候,表示事务已成功执行。随后,事务管理器就会寻找当前所有已注册的条目,也就是 IEnlistmentNotification 的每一个实现,依次调用它们的 Prepare 方法,即通知每个条目做好提交准备,当所有条目都调用了 Prepared() 表示自己已经准备妥当之后,再依次调用它们的 Commit 方法进行提交。如果其中有一个没有调用 Prepared 而是调用了 ForceRollback 的话,整个事务都将回滚,此时事务管理器再调用每个条目的 Rollback 方法。

  而如果我们将前面的 ts.Complete() 行注释掉,显然执行结果就将变为:

回滚!

  三、一个实现赋值的自定义操作

  考虑一下,我们要实现一个事务赋值操作。该如何做法?以下是一个例子:

 1class SampleEnlistment2 : IEnlistmentNotification
 2{
 3    public SampleEnlistment2(AssignTransactionDemo var, int newValue)
 4    {
 5        _var = var;
 6        _oldValue = var.i;
 7        _newValue = newValue;
 8    }
 9
10    private AssignTransactionDemo _var;
11    private int _oldValue;
12    private int _newValue;
13
14    void IEnlistmentNotification.Commit(Enlistment enlistment)
15    {
16        _var.i = _newValue;
17        Console.WriteLine(”提交!i的值变为:” + _var.i.ToString());
18        enlistment.Done();
19    }
20
21    void IEnlistmentNotification.InDoubt(Enlistment enlistment)
22    {
23        throw new Exception(”The method or operation is not implemented.”);
24    }
25
26    void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
27    {
28        preparingEnlistment.Prepared();
29    }
30
31    void IEnlistmentNotification.Rollback(Enlistment enlistment)
32    {
33        _var.i = _oldValue;
34        Console.WriteLine(”回滚!i的值变为:” + _var.i.ToString());
35        enlistment.Done();
36    }
37}
38
39class AssignTransactionDemo
40{
41    public int i;
42
43    public void AssignIntVarValue(int newValue)
44    {
45        SampleEnlistment2 myEnlistment2 = new SampleEnlistment2(this, newValue);
46        Guid guid = new Guid(”{3456789A-7654-2345-ABCD-098765434567}”);
47        Transaction.Current.EnlistDurable(guid, myEnlistment2, EnlistmentOptions.None);
48    }
49}
50
51

  然后,这样来使用:

 1AssignTransactionDemo atd = new AssignTransactionDemo();
 2atd.i = 0;
 3using (TransactionScope scope1 = new TransactionScope())
 4{
 5    atd.AssignIntVarValue(1);
 6    Console.WriteLine(”事务完成!”);
 7    scope1.Complete();
 8    Console.WriteLine(”退出区域之前,i的值为:” + atd.i.ToString());
 9}
10Thread.Sleep(1000);
11Console.WriteLine(”退出区域之后,i的值为:” + atd.i.ToString());

  好,运行这一段代码,我们可以看到如下结果:

事务完成!
退出区域之前,i的值为:0
提交!i的值变为:1
退出区域之后,i的值为:1

  从输出结果来看,赋值操作被成功执行了。可是有没有感觉有些奇怪?好,先做个讨论:

  1、如果前面没有 Thread.Sleep(1000) 这一行,那么我们多半会看到最后一行的输出中,i 的值依然会是 0!为什么?想想就容易明白,这里对 Commit 方法是采用的异步调用,如同另开了一个线程。如果主线程不作等待的话,当输出的时候事务的 Commit 方法多半还没有被执行,输出的结果当然就会不对。

  2、这个例子中,赋值操作是在 Commit 方法中才实际执行的。但实际上就本例而言,我们也可以做个调整:将赋值操作放在 AssignIntVarValue 方法的最后去执行,然后把 Commit 方法中的赋值操作去掉。相关的代码变化如下:

 1class SampleEnlistment2 : IEnlistmentNotification
 2{
 3    void IEnlistmentNotification.Commit(Enlistment enlistment)
 4    {
 5        enlistment.Done();
 6    }
 7    //其它略
 8}
 9
10class AssignTransactionDemo
11{
12    public int i;
13
14    public void AssignIntVarValue(int newValue)
15    {
16        SampleEnlistment2 myEnlistment2 = new SampleEnlistment2(this, newValue);
17        Guid guid = new Guid(”{3456789A-7654-2345-ABCD-098765434567}”);
18        Transaction.Current.EnlistDurable(guid, myEnlistment2, EnlistmentOptions.None);
19        i = newValue;
20        Console.WriteLine(”提交前改变!i的值为:” + i.ToString());
21    }
22}
23
24

  这样,执行结果将会变为:

提交前改变!i的值为:1
事务完成!
退出区域之前,i的值为:1
退出区域之后,i的值为:1

  3、在前面的基础上,当把调用的地方作如下改动,使事务失败:

1using (TransactionScope scope1 = new TransactionScope())
2{
3    atd.AssignIntVarValue(1);
4    Console.WriteLine(”事务失败!”);
5    //scope1.Complete();
6    Console.WriteLine(”退出区域之前,i的值为:” + atd.i.ToString());
7}

  此时的执行结果将变为:

提交前改变!i的值为:1
事务失败!
退出区域之前,i的值为:1
回滚!i的值变为:0
退出区域之后,i的值为:0

  可见,事务已成功回滚。

  四、进一步的讨论

  前面我们都是只进行了一次赋值操作,如果我们需要进行两次呢?

1using (TransactionScope scope1 = new TransactionScope())
2{
3    atd.AssignIntVarValue(1);
4    atd.AssignIntVarValue(2);
5    Console.WriteLine(”事务失败!”);
6    //scope1.Complete();
7    Console.WriteLine(”退出区域之前,i的值为:” + atd.i.ToString());
8}

  这时的执行结果将会是如何?我们当然是希望回滚的时候,i 的值能先变回为 1,再变回为 0。但是实际结果呢?

提交前改变!i的值为:1
提交前改变!i的值为:2
事务失败!
退出区域之前,i的值为:2
回滚!i的值变为:0
回滚!i的值变为:1
退出区域之后,i的值为:1

  显然,事务的回滚并没有按照我们希望的顺序来,是何原因?分析一下机制就能知道,事务管理器向每个条目发出回滚命令的时候只是发出了一个异步调用,并且很可能还是按登记的顺序来发出的,这样一来,Rollback 方法的调用顺序显然就不能保证了。

  这时,如果将 Rollback 方法作一个小调整:

 1void IEnlistmentNotification.Rollback(Enlistment enlistment)
 2{
 3    while (_var.i != _newValue)
 4    {
 5        Thread.Sleep(500);
 6    }
 7    _var.i = _oldValue;
 8    Console.WriteLine(”回滚!i的值变为:” + _oldValue.ToString());
 9    enlistment.Done();
10}

  再次运行之,结果就对了:

提交前改变!i的值为:1
提交前改变!i的值为:2
事务失败!
退出区域之前,i的值为:2
回滚!i的值变为:1
回滚!i的值变为:0

  结果的正确其实并不是调用的顺序就对了,只是 Rollback 方法在执行的时候先检查一下 _newValue 的值是否与当前 i 的值一致,不一致的话就等上一会儿。在等待的过程中,另一个实例的 Rollback 方法被执行,而它检查发现是匹配的,所以就会回滚到 1。第一个 Rollback 等待结束后再检查发现匹配了,于是就回滚为 0。

  当然实际应用中,这种方法是极不可取的。且不说执行顺序依然会有很大的风险,光是设计方式就有大问题。那么在实际应用中我们应当如何去做呢?这里只提供一下设计思想,具体的实现代码不再列出了。

  在前面的例子中,两次赋值共进行了两次登记,这一点是引发不稳定性的起因。我们应当考虑,两次赋值依然只登记一次,在第一次赋值的时候,建立一个 SampleEnlistment2 的实例并在 AssignTransactDemo 中保存下来,并且 SampleEnlistment2 需要记录当前的操作。下一次赋值时,仍然使用这个实例,只进行操作记录即可。这样,当回滚的时候,它根据记录的反顺序执行回滚操作就可以了。

  再进一步呢?如果说有多个 Transaction 需要进行赋值操作呢?这时我们可以在 AssignTransactionDemo 类中加入一个 Dictionary,使用的时候根据 Transaction 去寻找相应的条目即可。

  好,本文讨论暂到此为止。在微软的101个例子中,有一个使用事务进行文件拷贝的例子。那里面有比较深入的实现。如果你还没有看过,推荐去研究一下,相信你读过此篇随笔,研究它应当不再是个难题。

五月 22, 2006

   答案 当然是 肯定的。不作任何处理的默认情况下,会出问题!

    各位在同一电脑上同时安装并运行.net 1.1 和 .net 2.0 两个版本的朋友要小心啊!

    您的项目是不是时不时会出现 Server Application Unavailable 错误呢?或者一直连续都出这个错误!

             今天我朋友将其从1.1升级至2.0的项目发布至公司安装有windows 2003的服务器上,结果2.0的项目始终无法成功运行。甚至还造成服务器上正在为全公司服务的基于.net Framework 1.1的web程序出错。他感觉很奇怪,在自己电脑上明明运行的很好啊,怎么一到服务器上就不行了?
             他发布2.0 web程序的过程如下:

        1、在服务器上创建一个目录,然后将发布后的所有Web程序的文件复制到这个新建的目录。
        2、在服务器的IIS上新建一个虚拟目录,然后 将其配置成可以执行脚本的应用程序,(过程与创建.net Framework 1.1版本的程序相同。) 然后,将此虚拟目录的.net Framework 配置成2.0。
     大功告成后,开始访问刚配置的程序,却得到如下错误:

Server Application Unavailable

The web application you are attempting to access on this web server is currently unavailable.  Please hit the “Refresh” button in your web browser to retry your request.

Administrator Note: An error message detailing the cause of this specific request failure can be found in the application event log of the web server. Please review this log entry to discover what caused this error to occur.

      之后向我求助,经过仔细阅读了IIS帮助文档,才发现,原来这个错误是由 IIS6 应用程序隔离机制造成的。IIS默认的应用程序隔离机制被称为:“工作进程隔离模式”,在此模式中,应用程序被分为多个组,每个组就是一个“应用程序池” ,每个应用程序池之间是相互隔离的。 隔离的好处当然就是安全啦,稳定啦,等等。IIS中的每个应用程序池由一个“工作进程”分别进行管理,也就是”W3wp.exe” 。如果有多个应用程序池中的程序运行,我们就能看到多个w3wp.exe。 我们平时新建的虚拟目录都默认被指向IIS6的“DefaultAppPool” 中, 所以在默认情况下,不管你有多少个asp.net程序在运行,在“Windows任务管理器”中你只能看到一个w3wp.exe进程。

      出现上述错误的原因是: .net Framework 2.0的程序与.net Framework1.1(或1.0)的程序被放入同一个应用程序池(默认情况下放入DefaultAppPool池),也就是由同一个工作进程: w3wp.exe 进行管理,而单个工作进程是无法同时管理不同的程序(或者不同版本的程序)的。如果先访问.net framework 1.1的页面,则工作进程先加载并管理了 1.1版本的程序集,此时访问.net framework 2.0的web程序页面,Server Application Unavailable 错误就出来了。 反之,如果在默认应用程序池的w3wp.exe尚未启动前先访问了 2.0的web程序(此时应用程序集已经加载了.net framework2.0的Web程序集),再访问1.1或1.0的Web程序页面时,同样会出现“服务器应用程序不可用”  这样的中文提示。(您可以结束掉以前的w3wp.exe进行测试。)
   
    我朋友之所以在自己的开发机器上没出现此错误是因为他在开发基于.net framework 2.0的项目时,一直都只访问这个2.0的web程序,跟本未曾访问过其他web程序。而朋友的服务器上已经存在1.0 的项目,并且一直有人使用。这个错误在今天之前从未碰到过,其实大部分人在开发一个项目时,都只访问正在开发的项目。很有可能就忽略了这个问题,而到了发布的时候却出现错误,搞得手忙脚乱。

   解决办法: 在IIS中新建一个应用程序池,然后选中你的 基于.net framework2.0的虚拟目录,点“属性”-》在“应用程序池” 中选择刚才新建的的应用程序池,点击“确定”。

    如果你的电脑主要是为了学习,开发,测试web程序,完全可以建一个类似于名为”dotnet2.0″的应用程序池,以后所有新建的2.0的虚拟目录都指向此应用程序池。
    
    [2006-03-16 更新] : 如果通地vs 2005IDE 直接创建在IIS 上创建Web Site时,vs 2005会自动添加 ASP.NET 2.0 应用程序池,并且将新创建的Web Size 虚拟目录指向新建的应用程序池。

转:http://cwbboy.cnblogs.com/archive/2006/02/28/339710.aspx

五月 20, 2006

当在ASP.NET2.0的站点的根目录下添加一个app_offline.htm后,你的站点就可以停止了,所有的对aspx文件的请求都会取消,而且页面会定位在app_offline.htm页面。当你在对应用程序做大规模的升级或对数据库更新的时候这个页面将会为您提供极大的方便。

下一页 »