When you store information in cookies, remember that it’s quite different from storing data in the Sessionobject: ❑ Cookies are passed back and forth on every request.. Listing 22-9: Hid
Trang 1Figure 22-8
Additionally, all URLS must be relative Remember that the Session ID appears as if it were a directory.
The Session is lost if an absolute URL such as/myapp/retrieve.aspxis invoked If you are
generat-ing URLs on the server side, useHttpResponse.ApplyAppPathModifier() It changes a URL when the
Session ID is embedded, as shown here:
Response.Write(Response.ApplyAppPathModifier("foo/bar.aspx"));
The previous line generates a URL similar to the following:
/myapp/ (S(avkbnbml4n1n5mi5dmfqnu45))/foo/bar.aspx
Notice that not only was session information added to the URL, but it was also converted from a relative
URL to an absolute URL, including the application’s virtual directory This method can be useful when
you need to useResponse.Redirector build a URL manually to redirect from an HTTP page to an
HTTPS page while still maintaining cookieless session state
Choosing the Correct Way to Maintain State
Now that you’re familiar with the variety of options available for maintaining state in ASP.NET 2.0,
here’s some real-world advice from production systems The In-Process (InProc) Session provider is the
fastest method, of course, because everything held in memory is a live object reference This provider
is held in theHttpApplication’scache and, as such, it is susceptible to application recycles If you use
Windows 2000 Server or Windows XP, theaspnet_wp.exeprocess manages the ASP.NET HTTP pipeline
If you’re running Windows 2003 Server or Vista,w3wp.exeis the default process that hosts the runtime
You must find a balance between the robustness of the out-of-process state service and the speed of the
in-process provider In our experience, the out-of-process state service is usually about 15 percent slower
than the in-process provider because of the serialization overhead and marshaling SQL Session State is
about 25 percent slower than InProc Of course your mileage will likely vary Don’t let these numbers
concern you too much Be sure to do scalability testing on your applications before you panic and make
inappropriate decisions
Trang 2It’s worth saying again: We recommend that all developers use Out-Of-Process
Session State during development, even if this is not the way your application will
be deployed Forcing yourself to use the Out-Of-Process provider enables you to
catch any potential problems with custom objects that do not carry the
Serializable attribute If you design your entire site using the In-Process provider
and then discover, late in the project, that requirements force you to switch to the
SQL or Out-Of-Process providers, you have no guarantee that your site will work as
you wrote it Developing with the Out-Of-Process provider gives you the best of
both worlds and does not affect your final deployment method Think of it as an
insurance policy that costs you nothing upfront.
The Application Object
TheApplicationobject is the equivalent of a bag of global variables for your ASP.NET application
Global variables have been considered harmful for many years in other programming environments, and ASP.NET is no different You should give some thought to what you want to put in theApplication
object and why Often, the more flexibleCacheobject that helps you control an object’s lifetime is the
more useful Caching is discussed in depth in Chapter 23
TheApplicationobject is not global to the machine; it’s global to theHttpApplication If you are
running in the context of a Web farm, each ASP.NET application on each Web server has its own Appli-cationobject Because ASP.NET applications are multithreaded and are receiving requests that are being handled by your code on multiple threads, access to theApplicationobject should be managed using the
Application.LockandApplication.Unlockmethods If your code doesn’t call Unlock directly (which
it should, shame on you) the lock is removed implicitly at the end of theHttpRequestthat called Lock
originally
This small example shows you how to lock theApplicationobject just before inserting an object Other threads that might be attempting to write to theApplicationwill wait until it is unlocked This example assumes there is an integer already stored inApplicationunder the keyGlobalCount
VB
Application.Lock()
Application("GlobalCount") = CType(Application("GlobalCount"), Integer) + 1
Application.UnLock()
C#
Application.Lock();
Application["GlobalCount"] = (int)Application["GlobalCount"] + 1;
Application.UnLock();
Object references can be stored in theApplication, as in theSession, but they must be cast back to their known types when retrieved (as shown in the preceding sample code)
Trang 3Quer yStrings
The URL, or QueryString, is the ideal place for navigation-specific — not user-specific — data The
QueryString is the most hackable element on a Web site, and that fact can work for you or against you
For example, if your navigation scheme uses your own page IDs at the end of a query string (such as
/localhost/mypage.aspx?id = 54) be prepared for a user to play with that URL in his browser, and try
every value foridunder the sun Don’t blindly castidto an int, and if you do, have a plan if it fails
A good idea is to returnResponse.StatusCode=404when someone changes a URL to an unreasonable
value Another fine idea that Amazon.com implemented was the Smart 404 Perhaps you’ve seen these:
They say ‘‘Sorry you didn’t find what you’re looking for Did you mean _?’’
Remember, your URLs are the first thing your users may see, even before they see your HTML Hackable
URLs — hackable even by my mom — make your site more accessible Which of these URLs is friendlier
and more hackable (for the right reason)?
http://reviews.cnet.com/Philips_42PF9996/4505-6482_7-31081946.html?tag=cnetfd.sd
or
http://www.hanselman.com/blog/CategoryView.aspx?category=Movies
Cookies
Do you remember the great cookie scare of 1997? Most users weren’t quite sure just what a cookie was,
but they were all convinced that cookies were evil and were storing their personal information Back then,
it was likely personal information was stored in the cookie! Never, ever store sensitive information, such
as a user ID or password, in a cookie Cookies should be used to store only non-sensitive information, or
information that can be retrieved from an authoritative source Cookies shouldn’t be trusted, and their
contents should be able to be validated For example, if a Forms Authentication cookie has been tampered
with, the user is logged out and an exception is thrown If an invalid Session ID cookie is passed in for
an expired Session, a new cookie can be assigned
When you store information in cookies, remember that it’s quite different from storing data in the
Sessionobject:
❑ Cookies are passed back and forth on every request That means you are paying for the size of
your cookie during every HTTP GET and HTTP POST.
❑ If you have ten 1-pixel spacer GIFs on your page used for table layouts, the user’s browser is
sending the same cookie eleven times: once for the page itself, and once for each spacer GIF, even
if the GIF is already cached
❑ Cookies can be stolen, sniffed, and faked If your code counts on a cookie’s value, have a plan in
your code for the inevitability that cookie will get corrupted or be tampered with
❑ What is the expected behavior of your application if a cookie doesn’t show? What if it’s 4096
bytes? Be prepared You should design your application around the ’’principle of least surprise.’’
Your application should attempt to heal itself if cookies are found missing or if they are larger
than expected
❑ Think twice before Base64 encoding anything large and placing it in a cookie If your design
depends on this kind of technique, rethink using either the Session or another backing-store
Trang 4PostBacks and Cross- Page PostBacks
In classic ASP, in order to detect logical events such as a button being clicked, developers had to inspect theFormcollection of theRequestobject Yes, a button was clicked in the user’s browser, but no object
model was built on top of stateless HTTP and HTML ASP.NET 1.x introduced the concept of the
post-back, wherein a server-side event was raised to alert the developer of a client-side action If a button is
clicked on the browser, the Form collection is POSTed back to the server, but now ASP.NET allowed the developer to write code in events such asButton1_ClickandTextBox1_Changed
However, this technique of posting back to the same page is counter-intuitive, especially when you are
designing user interfaces that aim to create wizards to give the user the sense of forward motion
This chapter is about all aspects of state management Postbacks and cross-page postbacks, however, are covered extensively in Chapter 3 so this chapter touches on them only in the context of state management
Postbacks were introduced in ASP.NET 1.x to provide an eventing subsystem for Web development It
was inconvenient to have only single-page postbacks in 1.x, however, and that caused many developers
to store small objects in theSessionon a postback and then redirect to the next page to pick up the stored data With cross-page postbacks, data can be posted ’’forward’’ to a different page, often obviating the
need for storing small bits of data that could be otherwise passed directly
ASP.NET 2.0 and above includes the notion of aPostBackUrlto all the Button controls including LinkBut-ton and ImageButLinkBut-ton ThePostBackUrlproperty is both part of the markup when a control is presented
as part of the ASPX page, as seen in the following, and is a property on the server-side component that’s available in the code-behind:
<asp:Button PostBackUrl="url" >
When a button control with thePostBackUrlproperty set is clicked, the page does not post back to itself; instead, the page is posted to the URL assigned to the button control’sPostBackUrlproperty When
a cross-page request occurs, thePreviousPageproperty of the currentPageclass holds a reference to
the page that caused the postback To get a control reference from thePreviousPage, use theControls
property or use theFindControlmethod
Create a fresh site with aDefault.aspx(as shown in Listing 22-8) Put aTextBoxand aButtonon it, and set theButton PostBackUrlproperty toStep2.aspx Then create aStep2.aspxpage with a singleLabel
and add aPage_Loadhandler by double-clicking the HTML Designer
Listing 22-8: Cross-page postbacks
Default.aspx
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/
xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Cross-page PostBacks</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:TextBox ID="TextBox1" Runat="server"></asp:TextBox>
<asp:Button ID="Button1" Runat="server" Text="Button"
Trang 5PostBackUrl="~/Step2.aspx" />
</div>
</form>
</body>
</html>
Step2.aspx
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Step 2</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
</div>
</form>
</body>
</html>
VB — Step2.aspx.vb
Partial Class Step2
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Me.Load
If PreviousPage IsNot Nothing AndAlso PreviousPage.IsCrossPagePostBack Then Dim text As TextBox = _
CType(PreviousPage.FindControl("TextBox1"), TextBox)
If text IsNot Nothing Then Label1.Text = text.Text End If
End If End Sub
End Class
CS — Step2.aspx.cs
using System;
using System.Web.UI.WebControls;
public partial class Step2 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (PreviousPage != null && PreviousPage.IsCrossPagePostBack) {
TextBox text = PreviousPage.FindControl("TextBox1") as TextBox;
if (text != null) {
Trang 6Label1.Text = text.Text;
} }
}
}
In Listing 22-8,Default.aspxposts forward toStep2.aspx, which can then access thePage.PreviousPage
property and retrieve a populated instance of thePagethat caused the postback A call toFindControl
and a cast retrieves theTextBoxfrom the previous page and copies its value into the Label ofStep2.aspx
Hidden F ields, V iewState, and ControlState
Hidden input fields such as<input type=""hidden" name="foo">are sent back as name/value pairs
in a Form POST exactly like any other control, except they are not rendered Think of them as hidden
text boxes Figure 22-9 shows a HiddenField control on the Visual Studio Designer with its available
properties Hidden fields are available in all versions of ASP.NET
Figure 22-9
ViewState, on the other hand, exposes itself as a collection of key/value pairs like theSessionobject, but renders itself as a hidden field with the name" VIEWSTATE"like this:
<input type="hidden" name=" VIEWSTATE" value="/AAASSDAS Y/lOI=" />
Trang 7Any objects put into the ViewState must be markedSerializable ViewState serializes the objects with
a special binary formatter called the LosFormatter LOS stands for limited object serialization It serializes
any kind of object, but it is optimized to contain strings, arrays, and hashtables
To see this at work, create a new page and drag aTextBox,Button, andHiddenFieldonto it Double-click
in the Designer to create aPage_Loadand include the code from Listing 22-9 This example adds a string
toHiddenField.Value, but adds an instance of aPersonto theViewStatecollection This listing
illus-trates that while ViewState is persisted in a single HTMLTextBoxon the client, it can contain both simple
types such as strings, and complex types such asPerson This technique has been around since ASP.NET
1.x and continues to be a powerful and simple way to persist small pieces of data without utilizing server
resources
Listing 22-9: Hidden fields and ViewState
ASPX
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Hidden Fields and ViewState</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:TextBox ID="TextBox1" Runat="server"></asp:TextBox>
<asp:Button ID="Button1" Runat="server" Text="Button" />
<asp:HiddenField ID="HiddenField1" Runat="server" />
</div>
</form>
</body>
</html>
VB
<Serializable> _
Public Class Person
Public firstName As String
Public lastName As String
End Class
Partial Class _Default
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Me.Load
If Not Page.IsPostBack Then HiddenField1.Value = "foo"
ViewState("AnotherHiddenValue") = "bar"
Dim p As New Person
Trang 8p.firstName = "Scott"
p.lastName = "Hanselman"
ViewState("HiddenPerson") = p End If
End Sub
End Class
C#
using System;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
[Serializable]
public class Person
{
public string firstName;
public string lastName;
}
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
HiddenField1.Value = "foo";
ViewState["AnotherHiddenValue"] = "bar";
Person p = new Person();
p.firstName = "Scott";
p.lastName = "Hanselman";
ViewState["HiddenPerson"] = p;
}
}
}
In Listing 22-9, a string is added to aHiddenFieldand to theViewStatecollection Then aPerson
instance is added to theViewStatecollection with another key A fragment of the rendered HTML is
shown in the following code:
<form method="post" action="Default.aspx" id="form1">
<div>
<input type="hidden" name=" VIEWSTATE"
value="/wEPDwULLTIxMjQ3OTEzODcPFgQeEkFub3RoZXJIaWRkZW5WYWx1ZQUDYmFyHgxIaWRkZW5QZXJz
b24ypwEAAQAAAP////8BAAAAAAAAAAwCAAAAP3ZkcTVqYzdxLCBWZXJzaW9uPTAuMC4wLjAsIEN1bHR1cmU
9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49bnVsbAUBAAAAE0RlZmF1bHRfYXNweCtQZXJzb24CAAAACWZpcn
N0TmFtZQhsYXN0TmFtZQEBAgAAAAYDAAAABVNjb3R0BgQAAAAJSGFuc2VsbWFuC2RkI/CLauUviFo58BF8v
pSNsjY/lOI=" />
</div>
Trang 9<input name="TextBox1" type="text" id="TextBox1" />
<input type="submit" name="Button1" value="Button" id="Button1" />
<input type="hidden" name="HiddenField1" id="HiddenField1" value="foo" />
</div>
</form>
Notice that theViewStatevalue uses only valid ASCII characters to represent all its contents Don’t let
the sheer mass of it fool you It is big and it appears to be opaque However, it’s just a hidden text box
and is automatically POSTed back to the server The entireViewStatecollection is available to you in the
Page_Load The value of theHiddenFieldis stored as plain text
Neither ViewState nor Hidden Fields are acceptable for any kind of sensitive data
People often complain about the size of ViewState and turn if off completely
without realizing its benefits ASP.NET 2.0 cut the size of serialized ViewState
nearly in half You can find a number of tips on using ViewState on my blog by
Googling for ’’Hanselman ViewState’’ Fritz Onion’s free ViewStateDecoder tool
from www.pluralsight.com is a great way to gain insight into what’s stored in your
pages’ ViewState Note also Nikhil Kotari’s detailed blog post on ViewState
improvements at www.nikhilk.net/ViewStateImprovements.aspx
By default, the ViewState field is sent to the client with a salted hash to prevent tampering Salting means
that the ViewState’s data has a unique value appended to it before it’s encoded As Keith Brown says
’’Salt is just one ingredient to a good stew.’’ The technique used is called HMAC, or hashed message
authentication code As shown in the following code, you can use the<machineKey>element of the
web.config fileto specify thevalidationKey, as well as the algorithm used to protect ViewState This
section of the file and thedecryptionKeyattribute also affect how Forms Authentication cookies are
encrypted (see Chapter 21 for more on forms authentication)
<machineKey validationKey="AutoGenerate,IsolateApps"
decryptionKey="AutoGenerate,IsolateApps" validation="SHA1" />
If you are running your application in a Web farm,<validationKey>and<decryptionKey>have to
be manually set to the same value Otherwise, ViewState generated from one machine could be POSTed
back to a machine in the farm with a different key! The keys should be 128 characters long (the maximum)
and generated totally by random means If you addIsolateAppsto these values, ASP.NET generates a
unique encrypted key for each application using each application’s application ID
I like to use security guru Keith Brown’s GenerateMachineKey tool, which you can find atwww
pluralsight.com/tools.aspx, to generate these keys randomly.
Thevalidationattribute can be set to SHA1 or MD5 to provide tamper-proofing, but you can include
added protection by encrypting ViewState as well In ASP.NET 1.1 you can encrypt ViewState only by
using the value 3DES in thevalidationattribute, and ASP.NET 1.1 will use the key in the
decryp-tionKeyattribute for encryption However, ASP.NET 2.0 adds a new decryption attribute that is used
exclusively for specifying the encryption and decryption mechanisms for forms authentication tickets,
Trang 10and thevalidationattribute is used exclusively for ViewState, which can now be encrypted using 3DES
or AES and the key stored in thevalidationKeyattribute
ASP.NET 2.0 also adds theViewStateEncryptionModeattribute to the<pages>configuration element
with two possible values,AutoorAlways Setting the attribute toAlwayswill force encryption of View-State, whereas setting it toAutowill encrypt ViewState only if a control requested encryption using the newPage.RegisterRequiresViewStateEncryptionmethod
Added protection can be applied to ViewState by settingPage.ViewStateUserKeyin thePage_Initto a unique value such as the user’s ID This must be set inPage_Initbecause the key should be provided to ASP.NET before ViewState is loaded or generated For example:
protected void Page_Init (Object sender, EventArgs e)
{
if (User.Identity.IsAuthenticated)
ViewStateUserKey = User.Identity.Name;
}
When optimizing their pages, ASP.NET programmers often disable ViewState for many controls when
that extra bit of state isn’t absolutely necessary However, in ASP.NET 1.x, disabling ViewState was a
good way to break many third-party controls, as well as the included DataGrid’s sorting functionality
ASP.NET now includes a second, parallel ViewState-like collection calledControlState This dictionary can be used for round-tripping crucial information of limited size that should not be disabled even when ViewState is You should only store data in theControlStatecollection that is absolutely critical to the functioning of the control
Recognize that ViewState, and also ControlState, although not secure, is a good place to store small bits of
a data and state that don’t quite belong in a cookie or theSessionobject If the data that must be stored is relatively small and local to that specific instance of your page, ViewState is a much better solution than littering theSessionobject with lots of transient data
Using HttpContext.Current.Items for Ver y
Shor t- Term Storage
TheItemscollection ofHttpContextis one of ASP.NET’s best-kept secrets It is anIDictionary
key/value collection of objects that’s shared across the life of a singleHttpRequest That’s a single
HttpRequest Why would you want to store state for such a short period of time? Consider these reasons:
❑ When you share content between IHttpModules and IHttpHandlers:If you write a custom
IHttpModule, you can store context about the user for use later in a page
❑ When you communicate between two instances of the same UserControl on the same page:
Imagine you are writing a UserControl that serves banner ads Two instances of the same control could select their ads fromHttpContext.Itemsto prevent showing duplicates on the same page
❑ When you store the results of expensive calls that might otherwise happen twice or more on a page:If you have multiple UserControls that each show a piece of data from a large, more