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

Professional ASP.NET 3.5 in C# and Visual Basic Part 111 doc

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 (216.33 KB, 10 trang )

Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1058
Chapter 22: State Management
Figure 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.aspx
is invoked. If you are generat-
ing URLs on the server side, use
HttpResponse.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 use
Response.Redirect
or 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 the
HttpApplication’s
cache and, as such, it is susceptible to application recycles. If you use
Windows 2000 Server or Windows XP, the
aspnet_wp.exe
process manages the ASP.NET HTTP pipeline.
If you’re running Windows 2003 Server or Vista,


w3wp.exe
is 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.
1058
Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1059
Chapter 22: State Management
It’s worth saying again: We recommend that all developers use Out-Of-Process
Session State during development, e ven 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
The
Application
object 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 the
Application
object and why. Often, the more flexible

Cache
object that helps you control an object’s lifetime is the
more useful. Caching is discussed in depth in Chapter 23.
The
Application
object is not global to the machine; it’s global to the
HttpApplication
.Ifyouare
running in the context of a Web farm, each ASP.NET application on each Web server has its own
Appli-
cation
object. Because ASP.NET applications are multithreaded and are receiving requests that are being
handled by your code o n multiple threads, access to the
Application
object should be managed using the
Application.Lock
and
Application.Unlock
methods. If your code doesn’t call Unlock directly (which
it should, shame on you) the lock is removed implicitly at the end of the
HttpRequest
that called Lock
originally.
This small example shows you how to lock the
Application
object just before inserting an object. Other
threads that might be attempting to write to the
Application
will wait until it is unlocked. This example
assumes there is an integer already stored in

Application
under the key
GlobalCount
.
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 the
Application
,asinthe
Session
, but they must be cast back to their
known types when retrieved (as shown in the preceding sample code).
1059
Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1060
Chapter 22: State Management
QueryStrings
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 for
id
under the sun. Don’t blindly cast

id
to an int, and if you do, have a plan if it fails.
A good idea is to return
Response.StatusCode=404
when 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)?
/>or
/>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
Session
object:
❑ 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.
1060
Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1061
Chapter 22: State Management
PostBacks and Cross-Page PostBacks
In classic ASP, in order to detect logical events such as a button being clicked, developers had to inspect
the
Form
collection of the
Request
object. 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 as
Button1_Click
and
TextBox1_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 the
Session
on 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 a
PostBackUrl
to all the Button controls including LinkBut-
ton and ImageButton. The
PostBackUrl
property 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 the
PostBackUrl
property set is clicked, the page does not post back to itself;
instead, the page is posted t o the URL assigned to the button control’s
PostBackUrl
property. When
a cross-page request occurs, the
PreviousPage
property of the current
Page
class holds a reference to
the page that caused the postback. To get a control reference from the
PreviousPage
,usethe

Controls
property or use the
FindControl
method.
Create a fresh site with a
Default.aspx
(as shown in Listing 22-8). Put a
TextBox
and a
Button
on it, and
set the
Button PostBackUrl
property to
Step2.aspx
. Then create a
Step2.aspx
page with a single
Label
and add a
Page_Load
handler by double-clicking the HTML Designer.
Listing 22-8: Cross-page postbacks
Default.aspx
<
!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" " />xhtml11/DTD/xhtml11.dtd"
>
<
html xmlns=" />>
<

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"
1061
Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1062

Chapter 22: State Management
PostBackUrl="~/Step2.aspx" /
>
<
/div
>
<
/form
>
<
/body
>
<
/html
>
Step2.aspx
<
!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
" />>
<
html xmlns=" />>
<
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)
{
1062
Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1063
Chapter 22: State Management
Label1.Text = text.Text;

}
}
}
}
In Listing 22-8,
Default.aspx
posts forward to
Step2.aspx
, which can then access the
Page.PreviousPage
property and retrieve a populated instance of the
Page
that caused the postback. A call to
FindControl
and a cast retrieves the
TextBox
from the previous page and copies its value into the Label of
Step2.aspx
.
Hidden Fields, ViewState, 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 the
Session
object, but

renders itself as a hidden field with the name
"__VIEWSTATE"
like this:
<
input type="hidden" name="__VIEWSTATE" value="/AAASSDAS Y/lOI=" /
>
1063
Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1064
Chapter 22: State Management
Any objects put into the ViewState must be marked
Serializable
. ViewState serializes the objects with
a special binary formatter called the LosFormatter. LOS stands for limited object serialization. It serializes
any kind of o bject, but it is optimized to contain strings, arrays, and hashtables.
To see this at work, create a new page and drag a
TextBox
,
Button
,and
HiddenField
onto it. Double-click
in the Designer to create a
Page_Load
and include the code from Listing 22-9. This example adds a string
to
HiddenField.Value
, but adds an instance of a
Person
to the
ViewState

collection. This listing illus-
trates that while ViewState is persisted in a single HTML
TextBox
on the client, it can contain both simple
types such as strings, and complex types such as
Person
. This technique has been around since ASP.NET
1.x and continues to be a powerful and simple way to persist sma l l pieces of data without utilizing server
resources.
Listing 22-9: Hidden fields and ViewState
ASPX
<
!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
" />>
<
html xmlns=" />>
<
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
1064
Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1065
Chapter 22: State Management
p.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 a
HiddenField
and to t he
ViewState
collection. Then a
Person
instance is added to the
ViewState

collection 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
>
1065
Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1066
Chapter 22: State Management
<
div
>
<
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 the
ViewState
value 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 entire
ViewState
collection is available to you in the
Page_Load
. The value of the
HiddenField
is 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 file
to specify the
validationKey
, as well as the algorithm used to protect ViewState. This
section of the file and the
decryptionKey
attribute 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 add

IsolateApps
to 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 at
www.
pluralsight.com/tools.aspx
, to generate these keys randomly.
The
validation
attribute 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 the
validation
attribute, and ASP.NET 1.1 will use the key in the
decryp-
tionKey
attribute 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,
1066
Evjen c22.tex V2 - 01/28/2008 3:19pm Page 1067
Chapter 22: State Management
and the
validation
attribute is used exclusively for ViewState, w hich can now be encrypted using 3DES
or AES and the key stored in the
validationKey
attribute.
ASP.NET 2.0 also adds the
ViewStateEncryptionMode
attribute to the <

pages
> configuration element
with two possible values,
Auto
or
Always
. Setting the attribute to
Always
will force encryption of View-
State, whereas setting it to
Auto
will encrypt ViewState only if a control requested encryption using the
new
Page.RegisterRequiresViewStateEncryption
method.
Added protection can be applied to ViewState by setting
Page.ViewStateUserKey
in the
Page_Init
to a
unique value such as the user’s ID. This must be set in
Page_Init
because 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 called
ControlState
. 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 the
ControlState
collection 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 the
Session
object. 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 the
Session
object with lots of transient data.
Using HttpContext.Current.Items for Very
Short-Term Storage
The
Items
collection of
HttpContext
is one of ASP.NET’s best-kept secrets. It is an
IDictionary
key/value collection of objects that’s shared across the life of a single
HttpRequest
. T hat’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 t heir ads from
HttpContext.Items
to 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 o f data from a large, more
1067

×