Share via


This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.

ASP.NET State Services

Rob Macdonald

It's all very well to know from an intellectual point of view that the ASP.NET objects you write for Web Forms or Web Services are stateless, but the reality is that, more often than not, you need to hold information on behalf of your users while they work with your system. This month, Rob Macdonald investigates the services provided by ASP.NET to help manage state in Web applications.

Whether you're building Web Form or Web Service applications with ASP.NET, you have access to much the same system plumbing in both cases. This is especially true for state management. Of the four state management services provided by ASP.NET, three can be used with both Web Forms and Web Services. The fourth, known as View State, stores state information in a hidden HTML element sent to a browser, and it can only be used for Web Forms since Web Services simply don't use HTML.

Before launching into a discussion of the state services offered by ASP.NET, it's worth considering the full range of state management options available to a Web-based system. Figure 1 shows the typical architecture of an ASP.NET application.

An ASP.NET application will have a client, which may or may not be a browser. Storing state in a client is a highly scalable solution. At the same time, getting state information to the client can be an expensive and insecure process, and pure HTML clients are limited in their abilities to hold state information.

Web clients talk to a Web server, which in the case of an ASP.NET application is most likely to mean IIS (inetinfo.exe). IIS will pass all requests for ASPX or ASMX files to a separate process called the HTTP Runtime (aspnet_wp.exe), which contains all of the functionality we call ASP.NET and is where ASP.NET state services store their state, unless configured otherwise.

Another obvious location for state is in persistent storage such as a database, an XML file, or a message queue. In fact, in an ideal world, we'd probably all choose to store virtually all state in a database or disk file simply because persistent storage provides the most protection for our data, and, of course, transaction-based databases and message queues guarantee us significant data integrity. However, there's a very good reason why we can't rely on persistent storage for all our state—performance. Reading and writing from disk is thousands of times slower than reading data directly from memory, and in reality, all but the smallest systems can benefit profoundly from the sensible use of caching techniques to hold state in the memory of the HTTP Runtime instead of in disk storage.

Application state

The simplest state mechanism provided by ASP.NET is Application state. This will be familiar to ASP programmers, as ASP.NET provides a similar storage mechanism to classic ASP Application state. Application state provides the Web application equivalent of global variables, where each global variable is defined by a key that allows the value to be extracted from the Application object.

As with any global variables, it's common to initialize Application state when an application begins. In ASP.NET, this means adding code to the Application_Start—even in your application's global.asax.vb file—such as:

Sub Application_Start _
        (ByVal sender As Object, ByVal e As EventArgs)
  Application("DB_SERVER") = "Data Source=POLECAT;"
End Sub

Now you can access DB_SERVER anywhere in your Web application to get the name of the database to use for data queries. However, you need to be careful when accessing data in Application state. Because Web systems are likely to have multiple concurrent users, the HTTP Runtime uses a pool of threads to support multiple users. This means that any shared data is prone to being processed by two threads at the same time—a situation that can lead to all manner of subtle (and sometimes not so subtle) problems. ASP.NET provides the same solution to this problem (as did classic ASP), and it's advisable to wrap a pair of Lock/Unlock methods around any access to Application state unless you know exactly what you're doing:

Dim ConnStr As String
Application.Lock()
ConnStr = Application("DB_SERVER") + "Database=pubs;"
Application.UnLock()

It's worth pointing out that there's no restriction on placing VB.NET objects into Application state. VB6 objects have thread affinity (they can only be run using the thread that created them), so placing them in Application state where they're exposed to different threads made no sense. VB.NET objects are free-threaded and can be used in Application state, but you're ultimately responsible for synchronizing access to shared data.

Cache state

I've discussed Application state mostly to show that a popular classic ASP feature is still supported in ASP.NET. In practice, I've found very little need to use Application state because of a new ASP.NET feature called Cache state. Don't confuse Cache state with ASP.NET's Output Caching, which you can use to have ASP.NET cache entire pages (to avoid repeatedly executing code that generates relatively static pages). The Cache state I'm talking about is a programmatic mechanism that you can use as an alternative to Application state.

In its simplest form, you can use Cache state exactly as you'd use Application state—except that you don't need to provide locking, because Cache state uses built-in synchronization. Therefore, assuming you've stored some data in Cache state, you can always safely retrieve it using code such as:

Dim ConnStr As String
Application.Lock()
ConnStr = Context.Cache("DB_SERVER") + "Database=pubs;"
Application.UnLock()

However, the real benefits of Cache state derive from the way it directly addresses some of the key design issues that apply to state management.

