Scroll Call

Prevent unwanted scrolling, make scrollable DataGrids, and learn why paranoia can be a goodthing.

Ask the Pro

LANGUAGES: C#

TECHNOLOGIES: JavaScript | DataGrids | Validation Controls

 

Scroll Call

Prevent unwanted scrolling, make scrollable DataGrids, and learn why paranoia can be a good thing.

 

By Jeff Prosise

 

Q. When a control in a Web form generates a postback, ASP.NET scrolls back to the top of the page. Can you prevent this unwanted scrolling so a page retains its scroll position even after posting back to the server?

 

A. You bet. The scrolling isn't really ASP.NET's doing - at least not directly. It's a consequence of the fact that posting back to the server causes a brand-new page to be generated and returned to the browser. It's annoying to click on a date in a Calendar control and suddenly find yourself back at the top of the page. The page in Figure 1 demonstrates this behavior in spades. Scroll down and click on any of the controls at the bottom of the page, and presto! - you go right back to the top.

 

  

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    

      

      

      

    

  

Figure 1. Clicking on any of the controls in this Web form scrolls back to the top of the page because it generates a postback.

 

The solution is to save the scroll position before the postback occurs and restore it afterward. One way to do this is to replace the page's tag with these (many thanks to reader Shaun Walker for his contribution to the ensuing logic):

 

<%

  if (Request["__SCROLLPOS"] != null &&

      Request["__SCROLLPOS"] != String.Empty) {

      int pos = Convert.ToInt32 (Request["__SCROLLPOS"]);

      Response.Write ("

          "onscroll=\"javascript:document.forms[0]" +

          ".__SCROLLPOS.value = theBody.scrollTop;\" " +

          "onload=\"javascript:theBody.scrollTop=" +

          pos + ";\">");

  }

  else

      Response.Write ("

          "onscroll=\"javascript:document.forms[0]" +

          ".__SCROLLPOS.value = theBody.scrollTop;\">");

%>

 

You also need to add this statement somewhere (anywhere) between the

and
tags:

 

 

And if you don't have one already, add this directive to the top of the page:

 

<%@ Page Language="C#" %>

 

What do these statements do? When the page is fetched outside a postback, the C# script emits a tag containing an onscroll attribute that tracks the current scroll position and records it in the hidden control, named __SCROLLPOS. If you execute a View/Source command, you'll see something like this:

 

  "javascript:document.forms[0].__SCROLLPOS.value =

  theBody.scrollTop;">

 

When a postback occurs, the last scroll position accompanies the form's postback data by virtue of having been assigned to the __SCROLLPOS control. The C# script responds by emitting a tag, containing both an onscroll attribute and an onload attribute, that restores the page's last scroll position:

 

  "javascript:document.forms[0].__SCROLLPOS.value =

  theBody.scrollTop;"

  onload="javascript:theBody.scrollTop=615;">

 

To see for yourself, run the page in Figure 2. The new and improved page retains its scroll position no matter which control you click.

 

<%@ Page Language="C#" %>

 

  <%

    if (Request["__SCROLLPOS"] != null &&

        Request["__SCROLLPOS"] != String.Empty) {

        int pos = Convert.ToInt32 (Request["__SCROLLPOS"]);

        Response.Write ("

            "onscroll=\"javascript:document.forms[0]" +

            ".__SCROLLPOS.value = theBody.scrollTop;\" " +

            "onload=\"javascript:theBody.scrollTop=" +

            pos + ";\">");

    }

    else

        Response.Write ("

            "onscroll=\"javascript:document.forms[0]" +

            ".__SCROLLPOS.value = theBody.scrollTop;\">");

  %>

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    Hello, world!
Hello, world!
Hello, world!

    

      

      

      

      

    

  

Figure 2. This page retains its scroll position, even in the face of postbacks. Changes are highlighted in bold.

 

Now for the caveats. As presented, this code works with Internet Explorer but not Netscape Navigator. Because Navigator doesn't support onscroll, you must take more extraordinary measures to retain the scroll position there. Also, the client-side script generated by my server-side script assumes the page contains only one form (or that the runat="server" form is the first form on the page). This usually is a valid assumption for ASP.NET pages because a page can have only one runat="server" form. But if your page contains multiple forms and the runat="server" form isn't the first one, you'll need to assign the form an ID and replace document.forms[0] in my script with that ID.

 

Incidentally, you easily could wrap all that messy server-side script in a custom control and enable developers to eliminate unwanted scrolling by replacing a tag with, say, an tag and including the hidden control in their forms. Because that's simply too much fun for one person to contemplate, I'll leave the implementation to you.

 

Finally, note that you also can eliminate unwanted scrolling by including this directive in an ASPX file:

 

<%@ Page SmartNavigation="true" %>

 

This technique is far easier, but it works only with Internet Explorer 5.0 or higher and it's incompatible with some Web controls.

 

Q. Pageable DataGrids are cool. But what about scrolling DataGrids? Are there any simple tricks I can use to make a DataGrid scroll?

 

A. Is a

