Joycode@Ab110.com

December 2006 - Posts

工商银行网站的弱智bug

举个例子来说,请访问以下地址:

http://www.icbc.com.cn/news/hotspot.jsp?column=%3ccenter%3e%3cfont+size%3d7%3e%b9%a4%c9%cc%d2%f8%d0%d0%b8%f8%b2%a9%bf%cd%cc%c3%d7%a2%d7%ca%a3%b5%a3%b0%a3%b0%cd%f2%3c%2ffont%3e%3c%2fcenter%3e

地址的访问效果如下:

工行的bug截图2  

最初的报道可以看CSDN首页的这篇博客:

http://blog.csdn.net/hcat1999/archive/2006/12/31/1470770.aspx

如果这里显示的是某些文字的话,或者一些带木马的内容,将非常非常严重。

这可不是钓鱼网站,而是实实在在存在的工行网页。

Run As Administrator

这个post主要用来share我的IdentityScope类

在Windows Vista运行Visual Studio 2005(without SP1)时,最好使用Vista的Run as administrator的功能,否则有些功能就不能正常工作。而在我们开发的.NET程序内部,有时也会碰到需要临时提高权限的情形。ASP.NET程序员经常问的一个问题是,我的代码为什么没有权限创建一个文件?基本的解决方案有三个:

  • 提升ASPNET帐户(在Windows 2003则是Network Service)的权限(不推荐)
  • 为目标文件或文件夹设置ASPNET帐户的读写权限(如果需要访问的文件路径是固定的)
  • 在web.config设置impersonate,以另一个帐户的身份运行程序,比如Administrator...

论坛和新闻组里面常出现的另一个问题是,如何访问网上邻居或者映射的网络驱动器?常见的答案是使用Process.Start方法调用cmd.exe执行一个net use命令,这个方案是可行的但是显然不科学... 这里同样是权限问题。

.NET类库自带了个WindowsImpersonationContext类,可以用来进行用户身份模拟。不过创建这个对象的唯一办法调用WindowsIdentity.Impersonate(IntPtr userToken)方法,而userToken却只有通过Windows API调用而来(根本就是没打算让人用的)... MSDN上语焉不详,而实际上Windows SDK解释的更清楚,在LogonUser页有完整的说明和示例。于是使用P/Invoke封装了LogonUser、ImpersonateLoggedOnUser、RevertToSelf这几个API以及相关的一些枚举类,实现了一个IdentityScope类。演示代码:
Console.WriteLine(WindowsIdentity.GetCurrent().Name);
using (new IdentityScope("SUMA-LP", "Administrator", "********"))
{
    Console.WriteLine(WindowsIdentity.GetCurrent().Name);
}
Console.WriteLine(WindowsIdentity.GetCurrent().Name);

Console将输出:
Redmond\V-Wexia
SUMA-LP\Administrator
Redmond\V-Wexia

你可以看到在这个using代码块里面执行者身份成功的扮演了本机的Administrator。在访问网上共享资源的时候这个类同样有效,比如:
using (new IdentityScope("Domain", "User", "Password", LogonType.NewCredentials, LogonProvider.WinNT50))
{
    File.Copy("file.ext", "\\shared\folder\file.ext");
}

这里使用LogonType.NewCredentials登陆类型意味着本地身份不变,访问网络资源时使用扮演的身份。

IdentityScope类的源代码见:http://blog.joycode.com/sunmast/articles/identityscope_class.aspx

Expression Blend - 创建一个RSS新闻阅读器

从一个比较有趣的例子开始,英文原文见Expression Blend -> Help ->Welcome Screen -> User Guide -> Data -> Create an RSS news Reader

创建数据连结

  1. New Project ( File 菜单), 然后点 Standard Application (.exe).
  2. 在 Project面板的Data下面,点 +XML Source按钮
  3. 输入Joycode作为 Connection Name 并输入 “http://www.joycode.com/MainFeed.aspx” 作为 URL for XML data, 然后点 OK.
  4. 如果你连在Internet上, 你将看到如下图所示的 Data。(但是,注意这里由于没有机会写代码,所以用的是同步方式载入数据,有可能很慢,界面处于不响应状态 - 亲身体会)

 

创建UI和绑定

  1. 展开rss > channel , 然后把description拖拽到artboard上(就是XAML的design视图上)。选择弹出窗口上的 Label并点Create Data Binding 对话框里的OK。
  2. 如果你的机器连在Internet上,你会看到“博客堂”三个字。
  3. 同样的, 展开 rss > channel, 拖拽 link 到artboard上。选择 Label 并接受缺省设置。
  4. 现在创立一个Grid 面板,并覆盖刚才两个控件下方的部分。选中 Grid 面板并找到Data Context 属性,(在Properties 面板中的 Common Properties中)。
  5. 点Data context property的 New 按钮边上的属性标记(小黑框)并在弹出菜单中选择 Data Binding.
  6. 在 Create Data Binding 对话框中, 选中 Data Field tab (缺省), 选中Joycode,然后在右边面板中展开 rss > channel ,并选择 item(Array)。Finish。
  7. 双击Grid 面板激活它, 然后创建一个 ListBox 并让它占据Grid面板的左半边。
  8. 选中 ListBox, 然后找到Properties 面板中的ItemsSource属性。
  9. 点 ItemsSource 属性右边的小黑框,然后点弹出菜单中的 Data Binding。
  10. 选中 Explicit Data Context tab, 然后展开 rss > channel 并选择 item(Array)。 Define Data Template 按钮并在Create Data Template对话框中选择第三个选项 New Data Template。 清除 item 的check box, 勾上title 边上的check box, 然后 OK。 此时如果你连接在internet上,你的标题清单应该出现在listbox里面。
  11. 插入一个TextBlock并占据Grid的右半边,找到Properties面板中的 Text property。
  12. 点 Text properties 右边的小黑框,并选择 Data Binding。
  13. 在 Create Data Binding 对话框中,选中 Explicit Data Context tab, 展开 rss > channel > item(Array), 选中 description 然后点 Finish。 如果你连在internet上,你的TextBlock中应该显示出某个标题的正文。
  14. 按 F5 运行你的应用,你可以改变 ListBox 中的选项来查看不同的文章。

很难看的界面,而且载入时需要等待一会儿。不过,不需要写代码,至少设计人员可以在此基础上设计一个好看一点的界面了。

System.OutOfMemory Exception

貌似PageParser的大量调用,会导致大量的小size的dll、众多memory hole的出现。得到eparg的指教,越来越感觉是这个样子。据joycode老大说,只有2.0这个样子,只有特定的这一个app这个样子。

准备写一个.net 2的测试程序,看这个hole是不是确实很明显?

(详情等下文)

Expression Blend简介

这段时间在学习Expression Blend(之前曾经叫作Expression Interactive Designer),因为这是我将分管的一个新产品。不过由于是Beta版,所以所有的文档都还是英文的。我想,既然一样都花了时间在学习上,不如干脆也试着翻译一些东西,这样有兴趣的人也可以一起来学。水平有限,如果理解不了我的翻译,请参考产品附带的user guide。

这个产品,应该说不是仅仅给开发者用的,更多的是给界面设计(UI或者UE)用的。所以,我学习的时候,出发点是“如何不通过写代码来完成各项界面元素的设计”或者是“原型设计”。

Overview

Microsoft? Expression Blend? BETA—一个全新的,全功能的职业设计工具,用于创造基于Microsoft Windows平台的丰富、复杂的应用界面。 使用 Expression Blend, 可以提供更出色的应用软件并提升客户的体验和满意度。Expression Blend 包括了与 Microsoft Visual Studio? 2005 的集成性,让设计者(designers)和开发者(developers)能够更紧密地作为一个团队一起协作!

Expression Blend 包含:

  • 全套矢量图形工具,包括文本和三维工具
  • 易用的可视化界面,(dockable panels, on-object context menus)
  • 健壮的动画、三维和媒体集成(animation, 3D, and media integration)
  • Advanced, flexible, and reusable customization and skinning options for a variety of common controls
  • 与数据源、外部资源的强大集成
  • 实时设计和markup视图 (Design and XAML view) 

 

针对的客户化需求(Target customization needs)

  • Themes
  • Branding
  • Custom controls
  • 3D
  • Animations
  • Dynamic behaviors
  • Direct manipulation
  • Browsers

针对的应用类型(Target application types)

  • 生产力应用Productivity applications - productivity, efficiency for broader customer base; line of business applications such as Microsoft Office
  • 消费者应用consumer applications - media player, security tools, gadgets
  • 游戏games - entertainment, immersive; simple desktop games or online games
  • kiosks - get information, product directory, airport, museum
  • 专业IT人员工具IT pro utilities - track small jobs with tools such as bug tracking tools to improve efficiency given specific needs that might not be scalable

最佳实践Best practices

