Tải bản đầy đủ (.pdf) (10 trang)

Professional ASP.NET 3.5 in C# and Visual Basic Part 112 ppsx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (226.36 KB, 10 trang )

Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1068
Chapter 22: State Management
expensive database retrieval, those UserControls can retrieve the necessary data from
HttpContext.Items
. The database is hit only once.
❑ When individual units within a single HttpRequest need to act on the same or similar data: If
the lifetime of your data is just one request, consider using
HttpContext.Items
,asashortterm
cache.
The
Items
collection holds objects, just like m any of the collections that have been used in this chapter.
You need to cast those objects back to their specific type when they are retrieved.
Within a Web-aware Database Access Layer, per-request caching can be quickly implemented with the
simple coding pattern shown here. Note that this sample code is a design pattern and there is no
MyData
class; it’s for illustration.
VB
Public Shared Function GetExpensiveData(ID As Integer) As MyData
Dim key as string = "data" & ID.ToString()
Dim d as MyData = _
CType(HttpContext.Current.Items(key), MyData)
If d Is Nothing Then
d = New Data()
’Go to the Database, do whatever
HttpContext.Current.Items(key) = d
End If
Return d
End Function
C#


public static MyData GetExpensiveData(int ID)
{
string key = "data" + ID.ToString();
MyData d = (MyData) HttpContext.Current.Items[key];
if (d == null)
{
d = new Data();
//Go to the Database, do whatever
HttpContext.Current.Items[key] = d;
}
return d;
}
This code checks the
Items
collection of the current
HttpContext
to see if the data is already there. If
not, the data is retrieved from the appropriate backing store and then stored in the
Items
collection.
Subsequent calls to this function within the same
HttpRequest
receive the already-cached object.
As with all optimizations and caching, premature optimization is the root of all evil. Measure your need
for caching, and measure your improve ments. Don’t cache just because it feels right;cachebecauseit
makes sense.
1068
Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1069
Chapter 22: State Management
Summary

This chapter explored the many ways to manage State within your ASP.NET application. The
Session
object and its providers offer many choices. Each has its own pros and cons for managing state in the
form of object references and serialized objects in a way that can be made largely transparent to
the application. Server-side Session state data can have its unique identifying key stored in a cookie
or the key can be carried along in the URL. Cookies can also be used independently to store small
amounts of data and persist it between visits, albeit in much smaller amounts and with simpler types.
Hidden fields, ViewState, ControlState, postbacks, and new cross-page postbacks offer new possibilities
for managing small bits of state within a multi-page user experience.
HttpContext.Current.Items
offers
a perfect place to hold transient state, living the life of only a single
HttpRequest
. QueryStrings are an
old standby for holding non-private state that is appropriate for navigation.
ASP.NET has improved on ASP.NET 1.x’s state management options with a flexible Session State
Provider module, the addition of Control State for user controls, and cross-page postbacks for a more
mature programming model.
1069
Evjen c23.tex V2 - 01/28/2008 3:38pm Page 1071
Caching
Performance is a key requirement for any application or piece of code that you develop. The browser
helps with client-side caching of text and images, whereas the server-side caching you choose to
implement is vital for creating the best possible performance. Caching is the process of storing
frequently used data on the server to fulfill subsequent requests. You will discover that grab-
bing objects from memory is much faster than re-creating the Web pages or items contained in
them from scratch e ach time they are requested. Caching increases your application’s performance,
scalability, and availability. The more you fine -tune your application’s caching approach, the better
it performs.
This chapter focuses on caching, including the SQL invalidation caching capabilities that ASP.NET

provides. This chapter takes a close look at this unique aspect of caching. When you are using SQL
cache invalidation, if the result set from SQL Server changes, the output cache can be triggered to
change automatically. This ensures that the end user always sees the latest result set, and the data
presented is never stale. After introducing SQL cache invalidation, this chapter also covers other
performance enhancements. It discusses the new Post-Cache Substitution feature, which caches
entire pages while dynamically replacing specified bits of content. Lastly, this chapter covers a new
capability that enables a developer to create custom dependencies.
Caching
There are several ways to deal with caching in ASP.NET. First, you can cache an entire HTTP
response (the entire Web page) using a mechanism called output caching. Two other methods are
partial page caching and data caching. The following sections describe these methods.
Output Caching
Output caching is a way to keep the dynamically generated page content in the server’s memory
or disk for later retrieval. This type of cache saves post-rendered content so it won‘t have to be
regenerated again the next time it’s requested. After a page is cached, it can be served up again
Evjen c23.tex V2 - 01/28/2008 3:38pm Page 1072
Chapter 23: Caching
when any subsequent requests are made to the server. You apply output caching by inserting an
OutputCache
page directive at the top of an
.aspx
page, as follows:
<
%@ OutputCache Duration="60" VaryByParam="None" %
>
The
Duration
attribute defines the number of seconds a page is stored in the cache. The
VaryByParam
attribute determines which versions of the page output are actually cached. You can generate different

responses based on whether an HTTP-POST or HTTP-GET response is required. Other than the attributes
for the
OutputCache
directive, ASP.NET includes the
VaryByHeader
,
VaryByCustom
,
VaryByControl
,and
Location
attributes. Additionally, the
Shared
attribute can affect UserControls, as you’ll see later.
Caching in ASP.NET is implemented as an
HttpModule
that listens to all
HttpRequest
s that come through
the ASP.NET worker process. The
OutputCacheModule
listens to the application’s
ResolveRequestCache
and
UpdateRequestCache
events, handles cache hits and misses, and returns the cached HTML, bypass-
ing the Page Handler if need be.
VaryByParam
The
VaryByParam

attribute can specify which
QueryString
parameters cause a new version of the page
to be cached:
<
%@ OutputCache Duration="90" VaryByParam="pageId;subPageId" %
>
For example, if y ou have a page called
navigation.aspx
that includes navigation information in the
QueryString
,suchas
pageId
and
subPageId
,the
OutputCache
directive shown here caches the page for
every different value of
pageId
and
subPageId
. In this example, the number of pages is best expressed
with an equation:
cacheItems = (num of pageIds) * (num of subPageIds)
where
cacheItems
is the number of rendered HTML pages that would be stored in the cache. Pages are
cached only after they’re requested and pass through the
OutputCacheModule

. The maximum amount of
cache memory in this case is used only after every possible combination is visited at least once. Although
these are just potential maximums, creating an equation that represents your system’s potential maximum
is an important exercise.
If you want to cache a new version of the page based on any differences in the
QueryString
parameters,
use
VaryByParam = "*"
, as in the following code.
<
%@ OutputCache Duration="90" VaryByParam="*" %
>
It’s important to ‘‘do the math’’ when using the VaryBy attributes. For example, you could add
VaryByHeader
and cache a different version of the page based on the browser’s reported
User-Agent
HTTP Header.
<
%@ OutputCache Duration="90" VaryByParam="*" VaryByHeader="User-Agent"%
>
The
User-Agent
identifies the user’s browser type. ASP.NET can automatically generate different ren-
derings of a given page that are customized to specific browsers, so it makes sense in many cases to
save these various renderings in the cache. A Firefox user might have slightly different HTML than an
IE user so we don’t want to send all users the exact same post-rendered HTML. Literally dozens, if not
hundreds, of
User-Agent
strings exist in the wild because they identify more than just the browser type;

1072
Evjen c23.tex V2 - 01/28/2008 3:38pm Page 1073
Chapter 23: Caching
this
OutputCache
directive could multiply into thousands of different versions of this page being cached,
depending on server load. In this case, you should measure the cost of the caching against the cost of
re-creating the page dynamically.
Always cache what will give you the biggest performance gain, and prove that
assumption with testing. Don’t ‘‘cache by coincidence’’ using attributes like
VaryByParam = "*"
. A common rule of thumb is to cache the least possible amount
of data at first and add more caching later if you determine a need for it. Remember
that the server memory is a limited resource so you may want configure the use of
disk caching in some cases. Be sure to balance your limited resources with security
as a primary concern; don’t put sensitive data on the disk.
VaryByControl
VaryByControl
can be a very easy way to get some serious performance gains from complicated User-
Controls that render a lot of HTML that doesn’t change often. For example, imagine a UserControl that
renders a ComboBox showing the names of all the countries in the world. Perhaps those names are
retrieved from a database and rendered in the combo box as follows:
<
%@ OutputCache Duration="2592000" VaryByControl="comboBoxOfCountries" %
>
Certainly the names of the world’s countries don’t change that often, so the
Duration
might be set to a
month (in seconds). The rendered output of the UserControl is cached, allowing a page using that control
to reap performance benefits of caching the control while the page itself remains dynamic.

VaryByCustom
Although the
VaryBy
attributes offer a great deal of power, sometimes you need more flexibility. If
you want to take the
OutputCache
directive from the previous navigation example and cache by a
value stored in a cookie, you can add
VaryByCustom
.Thevalueof
VaryByCustom
is passed into the
GetVaryByCustomString
method that can be added to the
Global.asax.cs
. This method is called every
time the page is requested, and it is the function’s responsibility to return a value.
A different version of the page is cached for each unique value returned. For example, say your users
have a cookie called
Language
that has three potential values: en, es, and fr. You want to allow users to
specify their preferred language, regardless of their language reported by their browser.
Language
also
has a fourth potential value — it may not exist! Therefore, the
OutputCache
directive in the following
example caches many versions of the page, as described in this equation:
cacheItems = (num of pageIds) * (num of subPageIds) * (4 possible Language values)
To summarize, suppose there were 10 potential values for

pageId
,fivepotential
subPageId
values fo r
each
pageId
, and 4 possible values for
Language
. That adds up to 200 different potential cached versions
of this single navigation page. T his math isn’t mea nt to scare you away f rom caching, but you should
realize that with great (caching) power comes great responsibility.
The following
OutputCache
directive includes
pageId
and
subPageId
as values for
VaryByParam
,and
VaryByCustom
passes in the value of
"prefs"
to the
GetVaryByCustomString
callback function in
Listing 23-1:
1073
Evjen c23.tex V2 - 01/28/2008 3:38pm Page 1074
Chapter 23: Caching

<
%@ OutputCache Duration="90" VaryByParam="pageId;subPageId" VaryByCustom="prefs"%
>
Caching in ASP.NET involves a tradeoff between CPU and memory: how hard is it to make this page,
versus whether you can afford to hold 200 versions of it. If it’s only 5 KB of HTML, a potential megabyte
of memory could pay off handsomely versus thousands and thousands of database accesses. Since most
pages will hit the database a t least once during a page cycle, every page request served from the cache
saves you a trip to the database. Efficient use of caching can translate into cost savings if fewer database
servers and licenses are needed.
The code in Listing 23-1 returns the value stored in the
Language
cookie. The
arg
parameter to the
GetVaryByCustomString
method contains the string
"prefs"
,asspecifiedin
VaryByCustom
.
Listing 23-1: GetVaryByCustomString callback method in the HttpApplication
VB
Overrides Function GetVaryByCustomString(ByVal context As HttpContext, _
ByVal arg As String) As String
If arg.ToLower() = "prefs" Then
Dim cookie As HttpCookie = context.Request.Cookies("Language")
If cookie IsNot Nothing Then
Return cookie.Value
End If
End If

Return MyBase.GetVaryByCustomString(context, arg)
End Function
C#
public override string GetVaryByCustomString(HttpContext context, string arg)
{
if(arg.ToLower() == "prefs")
{
HttpCookie cookie = context.Request.Cookies["Language"];
if(cookie != null)
{
return cookie.Value;
}
}
return base.GetVaryByCustomString(context, arg);
}
The
GetVaryByCustomString
method in Listing 23-1 is used by the
HttpApplication
in
Global.asax.cs
and will be called for every page that uses the
VaryByCustom OutputCache
directive. If your application
has many pages that use
VaryByCustom
, you can create a switch statement and a series of helper functions
to retrieve whatever information you want from the user’s HttpContext and to generate unique values
for cache keys.
Partial Page (UserControl) Caching

