Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]

.pdf
Скачиваний:
107
Добавлен:
16.08.2013
Размер:
29.8 Mб
Скачать

388 C H A P T E R 1 0 R I C H D ATA C O N T R O L S

There’s one last detail. To save overhead, there’s no point in performing the query for the second DetailsView unless it’s absolutely necessary because a concurrency error occurred. To implement this logic, the code reacts to the SqlDataSource.Selecting event and cancels the query if the error panel isn’t currently visible.

protected void sourceUpdateValues _Selecting(object sender, SqlDataSourceSelectingEventArgs e)

{

if (!ErrorPanel.Visible) e.Cancel = true;

}

To try this example, open two copies of the page in separate browser windows and put both into edit mode for the same row. Apply the first change, and then apply the second one. When you attempt to apply the second one, the error panel will appear, with the explanation (see Figure 10-24). You can then choose to continue with the edit by clicking Update or to abandon it by clicking Cancel.

Figure 10-24. Detecting a concurrency error during an edit

C H A P T E R 1 0 R I C H D ATA C O N T R O L S

389

Summary

In this chapter, you considered everything you need to build rich data-bound pages. You took an exhaustive tour of the GridView and considered its support for formatting, selection, sorting, paging, templates, and editing. You also considered the more modest DetailsView and FormView. Finally, the chapter wrapped up by looking at several common advanced scenarios with databound pages.

C H A P T E R 1 1

■ ■ ■

Caching

One of the most valuable features in ASP.NET 1.x is caching, and in ASP.NET 2.0 a good story gets even better.

Caching is the technique of storing an in-memory copy of some information that’s expensive to create. For example, you could cache the results of a complex query so that subsequent requests don’t need to access the database at all. Instead, they can grab the appropriate object directly from server memory—a much faster proposition. The real beauty of caching is that unlike many other performance-enhancing techniques, caching bolsters both performance and scalability. Performance is better because the time taken to retrieve the information is cut down dramatically. Scalability is improved because you work around bottlenecks such as database connections. As a result, the application can serve more simultaneous page requests with fewer database operations.

Of course, storing information in memory isn’t always a good idea. Server memory is a limited resource; if you try to store too much, some of that information will be paged to disk, potentially slowing down the entire system. That’s why the best caching strategies (such as those hard-wired into ASP.NET) are self-limiting. When you store information in a cache, you expect to find it there on a future request, most of the time. However, the lifetime of that information is at the discretion of the server. If the cache becomes full or other applications consume a large amount of memory, information will be selectively evicted from the cache, ensuring that performance is maintained. It’s this self-sufficiency that makes caching so powerful (and so complicated to implement on your own).

With ASP.NET, you get first-rate caching for free, and you have a variety of options. You can cache the completely rendered HTML for a page, a portion of that HTML, or arbitrary objects. You can also customize expiration policies and set up dependencies so that items are automatically removed when other resources—such as files or database tables—are modified.

CACHING CHANGES IN .NET 2.0

ASP.NET 2.0 improves its already impressive caching model. Here is a list of changes, in their order of appearance in this chapter:

The Substitution control: Use this control to define some dynamic content on a cached page. The end result is that this content is always rendered at runtime, even when the rest of the page is pulled from the output cache.

Cache profiles: Managing a site with dozens of cached pages can be tedious, because if you need to tweak caching settings, then you need to modify each page. With cache profiles, you can define your settings in the web.config file and apply them to a batch of pages.

391

392C H A P T E R 1 1 C A C H I N G

The disk output cache: You can now explicitly tell ASP.NET to save cached information to a disk, as well as in memory. This allows a cached item to linger much longer—even if it’s removed from memory or the application restarts. Disk caching is enabled by default, but you can configure it or disable it.

SQL cache dependencies: You can remove cached data objects when the related data in the database changes, using one of ASP.NET’s hottest new features.

Custom dependencies: Need to automatically invalidate cached items when other resources change? Now the CacheDependency class is unsealed so you can derive your own custom dependencies.

You’ll learn about these innovations in this chapter.

Understanding ASP.NET Caching

Many developers who learn about caching see it as a bit of a frill, but nothing could be further from the truth. Used intelligently, caching can provide a twofold, threefold, or even tenfold performance improvement by retaining important data for just a short period of time.

ASP.NET really has two types of caching. Your applications can and should use both types, because they complement each other:

Output caching: This is the simplest type of caching. It stores a copy of the final rendered HTML page that is sent to the client. The next client that submits a request for this page doesn’t actually run the page. Instead, the final HTML output is sent automatically. The time that would have been required to run the page and its code is completely reclaimed.

Data caching: This is carried out manually in your code. To use data caching, you store important pieces of information that are time-consuming to reconstruct (such as a DataSet retrieved from a database) in the cache. Other pages can check for the existence of this information and use it, thereby bypassing the steps ordinarily required to retrieve it. Data caching is conceptually the same as using application state, but it’s much more server-friendly because items will be removed from the cache automatically when it grows too large and performance could be affected. Items can also be set to expire automatically.

Also, two specialized types of caching build on these models:

Fragment caching: This is a specialized type of output caching—instead of caching the HTML for the whole page, it allows you to cache the HTML for a portion of it. Fragment caching works by storing the rendered HTML output of a user control on a page. The next time the page is executed, the same page events fire (and so your page code will still run), but the code for the appropriate user control isn’t executed.

Data source caching: This is the caching that’s built into the data source controls, including the SqlDataSource, ObjectDataSource, and XmlDataSource. Technically, data source caching uses data caching. The difference is that you don’t need to handle the process explicitly. Instead, you simply configure the appropriate properties, and the data source control manages the caching storage and retrieval.

In this chapter, you’ll consider every caching option. You’ll begin by considering the basics of output caching and data caching. Next, you’ll consider the caching in the data source controls. Finally, you’ll explore one of ASP.NET’s hottest new features—linking cached items to tables in a database with SQL cache dependencies.

C H A P T E R 1 1 C A C H I N G

393

Output Caching

With output caching, the final rendered HTML of the page is cached. When the same page is requested again, the control objects are not created, the page lifecycle doesn’t start, and none of your code executes. Instead, the cached HTML is served. Clearly, output caching gets the theoretical maximum performance increase, because all the overhead of your code is sidestepped.

Note An ASP.NET page may use other static resources (such as images) that aren’t handled by ASP.NET. Don’t worry about caching these items. IIS automatically handles the caching of files in a more efficient way than the ASP.NET cache.

Declarative Output Caching

To see output caching in action, you can create a simple page that displays the current time of day. Figure 11-1 shows an example.

Figure 11-1. Caching an entire page

The code for this page is straightforward. It simply sets the date to appear in a label when the Page.Load event fires:

protected void Page_Load(Object sender, EventArgs e)

{

lblDate.Text = "The time is now:<br />"; lblDate.Text += DateTime.Now.ToString();

}

You have two ways to add this page to the output cache. The most common approach is to insert the OutputCache directive at the top of your .aspx file, as shown here:

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

In this example, the Duration attribute instructs ASP.NET to cache the page for 20 seconds. The VaryByParam attribute is also required, but you’ll learn about its effect in the next section.

When you run the test page, you’ll discover some interesting behavior. The first time you access the page, the current date will be displayed. If you refresh the page a short time later, however, the

394C H A P T E R 1 1 C A C H I N G

page will not be updated. Instead, ASP.NET will automatically send the cached HTML output to you (assuming 20 seconds haven’t elapsed, and therefore the cached copy of the page hasn’t expired). When the cached page expires, ASP.NET will run the page code again, generate a new cached copy, and use that for the next 20 seconds.

Twenty seconds may seem like a trivial amount of time, but in a high-volume site, it can make a dramatic difference. For example, you might cache a page that provides a list of products from a catalog. By caching the page for 20 seconds, you limit database access for this page to three operations per minute. Without caching, the page will try to connect to the database once for each client and could easily make dozens of requests in a minute.

Of course, just because you request that a page should be stored for 20 seconds doesn’t mean it actually will be. The page could be evicted from the cache early if the system finds that memory is becoming scarce. This allows you to use caching freely, without worrying too much about hampering your application by using up vital memory.

Tip When you recompile a cached page, ASP.NET will automatically remove the page from the cache. This prevents problems where a page isn’t properly updated because the older, cached version is being used. However, you might still want to disable caching while testing your application. Otherwise, you may have trouble using variable watches, breakpoints, and other debugging techniques, because your code will not be executed if a cached copy of the page is available.

Caching and the Query String

One of the main considerations in caching is deciding when a page can be reused and when information must be accurate up to the latest second. Developers, with their love of instant gratification (and lack of patience), generally tend to overemphasize the importance of real-time information.

You can usually use caching to efficiently reuse slightly stale data without a problem, and with a considerable performance improvement.

Of course, sometimes information needs to be dynamic. One example is if the page uses information from the current user’s session to tailor the user interface. In this case, full page caching just isn’t appropriate (although fragment caching may help). Another example is if the page is receiving information from another page through the query string. In this case, the page is too dynamic to cache—or is it?

The current example sets the VaryByParam attribute to None, which effectively tells ASP.NET that you need to store only one copy of the cached page, which is suitable for all scenarios. If the request for this page adds query string arguments to the URL, it makes no difference—ASP.NET will always reuse the same output until it expires. You can test this by adding a query string parameter manually in the browser window (such as ?a=b).

Based on this experiment, you might assume that output caching isn’t suitable for pages that use query string arguments. But ASP.NET actually provides another option. You can set the VaryByParam attribute to * to indicate that the page uses the query string and to instruct ASP.NET to cache separate copies of the page for different query string arguments, as shown here:

<%@ OutputCache Duration="20" VaryByParam="*" %>

Now when you request the page with additional query string information, ASP.NET will examine the query string. If the string matches a previous request, and a cached copy of that page exists, it will be reused. Otherwise, a new copy of the page will be created and cached separately.

To get a better idea how this process works, consider the following series of requests:

C H A P T E R 1 1 C A C H I N G

395

1.You request a page without any query string parameter and receive page copy A.

2.You request the page with the parameter ProductID=1. You receive page copy B.

3.Another user requests the page with the parameter ProductID=2. That user receives copy C.

4.Another user requests the page with ProductID=1. If the cached output B has not expired, it’s sent to the user.

5.The user then requests the page with no query string parameters. If copy A has not expired, it’s sent from the cache.

You can try this on your own, although you might want to lengthen the amount of time that the cached page is retained to make it easier to test.

Caching with Specific Query String Parameters

Setting VaryByParam="*" allows you to use caching with dynamic pages that vary their output based on the query string. This approach could be extremely useful for a product detail page, which receives a product ID in its query string. With vary-by-parameter caching, you could store a separate page for each product, thereby saving a trip to the database. However, to gain performance benefits you might have to increase the cached output lifetime to several minutes or longer.

Of course, this technique has some potential problems. Pages that accept a wide range of different query string parameters (such as a page that receives numbers for a calculation, client information, or search keywords) just aren’t suited to output caching. The possible number of variations is enormous, and the potential reuse is low. Though these pages will be evicted from the cache when the memory is needed, they could inadvertently force other more important information from the cache first or slow down other operations.

In many cases, setting VaryByParam to the wildcard asterisk (*) is unnecessarily vague. It’s usually better to specifically identify an important query string variable by name. Here’s an example:

<%@ OutputCache Duration="20" VaryByParam="ProductID" %>

In this case, ASP.NET will examine the query string looking for the ProductID parameter. Requests with different ProductID parameters will be cached separately, but all other parameters will be ignored. This is particularly useful if the page may be passed additional query string information that it doesn’t use. ASP.NET has no way to distinguish the “important” query string parameters without your help.

You can specify several parameters, as long as you separate them with semicolons, as follows:

<%@ OutputCache Duration="20" VaryByParam="ProductID;CurrencyType" %>

In this case, the query string will cache separate versions, provided the query string differs by ProductID or CurrencyType.

Note Output caching works well with pages that vary only based on server-side data (for example, the data in a database) and the data in query strings. However, output caching doesn’t work if the page output depends on user-specific information such as session data or cookies. Output caching also won’t work with event-driven

pages that use forms. In these cases, events will be ignored, and a static page will be re-sent with each postback, effectively disabling the page. To avoid these problems, use fragment caching instead to cache a portion of the page or use data caching to cache specific information.

396 C H A P T E R 1 1 C A C H I N G

Custom Caching Control

Varying by query string parameters isn’t the only option when storing multiple cached versions of a page. ASP.NET also allows you to create your own procedure that decides whether to cache a new page version or reuse an existing one. This code examines whatever information is appropriate and then returns a string. ASP.NET uses this string to implement caching. If your code generates the same string for different requests, ASP.NET will reuse the cached page. If your code generates a new string value, ASP.NET will generate a new cached version and store it separately.

One way you could use custom caching is to cache different versions of a page based on the browser type. That way, Netscape browsers will always receive Netscape-optimized pages, and Internet Explorer users will receive Internet Explorer–optimized HTML. To set up this sort of logic, you start by adding the OutputCache directive to the pages that will be cached. Use the VaryByCustom attribute to specify a name that represents the type of custom caching you’re creating. The following example uses the name browser because pages will be cached based on the client browser:

<%@ OutputCache Duration="10" VaryByParam="None" VaryByCustom="browser" %>

Next, you need to create the procedure that will generate the custom caching string. This procedure must be coded in the global.asax application file (or its code-behind file) and must use the following syntax:

public override string GetVaryByCustomString( HttpContext context, string arg)

{

// Check for the requested type of caching. if (arg == "browser")

{

//Determine the current browser. string browserName;

browserName = Context.Request.Browser.Browser;

browserName += Context.Request.Browser.MajorVersion.ToString();

//Indicate that this string should be used to vary caching. return browserName;

}

else

{

base.GetVaryByCustomString(context, arg)

}

}

The GetVaryByCustomString() function passes the VaryByCustom name in the arg parameter. This allows you to create an application that implements several types of custom caching in the same function. Each different type would use a different VaryByCustom name (such as Browser, BrowserVersion, or DayOfWeek). Your GetVaryByCustomString() function would examine the VaryByCustom name and then return the appropriate caching string. If the caching strings for different requests match, ASP.NET will reuse the cached copy of the page. Or, to look at it another way, ASP.NET will create and store a separate cached version of the page for each caching string it encounters.

Interestingly, the base implementation of the GetVaryByCustomString() already includes the logic for browser-based caching. That means you don’t need to code the method shown previously. The base implementation of GetVaryByCustomString() creates the cached string based on the browser name and major version number. If you want to change how this logic works (for example, to vary based on name, major version, and minor version), you could override the GetVaryByCustomString() method, as in the previous example.

C H A P T E R 1 1 C A C H I N G

397

Note Varying by browser is an important technique for cached pages that use browser-specific features. For example, if your page generates client-side JavaScript that’s not supported by all browsers, you should make the caching dependent on the browser version. Of course, it’s still up to your code to identify the browser and choose what JavaScript to render. You’ll learn more about adaptive pages and JavaScript in Part 5.

The OutputCache directive also has a third attribute that you can use to define caching. This attribute, VaryByHeader, allows you to store separate versions of a page based on the value of an HTTP header received with the request. You can specify a single header or a list of headers separated by semicolons. You could use this technique with multilingual sites to cache different versions of a page based on the client browser language, as follows:

<%@ OutputCache Duration="20" VaryByParam="None" VaryByHeader="Accept-Language" %>

Caching with the HttpCachePolicy Class

Using the OutputCache directive is generally the preferred way to cache a page, because it separates the caching instruction from the rest of your code. The OutputCache directive also makes it easy to configure several advanced properties in one line.

However, you have another choice: You can write code that uses the built-in special Response.Cache property, which provides an instance of the System.Web.HttpCachePolicy class. This object provides properties that allow you to turn on caching for the current page.

In the following example, the date page has been rewritten so that it automatically enables caching when the page is first loaded. This code enables caching with the SetCacheability() method, which specifies that the page will be cached on the server and that any other client can use the cached copy of the page. The SetExpires() method defines the expiration date for the page, which

is set to be the current time plus 60 seconds.

protected void Page_Load(Object sender, EventArgs e)

{

//Cache this page on the server. Response.Cache.SetCacheability(HttpCacheability.Public);

//Use the cached copy of this page for the next 60 seconds. Response.Cache.SetExpires(DateTime.Now.AddSeconds(60));

//This additional line ensures that the browser can't

//invalidate the page when the user clicks the Refresh button

//(which some rogue browsers attempt to do). Response.Cache.SetValidUntilExpires(true);

lblDate.Text = "The time is now:<br />" + DateTime.Now.ToString();

}

Programmatic caching isn’t as clean from a design point of view. Embedding the caching code directly in your page is often awkward, and it’s always messy if you need to include other initialization code in your page. Remember, the code in the Page.Load event handler runs only if your page isn’t in the cache (either because this is the first request for the page, because the last cached version has expired, or because the request parameters don’t match).