一些好的设计之所以好,是因为提高了可用性。这里有些例子:使用Expression Blend 和 .NET Framework 3.0来提高可用性:

  • 真实世界的建模。你可以使用可视化交互使某个特定的控件与其在真实世界的对照物看上去相似并有类似的行为。这项技术在使用者对真实世界的对象很熟悉时特别管用。比如,象计算器这样的简单工具,因为模拟了真实世界的对照物而非常好用。
  • 演示,而不是解释。你可以用动画来演示“关系”、“因果”和“效果”。这项技术可以代替文字解释,让使用者更容易理解。比如,给小孩演示书的控件如何翻页比普通的切换页面更容易被理解。(真的吗?小孩这么白痴?还是我们白痴,所以认为小孩理解不了?这不是一个好例子-这句是我加的。)
  • (大概的意思就是“一看就会,不需要特别的解释”,太难翻译了)Improve affordance. Affordance is a property of an object that suggests how the object is used (as opposed to using a label to explain it). You can use custom control visuals and animations to suggest how nonstandard controls are used.
  • Use natural mapping. Natural mapping is a clear relationship between what the user wants to do and how to do it. You can use custom appearances and interactions to create natural mappings when the standard common controls won’t do.
  • 不需特定的知识。 You can use custom interactions to limit the number of ways to perform an operation and the amount of knowledge required to perform a task.
  • 改进反馈。你可以用客户化的控件外观或者动画,来告诉使用者某些事情是否正在被正确的执行,并展示出相应的进程(例如,花哨的状态栏/状态条,反正时时刻刻都不要人觉得死机了 - 我加的) For example, the Address bar in Internet Explorer in Windows Vista shows the progress for loading the page in the background.
  • 让对象更容易交互。 Fitts定律(Fitts’ law)证明鼠标点一个东西的难度,同与目标的距离成正比,同目标的大小成反比。例如,当鼠标距离近时,你可以用动画效果让一个对象变大,鼠标远时变小,这样可以让对象更容易被点中,并可以在对象较小时节约屏幕。
  • 注意力。你可以通过排版和客户化的外观来突出屏幕上的关键元素,并让其他非关键元素被使用者忽视。(想办法突出重点,减少“噪声”- 我加的)

If designing for Windows Vista, consider adhering to the Windows Vista User Experience Guidelines to establish a high-quality, consistent baseline for all Windows Vista-based applications, no matter how they are implemented.

一个简单的进程互斥类

原来只是打算实现一个进程互斥的功能,而.NET似乎没有内置,所以决定调Windows API自己写。原来简单的几句代码,加上了Exception Handling和Dispose Pattern后,居然搞出了近一百行代码,还没怎么写注释。

/// <summary>
/// Process level simple mutex class. By Sunmast.
/// </summary>
/// <remarks>
/// Always avoid duplicate mutex names.
/// </remarks>
class ProcessMutex : IDisposable
{
    [DllImport("Kernel32.dll")]
    static extern IntPtr CreateMutex(IntPtr lpMutexAttributes, bool bInitialOwner, string lpName);
    [DllImport("Kernel32.dll")]
    static extern bool ReleaseMutex(IntPtr hMutex);
    [DllImport("Kernel32.dll")]
    static extern uint WaitForSingleObject(IntPtr hHandle, int dwMilliseconds);

    const uint WAIT_ABANDONED = 0x00000080;
    const uint WAIT_OBJECT_0 = 0x00000000;
    const uint WAIT_TIMEOUT = 0x00000102;
    const uint WAIT_FAILED = 0xFFFFFFFF;
    const int MAX_PATH = 260;

    IntPtr hMutex;
    bool disposed;

    public ProcessMutex(string mutexName)
        : this(mutexName, int.MaxValue)
    {
    }

    public ProcessMutex(string mutexName, int timeout)
    {
        if (mutexName == null)
        {
            throw new ArgumentNullException("mutexName");
        }
        if (mutexName.Length > MAX_PATH)
        {
            throw new PathTooLongException("The name is limited to MAX_PATH characters.");
        }
        hMutex = CreateMutex(IntPtr.Zero, false, mutexName);
        if (hMutex == IntPtr.Zero)
        {
            throw new InvalidOperationException("Mutex creation failed.");
        }
        uint waitResult = WaitForSingleObject(hMutex, timeout);
        switch (waitResult)
        {
            case WAIT_TIMEOUT:
                throw new TimeoutException("Cannot get mutex ownership due to time-out.");
            case WAIT_OBJECT_0:
                break;
            case WAIT_ABANDONED:
                throw new InvalidOperationException(
                    "The specified object is a mutex object that was not released by the " +
                    "thread that owned the mutex object before the owning thread terminated.");
            case WAIT_FAILED:
                throw new InvalidOperationException("Function failue.");
            default:
                break;
        }
    }

    ~ProcessMutex()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Nothing to do.
            }
            ReleaseMutex(hMutex);
            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

其用法有点类似C#的lock关键字,可以定义一个scope,例:

using (new ProcessMutex("MUTEX_TEST"))
{
    Console.WriteLine("Wait for ENTER key pressed.");
    Console.ReadLine();
}

看来白忙了,.NET已经内置了这个功能了。原来一直以为所有的WaitHandle都是线程级别的...

Don't make me think

Steve Krug的一本好书。很简单,很有趣,大约3个钟头可以翻一遍。这本书讲的是一个浅显的问题--什么是好的用户体验?

作为开发人员,多数情况下,以考虑是否完成老板布置的任务为主,主要精力放在如何高效、可靠的完成代码和测试,以保证软件的各项功能正常运作。可是,我们是否忽略了软件最主要的属性--“让人使用”了呢?我们是否关心过我们的软件是否容易使用?

举个简单的例子,就以joycode这个网站来说,当我第一次使用时,不论阅读还是写作,我是否能够很快找到相关功能的入口?对我来说,使用这个界面,有多容易,有多少东西需要记忆?我们在设计它的时候,到底是以“我”作为开发人员的出发点来堆砌功能模块,还是以“客户”为出发点根据易用性来设计?这也许不是一个很好的例子,因为这里的人大多都是技术热衷者,易用性不是一个问题。但是如果要以其他软件为例子,我想每个人都可以很快找到自己生活中曾经看到过的坏例子。

我家楼下的停车库管理软件就是一个极其愚蠢的例子。我的停车IC卡到期了,需要延长有效期,可是这个软件的操作让人百思不得其解。需要重新登录以获取相应权限,但反复试验、猜测,还是找不到该功能入口。。。。让我看着着急,说我来吧,可是同样不得其门而入。最后,来了一个明显接受过特殊技能培训的人,经过十几步操作才搞定。这个use case怎么做的?。。。

所以,用这个名字。希望时时刻刻提醒自己。技术是手段,不是目的。

更多WPF/E相关信息/例子(2)

1。[来源:Barak's Blog about "WPF/E" and beyond] WPF/E产品组的设计师提供了一张使用了WPF/E,可以设置内容的贺卡

 

2。[来源:Mike Harsh's Blog] Richard Legget几个WPF/E示例Winter Card,Interactive Menorah,Tween Test,Rectangle Test,EventDispatcher Test。其中的Winter Card非常之酷:

 

3。[来源:Mike Harsh's Blog] Robby IngebretsenWFP/E Scratchpad

 

好像以前听说过有人使用VML或SVG制作流程图或UML,WPF/E也应该是个很好的选项

4。[来源:Mike Harsh's Blog] Mike Harsh的WPF/E Pad更新版,允许你输入XAML,然后在控件中显示出来,是个测试/把玩XAML的好工具

 

5。[来源:Mike Harsh's Blog] Mike Taulty 推出了更多的示例:

在背景录像内容上方,使用鼠标拖拉操作一个Path
http://mtaulty.com/wpfe/ex8.html

Virtual Earth 和 WPF/E mash-up
http://mtaulty.com/wpfe/ex9.html

6。[来源:Mike Harsh's BlogShawn Wildermuth2个WPF/E示例, The ADO Guy Logo + Video Player:

http://adoguy.com/wpfesamples/

WPF/E SDK更新

[来源:WPF/E Developer ContentBarak's Blog about "WPF/E" and beyondUS ISV Developer Evangelism Team]

上个星期,WPF/E 产品组推出了更新过的WPF/E SDK:

“WPF/E” (codename) Software Development Kit (SDK) Community Technology Preview (Dec 2006)
http://www.microsoft.com/downloads/details.aspx?FamilyId=2B01EC7E-C3B8-47CC-B12A-67C30191C3AA&displaylang=en

安装包内含更新过的SDK文档,快速入门教程以及针对VS 2005 SP1的模板支持。 相关文档也在MSDN上推出:

"WPF/E" (codename) CTP SDK (Dec 2006)
http://msdn2.microsoft.com/en-us/library/bb188266.aspx

快速入门教程的在线地址(其中的导航使用的WPF/E,需要使用WPF/E插件):
http://wpfesdk.members.winisp.net/quickstart/

最近的几篇NLog相关文章 &amp; 《The Zen of CSS Design》

感谢CrazyCoder朋友的热心,这几篇文章均由他翻译完成。更多关于NLog的中文文章,请参考《NLog文章系列》。

  1. NLog文章系列——如何排错
  2. NLog文章系列——如何写自定义布局生成器(Layout Renderer)
  3. NLog文章系列——如何编写自定义的过滤器(Filter)
  4. NLog文章系列——如何优化日志性能

最近忙于翻译《The Zen of CSS Design》,争取在春节前上市(CSDN的介绍页面)。实在无暇顾及太多Blog,见谅……

Everything's gonna be fine...

Recipe: 把SQL数据库部署到远程主机环境(第一部分)

【原文地址】 Recipe: Deploying a SQL Database to a Remote Hosting Environment (Part 1)
【原文发表日期】 Friday, December 22, 2006 12:58 AM

场景:

你开发完了一个得意的ASP.NET应用,所有的东西都测试过了而且在你本机上一切工作都正常,充分利用了ASP.NET 2.0中的成员,角色和用户信息等新功能,万事俱备,准备将它发布到一个远程主机环境,跟整个世界分享这个应用。

将.aspx文件和编译的程序集拷贝到远程系统是非常容易的,只要使用FTP或者直接向上拷贝文件即可。但许多开发人员面临的挑战是,如何在远程主机的站点上架设和重建数据库内容,包括数据定义数据本身。不幸的是,过去以来一直没有极其简单的方式来达成这个目的。

好消息是,这个星期,SQL Server产品组推出了新的 SQL Server Hosting Toolkit的发布候选版 ,这个工具包将使部署你的SQL解决方案到远程主机环境容易之极。这个工具包允许你在本地操作SQL Express,SQL Server 2000,和 SQL Server 2005数据库,然后把你的数据定义数据轻松地迁移,安装到一个共享的远程主机SQL Server账号上。

下面的内容将描述你可以如何开始使用这个工具包。

SQL Server Hosting Toolkit

SQL Server Hosting Toolkit可以免费获取,同时发布的还有一个数据库发布向导(Database Publishing Wizard),该向导支持2种数据库主机部署场景:

1) 数据库发布向导允许你指向一个你正在本机上操作的数据库,然后自动生成一个.SQL脚本文件,该文件包含了在任何远程系统上重建一个当前数据库的完整拷贝所需的安装逻辑。这个.SQL脚本包括了需要创建数据库定义(表,视图,存储过程,触发器,全文索引目录,角色,规则等等,可在此处参考所有细节),以及把与你本地数据库同样的数据内容填充到新数据库里去的所有的东西(这类似于MySQL 的dump工具)。把这些安装逻辑封装在单个.SQL 文件的好处是,大多数主机供应商已经支持上传 .SQL 文件到他们的主机环境,并且通过他们的管理控制面板运行这些脚本的能力。假定你现有一个支持这个功能的web主机供应商,你可以马上开始使用数据库发布向导来轻松地部署你的网站,而不需要主机供应商安装或配置什么东西。

2) 数据库发布向导也允许你指向一个你正在本机上操作的数据库,然后使用 web service 向你的远程主机环境迁移和重建数据库(而不用创建一个.SQL 文件或者使用主机供应商的管理控制面板运行这个文件)。不过,这个发布选项需要主机环境提供SQL发布web-service。 SQL Server Hosting Toolkit包含了一个免费的SQL发布web-service实现,我们将与主机供应商密切合作来部署这个web-service。

数据库发布向导允许你在本地使用 SQL Express 或 SQL Server 2000/2005 ,然后在远程主机环境中使用SQL 2000 或 SQL 2005。它不要求SQL服务器的版本是一样的,所以,你可以在本地使用SQL Express 2005,然后上传到主机环境中的SQL 2000服务器,而不用改动任何编码。

数据库发布向导也支持对内置的ASP.NET 2.0成员,角色管理,用户信息和健康监测等数据定义的处理。许多人遇上问题,因为随 ASP.NET一起发布的来建立这些数据定义的内置.SQL 脚本在安装时(install-time)需要DBO权限,但很多主机供应商对此并不支持(注:脚本在运行时(runtime)需要DBO权限,只是在安装时(install time)才需要,但这有时会是个障碍,除非主机供应商愿意为你安装)。而在另一方面,数据库发布向导在安装ASP.NET 2.0成员,角色管理,用户信息等数据定义和数据时,并需要DBO权限,应该允许你,和使用数据库发布向导部署其他数据库一样,轻松地部署 ASPNETDB数据表和存储过程。

教程一:使用.SQL文件把SQL Express数据库部署到一个SQL Server主机账号上去

在接下来的几周内,我将写一系列的帖子示范如何使用SQL Server Hosting Toolkit中的种种功能。这系列中的第一个教程讨论如何使用它来轻松地生成一个本地SQL Express数据库的 .SQL 安装文件,然后你可以将它拷贝到一个远程主机账号上,用它来重新建立一个 SQL Server 数据库,为你的网站所用。

起始准备:下载和安装数据库发表向导

我们要做的第一步是确认我们安装了SQL Hosting Toolkit中的数据库发布向导。点击这里下载安装

数据库发布向导既有图形界面的向导,也有命令行工具。图形界面的向导既可以单独运行,也可以通过它向Visual Studio 2005 和 Visual Web Developer Express的解决方案管理器中添加的context-menu支持来运行。为本教程的目的,我们将使用后面这个解决方案管理器的集成方法,它使得发布极其容易。

第一步:创建一个使用本地SQL Express或SQL Server数据库的ASP.NET网站

为方便这个演示,我们将使用随VS 2005(VS) 和Visual Web Developer Express(VWD)而来的内置的Personal Starter Kit模板。为创建一个基于这个模板的新web项目,在VWD或VS中选择文件->新网站,然后在新网站对话框中选择Personal Starter Kit模板。在默认情形下,这个personal starter kit应用是配置使用SQL Express(这数据库是免费的,可以在这里下载)的。执行之后,如下图所示:

在创建应用后,你可以在VWD/VS中选“网站”->“ASP.NET配置”菜单项来运行web管理工具,创建一个新的用户,把该用户添加到网站的“admin”角色中去。然后你就可以这个新的管理员用户账号登录,试着上传新的相片或者定制网站上现有的相片(注意,这么做的话,相片的元数据以及原相片的二进制数据都是储存在数据库里的):

 

上述步骤结束之后,我们将拥有2个SQL Express数据库,安装在我们项目的\app_data文件夹里。其中一个SQL Express数据库名为personal.mdf,内含特定于我们网站的数据表和存储过程,譬如photo(相片)和album(相册)表,以及基本的内容管理支持等。另外一个SQL Express数据库名为aspnetdb.mdf,内含默认的ASP.NET 2.0成员,角色和用户信息提供器的数据库贮藏,被我们的应用用来做登录和管理之用。

第二步:为我们的数据库创建 .SQL 安装脚本

至此,我们建立了新的应用和本地数据库,向数据库中添加了自定义数据,象新用户账号以及他们的角色成员,以及新的相片和相册等,我们想要把这应用部署到一个远程主机服务器上去。

我们要做的第一步是创建 .SQL 脚本文件,这些文件将允许我们在远程主机账号上自动重建完全一样的数据库定义和数据库内容。我们将使用作为SQL Hosting Toolkit的一部分安装在本机上的数据库发布向导(Database Publishing Wizard)来做。

一开始,点击 Visual Studio 或 Visual Web Developer中的服务器管理器页,查看一下我们的程序所用的数据库:

在上图中可以看到,我们用到了2个SQL Express数据库:ASPNETDB.MDF 和 Personal.MDF。想为每个数据库生成一个 .SQL 安装文件的话,只要选择管理器里的数据库,然后按右鼠标,在其上选择“Publish to Provider(发布到提供者)”上下文菜单项(是由数据库发布向导添加的):

这会启动数据库发布向导,允许我们按部就班地生成数据库的安装脚本。就象在本帖子里的简介部分提到的一样,数据库发布向导支持2个部署选项: 1) 生成 .SQL 安装脚本文件,你能将它们拷贝到远程主机上然后使用他们现有的管理控制面板工具来运行它们,或者 2) 使用主机网站提供的数据库发布Web Service直接把数据库上传。

在这第一个教程里,我们将使用 .SQL 脚本文件的方法,所以,保留默认的单选按钮选项,给你想生成的 .SQL 安装脚本文件提供一个名字:

 

在点击“下一步”之后,向导会提供选项让你定制生成 .SQL 安装文件时的一些设置。注意,你可以控制是否要删除目标数据库中的现有对象,脚本针对的是SQL 2000 还是SQL 2005 ,是否要同时安装数据定义和数据,还是只要数据定义,或者只要数据:

在本教程中,保留默认的选项,点击“下一步”,生成.SQL 脚本:

至此,你就拥有一个名为Personal.SQL的文件,内含你可以在任何SQL服务器上运行的脚本,该脚本能用来重建数据库中的数据表,存储过程,视图,全文索引目录等,以及导入创建.SQL文件时对应数据库中的所有的数据记录。

该.SQL文件本身是个文本文件,所以你可以用任何文本编辑器将它打开,查看其中的内容,或者添加你自己的语句来做任何定制:

注意,上面的.SQL文件既包括了创建Photos表所需的DDL SQL语句(包括该表所有约束和主键/外键等关系),也包括了在该表创建后在改该表内插入数据的SQL语句。在上面的例子中,甚至包括了插入相片的二进制数据的SQL语句,因为这些数据是保存在数据库里的。

把上述步骤对ASPNETDB SQL Express数据库重复执行之后,你将拥有2个你可以用来在任何SQL服务器上自动重建你的SQL数据库的.SQL 安装脚本:

 

注意,我们建立的.SQL文可以用来在一个服务器上创建2个单独的数据库,或者也可以在同个数据库里运行,从而生成一个包含了你程序的所有的数据表,存储过程和数据的单一数据库。要实现这个目的,只要对同个数据库运行这两个脚本就可以了。假如没有表名和存储过程名冲突的话,你将拥有一个包括所有东西的单一数据库。当你的主机账号只提供了一个可用的数据库实例时,这后一个选项是非常有用的。

第三步:用我们的 .SQL 文件创建我们的远程数据库

至此,我们生成了 .SQL 文件,我们就可以用它们来在我们的主机环境中安装数据库了。我们怎么使用 .SQL 文件安装数据库的细节取决于主机供应商是如何让我们访问我们的SQL账号的。有些主机供应商提供了一个基于HTML的文件上传工具,允许你提供一个 .SQL 文件,然后他们可以在你拥有的SQL数据库里执行这个文件。

其他的主机供应商提供一个在线的查询工具(如下图所示),允许你拷贝/粘贴SQL语句来在你的数据库里运行。如果你的主机供应商提供了类似的在线查询工具的话,那么你可以用一个文本编辑器打开.SQL文件,然后把其中的内容拷贝/粘贴到查询文本框里,然后运行这些语句。

不同主机供应商提供的SQL工具的质量不尽相同。在测试数据库发布向导时,我们发现主机供应商提供的一些定制的SQL管理工具会不正确地分析合法的SQL语句从而导致问题,特别是对于GOTO语句。这个页面描述了一个你也许见过的一些主机供应商的工具遭遇GOTO语句的问题。为了帮助全面提高SQL主机管理工具的质量,SQL Server产品组在明年的早些时候会发布一个免费的SQL HTML管理工具的源码,主机供应商可以将其集成到他们的界面中去。如果顺利的话,这将有助于改进所有Windows主机环境的标准体验。

如果你的主机供应商没有可用的HTML web管理工具让你轻松管理你的SQL数据库的话,那么你也可以编写一个简单的ASP.NET 网页,你可以将它和你的 .SQL 文件一起FTP到你的网站,然后访问该网页,该网页则把 .SQL 文件当作文本文件读入内存,然后将其变成一个字符串传给ADO.NET来执行。这将给予你和上面的查询分析器一样的结果,为你完全重建你的数据库。

Step 4: 第四步:更新web.config中的连接字符串

一旦在我们的主机环境的数据库里上传数据完毕,我们要向远程网站上传我们的.aspx文件,程序集和其他内容(一般是通过FTP来做的)。

我们要做的最后一步是打开我们的 web.config 文件,更新 <connectionStrings> 部分来指向我们远程主机的新数据库位置。注意,你需要从主机供应商那里得到确切的SQL服务器,数据库名字,以及用户名/密码。

用我们上面的personal starter kit作为例子,我们要改动 web.config 文件中的 <connectionStrings> 部分中的默认连接字符串(原本使用的是本地\app_data文件夹中的2个SQL Express数据库):

<connectionStrings>
   
<add name="Personal" connectionString="Data Source=.\SQLExpress;Integrated Security=True;User Instance=True;AttachDBFilename=|DataDirectory|Personal.mdf" />   
   <
remove name="LocalSqlServer"/>
   <
add name="LocalSqlServer" connectionString="Data Source=.\SQLExpress;Integrated Security=True;User Instance=True;AttachDBFilename=|DataDirectory|aspnetdb.mdf" />
</
connectionStrings>

来使用单个SQL Server 2000数据库(Server123机器上的scottguDB数据库)。

<connectionStrings>
   
<add name="Personal" connectionString="Data Source=Server123;Initial Catalog=scottguDB;Integrated Security=True" providerName="System.Data.SqlClient" />
   <
remove name="LocalSqlServer"/>
   <
add name="LocalSqlServer" connectionString="Data Source=Server123;Initial Catalog=scottguDB;Integrated Security=True" providerName="System.Data.SqlClient" />
</
connectionStrings>

我们能够使用单个数据库(而不是上面的2个数据库),因为我们对单一数据库运行了2个 .SQL文件,这样,把所有的数据定义和数据库合并到单个数据库实例里去了。

第五步:完成

至此,我们就可以在主机环境里远程运行我们的应用了,它应该是工作的。

总结

作为SQL Hosting Toolkit的一部分推出的数据库发布向导应该使得为任何数据库(SQL Express 或者 SQL Server)创建 .SQL 文件非常容易。你可以使用它来轻松地dump你本机的数据库,然后用来在远程系统上重新建立完全一样的数据库。

在将来的教程里,我将示范你如何不用 .SQL 文件就可以重建远程数据库(而是在VS里通过web service把数据库直接发布到你的主机环境中去),请静候相关细节。

希望本文对你有所帮助,

Scott

附注:我们正计划把数据库发布向导加到 Visual Studio "Orcas"(下一个VS版本)中去,这意味着在那个时段里,你就不用单独下载数据库发布向导了。但我们要确定你不必等到那个时候,这是我们今天就将它提供给Visual Studio 2005和Visual Web Developer Express 2005的原因。

附注2:请访问此页阅读我以前写的其他方面的ASP.NET Tips,Tricks 和 Recipes。

如何在Vista IIS 7 中用 vs2005 调试 Web 项目?

在Vista IIS 7 中用 vs2005 调试 Web 项目核心是要解决以下几个问题:

1、Vista 自身在安全性方面的User Account Control (UAC)

2、安装必须的IIS7 组件。

3、Vista 自带的IIS7 跟 IIS 6 在底层都发生了变化,我们如何让IIS7以兼容IIS6 的方式运行。因为VS2005并不支持IIS7。

下面通过截图方式来说明如何配置上述几点,以保证VS2005可以调试 Vista 操作系统中基于IIS的Web 项目。

一、安装必须的IIS7 组件

IIS7 被分成了很多个组件,默认是不安装的,你需要安装这些组件。

下图对应的功能,可以通过以下步骤打开:
开始 --> 控制面板 --> 程序 --> 打开或关闭 Windows 功能

1、VS2005 并不能识别 IIS7 ,要让它识别IIS7, 就需要安装IIS7 的一个插件:IIS6 管理兼容性;

2、IIS 默认安全性:VS2005 中,如果要调试站点的话,必须有“集成 Windows 身份验证”

其他两个,在一些情况下也能会用到:

摘要式身份验证 是使用 Windows 域控制器对请求访问 Web 服务器上内容的用户进行身份验证。

基本身份验证 是要求用户提供有效的用户名和密码才能访问内容。

3、要调试 ASP.net 当然要安装IIS支持 ASP.net 的组件了。

二、配置IIS7

1、IIS7 在安装了上述组件后,一些功能并没有启用,你需要把它启用起来:

下述配置功能你可以在

控制面板 --> 管理工具 -->Internet 信息服务(IIS)管理器 程序中找到

打开 Internet 信息服务(IIS)管理器 后,在中间部分的下部,可以看到 身份验证 配置项,双击就是如下界面:

刚才我们增加的几个身份验证,需要在你要调试的站点上启用。

注意:是你要调试的站点,而不是你要调试的应用程序目录!

2、IIS7 应用程序池的设置

IIS 的应用程序池根据托管管道模式分了两种类型:集成和经典

集成 .NET 模式下,应用程序不应在 <system.web>/<httpModules> 配置节中指定 ASP.NET 模块组件,而应使用 <system.webServer>/<modules> 配置节来加载 ASP.NET 模块组件。

经典 .NET 模式 则是我们以前习惯的IIS 6 的方式,没有上述限制。

毕竟现在 Longhorn Server 离我们还远,我们当然要兼容Window 2003 的IIS6 的模式,所以我们需要修改站点的应用程序池设置,如下图:

我上面的截图是使用了现有的经典模式的应用程序池,你也可以自己先建立一个经典模式的应用程序池,然后再用上图方式配置对应站点使用的这个经典模式的应用程序池。

三、 VS2005 的启动。

由于 Vista 的 User Account Control (UAC)  ,要避免调试中出现没有权限问题,我们应该以管理员身份来运行 Vista.如下图:

 


参考资料:

Enabling ASP.NET 2.0 Debugging on Visual Studio 2005, IIS 7.0 and Vista
http://www.thousandtyone.com/blog/EnablingASPNET20DebuggingOnVisualStudio2005IIS70AndVista.aspx

技巧和诀窍:在VS 2005里使用Vista的IIS7
http://weblogs.asp.net/scottgu/archive/2006/09/19/Tip_2F00_Trick_3A00_-Using-IIS7-on-Vista-with-VS-2005.aspx
http://blog.joycode.com/scottgu/archive/2006/09/20/83906.aspx

Running Web Applications on Windows Vista with Visual Studio 2005 
http://msdn2.microsoft.com/en-US/library/aa964620(vs.80).aspx

不是我舍不得 - .NET里面的Out Of Memory

Session,Session,Session!(请耐心阅读………………)

地球人都知道,asp.net中有三种方式存放我们的session objects。In Proc模式,在cache中存放对象。StateServer在State Service中存放,最后一种是存放在SQL Server中。对于In Proc模式,太多的session对象,意味着高内存占用;对于后两者,意味着序列化和反序列化的性能损失。
Session存放的东西太多,不一定意味着性能的问题,但这依赖于你往session里面存放的东西。
让我们假设一个场景,你开发了一个网上商店,有很少的人在用,所以你只用了一台web server,使用了In Proc模式,你的程序对每个用户的订单,都存放了一些dataset。
突然,你的程序出名了!一个大公司来找你,于是,你增加了N台web server,同时把Session State也修改成了SQL Server模式。

问题来了!
asp.net进程,内存占用很好(800MB – 1GB),有时候会提示OutOfMemory异常,或者提示.net进程被recycled了。dump的大小为1,473,913 bytes,所以,大概就是1.4G。首先,我们看看托管堆上面都有些啥东西?

0:023> !eeheap -gc
Number of GC Heaps: 2
------------------------------
Heap 0 (0x000b7198)
generation 0 starts at 0x022104d4
generation 1 starts at 0x022037c0
generation 2 starts at 0x02170030
ephemeral segment allocation context: none
 segment    begin       allocated     size
0x2170000 0x2170030  0x224a4e0 0xda4b0(894,128)
Large object heap starts at 0x0a170030
 segment    begin       allocated     size
0x0a170000 0x0a170030  0x0acf0b20 0x00b80af0(12,061,424)
0x0d490000 0x0d490030  0x0e3d2450 0x00f42420(16,000,032)
0x12010000 0x12010030  0x12f52460 0x00f42430(16,000,048)
0x13010000 0x13010030  0x13f52460 0x00f42430(16,000,048)
0x15010000 0x15010030  0x15f52460 0x00f42430(16,000,048)
0x1a010000 0x1a010030  0x1af52460 0x00f42430(16,000,048)

0x71ca0000 0x71ca0030  0x72be2470 0x00f42440(16,000,064)
0x748b0000 0x748b0030  0x757f2470 0x00f42440(16,000,064)
0x7d0e0000 0x7d0e0030  0x7d881250 0x007a1220(8,000,032)
Heap Size  0x2d5b4e10(760,958,480)
------------------------------
Heap 1 (0x000ede88)
generation 0 starts at 0x06249b58
generation 1 starts at 0x0623e190
generation 2 starts at 0x06170030
ephemeral segment allocation context: none
 segment    begin       allocated     size
0x6170000 0x6170030  0x6283b64 0x113b34(1,129,268)
Large object heap starts at 0x0b170030
 segment    begin       allocated     size
0x0b170000 0x0b170030  0x0b9f1c90 0x00881c60(8,920,160)
0x0c3e0000 0x0c3e0030  0x0d322460 0x00f42430(16,000,048)
0x0e490000 0x0e490030  0x0f3d2460 0x00f42430(16,000,048)
0x11010000 0x11010030  0x11f52460 0x00f42430(16,000,048)
0x14010000 0x14010030  0x14f52450 0x00f42420(16,000,032)
0x16010000 0x16010030  0x16f52480 0x00f42450(16,000,080)
0x17010000 0x17010030  0x17b81b60 0x00b71b30(12,000,048)
0x18010000 0x18010030  0x18b81b60 0x00b71b30(12,000,048)
0x19010000 0x19010030  0x19f52460 0x00f42430(16,000,048)
0x1b010000 0x1b010030  0x1bf52460 0x00f42430(16,000,048)

0x61010000 0x61010030  0x61f52470 0x00f42440(16,000,064)
0x62db0000 0x62db0030  0x63cf2470 0x00f42440(16,000,064)
0x657e0000 0x657e0030  0x66722470 0x00f42440(16,000,064)
0x685c0000 0x685c0030  0x69502470 0x00f42440(16,000,064)
0x6e110000 0x6e110030  0x6ec81b70 0x00b71b40(12,000,064)
0x72ca0000 0x72ca0030  0x73be2470 0x00f42440(16,000,064)
0x77ff0000 0x77ff0030  0x78f32470 0x00f42440(16,000,064)
0x7e0e0000 0x7e0e0030  0x7f022470 0x00f42440(16,000,064)
Heap Size  0x286a4124(678,052,132)
------------------------------
GC Heap Size  0x55c58f34(1,439,010,612)

!eeheap –gc告诉我们两个事情:
a) the GC Heap大小在1.4G左右,和上面的总内存占用非常接近。这说明大部分内存占用,都在托管堆上。
b) 大部分内存占用都在LargeObjects上面(>85000bytes的东西)

很自然的,我们要看看在heap里面的LO到底都是啥东西?
0:023> !dumpheap -min 85000 -stat
Using our cache to search the heap.
Statistics:
        MT      Count     TotalSize Class Name
0x000eda20          1       920,144      Free
0x01b2209c         33   132,000,528 System.Object[]
0x01b226b0        163 1,304,001,956 System.Int32[]
Total 197 objects, Total size: 1,436,922,628
大部分内存被Int32数组占用了,还有132M被一个对象数组占用了。注意啊!这里的132M和那个1.3G,都只是object本身的大小,而不包括存储在Object数组里面的对象的大小。如果要看详细情况,我们需要跑一下!objsize才可以。
0:023> !dumpheap -min 85000
Using our cache to search the heap.
   Address         MT     Size  Gen
0x0b170030 0x000eda20  920,144   -1      Free
0x0a920210 0x01b2209c 4,000,016   -1 System.Object[]
0x247b1250 0x01b2209c 4,000,016   -1 System.Object[]
0x277b1250 0x01b2209c 4,000,016   -1 System.Object[]
0x287b1250 0x01b2209c 4,000,016   -1 System.Object[]

0x51af0030 0x01b226b0 8,000,012   -1 System.Int32[]
0x52291250 0x01b226b0 8,000,012   -1 System.Int32[]
0x53b40950 0x01b226b0 8,000,012   -1 System.Int32[]
0x56620030 0x01b226b0 8,000,012   -1 System.Int32[]
0x57620030 0x01b226b0 8,000,012   -1 System.Int32[]
0x5a440030 0x01b226b0 8,000,012   -1 System.Int32[]
0x5abe1250 0x01b226b0 8,000,012   -1 System.Int32[]
0x5c660030 0x01b226b0 8,000,012   -1 System.Int32[]
0x5ce01250 0x01b226b0 8,000,012   -1 System.Int32[]
0x61010030 0x01b226b0 8,000,012   -1 System.Int32[]
0x617b1250 0x01b226b0 8,000,012   -1 System.Int32[]
0x62db0030 0x01b226b0 8,000,012   -1 System.Int32[]
0x63551250 0x01b226b0 8,000,012   -1 System.Int32[]
0x657e0030 0x01b226b0 8,000,012   -1 System.Int32[]
0x65f81250 0x01b226b0 8,000,012   -1 System.Int32[]
注意Gen这一列,这些object都没有被GC,为啥?随便找一个,看看吧!
0:023> !gcroot 0x72ca0030
Scan Thread 16 (0xbd8)
ESP:1a3f5e0:Root:0x6280d38(System.Web.HttpContext)->
0x62809ec(System.Web.Hosting.ISAPIWorkerRequestInProcForIIS6)->
0x61b7030(System.Web.HttpWorkerRequest/EndOfSendNotification)->
0x61b47d4(System.Web.HttpRuntime)->
0x61b4ca0(System.Web.Caching.CacheMultiple)->0x61b4cc4(System.Object[])->
0x61b4cdc(System.Web.Caching.CacheSingle)->
0x61b4dac(System.Web.Caching.CacheExpires)->0x61b4ff8(System.Object[])->
0x61b5ab8(System.Web.Caching.ExpiresBucket)->
0x222e63c(System.Web.Caching.ExpiresEntry[])->
0x220292c(System.Web.Caching.CacheEntry)->
0x22028fc(System.Web.SessionState.InProcSessionState)->
0x2202690(System.Web.SessionState.SessionDictionary)->
0x220273c(System.Collections.Hashtable)->
0x2202770(System.Collections.Hashtable/bucket[])->
0x2202810(System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)->
0x72ca0030(System.Int32[])
Scan Thread 20 (0x89c)
Scan Thread 22 (0xa5c)
Scan HandleTable 0xdc9d8
Scan HandleTable 0xea6e8
Scan HandleTable 0x1531e8
看这条链,我们发现Int32被Cache root了(看GC的原理就知道这句话啥意思了),看上面的InProcSessionState,我们知道现在是InProc模式。
继续看……
0:023> !dumpaspnetcache -stat
Going to dump the ASP.NET Cache.
        MT      Count     TotalSize Class Name
0x0211cc9c          1            20 System.Web.Security.FileSecurityDescriptorWrapper
0x020c242c          2            56 System.Web.UI.ParserCacheItem
0x0206c66c          5           260 System.Web.Configuration.HttpConfigurationRecord
0x0c2e7014          1           316 System.Web.Mobile.MobileCapabilities
0x79b94638          4           376 System.String
0x0c2eaeb4        151         7,248 System.Web.SessionState.InProcSessionState
Total 164 objects, Total size: 8,276
好,我们找到了151个Session对象。
0:023> .foreach (obj {!dumpheap -mt 0x0c2eaeb4 -short}){!objsize ${obj}}
sizeof(0x22028fc) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2202a10) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2202cfc) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2202fe8) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x22032d4) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x22035c0) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2203a38) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2203d24) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2204010) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)

sizeof(0x6248080) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6248334) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x62485e8) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x624a84c) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x624d7b8) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6250724) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6253690) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x62565fc) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6259568) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x625c4d4) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x625f440) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x62623ac) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6265318) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6268284) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x626b1b8) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
看到了!151个session对象,每个拿到了8-12M左右的数据。就是这个鸟玩意搞的内存有问题啦!
随便找一个,看看什么在里面???
0:023> !do 0x626b1b8
Name: System.Web.SessionState.InProcSessionState
MethodTable 0x0c2eaeb4
EEClass 0x0c1c5660
Size 48(0x30) bytes
GC Generation: 0
mdToken: 0x02000132  (c:\windows\assembly\gac\system.web\1.0.5000.0__b03f5f7f11d50a3a\system.web.dll)
FieldDesc*: 0x0c2eae0c
        MT      Field     Offset                 Type       Attr      Value Name
0x0c2eaeb4 0x40009f2      0x4                CLASS   instance 0x06269f74 dict
0x0c2eaeb4 0x40009f3      0x8                CLASS   instance 0x00000000 staticObjects
0x0c2eaeb4 0x40009f4      0xc         System.Int32   instance 20 timeout
0x0c2eaeb4 0x40009f5     0x18       System.Boolean   instance 0 isCookieless
0x0c2eaeb4 0x40009f6     0x10         System.Int32   instance 0 streamLength
0x0c2eaeb4 0x40009f7     0x19       System.Boolean   instance 0 locked
0x0c2eaeb4 0x40009f8     0x1c            VALUETYPE   instance start at 0x0626b1d4 utcLockDate
0x0c2eaeb4 0x40009f9     0x14         System.Int32   instance 1 lockCookie
0x0c2eaeb4 0x40009fa     0x24            VALUETYPE   instance start at 0x0626b1dc spinLock
每个InProcSessionState对象都有一个叫做_entriesArray的dict成员,装有实际的session对象列表。就是上面偏移为0x04的那行。
0:023> !do 0x06269f74
Name: System.Web.SessionState.SessionDictionary
MethodTable 0x0c2e0c54
EEClass 0x0c1c1308
Size 44(0x2c) bytes
GC Generation: 0
mdToken: 0x0200013b  (c:\windows\assembly\gac\system.web\1.0.5000.0__b03f5f7f11d50a3a\system.web.dll)
FieldDesc*: 0x0c2e0b30
        MT      Field     Offset                 Type       Attr      Value Name
0x0206b338 0x4000a8b     0x24       System.Boolean   instance 0 _readOnly
0x0206b338 0x4000a8c      0x4                CLASS   instance 0x06269fb8 _entriesArray
0x0206b338 0x4000a8d      0x8                CLASS   instance 0x06269fa0 _hashProvider
0x0206b338 0x4000a8e      0xc                CLASS   instance 0x06269fac _comparer
0x0206b338 0x4000a8f     0x10                CLASS   instance 0x0626a020 _entriesTable
0x0206b338 0x4000a90     0x14                CLASS   instance 0x00000000 _nullKeyEntry
0x0206b338 0x4000a91     0x18                CLASS   instance 0x00000000 _keys
0x0206b338 0x4000a92     0x1c                CLASS   instance 0x00000000 _serializationInfo
0x0206b338 0x4000a93     0x20         System.Int32   instance 4 _version
0x0c2e0c54 0x4000a0f     0x25       System.Boolean   instance 1 _dirty
0x0c2e0c54 0x4000a0e        0                CLASS     shared   static s_immutableTypes
    >> Domain:Value 0x000dad08:NotInit  0x00104f30:0x021be0dc <<
同样,还是看_entriesArray,偏移为0x04的那行
0:023> !do 0x06269fb8
Name: System.Collections.ArrayList
MethodTable 0x79ba2ee4
EEClass 0x79ba3020
Size 24(0x18) bytes
GC Generation: 0
mdToken: 0x02000100  (c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll)
FieldDesc*: 0x79ba3084
        MT      Field     Offset                 Type       Attr      Value Name
0x79ba2ee4 0x4000362      0x4                CLASS   instance 0x06269fd0 _items
0x79ba2ee4 0x4000363      0xc         System.Int32   instance 3 _size
0x79ba2ee4 0x4000364     0x10         System.Int32   instance 3 _version
0x79ba2ee4 0x4000365      0x8                CLASS   instance 0x00000000 _syncRoot
哦,Name是ArrayList,好,我们继续看0x04对应的那个_items吧!
0:023> !do -v 0x06269fd0
Name: System.Object[]
MethodTable 0x01b2209c
EEClass 0x01b22018
Size 80(0x50) bytes
GC Generation: 0
Array: Rank 1, Type CLASS
Element Type: System.Object
Content: 16 items
------ Will only dump out valid managed objects ----
   Address         MT Class Name
0x0626a81c 0x0206b784 System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry
0x0626a82c 0x0206b784 System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry
0x0626a83c 0x0206b784 System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry
----------
对于每个对象,我们打印出0x04位置的Name和0x08位置的Size。
0:023> .foreach (obj {!do -v 0x06269fd0 -short}){.echo ***;!do poi(${obj}+0x4);!do poi(${obj}+0x8);!objsize ${obj}}
***
String: somestring
String: this is a string i stored in session scope
sizeof(0x626a81c) =      160 (    0xa0) bytes (System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)
***
String: alargeintarray
Name: System.Int32[]
MethodTable 0x01b226b0
EEClass 0x01b22638
Size 8000012(0x7a120c) bytes
GC Generation: 3
Array: Rank 1, Type System.Int32
Element Type: System.Int32
Content: 2,000,000 items
sizeof(0x626a82c) = 8,000,076 (0x7a124c) bytes (System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)
***
String: sometimesbig
Name: System.Object[]
MethodTable 0x01b2209c
EEClass 0x01b22018
Size 4000016(0x3d0910) bytes
GC Generation: 3
Array: Rank 1, Type CLASS
Element Type: System.Object
Content: 1,000,000 items
sizeof(0x626a83c) = 4,000,076 (0x3d094c) bytes (System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)
到这里,我们看到,一个很小的字符串:this is a string i stored in session scope。在Session的 somestring里面。然后是在Session的alargeintarray有一个8M的Int32数组,最后,是一个4M的sometimesbig在session的sometimesbig。
下面如何解决?就看我们的业务逻辑和代码实现啦!和偶无关了,嗬嗬!

不是我舍不得 - High CPU in GC

(序 & 跋)

   此文及后面的系列,都是从tess老大那里翻译过来的。一直和GTEC的老牛们作CASE(此句有误,一直提CASE,等老牛们提供答案),算是粘到了一点仙气。偶一直比较懒,所以以前精心抄袭的文章,今天再次精心作序于此,希望对各位挣扎于现实与理想的各位,共享,共勉。tess老大的文章国内似乎有人翻译过,但偶个人观点,不看好,因为好东西都没了哦。

   不是我不舍得,意思是,我的post里面基本上没有link,但是从google上都能搞到,如tess老大的系列debug文章。写程序的人,用好google应该是第一要素啊,哇哈哈哈!(百度除外,偶鄙视的公司)

   每个post的题目都挺吓人,但是最终发现问题以及解决掉的方法,都异常简单。简单,产生丑陋。

 

问题描述:

程序慢的要死,CPU占用始终持续在70%-80%之间

解决步骤:

性能监视器。对于高CPU占用,一般的是这三个原因:

·         高的离谱的循环

·         太多的加载(比如,许多小的对象被频繁的处理)

·         GC作了太多的事情

第一种情况,当你在恰当的时机抓到一个dump,就非常容易解决,一般而言,都是因为业务处理逻辑造成的。第二种情况,一般需要从硬件上考虑,scale up或者scale out,都行。

是否是GC的问题,我们需要看性能监视器里面的.NET CLR Memory计数器。这里面,最重要的是.net CLR Memory / % Time in GC. 这个值的阀值,可能是5%或者30%或者20%。实际上,没有一个准确的阀值存在的。当然,这个数字理论上应该接近于0%才对。

在GC里面,导致高CPU占用的原因,通常是因为过高的分配速率(对应到性能监视器里面的.net CLR Memory / allocated bytes/sec计数器。但实际上,如果所有的GC操作都在第0代上,则不会导致这个问题。真正的元凶,是大量的2代操作。如N多的对象在被移动到2代或者从2代中被释放。另一个原因就是我们熟知的大对象操作(LOH)

再强调一次,没有什么准确的指标,就告诉我们,超过了它就是出问题了,这是不可能的。包括微软给我们的大多数Practics Training,只有“尽量”、“尽可能”、“如果”等,而不是“一定”、“必须”。

如同我在那篇“浅谈GC中”讲到的一样,如果你搞了GC.Collect(2)或者GC.GetTotalMemory(true),那么也会导致大量的2代回收。

对于这个问题,我从性能监视器中抓到了这些数据:

% Time in GC                        ~40 % average
allocated bytes / sec              400 MB average
# Induced GC                        0
# Gen 0 Collections                28.379
# Gen 1 Collections                28.378
# Gen 2 Collections                28.378

看第二行,真是让人晕倒!每秒分配400M字节!但实际上,我的代码中没有分配任何这么大的东西,有点太离谱了吧?!如果看最后面三行,也比较搞笑,0、1、2代的分配几乎完全相同。这实际上说明有LOH在压缩,或者有大量的对象冲进了第2代,然后又被立刻释放掉。

开始debug吧!

GC问题很难debug,因为:

1. 如果在GC中间过程中用adplus -hang模式抓了一个dump,基本上从dump里面看不到任何高CPU占用的原因。
2. 即时你通过性能监视器找到了GC的问题,也抓到了dump,但是很难分析。

实际上,通用的做法是每隔一小段时间,你就抓一次dump。如果看起来都差不多,那么有可能就能分析出哪里的问题了。

步骤1 - 我们在GC里面不?

如果我们的OS是server,那么每个CPU有一个GC线程(如果超线程的话,就是2个)。如果是非server的,那么只有一个GC线程。我们正在看到的这个dump,是在一个双核的、带有.NET2.0的机器上产生的。

如果我们不在GC过程中,那么我们会有两个线程(每个CPU一个):

  18  Id: 134c.918 Suspend: 1 Teb: 7ffa8000 Unfrozen
ChildEBP RetAddr  Args to Child             
01e1fe68 7c822124 77e6baa8 00000398 00000000 ntdll!KiFastSystemCallRet
01e1fe6c 77e6baa8 00000398 00000000 00000000 ntdll!NtWaitForSingleObject+0xc
01e1fedc 79e77fd1 00000398 ffffffff 00000000 kernel32!WaitForSingleObjectEx+0xac
01e1ff20 79e77f9a 00000398 ffffffff 00000000 mscorwks!PEImage::LoadImage+0x199
01e1ff70 79e77f50 ffffffff 00000000 00000000 mscorwks!CLREvent::WaitEx+0x117
01e1ff80 79f3549b ffffffff 00000000 00000000 mscorwks!CLREvent::Wait+0x17
01e1ffa8 79f6ece3 00000000 b28c067c 01e1ffec mscorwks!SVR::gc_heap::gc_thread_function+0x2e
01e1ffb8 77e66063 000e7660 00000000 00000000 mscorwks!SVR::gc_heap::gc_thread_stub+0x9b
01e1ffec 00000000 79f6ec79 000e7660 00000000 kernel32!BaseThreadStart+0x34

上面的代码表明,GC正在等着干活……在我抓到的dump中,GC看起来这个样子:

16  Id: f28.1150 Suspend: 1 Teb: fff82000 Unfrozen
ChildEBP RetAddr  Args to Child             
0248fd28 7d4d8c46 000002dc 00000000 00000000 ntdll!ZwWaitForSingleObject+0x15
0248fd98 79e77fd1 000002dc ffffffff 00000000 kernel32!WaitForSingleObjectEx+0xac
0248fddc 79e77f9a 000002dc ffffffff 00000000 mscorwks!PEImage::LoadImage+0x199
0248fe2c 79e77f50 ffffffff 00000000 00000000 mscorwks!CLREvent::WaitEx+0x117
0248fe3c 79f35e1a ffffffff 00000000 00000000 mscorwks!CLREvent::Wait+0x17
0248fe54 7a0d5a3b 001afbf0 0000000d 00000002 mscorwks!SVR::t_join::join+0x61
0248ff54 79f391bf 00000002 001afbf0 00000000 mscorwks!SVR::gc_heap::plan_phase+0xd78
0248ff70 79f39954 00000002 ffffffff 001afbf0 mscorwks!SVR::gc_heap::gc1+0x57
0248ff88 79f35422 00000000 00000000 001afbf0 mscorwks!SVR::gc_heap::garbage_collect+0x37c
0248ffa8 79f6ece3 00000000 914b1904 0248ffec mscorwks!SVR::gc_heap::gc_thread_function+0x68
0248ffb8 7d4e0729 001afbf0 00000000 00000000 mscorwks!SVR::gc_heap::gc_thread_stub+0x9b
0248ffec 00000000 79f6ec79 001afbf0 00000000 kernel32!BaseThreadStart+0x34

看上面的粗体,哦,正在Collect。就是说,我们闯到了GC的肚子里面。

步骤2 - GC为什么开始工作了?

先看一下,CPU都被谁用掉了?

0:029> !runaway
 User Mode Time
  Thread       Time
 
 14:11b8      0 days 0:00:55.687
  16:1150      0 days 0:00:45.500
  17:7a8       0 days 0:00:43.875
  21:1244      0 days 0:00:23.140
 
  0:ea0       0 days 0:00:00.046
  29:fc8       0 days 0:00:00.000
  28:11f0      0 days 0:00:00.000

0:021> .time
Debug session time: Tue Jun 20 09:46:04.000 2006 (GMT+2)
System Uptime: 4 days 6:36:51.031
Process Uptime: 0 days 0:16:53.000
  Kernel time: 0 days 0:00:45.000
  User time: 0 days 0:02:48.000

上面来看,大约有17分钟被程序用掉了。我们看一下14号在作什么?

  14  Id: f28.11b8 Suspend: 1 Teb: fff88000 Unfrozen
ChildEBP RetAddr  Args to Child             
020afc8c 7d4d8c46 00000224 00000000 020afcd0 ntdll!ZwWaitForSingleObject+0x15
020afcfc 79e77fd1 00000224 00009c40 00000000 kernel32!WaitForSingleObjectEx+0xac
020afd40 79e77f9a 00000224 00009c40 00000000 mscorwks!PEImage::LoadImage+0x199
020afd90 79e77f50 00009c40 00000000 00000000 mscorwks!CLREvent::WaitEx+0x117
020afda0 79f5b69c 00009c40 00000000 00000000 mscorwks!CLREvent::Wait+0x17
020afe20 7a1121c3 001862f8 00009c40 00000000 mscorwks!ThreadpoolMgr::SafeWait+0x73
020afe94 79f71123 00000000 00000000 00000000 mscorwks!ThreadpoolMgr::WorkerThreadStart+0xf1
020affb8 7d4e0729 0019cab8 00000000 00000000 mscorwks!ThreadpoolMgr::intermediateThreadProc+0x49
020affec 00000000 79f710dd 0019cab8 00000000 kernel32!BaseThreadStart+0x34

哦,很清闲,什么都没干。但是,那17分钟中,有56秒在用着CPU,什么意思呢???我们继续看一下21号线程的托管堆。

0:021> !clrstack
OS Thread Id: 0x1244 (21)
ESP       EIP    
029ef1e8 7d61c824 [HelperMethodFrame: 029ef1e8]
029ef254 02920df3 LargeObjectHeap.GetLotsOfDatesXML(Int32)
029ef2a4 02920b20 LargeObjectHeap.Button1_Click(System.Object, System.EventArgs)
029ef2b4 6881bdc6 System.Web.UI.WebControls.Button.OnClick(System.EventArgs)
029ef2c8 6881bfbc System.Web.UI.WebControls.Button.RaisePostBackEvent(System.String)
029ef2dc 6881bf38 System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(System.String)
029ef2e0 687d91e0 System.Web.UI.Page.RaisePostBackEvent(System.Web.UI.IPostBackEventHandler, System.String)
029ef2e8 687d912a System.Web.UI.Page.RaisePostBackEvent(System.Collections.Specialized.NameValueCollection)
029ef2f8 687dcbbf System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
029ef4b0 687db521 System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
029ef4e0 687db487 System.Web.UI.Page.ProcessRequest()
029ef518 687db3a7 System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext)
029ef520 687db33a System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)
029ef534 02920795 ASP.largeobjectheap_aspx.ProcessRequest(System.Web.HttpContext)
029ef538 686888df System.Web.HttpApplication+CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
029ef56c 6865a071 System.Web.HttpApplication.ExecuteStep(IExecutionStep, Boolean ByRef)
029ef5ac 6865a39b System.Web.HttpApplication.ResumeSteps(System.Exception)
029ef5f4 6865912d System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(System.Web.HttpContext, System.AsyncCallback, System.Object)
029ef610 6865e0bd System.Web.HttpRuntime.ProcessRequestInternal(System.Web.HttpWorkerRequest)
029ef644 6865dd72 System.Web.HttpRuntime.ProcessRequestNoDemand(System.Web.HttpWorkerRequest)
029ef650 6865c447 System.Web.Hosting.ISAPIRuntime.ProcessRequest(IntPtr, Int32)
029ef800 79f1ef33 [ContextTransitionFrame: 029ef800]
029ef850 79f1ef33 [GCFrame: 029ef850]
029ef9a8 79f1ef33 [ComMethodFrame: 029ef9a8]

看上面的call stack,我们看到了LargeObjectHeap.GetLotsOfDatesXML(),这个咚咚触发了LOH的回收,然后按次序触发了2代、1代、0代。

但是只有一个大对象,并不会导致高CPU的占用啊!那我们看一下实际的代码吧!

    String GetLotsOfDatesXML(int i)

    {

        String dates = "<Dates>";

        for (int j = 0; j < i; j++)

        {

            DateTime dt = DateTime.Now.Add(new TimeSpan(j, 0, 0, 0));

            dates += "<Date><Month>" + dt.Month + "</Month><Day>" + dt.Day + "</Day><DayOfWeek>" + dt.DayOfWeek + "</DayOfWeek><Date>";

            dates += "<Date><Month>" + dt.Month + "</Month><Day>" + dt.Day + "</Day><DayOfWeek>" + dt.DayOfWeek + "</DayOfWeek><Date>";

        }

        dates += "</Dates>";

        return dates;

    }

如果外面传来的i小点还好,但是如果很大呢?对于确切地i是多少,我们需要一点一点地开始找。从callstack上面,我们看到是LargeObjectHeap.Button1_Click调用了GetLotsOfDatesXML方法。

    protected void Button1_Click(object sender, EventArgs e)

    {

        String str = GetLotsOfDatesXML(Int32.Parse(txtNumIterations.Text));

    }

在这里,txtNumIterations是一个textbox控件。下面,基本上都是!do和!dso的工作了。

0:021> !dso
OS Thread Id: 0x1244 (21)
ESP/REG  Object   Name
029ef0f0 0b1a2270 System.String    <String is invalid or too large to print>
029ef1a8 0b1a2270 System.String    <String is invalid or too large to print>
029ef1bc 0b1a2270 System.String    <String is invalid or too large to print>
029ef228 06a759e4 System.String    </DayOfWeek><Date>
029ef28c 06a76df0 ASP.largeobjectheap_aspx
029ef2b4 06a77b84 System.ComponentModel.EventHandlerList
029ef2bc 06a77a84 System.Web.UI.WebControls.Button
029ef2c8 06a76df0 ASP.largeobjectheap_aspx
029ef304 06a76880 System.Web.HttpContext
...

0:021> !do 06a76df0
Name: ASP.largeobjectheap_aspx
MethodTable: 02746ccc
EEClass: 028d24cc
Size: 380(0x17c) bytes
 (C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\debuggersamples\e7443224\5232f845\App_Web_h0ctkxwz.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790fa3e0  4001fe0        4        System.String  0 instance 02a7cba4 _id
790fa3e0  4001fe1        8        System.String  0 instance 00000000 _cachedUniqueID
68a2af44  4001fe2        c ...em.Web.UI.Control  0 instance 00000000 _parent
68a91070  4001fe3       2c         System.Int32  0 instance        5 _controlState
68a85ea0  4001fe4       10 ...m.Web.UI.StateBag  0 instance 00000000 _viewState
68a2af44  4001fe5       14 ...em.Web.UI.Control  0 instance 00000000 _namingContainer
68a273d0  4001fe6       18   System.Web.UI.Page  0 instance 06a76df0 _page
...
68a7d910  4000004      16c ...ebControls.Button  0 instance 06a77a84 Button1
68a95f40  4000005      170 ...bControls.TextBox  0 instance 06a77be4 txtNumIterations
68a2bc80  4000006      174 ...Controls.HtmlForm  0 instance 06a77688 form1

好,继续看一下这个textbox。

0:021> !do 06a77be4
Name: System.Web.UI.WebControls.TextBox
MethodTable: 68a95f40
EEClass: 68a95ebc
Size: 80(0x50) bytes
 (C:\WINDOWS\assembly\GAC_32\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790fa3e0  4001fe0        4        System.String  0 instance 02a85a04 _id
790fa3e0  4001fe1        8        System.String  0 instance 00000000 _cachedUniqueID
68a2af44  4001fe2        c ...em.Web.UI.Control  0 instance 06a77688 _parent
68a91070  4001fe3       2c         System.Int32  0 instance        5 _controlState
68a85ea0  4001fe4       10 ...m.Web.UI.StateBag  0 instance 06a788f0 _viewState
68a2af44  4001fe5       14 ...em.Web.UI.Control  0 instance 06a76df0 _namingContainer
68a273d0  4001fe6       18   System.Web.UI.Page  0 instance 06a76df0 _page
68a92e2c  4001fe7       1c ...+OccasionalFields  0 instance 06a78974 _occasionalFields
68a2b378  4001fe8       20 ...I.TemplateControl  0 instance 00000000 _templateControl
...

0:021> !do 06a788f0
Name: System.Web.UI.StateBag
MethodTable: 68a85ea0
EEClass: 68a85e30
Size: 16(0x10) bytes
 (C:\WINDOWS\assembly\GAC_32\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
79113dfc  400235f        4 ...tions.IDictionary  0 instance 06a78900 bag
79104f64  4002360        8       System.Boolean  0 instance        1 marked
79104f64  4002361        9       System.Boolean  0 instance        0 ignoreCase

0:021> !do 06a78900
Name: System.Collections.Specialized.HybridDictionary
MethodTable: 7a747ad4
EEClass: 7a7aa890
Size: 20(0x14) bytes
 (C:\WINDOWS\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
7a747bac  4001145        4 ...ed.ListDictionary  0 instance 06a78924 list
790fea70  4001146        8 ...ections.Hashtable  0 instance 00000000 hashtable
79104f64  4001147        c       System.Boolean  0 instance        0 caseInsensitive

0:021> !do 06a78924
Name: System.Collections.Specialized.ListDictionary
MethodTable: 7a747bac
EEClass: 7a7aa918
Size: 28(0x1c) bytes
 (C:\WINDOWS\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
7a747c78  4001148        4 ...ry+DictionaryNode  0 instance 06a78940 head
790fed1c  4001149       10         System.Int32  0 instance        1 version
790fed1c  400114a       14         System.Int32  0 instance        1 count
791117c8  400114b        8 ...ections.IComparer  0 instance 00000000 comparer
790f9c18  400114c        c        System.Object  0 instance 00000000 _syncRoot

0:021> !do 06a78940
Name: System.Collections.Specialized.ListDictionary+DictionaryNode
MethodTable: 7a747c78
EEClass: 7a7aa9b8
Size: 20(0x14) bytes
 (C:\WINDOWS\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790f9c18  4001158        4        System.Object  0 instance 06a66bd8 key
790f9c18  4001159        8        System.Object  0 instance 06a78914 value
7a747c78  400115a        c ...ry+DictionaryNode  0 instance 00000000 next

0:021> !do 06a66bd8
Name: System.String
MethodTable: 790fa3e0
EEClass: 790fa340
Size: 26(0x1a) bytes
 (C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: Text
...

找到了Text属性,看看是多少?

0:021> !do 06a78914
Name: System.Web.UI.StateItem
MethodTable: 68a131b4
EEClass: 68a13144
Size: 16(0x10) bytes
 (C:\WINDOWS\assembly\GAC_32\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790f9c18  4002362        4        System.Object  0 instance 06a78184 value
79104f64  4002363        8       System.Boolean  0 instance        1 isDirty

0:021> !do 06a78184
Name: System.String
MethodTable: 790fa3e0
EEClass: 790fa340
Size: 28(0x1c) bytes
 (C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: 40000
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790fed1c  4000096        4         System.Int32  0 instance        6 m_arrayLength
790fed1c  4000097        8         System.Int32  0 instance        5 m_stringLength
790fbefc  4000098        c          System.Char  0 instance       34 m_firstChar
790fa3e0  4000099       10        System.String  0   shared   static Empty
    >> Domain:Value  001a8868:790d6584 001ca990:790d6584 <<
79124670  400009a       14        System.Char[]  0   shared   static WhitespaceChars
    >