Similar to output caching, partial page caching enables you to cache only specific blocks of a Web page. You
can, for example, cache only the center of the page the user sees. Partial page caching is achieved w ith
the caching of user controls so you can build your ASP.NET pages to utilize numerous user controls and
1074
Evjen c23.tex V2 - 01/28/2008 3:38pm Page 1075
Chapter 23: Caching
then apply output caching to the selected user controls. This, in essence, caches only the parts of the page
that you want, leaving other parts of the page outside the reach of caching. This is a nice feature and, if
done correctly, it can lead to pages that perform better. This requires a modular design to be planned up
front so you can partition the components of the page into logical units composed of user controls.
Typically, UserControls are designed to be placed on multiple pages to maximize reuse of common func-
tionality. However, when these UserControls (ASCX files) are cached with the
@OutputCache
directive’s
default attributes, they are cached on a per-page basis. That means that even if a UserControl outputs the
identical HTML when placed on
pageA.aspx
as it does when placed on
pageB.aspx
, its output is cached
twice. By enabling the
Shared = "true"
attribute, the UserControl’s output can be shared among multiple
pages and on sites that make heavy use of shared UserControls:
<
%@ OutputCache Duration="300" VaryByParam="*" Shared="true" %
>
The resulting memory savings can be surprisingly large since you only cache one copy of the post-
rendered user control instead of caching a copy for each page. As with all optimizations, you need to test
both for correctness of output as well as memory usage.

If you have an ASCX UserControl using the
OutputCache
directive, remember that
the UserControl exists only for the first request. If a UserControl has its HTML
retrieved from the
OutputCache
, the control doesn’t really exist on the ASPX page.
Instead, a PartialCachingControl is created that acts as a proxy or ghost of that
control.
Any code in the ASPX page that requires a UserControl to be constantly available will fail if that control is
reconstituted from the
OutputCache
. So be sure to always check for this type of caching before using a ny
control. The following code fragment illustrates the kind of logic required when accessing a potentially
cached UserControl:
VB
Protected Sub Page_Load()
If Not PossiblyCachedUserControl is Nothing Then
" Place code manipulating PossiblyCachedUserControl here.
End If
End Sub
C#
protected void Page_Load()
{
if (PossiblyCachedUserControl != null)
{
// Place code manipulating PossiblyCachedUserControl here.
}
}
Post-Cache Substitution

Output caching has typically been an all-or-nothing proposition. The output of the entire page is cached
for later use. However, often you want the benefits of output caching, but you also want to keep a small
bitofdynamiccontentonthepage.Itwouldbeashametocacheapagebutbeunabletooutputa
dynamic ‘‘Welcome, Scott!’’
1075
Evjen c23.tex V2 - 01/28/2008 3:38pm Page 1076
Chapter 23: Caching
ASP.NET 2.0 added post-cache substitution as an opportunity to affect the about-to-be-rendered page.
A control is added to the page that acts as a placeholder. It calls a method that you specify after the
cached content has been returned. The method returns any string output you like, but you should be
careful not to abuse the feature. If your post-cache substitution code calls an expensive stored procedure,
you could easily lose any performance benefits you might have expected.
Post-cache substitution is an easy feature to use. It gives you two ways to control the substitution:
❑ Call the new
Response.WriteSubstitution
method, passing it a reference to the desired substi-
tution method callback.
❑ Add a
<
asp:Substitution
> control to the page at the desired location, and set its
methodName
attribute to the name of the callback method.
To try this feature, create a new Web site with a
Default.aspx
. Drag a label control and a substitution
control to the design surface. The code in Listing 23-2 updates the label to display the current time, but the
page is cached immediately and future requests return that cached value. Set the
methodName
property

