随笔-120  评论-795  文章-4 

[翻译]避免常见 ASP.NET 缺陷,使网站平稳运行

Keep Sites Running Smoothly By Avoiding These 10 Common ASP.NET Pitfalls
By 
Jeff Prosise

通过避免下列 10 个常见 ASP.NET 缺陷使网站平稳运行
作者:Jeff Prosise

--------------------------------------摘自 MSDN Magazine 的 2006 年 7 月 刊。


This article discusses:
本文将讨论: 
  • Caching and forms authentication
  • 缓存和Forms身份验证
  • View state and session state
  • 视图状态和会话状态
  • Profile property serialization
  • 配置文件属性系列化
  • Thread pool saturation
  •  线程池饱和
  • Impersonation and profiling
  • 模拟和设置配置文件
This article uses the following technologies:
本文使用了以下技术:
.NET Framework, ASP.NET, Windows Server 2003

Contents:本文内容:

1.LoadControl and Output Caching    ==   LoadControl 和输出缓存
2.Sessions and Output Caching    ==   会话和输出缓存
3.Forms Authentication Ticket Lifetime    ==   Froms身份验证票证生存期
4.View State: The Silent Perf Killer    ==   视图状态:无声的性能杀手
5.SQL Server Session State: Another Perf Killer    ==   SQL Server会话状态:另一个性能杀手
6.Uncached Roles    ==   未缓存的角色
7.Profile Property Serialization    ==   配置文件属性系列化
8.Thread Pool Saturation    ==   线程池饱和
9.Impersonation and ACL Authorization    ==   模拟和ACL授权
10.Don’t Just Trust It—Profile Your Database!    ==   不要完全信赖它--请设置数据库的配置文件!
Conclusion    ==   结论

One of the reasons ASP.NET is successful is that it lowers the bar for Web developers. You don’t need a Ph.D. in computer science to write ASP.NET code. Many of the ASP.NET people I encounter in my work are self-taught developers who wrote Microsoft® Excel® spreadsheets before they wrote C# or Visual Basic®.Now they’re writing Web applications and, in general, they’re doing a commendable job.

ASP.NET 成功的其中一个原因在于它降低了 Web 开发人员的门槛。即便您不是计算机科学博士也可以编写 ASP.NET 代码。我在工作中遇到的许多 ASP.NET 开发人员都是自学成材的,他们在编写 C# 或 Visual Basic® 之前都在编写 Microsoft® Excel® 电子表格。现在,他们在编写 Web 应用程序,总的来说,他们所做的工作值得表扬。


But with power comes responsibility, and even veteran ASP.NET developers aren’t immune to mistakes. Years of consulting on ASP.NET projects has shown me that certain mistakes have an uncanny predisposition to keep pitfalls occurring. Some of these mistakes affect performance. Others inhibit scalability. Still others cost development teams precious time tracking down bugs and unexpected behavior.

但是与能力随之而来的还有责任,即使是经验丰富的 ASP.NET 开发人员也难免会出错。在多年的 ASP.NET 项目咨询工作中,我发现某些错误特别容易导致缺陷不断发生。其中某些错误会影响性能。其他错误会抑制可伸缩性。有些错误还会使开发团队耗费宝贵的时间来跟踪错误和意外的行为。


Here are 10 of the pitfalls that litter the path to releasing your production ASP.NET applications, and what you can do to avoid them. All of the examples draw from my experiences with real companies building real Web applications, and in some cases I provide background by describing some of the problems that the ASP.NET development team encountered along the way.

下面是会导致 ASP.NET 生产应用程序的发布过程中出现问题的 10 个缺陷以及可避免它们的方法。所有示例均来自我对真实的公司构建真实的 Web 应用程序的亲身体验,在某些情况下,我会通过介绍 ASP.NET 开发团队在开发过程中遇到的一些问题来提供相关的背景。


LoadControl and Output Caching
LoadControl 和输出缓存

Rare is the ASP.NET application that doesn’t employ user controls. Before the advent of Master Pages, developers employed user controls to factor out common content, such as headers and footers. Even in ASP.NET 2.0, user controls provide an effective means for encapsulating content and behavior, and for dividing pages into regions whose ability to be cached can be controlled independently of the page as a whole—a special form of output caching known as fragment caching.

极少有不使用用户控件的 ASP.NET 应用程序。在出现母版页之前,开发人员使用用户控件来提取公用内容,如页眉和页脚。即使在 ASP.NET 2.0 中,用户控件也提供了有效的方法来封装内容和行为以及将页面分为多个区域,这些区域的缓存能力可以独立于作为整体的页面进行控制(一种称为段缓存的特殊输出缓存形式)。


User controls can be loaded declaratively or imperatively. Imperative loading relies on Page.LoadControl, which instantiates a user control and returns a Control reference. If the user control contains custom type members (for example, public properties), then you can cast that reference and access the custom members from your code. The user control in Figure 1 implements a property named BackColor. The following code loads the user control and assigns a value to BackColor:

用户控件可以采用声明的方式加载,也可以强制加载。强制加载依赖于 Page.LoadControl,它实例化用户控件并返回控件引用。如果用户控件包含自定义类型的成员(例如,公共属性),则您可以转换该引用并从您的代码访问自定义成员。图1 中的用户控件实现名为 BackColor 的属性。以下代码加载用户控件并向 BackColor 分配一个值:

protected void Page_Load(object sender, EventArgs e)
{
// 加载用户控件并将其添加到页面中
Control control = LoadControl("~/MyUserControl.ascx");
PlaceHolder1.Controls.Add(control);
// 设置其背景色
((MyUserControl)control).BackColor = Color.Yellow;
}

This code, simple as it is, is a trap waiting to ensnare the unwary developer. Can you identify the flaw?

以上代码实际上很简单,但却是一个等待粗心的开发人员掉进去的陷阱。您能找出其中的破绽吗?


If you guessed that the problem is related to output caching, you are correct. These code samples compile and run fine as shown, but try adding the following (perfectly legal) statement to MyUserControl.ascx:

如果您猜到该问题与输出缓存有关,那么您是正确的。正如您所看到的一样,上述代码示例编译和运行都正常,但是如果尝试将以下语句(完全合法)添加到 MyUserControl.ascx 中:

<%@ OutputCache Duration="5" VaryByParam="None" %>

Next time you run the page, you’ll be greeted by an InvalidCastException (oh joy!) accompanied by the following error message:

则当您下一次运行该页面时,您将看到 InvalidCastException (oh joy!) 和以下错误消息:

"Unable to cast object of type ‘System.Web.UI.PartialCachingControl’ to type ‘MyUserControl’."
“无法将类型为‘System.Web.UI.PartialCachingControl’的对象转换为类型‘MyUserControl’。”


So, here’s code that works great without an OutputCache directive, but bombs when an OutputCache directive is added. ASP.NET isn’t supposed to work this way. Pages (and controls) are supposed to be agnostic towards output caching. So what gives?

因此,此代码在没有 OutputCache 指令时运行正常,但如果添加了 OutputCache 指令就会出错。ASP.NET 不应该以这种方式运行。页面(和控件)对于输出缓存应该是不可知的。那么,这代表什么意思?


The problem is that when output caching is enabled for a user control, LoadControl no longer returns a reference to an instance of the control; instead, it returns a reference to an instance of PartialCachingControl that might or might not wrap a control instance, depending on whether the control’s output is cached. Consequently, developers who call LoadControl to load a user control dynamically and who also cast the control reference in order to access control-specific methods and properties must take care in how they do it in order for the code to work with or without an OutputCache directive.

问题在于为用户控件启用输出缓存时,LoadControl 不再返回对控件实例的引用;相反,它返回对 PartialCachingControl 实例的引用,而 PartialCachingControl 可能会也可能不会包装控件实例,具体取决于控件的输出是否被缓存。因此,如果开发人员调用 LoadControl 以动态加载用户控件并且为了访问控件特定的方法和属性而转换控件引用,他们必须注意进行该操作的方式,以便不管是否具有 OutputCache 指令,代码都可以运行。

Figure 2 demonstrates the proper way to load user controls dynamically and cast the returned control references. Here’s a synopsis of how it works:

图2 说明动态加载用户控件以及转换返回的控件引用的正确方法。以下是其工作原理概要:
 

  • If the ASCX file lacks an OutputCache directive, LoadControl returns a MyUserControl reference. Page_Load casts the reference to MyUserControl and sets the control’s BackColor property.
  • If the ASCX file includes an OutputCache directive and the control’s output isn’t cached, LoadControl returns a reference to a PartialCachingControl whose CachedControl property contains a reference to the underlying MyUserControl. Page_Load casts PartialCachingControl.CachedControl to MyUserControl and sets the control’s BackColor property.
  • If the ASCX file includes an OutputCache directive and the control’s output is cached, LoadControl returns a reference to a PartialCachingControl whose CachedControl property is null. Seeing this, Page_Load does nothing more. It’s powerless to set the control’s BackColor property because the control’s output is delivered from the output cache. In other words, there is no MyUserControl on which to set a property.

The code in Figure 2 will work with or without an OutputCache directive in the .ascx file. It’s not pretty, but it averts nasty surprises. Simpler doesn’t always equate to more maintainable.

如果 ASCX 文件缺少 OutputCache 指令,则 LoadControl 返回一个 MyUserControl 引用。Page_Load 将该引用转换为 MyUserControl 并设置控件的 BackColor 属性。

如果 ASCX 文件包括一个 OutputCache 指令并且控件的输出没有被缓存,则 LoadControl 返回一个对 PartialCachingControl 的引用,此 PartialCachingControl 的 CachedControl 属性包含对基础 MyUserControl 的引用。Page_Load 将 PartialCachingControl.CachedControl 转换为 MyUserControl 并设置该控件的 BackColor 属性。

如果 ASCX 文件包括一个 OutputCache 指令并且控件的输出被缓存,则 LoadControl 返回一个对 PartialCachingControl(其 CachedControl 属性为空)的引用。注意,Page_Load 不再继续执行操作。无法设置控件的 BackColor 属性,因为该控件的输出来源于输出缓存。换句话说,根本没有要设置属性的 MyUserControl。

不管 .ascx 文件中是否具有 OutputCache 指令,图2 中的代码都将运行。虽然看起来复杂一点,但它会避免烦人的错误。简单并不总是代表易于维护。

Sessions and Output Caching
会话和输出缓存

Speaking of output caching, there’s a potential issue with ASP.NET 1.1 and ASP.NET 2.0 that affects output-cached pages on servers running on Windows Server™ 2003 and IIS 6.0. I’ve personally seen this manifest itself on production ASP.NET servers twice, and both times it was resolved by turning off output caching. I later learned there’s a better solution that doesn’t require output caching to be disabled. Here’s how I first encountered the problem.

谈到输出缓存,ASP.NET 1.1 和 ASP.NET 2.0 都存在一个潜在的问题,该问题会影响在 Windows Server™ 2003 和 IIS 6.0 上运行的服务器中的输出缓存页。我曾经亲眼看到该问题在 ASP.NET 生产服务器中出现过两次,这两次都是通过关闭输出缓冲来解决的。后来我了解到有一个比禁用输出缓存更好的解决方案。以下是我第一次遇到该问题时的情况。


The saga began when a dot-com—let’s call it Contoso.com—that runs a public e-commerce app on a small ASP.NET Web farm contacted my team and complained that they were experiencing "cross-threading" errors. Every now and then, a customer using the Contoso.com Web site would suddenly lose the data they had entered and instead would see data corresponding to another user. A bit of sleuthing revealed that cross-threading wasn’t an accurate description; "cross-session" errors was more like it. It seems that Contoso.com was storing data in session state, and for some reason users were occasionally—and randomly—being connected to other users’ sessions.

当时的情况是这样的,某个网站(我们在此称为 Contoso.com,它在小型 ASP.NET Web 领域中运行公共电子商务应用程序)与我的团队联系,抱怨他们遇到了“跨线程”错误。使用 Contoso.com 网站的客户常常突然丢失已经输入的数据,但却看到另一用户的相关数据。稍做分析即发现,跨线程这个描述并不准确;“跨会话”错误更为贴切。看起来 Contoso.com 是在会话状态中存储数据的,由于某些原因,用户会偶尔随机地连接到其他用户的会话。


One of my team members wrote a diagnostic tool to log key elements of each HTTP request and response, including cookie headers. Then he installed it on Contoso.com’s Web servers and let it run for a few days. The results were remarkable. Roughly once in every 100,000 requests, ASP.NET was correctly assigning a session ID to a brand new session and returning the session ID in a Set-Cookie header. It would then return the same session ID (that is, the same Set-Cookie header) in the very next request, even if that request was already associated with a valid session and was correctly submitting the session ID in a cookie. In effect, ASP.NET was randomly switching users away from their own sessions and connecting them to other sessions.

我的一个团队成员编写了一个诊断工具,用来将每个 HTTP 请求和响应的关键要素(包括 Cookie 标头)记录到日志中。然后,他将该工具安装在 Contoso.com 的 Web 服务器上,并让其运行了几天。结果非常明显。大概每 100000 个请求中会发生一次这样的情况:ASP.NET 正确地为全新会话分配一个会话 ID 并返回 Set-Cookie 标头中的会话 ID。然后,它会在下一个紧相邻的请求中返回相同的会话 ID(即,相同的 Set-Cookie 标头),即使该请求已经与一个有效的会话相关联并且正确提交了 Cookie 中的会话 ID。实际上,ASP.NET 是随机将用户从他们自己的会话中切换出去并将他们连接到其他会话。


Astonished, we began to look for causes. We first examined Contoso.com’s source code and satisfied ourselves that the problem lay elsewhere. Next, just to be sure the problem wasn’t related to the fact that the application was hosted on a Web farm, we turned off all the servers but one. The problem persisted, which wasn’t surprising since our logs showed that the matching Set-Cookie headers never came from two different servers. It wasn’t credible that ASP.NET accidentally generated duplicate session IDs because it uses the .NET Framework RNGCryptoServiceProvider class to generate those IDs, and session IDs are of sufficient length to ensure that the same one will never be generated twice (not in the next trillion years, anyway). Besides, even if RNGCryptoServiceProvider was erroneously generating duplicate random numbers, that wouldn’t explain why ASP.NET mysteriously replaced valid session IDs with new (and non-unique) ones.

我们很惊讶,于是开始寻找原因。我们首先检查了 Contoso.com 的源代码,让我们感到欣慰的是,问题不在那。接着,为了确保问题与应用程序宿主在 Web 领域无关,我们只保留一个服务器在运行,而关闭了所有其他服务器。问题仍然存在,这并不意外,因为我们的日志显示匹配的 Set-Cookie 标头绝不会来自两个不同的服务器。ASP.NET 意外地生成了重复的会话 ID,这令人难以置信,因为它使用 .NET Framework RNGCryptoServiceProvider 类生成这些 ID,并且会话 ID 的长度足以确保相同的 ID 决不会生成两次(至少在下一个万亿年内不会生成两次)。除此之外,即使 RNGCryptoServiceProvider 错误地生成了重复的随机数字,也无法解释 ASP.NET 为何不可思议地将有效的会话 ID 替换为新的 ID(不唯一)。


On a hunch, we decided to look at output caching. When OutputCacheModule caches HTTP responses, it must be careful not to cache Set-Cookie headers; otherwise, a cached response containing a new session ID would connect all recipients of the cached response (as well as the user whose request generated the cached response) to the same session. We checked the source code; Contoso.com had output caching enabled in two pages. We turned it off. Lo and behold, the application ran for days without a single cross-session incident. It has run without error for more than two years since. And we saw the exact same scenario play out at a different company with a different application and a different set of Web servers. As at Contoso.com, eliminating output caching made the problem go away.

凭直觉,我们决定看一下输出缓存。当 OutputCacheModule 缓存 HTTP 响应时,它必须小心不要缓存了 Set-Cookie 标头;否则,包含新会话 ID 的缓存响应会将缓存响应的所有接收者(以及其请求生成了缓存响应的用户)连接到同一会话。我们检查了源代码;Contoso.com 在两个页面中启用了输出缓存。我们关闭了输出缓存。结果,应用程序运行数天而没有发生一个跨会话问题。此后,它运行了两年多都没有发生任何错误。在具有不同应用程序和一组不同 Web 服务器的另一家公司中,我们看到完全相同的问题也消失了。就像在 Contoso.com 一样,消除输出缓存就能解决问题。


Microsoft has since confirmed that this behavior stems from a problem in OutputCacheModule. (There may be an update available by the time you read this.) When ASP.NET is paired with IIS 6.0 and kernel-mode caching is enabled, OutputCacheModule sometimes fails to strip Set-Cookie headers from the cached responses it passes to Http.sys. Here’s the specific sequence of events that causes the bug to manifest itself:

Microsoft 后来确认此行为源于 OutputCacheModule 中的问题。(当您阅读本文时,可能已经发布了更新。)当 ASP.NET 与 IIS 6.0 一起使用并且启用内核模式缓存时,OutputCacheModule 有时无法从它传递给 Http.sys 的缓存响应中删除 Set-Cookie 标头。下面是导致出现错误的特定事件顺序:
 
  1. A user who hasn’t visited the site recently (and therefore doesn’t have a corresponding session) requests a page for which output caching is enabled, but whose output isn’t currently available in the cache.
  2. The request executes code that accesses the user’s newly created session, causing a session ID cookie to be returned in a Set-Cookie header in the response.
  3. OutputCacheModule provides the output to Http.sys, but fails to strip the Set-Cookie header from the response.
  4. Http.sys returns the cached response in subsequent requests, inadvertently connecting other users to the session.

最近没有访问网站(因此也没有对应的会话)的用户请求一个启用了输出缓存的页面,但是其输出当前在缓存中不可用。

该请求执行用于访问用户最新创建的会话的代码,从而导致会话 ID Cookie 在响应的 Set-Cookie 标头中返回。

OutputCacheModule 向 Http.sys 提供输出,但是无法从响应中删除 Set-Cookie 标头。

Http.sys 在后续的请求中返回缓存响应,误将其他用户连接到会话。


The moral of the story? Session state and kernel-mode output caching don’t mix. If you use session state in a page that has output caching enabled, and if the application runs on IIS 6.0, then you need to turn off kernel-mode output caching. You’ll still get the benefit of output caching, but because kernel-mode output caching is substantially faster than ordinary output caching, the caching won’t be as effective. For more information on this issue, see support.microsoft.com/kb/917072.

故事的寓意又是什么呢?会话状态和内核模式输出缓存不能混合使用。如果您在启用输出缓存的页中使用会话状态,并且应用程序在 IIS 6.0 上运行,则您需要关闭内核模式输出缓存。您仍将受益于输出缓存,但是因为内核模式输出缓存比普通输出缓存快得多,所以缓存不会同样有效。有关此问题的详细信息,请参见 support.microsoft.com/kb/917072


You can turn off kernel-mode output caching for individual pages by including VaryByParam="*" attributes in the page’s OutputCache directives, although doing so can cause memory requirements to explode. The safer alternative is to turn off kernel-mode caching for the entire application by including the following element in web.config:

<httpRuntime enableKernelOutputCache="false" />

You can also disable kernel-mode output caching globally—that is, for entire servers—with a registry setting. For details, see support.microsoft.com/kb/820129.

Whenever I hear about inexplicable things happening with sessions, I ask the customer if they’re using output caching in any of their pages. If the answer is yes, and if the host OS is Windows Server 2003, then I advise them to disable kernel-mode output caching. The problem usually goes away. If it doesn’t, then the bug is in their code. Be warned!

您可以通过在页面的 OutputCache 指令中包含 VaryByParam="*" 属性来关闭单个页面的内核模式输出缓存,虽然这样做可能导致内存需求骤增。另一种更安全的方法是通过在 web.config 中包含下列元素来关闭整个应用程序的内核模式缓存:

<httpRuntime enableKernelOutputCache="false" />

您还可以使用注册表设置来全局性地禁用内核模式输出缓存,即禁用全部服务器的内核模式输出缓存。有关详细信息,请参见 support.microsoft.com/kb/820129

每次我听到客户报告会话发生了费解的问题,我都会询问他们是否在任何页面中使用了输出缓存。如果确实使用了输出缓存,并且宿主操作系统是 Windows Server 2003,我会建议他们禁用内核模式输出缓存。问题通常就会迎刃而解。如果问题没有解决,则错误存在于代码中。警惕!


Forms Authentication Ticket Lifetime
Forms 身份验证票证生存期

Can you spot the problem with this code:

FormsAuthentication.RedirectFromLoginPage(username, true);

As innocuous as it seems, this code should never be used in an ASP.NET 1.x app unless there is mitigating code elsewhere in the application to counteract this statement’s debilitating effects. If you’re not sure why, then read on.

您能找出以下代码的问题吗?

FormsAuthentication.RedirectFromLoginPage(username, true);

此代码看似没有问题,但决不能在 ASP.NET 1.x 应用程序中使用,除非应用程序中其他位置的代码抵消了此语句的负面作用。如果您不能确定原因,请继续阅读。


FormsAuthentication.RedirectFromLoginPage performs two tasks. First, it redirects a user to the page they originally requested when they were redirected by FormsAuthenticationModule to the login page. Second, it issues an authentication ticket—typically carried in a cookie, and always carried in a cookie in ASP.NET 1.x—that allows the user to remain authenticated for a predetermined period of time.

FormsAuthentication.RedirectFromLoginPage 执行两个任务。首先,当 FormsAuthenticationModule 将用户重定向到登录页时,FormsAuthentication.RedirectFromLoginPage 将用户重定向到他们原来请求的页面。其次,它发布一个身份验证票证(通常携带在 Cookie 中,而且在 ASP.NET 1.x 中总是携带在 Cookie 中),这个票证允许用户在预定的一段时间内保持已经过身份验证状态。


The problem is the period of time. In ASP.NET 1.x, passing RedirectFromLoginPage a second parameter equal to false issues a temporary authentication ticket that expires, by default, after 30 minutes. (You can change the time-out period using a timeout attribute in web.config’s <forms> element.) Passing a second parameter equal to true, however, issues a persistent authentication ticket that’s valid for—get this—50 years! That’s an accident waiting to happen, because if someone steals that authentication ticket, they can access the Web site using the victim’s identity for the life of the ticket. There’s no shortage of ways to swipe authentication tickets—sniffing unencrypted traffic at public wireless access points, cross-site scripting, gaining physical access to a victim’s PC, and so on—so passing true to RedirectFromLoginPage is little better than disabling security on your Web site. Fortunately, this problem was fixed in ASP.NET 2.0. Today’s RedirectFromLoginPage honors the timeout specified in web.config for temporary and persistent authentication tickets alike.

问题就在于这个时间段。在 ASP.NET 1.x 中,向 RedirectFromLoginPage 传递另一个为 false 的参数会发出一个临时身份验证票证,该票证默认情况下在 30 分钟之后到期。(您可以使用 web.config 的 元素中的 Timeout 属性来更改超时期限。)然而,传递另一个为 true 的参数则会发出一个永久身份验证票证,其有效期为 50 年!这样就会发生问题,因为如果有人窃取了该身份验证票证,他们就可以在票证的有效期内使用受害者的身份访问网站。窃取身份验证票证有多种方法 — 在公共无线访问点探测未加密的通信、跨网站编写脚本、以物理方式访问受害者的计算机等等 — 因此,向 RedirectFromLoginPage 传递 true 比禁用您的网站的安全性好不了多少。幸运的是,此问题已经在 ASP.NET 2.0 中得到了解决。现在的 RedirectFromLoginPage 以相同的方式接受在 web.config 中为临时和永久身份验证票证指定的超时。


One solution is to never pass true in RedirectFromLoginPage’s second parameter in ASP.NET 1.x apps. But that’s not very practical because login pages typically feature a "Keep me signed in" box that users can check to receive persistent rather than temporary authentication cookies. An alternate solution is a snippet of code in Global.asax (or, if you prefer, an HTTP module) that modifies cookies containing persistent authentication tickets before they go back to the browser.

一种解决方案是决不在 ASP.NET 1.x 应用程序的 RedirectFromLoginPage 的第二个参数中传递 true。但是这不切实际,因为登录页的特点通常是包含一个“将我保持为登录状态”框,用户可以选中该框以收到永久而不是临时身份验证 Cookie。另一种解决方案是使用 Global.asax(如果您愿意的话,也可以使用 HTTP 模块)中的代码段,此代码段会在包含永久身份验证票证的 Cookie 返回浏览器之前对其进行修改。

Figure 3 contains one such snippet. If present in Global.asax, this code modifies the Expires property of outgoing persistent forms authentication cookies so that the cookies expire after 24 hours. By modifying the line commented "New expiration date," you can set the timeout to whatever you like.

图3 包含一个这样的代码段。如果此代码段位于 Global.asax 中,它会修改传出永久 Forms 身份验证 Cookie 的 Expires 属性,以使 Cookie 在 24 小时后过期。通过修改注释为“新的过期日期”的行,您可以将超时设置为您喜欢的任何日期。


You may find it curious that the Application_EndRequest method calls a local helper method (GetCookieFromResponse) to check outgoing responses for authentication cookies. The helper method is a work-around for another bug in ASP.NET 1.1 that causes a bogus cookie to be added to the response if you check for a nonexistent cookie using HttpCookieCollection’s string indexer. Using the integer indexer as GetCookieFromResponse circumvents the problem.

您可能会觉得奇怪,Application_EndRequest 方法调用本地 Helper 方法 (GetCookieFromResponse) 来检查身份验证 Cookie 的传出响应。Helper 方法是解决 ASP.NET 1.1 中另一个错误的方法,如果您使用 HttpCookieCollection 的字符串索引生成器来检查不存在的 Cookie,此错误会导致虚假 Cookie 添加到响应中。使用整数索引生成器作为 GetCookieFromResponse 可以解决该问题。


View State: The Silent Perf Killer
视图状态:无声的性能杀手

In some ways, view state is the greatest thing since sliced bread. After all, it’s view state that allows pages and controls to persist state across postbacks. That’s why you don’t have to write code to keep the text in a TextBox from disappearing when a button is clicked as you did in classic ASP, or requery a database and rebind a DataGrid following a postback.

从某种意义上说,视图状态是有史以来最伟大的事情。毕竟,视图状态使得页面和控件能够在回发之间保持状态。因此,您不必像在传统的 ASP 中那样编写代码,以防止在单击按钮时文本框中的文本消失,或在回发后重新查询数据库和重新绑定 DataGrid。


But view state has a dark side, too: when it grows too large, it’s a silent performance killer. Some controls, such as TextBoxes, are judicious with view state. Others, notably DataGrids and GridViews, emit view state in proportion to the amount of information displayed. I cringe when I see a GridView displaying 200 or 300 rows of data. Even though ASP.NET 2.0 view state is roughly half the size of ASP.NET 1.x view state, one lousy GridView can easily cut the effective bandwidth of a connection between a browser and a Web server by 50 percent or more.

但是视图状态也有缺点:当它增长得过大时,它便成为一个无声的性能杀手。某些控件(例如文本框)会根据视图状态作出相应判断。其他控件(特别是 DataGrid 和 GridView)则根据显示的信息量确定视图状态。如果 GridView 显示 200 或 300 行数据,我会望而生畏。即使 ASP.NET 2.0 视图状态大致是 ASP.NET 1 x 视图状态的一半大小,一个糟糕的 GridView 也可以容易地将浏览器和 Web 服务器之间的连接的有效带宽减少 50% 或更多。


You can turn off view state for individual controls by setting EnableViewState to false, but some controls, particularly DataGrids, lose some of their functionality when denied the freedom to use view state. A much better solution to taming view state is keeping it on the server. In ASP.NET 1.x, you can override a page’s LoadPageStateFromPersistenceMedium and SavePageStateToPersistenceMedium methods and handle view state however you like. The overrides shown in the code in Figure 4 prevent view state from being persisted in a hidden field and persist it in session state instead. Storing view state in session state is particularly effective when paired with the default session state process model—that is, when session state is stored in memory in the ASP.NET worker process. If session state is stored in a database instead, then only testing will show whether keeping view state in session state improves or degrades performance.

您可以通过将 EnableViewState 设置为 false 来关闭单个控件的视图状态,但某些控件(特别是 DataGrid)在不能使用视图状态时会失去某些功能。控制视图状态的更佳解决方案是将其保留在服务器上。在 ASP.NET 1.x 中,您可以重写页面的 LoadPageStateFromPersistenceMedium 和 SavePageStateToPersistenceMedium 方法并按您喜欢的方式处理视图状态。图4  中的代码显示的重写可防止视图状态保留在隐藏字段中,而将其保留在会话状态中。当与默认会话状态进程模型一起使用时(即,会话状态存储在内存中的 ASP.NET 辅助进程中时),在会话状态中存储视图状态尤其有效。相反,如果会话状态存储在数据库中,则只有测试才能显示在会话状态中保留视图状态会提高还是降低性能。


The same technique works in ASP.NET 2.0, but ASP.NET 2.0 offers a simpler means for persisting view state in session state. You begin by defining a custom page adapter whose GetStatePersister method returns an instance of the .NET Framework SessionPageStatePersister class:

在 ASP.NET 2.0 中使用相同的方法,但是 ASP.NET 2.0 能够提供更简单的方法将视图状态保留在会话状态中。
首先,定义一个自定义页适配器,其 GetStatePersister 方法返回 .NET Framework SessionPageStatePersister 类的一个实例:

public class SessionPageStateAdapter :
System.Web.UI.Adapters.PageAdapter
{
public override PageStatePersister GetStatePersister ()
{
return new SessionPageStatePersister(this.Page);
}
}

Then you register the custom page adapter as the default page adapter by dropping an App.browsers file like the following into the application’s App_Browsers folder:

然后,通过将 App.browsers 文件按以下方式放入应用程序的 App_Browsers 文件夹,将自定义页适配器注册为默认页适配器:

<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.Page"
adapterType="SessionPageStateAdapter" />
</controlAdapters>
</browser>
</browsers>

(You can name the file anything you like as long as it has a .browsers extension.) Thereafter, ASP.NET will load the page adapter and use the returned SessionPageStatePersister to persist all page state, including view state.

One downside to using a custom page adapter is that it acts globally for every page in the application. If you’d prefer to persist view state in session state for some pages but not for others, use the technique shown in Figure 4. Additionally, you can run into issues with this technique if a user creates multiple browser windows within the same session.

(您可以将文件命名为您喜欢的任何名称,只要它的扩展名为 .browsers 即可。)此后,ASP.NET 将加载页适配器并使用返回的 SessionPageStatePersister 以保留所有页面状态,包括视图状态。

使用自定义页适配器的一个缺点是它全局性地作用于应用程序中的每一页。如果您更愿意将其中一些页面的视图状态保留在会话状态中而不保留其他页面的视图状态,请使用 图4  中显示的方法。另外,如果用户在同一会话中创建多个浏览器窗口,您使用该方法可能会遇到问题。


SQL Server Session State: Another Perf Killer
SQL Server 会话状态:另一个性能杀手

ASP.NET makes it easy to store session state in databases: just flip a switch in web.config and session state magically moves to a back-end database. This is a critical feature for applications that run on Web farms because it allows every server in the farm to share a common repository for session state. The added database activity slows the performance of individual requests, but the loss in performance is offset by an increase in scalability.

This is all fine and good until you take a moment to ponder the following points:

  • Even in an application that uses session state, most pages do not use session state.
  • By default, the ASP.NET session state manager performs two accesses—one read access and one write access—to the session data store in every request, regardless of whether the page requested uses session state.


ASP.NET 使得在数据库中存储会话状态变得简单:只需切换 web.config 中的开关,会话状态就会轻松地移动到后端数据库。对于在 Web 领域中运行的应用程序来说,这是一项重要功能,因为它允许该领域中的每个服务器共享会话状态的一个公共库。添加的数据库活动降低了单个请求的性能,但是可伸缩性的提高弥补了性能的损失。

这看起来都还不错,但是您略微考虑一下下列几点,情况就会有所不同:

即使在使用会话状态的应用程序中,大多数页也不使用会话状态。

默认情况下,ASP.NET 会话状态管理器对每个请求中的会话数据存储执行两个访问(一个读取访问和一个写入访问),而不管请求的页是否使用会话状态。

In other words, when you use the SQL Server™ session state option, you pay the price (two database accesses) in every request—even in requests for pages that do nothing with session state. This directly (and adversely) affects the throughput of the entire site.

换句话说,当您使用 SQL Server™ 会话状态选项时,您在每个请求中都要付出代价(两个数据库访问)— 甚至在与会话状态无关的页面的请求中。这会直接对整个网站的吞吐量造成负面影响。

a

图 5 消除不必要的会话状态数据库访问

So what do you do about it? Simple: you disable session state in pages that don’t use it. It’s always a good idea to do so, but it’s especially important when session state is stored in a database. Figure 5 shows how to disable it. If a page doesn’t use session state at all, include an EnableSessionState="false" in its Page directive, like so:

<%@ Page EnableSessionState="false" ... %>

This directive prevents the session state manager from reading and writing the session state database in each request. If a page reads data from session state but doesn’t write it (that is, doesn’t modify the contents of the user’s session), then set EnableSessionState to ReadOnly, as shown here:

<%@ Page EnableSessionState="ReadOnly" ... %>

Finally, if a page requires read/write access to session state, then either omit the EnableSessionState attribute or set it to true:

<%@ Page EnableSessionState="true" ... %>
那么您应该怎么办呢?很简单:禁用不使用会话状态的页中的会话状态。这样做总是一个好办法,但是当会话状态存储在数据库中时,该方法尤其重要。图 5 显示如何禁用会话状态。如果页面根本不使用会话状态,请在其 Page 指令中包含 EnableSessionState="false",如下所示:
<%@ Page EnableSessionState="false" ... %>

该指令阻止会话状态管理器在每个请求中读取和写入会话状态数据库。如果页面从会话状态中读取数据,但却不写入数据(即,不修改用户会话的内容),则将 EnableSessionState 设置为 ReadOnly,如下所示:

<%@ Page EnableSessionState="ReadOnly" ... %>

最后,如果页面需要对会话状态进行读/写访问,则省略 EnableSessionState 属性或将其设置为 true:

<%@ Page EnableSessionState="true" ... %>

By taming session state in this way, you’ll ensure that ASP.NET only accesses the session state database when it really needs to. Eliminating unnecessary database accesses is the first step toward building high-performance applications.

通过以这种方式控制会话状态,可以确保 ASP.NET 只在真正需要时才访问会话状态数据库。消除不必要的数据库访问是构建高性能应用程序的第一步。

Incidentally, the EnableSessionState attribute is no secret. It’s been documented since ASP.NET 1.0, yet I rarely see developers take advantage of it. Perhaps this is because it’s not terribly important with the default in-memory session state model. But it’s critical with the SQL Server model.

顺便说一下,EnableSessionState 属性是公开的。该属性自 ASP.NET 1.0 以来就已经进行了说明,但是我至今仍很少见到开发人员利用该属性。也许是因为它对于内存中的默认会话状态模型并不十分重要。但是它对于 SQL Server 模型却很重要。

Uncached Roles
未缓存的角色

The following statement is frequently found in the web.config files of ASP.NET 2.0 applications and in samples introducing the ASP.NET 2.0 role manager:

<roleManager enabled="true" />

As presented, however, this statement can have a demonstrably negative impact on performance. Do you know why?

以下语句经常出现于 ASP.NET 2.0 应用程序的 web.config 文件以及介绍 ASP.NET 2.0 角色管理器的示例中:

<roleManager enabled="true" />

但正如以上所示,该语句确实会对性能产生明显的负面影响。您知道为什么吗?

By default, the ASP.NET 2.0 role manager doesn’t cache roles data. Instead, it consults the roles data store each time it needs to determine which roles, if any, a user belongs to. This means that once a user is authenticated, any page that utilizes role data—for example, pages that use site maps with security trimming enabled, and pages to which access is restricted using role-based URL directives in web.config—causes the role manager to query the roles data store. If roles are stored in a database, that’s one more database access per request that you can easily do without. The solution is to configure the role manager to cache roles data in cookies:

<roleManager enabled="true" cacheRolesInCookie="true" />

You can use other <roleManager> attributes to control the characteristics of role cookies—for example, how long the cookies should remain valid (and consequently how frequently the role manager will go back to the roles database). Role cookies are signed and encrypted by default, so the security risk, while not zero, is mitigated.

默认情况下,ASP.NET 2.0 角色管理器不会缓存角色数据。相反,它会在每次需要确定用户属于哪个角色(如果有)时参考角色数据存储。这意味着一旦用户经过了身份验证,任何利用角色数据的页(例如,使用启用了安全裁减设置的网站图的页,以及使用 web.config 中基于角色的 URL 指令进行访问受到限制的页)将导致角色管理器查询角色数据存储。如果角色存储在数据库中,那么对于每个请求需要访问多个数据库的情况,您可以轻松地免除访问多个数据库。解决方案是配置角色管理器以在 Cookie 中缓存角色数据:

<roleManager enabled="true" cacheRolesInCookie="true" />

您可以使用其他<roleManager> 属性控制角色 Cookie 的特征 — 例如,Cookie 应保持有效的期限(以及角色管理器因此返回角色数据库的频率)。角色 Cookie 默认情况下是经过签名和加密的,因此安全风险虽然不为零,但也有所缓解。

Profile Property Serialization
配置文件属性序列化

The ASP.NET 2.0 profile service provides a ready-made solution to the problem of persisting per-user state, such as personalization preferences and language preferences. To use the profile service, you define an XML profile containing the properties you want to persist on behalf of individual users. ASP.NET then compiles a class containing the same properties and provides strongly typed access to class instances via the profile property added to the page.

ASP.NET 2.0 配置文件服务为保持每个用户的状态(例如个性化首选项和语言首选项)的问题提供了一个现成的解决方案。要使用配置文件服务,您可以定义一个 XML 配置文件,其中包含要保留的代表单个用户的属性。然后,ASP.NET 编译一个包含相同属性的类,并通过添加到页的配置文件属性提供对类实例的强类型访问。

Profiles are flexible enough to allow even custom data types to be used as profile properties. But therein lies the problem—one that I’ve personally seen trip developers up. Figure 6 contains a simple class named Posts, as well as a profile definition that uses Posts as a profile property. However, this class and this profile produce unexpected behavior at run time. Can you determine why?

配置文件灵活性很强,它甚至允许将自定义数据类型用作配置文件属性。但是,其中却存在一个问题,我亲眼看到该问题导致开发人员出差错。图6 包含一个名为 Posts 的简单类,以及将 Posts 用作配置文件属性的配置文件定义。但是,该类和该配置文件在运行时会产生意外的行为。您能找出其中的原因吗?

The problem is that Posts contains a private field named _count that must be serialized and deserialized to fully hydrate and rehydrate class instances. But _count doesn’t get serialized and deserialized because it’s private and, by default, the ASP.NET profile manager uses XML serialization to serialize and deserialize custom types. The XML serializer ignores nonpublic members. Therefore, instances of Posts will get serialized and deserialized, but each time a class instance is deserialized, _count is reset to 0.

问题在于 Posts 包含一个名为 _count 的私有字段,该字段必须进行序列化和反序列化,才能完全冻结和重新冻结类实例。但是 _count 却没有经过序列化和反序列化,因为它是私有的,而且默认情况下 ASP.NET 配置文件管理器使用 XML 序列化对自定义类型进行序列化和反序列化。XML 序列化程序将忽略非公共成员。因此,会对 Posts 的实例进行序列化和反序列化,但是每次反序列化类实例时,_count 都会重设为 0。

One solution is to make _count public rather than private. Another is to wrap _count with a public read/write property. The best solution, and one that preserves the design of the class itself, is to mark Posts as serializable (using the SerializableAttribute) and to configure the profile manager to use the .NET Framework binary serializer to serialize and deserialize class instances. The binary serializer, unlike the XML serializer, serializes fields, regardless of accessibility. Figure 7 shows a fixed version of the Posts class and the accompanying profile definition with changes highlighted.

一种解决方案是使 _count 成为公共字段而非私有字段。另一种解决方案是使用公共读/写属性封装 _count。最佳解决方案是将 Posts 标记为可序列化(使用 SerializableAttribute),并将配置文件管理器配置为使用 .NET Framework 二进制序列化程序对类实例进行序列化和反序列化。该解决方案能够保持类本身的设计。与 XML 序列化程序不同的是,二进制序列化程序序列化字段,而不管是否可以访问。图7  显示 Posts 类的修复版本并突出显示了更改的附带配置文件定义。

The thing you should remember is that if you use a custom data type as a profile property, and if that data type has nonpublic data members that must be serialized in order to fully serialize type instances, then use a serializeAs="Binary" attribute in the property declaration and make sure the type itself is serializable. Otherwise, complete serialization will not occur and you’ll waste time trying to determine why the profile isn’t working.

您应该牢记的一点是,如果您使用自定义数据类型作为配置文件属性,并且该数据类型具有必须序列化才能完全序列化类型实例的非公共数据成员,则在属性声明中使用 serializeAs="Binary" 属性并确保类型本身是可序列化的。否则,将无法进行完整的序列化,并且您还将浪费时间来尝试确定配置文件无法工作的原因。

Thread Pool Saturation
线程池饱和

I am often amazed at the number of real-life ASP.NET pages I see that perform database queries and wait 15 seconds or more for the queries to return. (I’ve also seen queries that take 15 minutes!) Sometimes the delay is an unavoidable by-product of the volume of data returned; other times it’s the result of poor database design. But whatever the reason, long database queries or lengthy I/O operations of any kind are throughput killers in an ASP.NET app.