Designing state management

Again, performance is really the only reason to use Application or Cache state—otherwise, it's simply safer to store data on disk. Storing data in Application or Cache state significantly improves system performance, but it also raises three important design issues:

  1. How do you ensure that data held in memory is kept up-to-date when the original source of data is modified?
  2. The performance benefits must be traded off against the cost of holding data in memory. It makes complete sense to hold a large DataSet (say, several megabytes in size) in memory (that is, in Application or Cache state) if it's being accessed several times per second, but it's harder to justify this decision if the data is only accessed a few times per day.
  3. When your Web application stops running, all data held in memory will be lost. There are all kinds of reasons why a Web application might stop running (for example, whenever its code is updated), and any modifications you make to cached data that aren't safely written to disk are in danger.

Cache state has some elegant functionality to address the first two of these issues. The third issue can really be safely addressed by only caching data that can be reloaded painlessly.

File dependencies

If you read last month's column ("Designing a Web Service"), you'll recall that I described a .NET quiz application and Web Service (test your .NET savvy at www.successwith.net). The Web Service can return questions on one of five .NET categories, at various levels of difficulty, and when you send answers back, it will calculate how many questions you got right. (No, I don't currently provide you with an annotated screen that supplies the correct answers.) The questions for each combination of category and level are stored in a separate XML file. Each time a Web Method is called, the appropriate XML file is loaded from disk and converted into a DataSet.

With the thousands of people who (in my dreams) use this service, the process of loading an XML file for each request is a potential bottleneck on the system. It's much, much quicker to hold the DataSets in memory. But what happens when a user spots an error or a typo? I can easily edit the XML file, but do I need to restart the application each time so that the data gets reloaded?

You've probably guessed that I don't, and this is because Cache state allows me to attach a file dependency to each cached item. If I change the file on which the cache item is dependent, ASP.NET automatically spots that the file has changed, and removes the dependent data from memory. I can then reload the data the next time the cached item is accessed. You can see how easy this is by looking at the following code:

Imports System.Web.Caching
...
Dim dsQ As DataSet
dsQ = Context.Cache(QFileName)

If IsNothing(dsQ) Then
  dsQ = New DataSet
  dsQ.ReadXml(Server.MapPath(QFileName))
  Dim dpnFile As New CacheDependency( _
       Server.MapPath(QFileName))
    Context.Cache.Insert(QFileName, dsQ, dpnFile)
  End If

Return dsQ.Question

In this code, QFileName contains the name of an XML file, and also the name of the item in cache that holds a DataSet containing the XML data. The first line of executable code simply assigns the data in cache to a DataSet called dsQ. If this turns out not to contain data, then either this is the first time this cache item has been used, or the underlying data file has been updated and a file dependency has caused the cached DataSet to be released.

In either case, the solution is to read the XML data from the file and store it on dsQ. The line of code after ReadXML is the line that creates the file dependency, and the line after that inserts the DataSet (along with its dependency) into Cache state. ASP.NET takes care of everything else.

Time-based dependencies and prioritization

You can also configure Cache state to address the memory vs. performance design issue. When you insert items into the cache, you can specify arguments that define either an absolute or sliding timeout for the data. The sliding timeout is especially useful, because it allows you to cause infrequently used data to be dropped from cache, while frequently used data is retained.

For example, some of my .NET quizzes are bound to be more popular than others, but I don't know which. The following code allows me to say that if a cached item isn't accessed within five minutes of the previous access, it should be dropped from the cache:

Context.Cache.Insert( _
    QFileName, dsQuestions, dpnFile, _
    DateTime.MaxValue, TimeSpan.FromMinutes(5))

Here, the last argument specifies the sliding expiration value. When using sliding expiration, the absolute expiration period must be set to DateTime.MaxValue because the two mechanisms can't be combined.

Time-based dependencies provide an elegant way to make sure that cache is used effectively. However, there's more. ASP.NET uses a process called "scavenging" to free up memory when resources become scarce, and you can specify the importance of your cached data using the CacheItemPriority and CacheItemPriorityDecay enumerations to determine which cached data should be dumped first when the heat is on.

Session state

The third state management technique provided by ASP.NET is Session state. Unlike Application state and Cache state, Session state allows you to store state that's specific to a given user. This is very convenient in Web applications because HTTP has no built-in concept of session—each HTTP request is treated as a brand-new request, with no knowledge that it may be one of a series of requests from a current user. Fortunately, HTTP allows information to be transmitted in HTTP headers, and (like classic ASP) ASP.NET can use a special HTTP header called a cookie to send a SessionID to each new client. If the client sends the SessionID cookie back with each subsequent request, ASP.NET is able to recognize this client as an existing user. The SessionID works a bit like Cache state with sliding expiration, because ASP.NET will forget about a user if he or she doesn't return to the Web application within a specified period that, by default, is 20 minutes.

ASP.NET addresses one of the major problems with Session state in classic ASP. Session state is a very convenient way of holding data about a current user, such as shopping cart purchases. Unfortunately, classic ASP Session state suffers from two weaknesses, both of which relate to the fact that it's held in the Web server's memory:

  • If the Web server stops, the Session data, including your customer's valuable shopping cart, is lost, and the user may not have the enthusiasm to enter it all over again.
  • Web applications that use Session state are limited to running on a single server. A common solution to increasing server demand is to set up two or more servers running the same application and listening to the same IP address (a "Web farm"). This is an effective way to spread the application load, but it isn't easy to make sure that a given user's requests are all directed to the same server. Data that's in Session state on one server won't be in Session state on another server—which can lead to great confusion!

By default, ASP.NET holds Session state "in process" just like classic ASP. What's new is that you can configure ASP.NET Session state to be stored outside the HTTP Runtime's memory, either in a special State Service (which provides Session state that will survive Web server restarts) or in SQL Server (which provides Session state that survives computer restarts). Even better, you can configure several Web servers in a Web farm to use a centralized State Service or a SQL Server database so that all Web servers store and retrieve state from the same location, thereby making Session state usable in Web farms.

Of course, none of these additional locations for Session state management are as efficient as storing the data in process, but they do provide additional resilience and scalability options and are therefore an attractive extension to the model inherited from classic ASP. What's more, you can change the Session state model you use without having to make any code changes—all you need to do is make changes to the web.config file, which specifies how Session state is configured.

I'm going to round off this month's column by discussing how Session state can be used in Web Services. Web Services can easily use Session state because they can send the Session cookie to a client via their HTTP headers, just like a Web page. In fact, a Web Service need only enable Session state through the WebMethod attribute and they can begin using Session state. Here's a simple example:

<WebMethod(EnableSession:=True)> _
 Public Function HelloWorld() As String
   Session("Str") += "Hello"
   HelloWorld = Session("Str").ToString
 End Function

This code will extend the return value of HelloWorld by five characters each time a user calls it. Easy—but there's a catch. You'll remember that Session state only works when a client extracts the SessionID cookie from an HTTP header, and then adds the cookie into the HTTP header of every subsequent request. Well, browsers do this automatically, which makes Session state so convenient for browser-based applications.

Unfortunately, Web Service clients typically aren't browsers; you might have a VB.NET Web or Windows application client, for example, or a simple HTTP Get or Post client, an ASP.NET client, a Mobile Internet Toolkit client, or even a VB6 or classic ASP client, enabled thanks to the SOAP toolkit. As a result, when we use Session state in our Web Services, any program using our Web Service needs to know that we're relying on a Session cookie and needs to be coded explicitly to handle this cookie. While cookie processing isn't exactly hard, you need to think about whether you really should expect your Web Service users to go to this extra effort. It may be easier to provide a more direct and obvious way to track a caller's identity, or simply design your Web Service to not require a session—the approach I take in the .NET Quiz example.

Conclusion

In this article, you've seen some of the issues that affect state management in Web Form or Web Service applications, and you've seen some of the ASP.NET features that are provided to make state management easier. The accompanying Download file contains the full code for the .NET Quiz Web Service, or you can download it from www.successwith.net.

I've been focusing on Web Services over the past three months and hope I've provided you with enough information so you can write your own Web Services. Once you've spent time on a Web Service, you start thinking about how to control who can use it—and maybe you even want to start charging people for the privilege! Next month will round out this series. Guess what the topic will be? Right! How to secure your Web Service.

Link to www.successwith.net/quiz

To find out more about Visual Basic Developer and Pinnacle Publishing, visit their website at http://www.pinpub.com/

Note: This is not a Microsoft Corporation website. Microsoft is not responsible for its content.

This article is reproduced from the December 2001 issue of Visual Basic Developer. Copyright 2001, by Pinnacle Publishing, Inc., unless otherwise noted. All rights are reserved. Visual Basic Developer is an independently produced publication of Pinnacle Publishing, Inc. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of Pinnacle Publishing, Inc. To contact Pinnacle Publishing, Inc., please call 1-800-788-1900.