in the substitution control to
GetUpdatedTime
, meaning the name of the static method that is called after
the page is retrieved from the cache.
The callback function must be static because the page that is rendered doesn’t really exist at this point (an
instance of it doesn’t). Because you don’t have a page instance to work with, this method is limited in its
scope. However, the current
HttpContext
is passed into the method, so you have access to the
Session
,
Request
,and
Response
. The string returned from this method is injected into the
Response
in place of
the substitution control.
Listing 23-2: Using the substitution control
ASPX
<
%@ Page Language="C#" CodeFile="Default.aspx.cs" Inherits="_Default" %
>
<
%@ OutputCache Duration="30" VaryByParam="None" %
>
<
html xmlns=" />>
<
head

>
<
title
>
Substitution Control
<
/title
>
<
/head
>
<
body
>
<
form id="form1" runat="server"
>
<
div
>
<
asp:Label ID="Label1" Runat="server" Text="Label"
><
/asp:Label
>
<
br /
>
<
asp:Substitution ID="Substitution1" Runat="server"

methodName="GetUpdatedTime" /
>
<
br /
>
<
/div
>
<
/form
>
<
/body
>
<
/html
>
VB
Partial Class _Default
Inherits System.Web.UI.Page
Public Shared Function GetUpdatedTime(ByVal context As HttpContext) As String
1076
Evjen c23.tex V2 - 01/28/2008 3:38pm Page 1077
Chapter 23: Caching
Return DateTime.Now.ToLongTimeString() + " by " + _
context.User.Identity.Name
End Function
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Me.Load
Label1.Text = DateTime.Now.ToLongTimeString()

End Sub
End Class
C#
public partial class _Default : System.Web.UI.Page
{
public static string GetUpdatedTime(HttpContext context)
{
return DateTime.Now.ToLongTimeString() + " by " +
context.User.Identity.Name;
}
protected void Page_Load(object sender, EventArgs e)
{
Label1.Text = DateTime.Now.ToLongTimeString();
}
}
The ASPX page in Listing 23-2 has a label and a Post-Cache Substitution Control. The control acts as a
placeholder in the spot where you want fresh content injected after the page is returned from the cache.
The very first time the page is visited only the label is updated because no cached content is returned. The
second time the page is visited, however, the entire page is retrieved from the cache — the page handler
isn’t called and, consequently, none of the page-level events fire. However, the
GetUpdatedTime
method
is called after the cache module completes its work. Figure 23-1 shows the result if the first line is cached
and the second line is created dynamically.
Figure 23-1
1077
Evjen c23.tex V2 - 01/28/2008 3:38pm Page 1078
Chapter 23: Caching
HttpCachePolicy and Client-Side Caching
Caching is more than just holding data in memory on the server-side. A good caching strategy should

also include the browser and its client-side caches, controlled by the Cache-Control HTTP Header. HTTP
Headers are hints and directives to the browser on how t o handle a request.
Some people recommend using HTML
<
META
> tags to control caching behavior. Be aware that neither
the browsers nor routers along the way are obligated to pay attention to these directives. You might have
more success using HTTP Headers to control caching.
Because HTTP Headers travel outside the body of the HTTP message, you have several options for
viewing them. You can enable tracing (see Chapter 21) and view the Headers from the tracing output. In
Figure 23-2 I’m using the free TcpTrace redirector from
PocketSoap.com.
For background information on HTTP headers and controlling caching, see the
document RFC 2616: Hypertext Transfer Protocol - HTTP/1.1, available on the World
Wide Web Consortium’s site at
www.w3c.org
. You might also check out Fiddler at
www.fiddlertool.com/fiddler
and FireBug for Firefox at
www.getfirebug.com
.
Commercial tools such as HttpWatch from
www.httpwatch.com
add more features.
Create a
Default.aspx
that writes the current time in its
Load
event. Now, view the default HTTP
Headers used by ASP.NET, as in Figure 23-2 with page-level tracing turned on. Note that one header,

Cache-Control: private
, indicates to routers and other intermediates that this response is intended only
for you (private).
Figure 23-2
1078

×