在执行数据库查询并等待 15 秒或更长时间来获得返回的查询结果时,我经常对看到的实际的 ASP.NET 页数感到非常惊讶。(我也等待了 15 分钟才看到查询结果!)有时,延迟是由于返回的数据量很大而导致的不可避免的无奈结果;而有时,延迟则是由于数据库的设计不佳导致的。但不管是什么原因,长时间的数据库查询或任何类型的长时间 I/O 操作在 ASP.NET 应用程序中都会导致吞吐量的下降。

I’ve written about this problem at length before, so I won’t belabor the point here. Suffice it to say that ASP.NET relies on a finite thread pool to process requests, and if all the threads are occupied waiting for database queries, Web service calls, or other I/O operations to complete, additional requests must be queued up until an operation completes and a thread is freed. Performance drops off precipitously when requests are queued. And if the queue fills up, ASP.NET fails subsequent requests with HTTP 503 errors. This is not a situation a production app on a production Web server ever wants to find itself in.

关于这个问题我以前已经详细地描述过,所以在此就不再作过多的说明了。我只说一点就够了,ASP.NET 依赖于有限的线程池处理请求,如果所有线程都被占用来等待数据库查询、Web 服务调用或其他 I/O 操作完成,则在某个操作完成并且释放出一个线程之前,其他请求都必须排队等待。当请求排队时,性能会急剧下降。如果队列已满,则 ASP.NET 会使随后的请求失败并出现 HTTP 503 错误。这种情况不是我们希望在 Web 生产服务器的生产应用程序上所乐见的。

The solution, of course, is asynchronous pages—one of the best but lesser-known features of ASP.NET 2.0. A request for an asynchronous page begins its life on one thread, but returns that thread and an IAsyncResult interface to ASP.NET when it begins an I/O operation. When the operation completes, the request signals ASP.NET through IAsyncResult and ASP.NET pulls another thread from the pool and finishes processing the request. Significantly, no thread pool threads are consumed while the I/O operation takes place. This can dramatically improve throughput by preventing requests for other pages—pages that don’t perform lengthy I/O operations—from waiting in a queue.

解决方案非异步页面莫属,这是 ASP.NET 2.0 中最佳却鲜为人知的功能之一。对异步页面的请求从一个线程上开始,但是当它开始一个 I/O 操作时,它将返回该线程以及 ASP.NET 的 IAsyncResult 接口。操作完成后,请求通过 IAsyncResult 通知 ASP.NET,ASP.NET 从池中提取另一个线程并完成对请求的处理。值得注意的是,当 I/O 操作发生时,没有占用线程池线程。这样可以通过阻止其他页面(不执行较长的 I/O 操作的页面)的请求在队列中等待,从而显著地提高吞吐量。

You can read all about asynchronous pages in the October 2005 issue of MSDN®Magazine. Any page that is I/O-bound rather than compute-bound and that takes more than a few seconds to execute is a candidate to be an asynchronous page.

When I tell developers about asynchronous pages, they often reply, "That’s cool, but I don’t really need them in my app." To which I reply, "Do any of your pages query a database? Do any of them call a Web service? Have you checked the ASP.NET performance counters for stats regarding queued requests and average wait times? Even if your app runs fine today, is it possible that the load on it will increase as your customer base grows?"

The reality is that few real-world ASP.NET apps have no need whatsoever for asynchronous pages. Write it down!

您可以在 MSDN®Magazine 的 2005 年 10 月刊 中阅读有关异步页面的所有信息。I/O 绑定而不是计算机绑定且需要很长时间执行的任何页面很有可能成为异步页面。

当我将关于异步页面的信息告知开发人员时,他们经常回答“那真是太棒了,但是我的应用程序中并不需要它们。”对此我回答说:“你们的任何页面需要查询数据库吗?它们调用 Web 服务吗?您是否已经检查 ASP.NET 性能计数器中关于排队请求和平均等待时间的统计信息?即使您的应用程序至今运行正常,但是随着您的客户规模的增长,应用程序的负载可能会增加。”

实际上,绝大多数实际的 ASP.NET 应用程序都需要异步页面。请切记这一点!

 

Impersonation and ACL Authorization
模拟和 ACL 授权

Here’s a simple configuration directive, but one that raises my eyebrows whenever I see it in web.config:

以下是一个简单的配置指令,但是每当在 web.config 中看到它时都让我眼前一亮:

<identity impersonate="true" />

This directive enables client impersonation in an ASP.NET app. It attaches access tokens representing clients to the threads that process requests so that any security checks performed by the operating system act against the client’s identity rather than the worker process’s identity. Impersonation is rarely necessary in an ASP.NET app; my experience is that when developers enable it, they usually do so for the wrong reasons. Here’s why.

此指令在 ASP.NET 应用程序中启用客户端模拟。它将代表客户端的访问令牌附加到处理请求的线程,以便操作系统执行的安全性检查针对的是客户端身份而不是辅助进程身份。ASP.NET 应用程序很少需要模拟;我的经验告诉我,开发人员通常都是由于错误的原因而启用模拟的。以下是原因所在。

Too often, developers enable impersonation in ASP.NET apps so they can use file system permissions to restrict access to pages. If Bob doesn’t have permission to view Salaries.aspx, then the devs enable impersonation so they can prevent Bob from viewing Salaries.aspx by setting the access control list (ACL) to deny Bob read permission. But here’s the kicker: impersonation isn’t necessary for ACL authorization. When Windows authentication is enabled in an ASP.NET app, ASP.NET automatically checks the ACL for each .aspx page requested and denies the request if the caller lacks permission to read the file. It does so even if impersonation is disabled.

开发人员经常在 ASP.NET 应用程序中启用模拟,以便可以使用文件系统权限来限制对页面的访问。如果 Bob 没有查看 Salaries.aspx 的权限,则开发人员将会启用模拟,以便可以通过将访问控制列表 (ACL) 设置为拒绝 Bob 的读取权限,阻止 Bob 查看 Salaries.aspx。但是存在以下隐患:对于 ACL 授权来说,模拟是不必要的。在 ASP.NET 应用程序中启用 Windows 身份验证时,ASP.NET 会自动为请求的每个 .aspx 页面检查 ACL 并拒绝没有读取文件权限的调用者的请求。即使禁用了模拟,它仍会这样操作。

There are times when impersonation is justified. But you can usually avoid it with good design. For example, suppose Salaries.aspx queries a database for salary info that should only be available to managers. By impersonating, you can use database permissions to deny non-managers the ability to query for salary data. Or you can forget about impersonation and restrict access to salary data by setting the ACL for Salaries.aspx so that non-managers lack read permission. The latter approach provides better performance because it avoids impersonation altogether. It eliminates unnecessary database accesses, too. Why query a database only to be denied for security reasons?

有的时候需要证明模拟的合理性。但是您通常可以用良好的设计来避免它。例如,假定 Salaries.aspx 在数据库中查询只有管理人员才能知道的工资信息。通过模拟,您可以使用数据库权限拒绝非管理人员查询工资数据的能力。或者您可以不考虑模拟,并且通过为 Salaries.aspx 设置 ACL 以使非管理人员不具有读取权限,从而限制对工资数据的访问。后一种方法提供的性能更佳,因为它完全避免了模拟。它也消除了不必要的数据库访问。为什么查询数据库仅由于安全原因被拒绝?

Incidentally, I once helped troubleshoot a classic ASP app that periodically rebooted due to unconstrained memory consumption. A junior developer had turned a targeted SELECT statement into a SELECT *, not considering the fact that the table being queried contained images—large images, and lots of them. The problem was exacerbated by an undetected memory leak. (My kingdom for managed code!) Suddenly, an app that had run fine for years began grinding to a halt because SELECT statements that previously returned a kilobyte or two of data now returned several megabytes. Coupled with inadequate version control, it made life pretty exciting for the development team—exciting, that is, if you consider sleeping at night and seeing your kids’ soccer games boring.

顺便说一下,我曾经帮助对一个传统的 ASP 应用程序进行故障排除,该应用程序由于内存占用不受限制而定期重新启动。一个没有经验的开发人员将目标 SELECT 语句转换成了 SELECT *,而没有考虑要查询的表包含图像,这些图像很大而且数目很多。问题由于未检测到内存泄漏而恶化。(我的托管代码领域!)多年来运行正常的应用程序开始突然停止工作,因为以前返回一两千字节数据的 SELECT 语句现在却返回了几兆字节。如果再加上不充分的版本控制,开发团队的生活将不得不“亢奋起来”— 这里所谓的“亢奋”,就如同当您在晚上要睡觉时,还不得不看着您的孩子玩令人厌烦的足球游戏一样。

In theory, classic memory leaks can’t happen in ASP.NET applications composed entirely of managed code. But inefficient memory usage impacts performance by forcing garbage collections to occur more frequently. Even in ASP.NET apps, beware of S E L E C T  *!

理论上,传统的内存泄漏不会发生在完全由托管代码组成的 ASP.NET 应用程序中。但是内存使用量不足会通过强制垃圾收集更频繁地发生而影响性能。即使是在 ASP.NET 应用程序中,也要警惕 S E L E C T  *!

Don’t Just Trust It—Profile Your Database!
不要完全信赖它 — 请设置数据库的配置文件!

As a consultant, I’m often called in when an app isn’t performing the way it should. Recently my team was asked to determine why an ASP.NET app was only achieving about 1/100th of the throughput (requests per second) that the requirements document called for. What we discovered was typical of what we find in underperforming Web apps—and a lesson that all of us can take to heart.

作为一名顾问,我经常被询问为何应用程序没有按预期执行。最近,有人询问我的团队为何 ASP.NET 应用程序只完成请求文档所需吞吐量(每秒的请求数)的大约 1/100。我们以前所发现的问题是我们在不能正常运行的 Web 应用程序中发现的问题特有的 — 和我们所有人应该认真对待的教训。

We ran SQL Server Profiler and watched the interactions between the app and the database on the back end. In one of the more extreme cases, a simple button click produced more than 1,500 trips to the database. You can’t build a high-performance application that way. Good architecture always begins with good database design. No matter how efficient your code is, it’s hamstrung if saddled with a poorly written database.

我们运行 SQL Server Profiler 并监视此应用程序和后端的数据库之间的交互情况。在一个更极端的案例中,仅仅只是一个按钮单击,就导致数据库发生了 1,500 多个错误。您不能那样构建高性能的应用程序。良好的体系结构总是从良好的数据库设计开始。不管您的代码的效率有多高,如果它被编写不佳的数据库所拖累,就会不起作用。

Bad data access architectures typically stem from one or more of the following:

  • Poor database design (usually designed by developers, not database administrators).
  • Use of DataSets and DataAdapters—in particular, DataAdapter.Update, which is great for Windows Forms apps and other thick clients, but usually not ideal for Web apps.
  • Poorly designed data access layers (DALs) with poor factorization and too many CPU cycles expended performing relatively simple operations.

糟糕的数据访问体系结构通常源于下面的一个或多个方面:

拙劣的数据库设计(通常由开发人员设计,而不是数据库管理员)。

DataSets 和 DataAdapters 的使用 — 尤其是 DataAdapter.Update,它适用于 Windows 窗体应用程序和其他胖客户端,但是对于 Web 应用程序来说通常不理想。

具有拙劣编制计算程序、以及执行相对简单的操作需消耗很多 CPU 周期的设计糟糕的数据访问层 (DAL)。

Problems must be identified before they can be treated. And the way to identify data access problems is to run SQL Server Profiler or an equivalent tool to see what’s going on behind the scenes. Performance tuning isn’t complete until you’ve examined the traffic between the app and the database. Try it—you may be surprised by what you find.

必须先确定问题才能对其进行处理。确定数据访问问题的方式是运行 SQL Server Profiler 或等效的工具以查看后台正在执行的操作。检查应用程序和数据库之间的通信之后,性能调整才完成。尝试一下 — 您可能会对您的发现大吃一惊。

 

Conclusion
结论

Now you’ve seen some of the problems and solutions you’re likely to encounter in the process of building production ASP.NET apps. The next step is to take a close look at your own code and try to avoid some of the issues I’ve outlined here. ASP.NET may lower the bar for Web developers, but there’s no reason your apps can’t be slick, solid, and speedy. Put on your thinking cap and avoid making those rookie mistakes.


现在您已经了解在生成 ASP.NET 生产应用程序过程中可能遇到的一些问题及其解决方案了。下一步是仔细查看您自己的代码并尝试避免我在此概述的一些问题。ASP.NET 可能降低了 Web 开发人员的门槛,但是您的应用程序完全有理由灵活、稳定和高效。请认真考虑,避免出现新手易犯的错误。

Figure 8 provides a short checklist you can use to avoid the pitfalls described in this article. You can build a similar checklist for security pitfalls. For example:

  • Have you encrypted configuration sections containing sensitive data?
  • Are you checking and validating input used in database operations, and are you HTML-encoding input used as output?
  • Do any of your virtual directories contain files with unprotected extensions?


图 8
提供了一个简短检查列表,您可以使用它来避免本文中描述的缺陷。您可以创建一个类似的安全缺陷检查列表。例如:

您是否已经对包含敏感数据的配置节进行加密?

您是否正在检查并验证在数据库操作中使用的输入,是否使用了 HTML编码输入作为输出?

您的虚拟目录中是否包含具有不受保护的扩展名的文件?

These are important questions to ask if you value the integrity of your Web site, the servers that host your site, and the back-end resources upon which these rely.
如果您重视网站、承载网站的服务器以及它们所依赖的后端资源的完整性,则这些问题非常重要。

Jeff Prosise 是对 MSDN Magazine 贡献很大的编辑以及多本书籍的作者,这些书籍中包括 Programming Microsoft .NET (Microsoft Press, 2002)。他也是软件咨询和教育公司 Wintellect 的共同创始人。

摘自 MSDN Magazine 的 2006 年 7 月 刊。

posted on 2007-04-06 15:47 xerwin 阅读(...) 评论(...) 编辑 收藏