tag simple enough? The ASPX file in Figure 3 displays selected data from Northwind's Products table in a scrolling DataGrid. The DataGrid scrolls inside a 256 pixel-high panel defined by a
tag as shown in Figure 4. To change the height, simply modify the style attribute.

 

<%@ Import Namespace="System.Data.SqlClient" %>

 

  

    

      

        

          RunAt="server" />

      

    

  

 

Figure 3. What do you get when you combine a DataGrid and a

? A scrolling DataGrid!

 


Figure 4. Here's a scrolling DataGrid in action. You can dress up the DataGrid for greater visual effect.

 

Q. One of my colleagues - I'll call him Bob - recommended that to reduce the number of database accesses performed by an electronic storefront we're building in ASP.NET, we should round-trip information regarding catalog items in hidden controls, like this:

 

 

Another member of the team - Alice - says this leaves us wide open to security breaches. Bob says Alice is paranoid and that ASP.NET uses hidden controls all the time. Who's correct?

 

A. Pay attention to Alice - she might be paranoid, but she's one smart cookie. And she'll probably save your client a lot of money - not to mention your company a couple of lawsuits.

 

Here's the deal. Suppose you use hidden controls as described to embed data in the Web page. When the form is submitted to the server, the resulting HTTP request will look something like this:

 

POST /monitorpage.html HTTP/1.1

  .

  .

  .

Content-Type: application/x-www-form-urlencoded

Content-Length: 50

 

ItemID=12345678&Description=Monitor&Price=1179.99

 

Presumably, you'll add the item to a shopping cart at the stated price. And by round-tripping the price to the client and back, you avoid the need to hit the database again to get the price of item 12345678.

 

It sounds good, but it's an accident waiting to happen. Even an unsophisticated hacker could spoof your application by submitting this HTTP request:

 

POST /monitorpage.html HTTP/1.1

  .

  .

  .

Content-Type: application/x-www-form-urlencoded

Content-Length: 48

 

ItemID=12345678&Description=Monitor&Price=11.99

 

Paying $11.99 for a $1,179.99 monitor is a pretty good deal. But if you don't validate the price on the server side, that's exactly what will happen. The world is full of people who patrol the Web looking for vulnerabilities like this one.

 

Does ASP.NET use hidden controls? Yes. The most common example is ASP.NET's __VIEWSTATE control, which round-trips view state to the client and back. View state earns its name because it's used by controls such as DropDownList and DataGrid to persist their contents (that's why you don't have to reinitialize ASP.NET server controls inside postbacks). But view state can contain other data as well. In fact, pages (and controls) can store virtually anything they want in view state.

 

A malicious user can modify the value of __VIEWSTATE before posting back to the server. It follows that you probably don't want to store prices and other sensitive data in view state unless you intend to validate them on the server. There is, however, an alternative. You can make view state cryptographically tamper-proof by including this directive in an ASPX file:

 

<%@ Page EnableViewStateMac="true" %>

 

Or, you can include this statement in web.config:

 

 

"Mac" stands for "message authentication code." Setting EnableViewStateMac to true appends to the view state returned to the client a hash value computed from the combined view state and a secret key known only to the server. Upon postback, ASP.NET rehashes the view state transmitted in the request and rejects it if the new hash doesn't match the old. (Note: In version 1.0, ASP.NET's machine.config file sets enableViewStateMac to true, turning view state verification on by default. You still should enable verification explicitly, however, so changes to machine.config won't disable the tamper-proofing inadvertently - especially in hosting scenarios.)

 

Tamper-proofing prevents view state from being changed, but it doesn't prevent it from being read. If you write passwords or other ultra-sensitive values to view state and want to hide them from prying eyes, you should go one step further and ask ASP.NET to encrypt view state entirely. You do this by setting EnableViewStateMac to true and including this statement in web.config:

 

 

Armed with this knowledge, you can satisfy both Bob and Alice's objectives by storing item information in view state and configuring ASP.NET to encrypt it. In C#, this statement writes the item's price into view state:

 

ViewState["Price"] = 1179.99m;

 

This statement reads it back following a postback:

 

decimal price = (decimal) ViewState["Price"];

 

Bob is happy because the price is round-tripped in a hidden control, and Alice is happy because it's round-tripped securely. Keep in mind, however, that when it comes to security, nothing beats keeping potentially injurious data on the server - even if that means performing additional database accesses.

 

Round-tripping data in view state is one way to minimize time-consuming database accesses, but ASP.NET offers other ways as well. In particular, the ASP.NET application cache is an ideal storage medium for retaining data between requests. Data stored in the application cache is available to all users of a site, and you can assign expiration policies to items stored in the cache that limit their lifetimes. You even can key a cached item's lifetime to that of other items or to file-system objects. The application cache is one of the most effective weapons you have for building high-performance Web applications. If you're not familiar with the application cache already, now's a great time to crack open the documentation and learn about it.

 

The sample code in this article is available for download.

 

Jeff Prosise is author of several books, including Programming Microsoft .NET (Microsoft Press). He also is a co-founder of Wintellect (http://www.wintellect.com), a software consulting and education firm that specializes in .NET. Got a question for this column? Submit queries to [email protected].

 

Tell us what you think! Please send any comments about this article to [email protected]. Please include the article title and author.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish