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

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

Đang tải... (xem toàn văn)

Thông tin tài liệu

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)? 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 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" "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" 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" "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) { 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" "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 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 . 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. 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 20 03 Server or Vista, w3wp.exe is the default process. 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

Ngày đăng: 05/07/2014, 19:20

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan