UpdatePanel Tips and Tricks


UpdatePanel Tips and Tricks
Jeff Prosise

For better or for worse, the UpdatePanel control is the darling of the ASP.NET AJAX community. I say "for better" because UpdatePanel makes partial-page rendering shockingly easy and "for worse" because its simplicity and ease-of-use come at the cost of efficiency and, ironically, bandwidth.
While UpdatePanel brings AJAX magic to ordinary Web pages, it doesn’t bring the efficiency we normally associate with AJAX. Did you know, for example, that when an UpdatePanel control performs an asynchronous AJAX callback to the server to update its content, the request contains everything a conventional ASP.NET postback contains, including view state? Most developers assume that AJAX eliminates view state. That’s not the case, though, with UpdatePanel’s brand of AJAX.
If you’re going to use an UpdatePanel control, you need to know what you’re getting in to. In many cases, from a performance point of view, an application is better off not using UpdatePanel and instead using asynchronous calls to WebMethods or page methods. Doing so can reduce the volume of data traveling over the wire by an order of magnitude or more. However, it’s also a fundamental shift where the UI updates need to be handled explicitly by the developer with JavaScript in the page.
In addition, ASP.NET AJAX discussion forums are already filled with questions about customizing UpdatePanel. Many of these questions are easily answered once you know about PageRequestManager, the JavaScript class in the Microsoft® AJAX Library that provides the client-side support for UpdatePanels.
Now that ASP.NET AJAX has shipped, I’d like to examine UpdatePanel more closely—taking a close look at how you can customize it, optimize it, and even live without it. And that’s exactly what this column is all about.

Update Highlighting
Sometimes you can’t help but feel sorry for the developers at Microsoft. If they don’t do their job well enough, they get pounded by the public. However, they sometimes also get slammed if they do their job too well. For example, I recently got e-mail from a customer complaining that the ASP.NET AJAX UpdatePanel works a little too well.
UpdatePanel makes it extremely easy to take all that flashing and flickering that occurs when an ASP.NET page posts back to the server and turn it into smooth, flicker-free updates. UpdatePanel works its magic by converting postbacks into asynchronous callbacks (XML-HTTP requests) and using JavaScript on the client to refresh the part of the page encapsulated by the UpdatePanel control. The flashing and flickering goes away because the browser doesn’t repaint the page as it does during a postback.
The customer’s complaint was that users were sometimes not noticing that part of the page had been updated with new content. His question was simple: can the folks on the ASP.NET AJAX team make UpdatePanel flicker just a little so important updates won’t be missed?
The bad news is that the ASP.NET AJAX team probably isn’t interested in making UpdatePanels flicker. After all, getting rid of the flicker is why UpdatePanel was invented in the first place. The good news is that you can work a little AJAX magic in the browser to draw attention to updated UpdatePanels. The secret is the Sys.WebForms.PageRequestManager class in the Microsoft AJAX Library—the library of JavaScript classes that comprise the client half of ASP.NET AJAX. PageRequestManager manages the asynchronous callbacks launched by UpdatePanel. It’s also responsible for updating the content inside an UpdatePanel when an asynchronous callback completes.
PageRequestManager fires events in the browser before and after an update occurs. You can hook these events in JavaScript and run code that draws a user’s attention to the updated content. The key event is named pageLoaded. This event fires each time the page loads in the browser (it’s analogous to Page_Load in ASP.NET). It also fires each time an asynchronous callback launched on behalf of an UpdatePanel control completes and the content inside that UpdatePanel is updated. You can register a JavaScript handler for the pageLoaded event with two lines of code (which can be combined into one):

var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_pageLoaded(pageLoaded);
The first line acquires a reference to the page’s PageRequestManager object. The second registers a JavaScript function named pageLoaded as a handler for pageLoaded events.
When called, a pageLoaded event handler receives an argument of type Sys.WebForms.PageLoadedEventArgs, which is another class in the Microsoft AJAX Library. PageLoadedEventArgs contains a get_panelsUpdated method that you can call to enumerate all the UpdatePanels, if any, whose content was just updated. By default, an UpdatePanel is nothing more than a DIV on the client-side so you can use JavaScript to flash the DIV, highlight it, or do whatever you want to do to draw a user’s attention to it.
The code listed in Figure 1 shows one way to use pageLoaded events to perform update highlighting. Each time an update occurs, this JavaScript flashes the Document Object Model (DOM) elements representing updated UpdatePanels by making them appear and disappear three times in quick succession. The flashing is performed by a helper function named flashPanels, which takes a flash count as an input parameter.


<script type=”text/javascript”>

var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_pageLoaded(pageLoaded);

var _panels, _count;

function pageLoaded(sender, args)
{
    if (_panels != undefined && _panels.length > 0)
    {
        for (i=0; i < _panels.length; i++)
            _panels[i].dispose();
    }

    var panels = args.get_panelsUpdated();

    if (panels.length > 0)
    {
        _panels = new Array(panels.length);

        for (i=0; i < panels.length; i++) 
            _panels[i] = new Sys.UI.Control(panels[i]);

        flashPanels(3);
    }
}

function flashPanels(count)
{
    _count = (count << 1) + 1;
        
    for (i=0; i < _panels.length; i++) 
        _panels[i].set_visible(false);

    window.setTimeout(toggleVisibility, 50);
}

function toggleVisibility()
{
    for (i=0; i < _panels.length; i++) 
        _panels[i].set_visible(!_panels[i].get_visible());
        
    if (--_count > 0)
        window.setTimeout(toggleVisibility, 50);
}
</script>

Note how an updated UpdatePanel’s visibility is toggled on and off to create a flashing effect. Rather than interacting with DOM elements directly, the code wraps the DOM elements representing the UpdatePanels with Sys.UI.Control objects. Then it uses Sys.UI.Control’s set_visible and get_visible methods to toggle the visibility:

_panels[i].set_visible(!_panels[i].get_visible());
Sys.UI.Control is a JavaScript class found in the Microsoft AJAX Library—specifically, in MicrosoftAjax.js. The benefit to toggling visibility in this manner is that this is browser-independent. This works equally well in every browser that supports ASP.NET AJAX (which is virtually all modern browsers). JavaScript code that interacts directly with the browser DOM, on the other hand, would have to be tweaked to work in different browser types.

Canceling UpdatePanel Updates
The pageLoaded event is one of several events that the PageRequestManager class fires when an UpdatePanel goes back to the server to update its content. Another important event that PageRequestManager fires is initializeRequest, which fires before an asynchronous callback occurs.
Someone recently asked me whether it’s possible to decide at runtime whether an AsyncPostBackTrigger should be allowed to trigger an UpdatePanel update. The answer is yes. This is done by processing initializeRequest events.
The second parameter passed to an initializeRequest handler is an object of type initializeRequestEventArgs. That object contains a get_postBackElement method that identifies the button or other element that triggered the update. It also has a set_cancel method that you can use to cancel a callback before it occurs. Here’s an example of the set_cancel method in action:

<script type=”text/javascript”>

var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_initializeRequest(initializeRequest);

function initializeRequest(sender, args)
{
    args.set_cancel(!confirm(‘Are you sure?’));
}
</script>
In this sample, before a callback executes, an intializeRequest handler pops up a confirmation box asking the user whether the update should proceed. Clicking Cancel in the confirmation box passes true to set_cancel, which stops the callback in its tracks. In real life, you probably aren’t interested in prompting the user for confirmation before allowing an update to proceed, but it might be useful to have the ability to cancel an update based on conditions elsewhere in the application.
Incidentally, it’s also possible to cancel asynchronous callbacks after they’ve been executed but before they’ve completed. PageRequestManager provides an abortPostBack method for doing this; it also provides a get_isInAsyncPostBack method for determining whether an asynchronous callback is pending. These methods are often used with UpdateProgress controls to present a cancellation UI.

Multiple UpdatePanels
A page can host several UpdatePanels. By default, when one UpdatePanel on a page updates, the other UpdatePanels on the page also update. Sometimes that’s what you want, but more often than not, you don’t need every UpdatePanel updating in response to other UpdatePanels.
You can be selective about which UpdatePanel instances update (and when) by setting the UpdateMode property of each UpdatePanel control on the page to "Conditional." Then, when one UpdatePanel updates and calls a server-side event handler, call UpdatePanel.Update on the other panels you want to update. This reduces the load on the server by reducing the number of controls that render, and it reduces the volume of data in the response because UpdatePanels that don’t update don’t add anything to the response.

Updates without UpdatePanels
AJAX isn’t just about creating a better user experience—it’s also about delivering greater efficiency on the wire. When a traditional ASP.NET postback occurs, all the data in the Web form, including view state, travels up to the server in the postback. View state is one reason ASP.NET pages, especially ones that use DataGrid and GridView controls, can seem sluggish. Pages with too much view state are a bane to performance—and pages with too much view state are all too common in ASP.NET applications.
One of the benefits of replacing ASP.NET postbacks with AJAX callbacks is that, done properly, AJAX callbacks transmit only the data that needs to be transmitted. This means they do not have to include view state in that transmission.
When you use UpdatePanel to perform flicker-free updates on a page, you may think you’re building in efficiency. After all, UpdatePanels use AJAX, right? Unfortunately, if you check out the traffic on the wire when an UpdatePanel updates, you’ll find you’re not saving much at all—at least not in the send. The View state data (and other data) that’s normally transmitted to the server during a postback is also transmitted during UpdatePanel callbacks. In fact, what goes up in an asynchronous XML-HTTP request from an UpdatePanel is almost identical to what goes up in a standard ASP .NET postback. Here’s the dirty little secret about ASP.NET AJAX: UpdatePanel is about ease of use, not efficiency on the wire.
There’s little you can do to make UpdatePanels more efficient, but you can forego the use of UpdatePanels and use other features of ASP.NET AJAX to update content on pages just as smoothly and much more efficiently. It takes a little more work, but the results are often worthwhile since you can dramatically reduce the volume of data traveling between the client and the server.
You can also reduce the load on your server. When an UpdatePanel calls back to the server, the page targeted by the callback goes through an almost complete lifecycle—the page is instantiated, the controls in the page are instantiated, and controls inside the UpdatePanel undergo a normal rendering cycle. That’s a lot of overhead just to update a portion of the page.
As an example, consider the page fragment in Figure 2. It provides a simple UI that allows the user to type in a ZIP Code and click a button to initialize the city and state fields with a city and state (see Figure 3). All the controls are hosted in an UpdatePanel, so the Button control’s postback is converted into an asynchronous callback and the event handler (GetCityAndState) is called on the server inside the callback. GetCityAndState (the code isn’t shown) reads the ZIP Code from the ZIP Code TextBox, converts it into a city and state, and initializes the TextBox and DropDownList representing the city and state, accordingly. Since this all happens inside an UpdatePanel, the update is smooth and flicker-free.


<asp:UpdatePanel ID=”UpdatePanel1” runat=”server”>
  <ContentTemplate>
    City:<br />
    <asp:TextBox ID=”City” runat=”server” />
    <br /><br />
    State:<br />
    <asp:DropDownList ID=”Region” runat=”server”>
        <asp:ListItem Value=”AL”>Alabama</asp:ListItem>
        <asp:ListItem Value=”AK”>Alaska</asp:ListItem>
        <asp:ListItem Value=”AZ”>Arizona</asp:ListItem>
          ...
        <asp:ListItem Value=”WV”>West Virginia</asp:ListItem>
        <asp:ListItem Value=”WI”>Wisconsin</asp:ListItem>
        <asp:ListItem Value=”WY”>Wyoming</asp:ListItem>
    </asp:DropDownList>
    <br /><br />
    Zip Code:<br />
    <asp:TextBox ID=”ZipCode” runat=”server” />&nbsp;
    <asp:Button ID=”AutofillButton” Text=”Autofill”
      OnClick=”GetCityAndState” runat=”server” />
  </ContentTemplate>
</asp:UpdatePanel>


Figure 3 City, State, and ZIP Code UI (Click the image for a larger view)
Here’s the problem. An UpdatePanel used this way improves the user experience, but it does little to reduce the volume of data being passed over the wire. The UpdatePanel hardly reduces the load on the server, either—up to the point that the controls inside the UpdatePanel render, the processing performed on the server is almost identical to what happens during a full-blown postback. It has to be this way because one of the benefits of the UpdatePanel control is that server-side event handlers like GetCityAndState work no differently inside an asynchronous callback than they do in a traditional postback. That means controls on the page have to be instantiated, they have to be initialized, they have to have access to view state, and so on.
Figure 4 demonstrates how to implement the same functionality without an UpdatePanel control. This time, the Autofill button is wired to a bit of JavaScript that fires off an asynchronous XML-HTTP request to an ASMX Web method named GetCityAndState. The call is placed through the JavaScript proxy named ZipCodeService, which is generated by the service reference in the ScriptManager control. GetCityAndState takes a ZIP Code string as input and returns a string array containing two items: the corresponding city and state. The completion function onGetCityAndStateCompleted retrieves the items from the array and inserts them into the city and state fields. Same result on the outside, but a very different way of going about it on the inside.


<asp:ScriptManager ID=”ScriptManager1” runat=”server”>
    <Services>
        <asp:ServiceReference Path=”ZipCodeService.asmx” />
    </Services>
    <Scripts>
      <asp:ScriptReference Name=”PreviewScript.js”
Assembly=”Microsoft.Web.Preview” />
    </Scripts>
</asp:ScriptManager>

City:<br />
<asp:TextBox ID=”City” runat=”server” />
<br /><br />
State:<br />
<asp:DropDownList ID=”Region” runat=”server”>
    <asp:ListItem Value=”AL”>Alabama</asp:ListItem>
    <asp:ListItem Value=”AK”>Alaska</asp:ListItem>
    <asp:ListItem Value=”AZ”>Arizona</asp:ListItem>
      ...
    <asp:ListItem Value=”WV”>West Virginia</asp:ListItem>
    <asp:ListItem Value=”WI”>Wisconsin</asp:ListItem>
    <asp:ListItem Value=”WY”>Wyoming</asp:ListItem>
</asp:DropDownList>
<br /><br />
Zip Code:<br />
<asp:TextBox ID=”ZipCode” runat=”server” />&nbsp;
<asp:Button ID=”AutofillButton” Text=”Autofill”
  OnClientClick=”autoFill(); return false;” runat=”server” />

<script type=”text/javascript”>
function autoFill()
{
    var tb = new Sys.Preview.UI.TextBox ($get(‘ZipCode’));
    var zip = tb.get_text();

    if (zip.length == 5)
        ZipCodeService.GetCityAndState (zip,
            onGetCityAndStateCompleted);
}

function onGetCityAndStateCompleted(result)
{
    if (result != null)
    {
        var tb = new Sys.Preview.UI.TextBox ($get(‘City’));
        tb.set_text(result[0]);

        var select =
            new Sys.Preview.UI.Selector ($get(‘Region’));
        select.set_selectedValue(result[1]);
    }
}
</script>

Here’s how the ASMX Web method called through the JavaScript proxy is implemented:

[ScriptService]
public class ZipCodeService : System.Web.Services.WebService
{
    [WebMethod]
    public string[] GetCityAndState(string zip)
    {
      ...
    }
}
This is a standard Web method in every respect except for the fact that the class to which it belongs is attributed ScriptService instead of WebService. ScriptService is synonymous with WebService, but it carries the added meaning that the Web service’s WebMethods are callable from client-side script.
In addition to allowing conventional WebMethods to serve as targets for XML-HTTP requests, ASP.NET AJAX also supports a special type of Web method known as the page method. Page methods are WebMethods implemented in Web pages—that is, in ASPX files or codebehind files rather than in ASMX files. Page methods allow developers to provide endpoints for XML-HTTP callbacks without building dedicated Web services.
Page methods must be public static methods and, like WebMethods, must be decorated with the WebMethod attribute. (WebMethods and page methods can also be decorated with the ScriptMethod attribute, which provides added control over what goes out over the wire.) On the client, page methods are called from JavaScript through the special PageMethods proxy.
Unlike Web services, page methods do not require service references. However, you do have to enable page methods by setting a ScriptManager control’s EnablePageMethods property to true, like so:

<asp:ScriptManager ID=”ScriptManager1” runat=”server”
  EnablePageMethods=”true” />
Under the hood, page methods offer the same efficiency as WebMethods. View state and other input is not transmitted to the server when page methods are called. And since page methods are static, they can be called without instantiating a page object. A call to a page method does not invoke the page lifecycle that is triggered by conventional ASP.NET requests.

Web Service != SOAP and XML
One of ASP.NET AJAX’s most important features is the ability to invoke WebMethods and page methods on a server using asynchronous XML-HTTP requests from browser clients. Yet when I tell people about this feature, I can’t help but cringe just a little.
When most of us hear the term Web service, we think about SOAP and XML. Neither of these technologies is typically mentioned in the same sentence as the word efficient. Yes, you can call WebMethods from JavaScript using ASP.NET AJAX. But no, ASP.NET AJAX doesn’t use SOAP and XML.
Figure 5 shows what travels over the wire when the Web method call in Figure 4 is executed. Other than the HTTP headers, the only data transmitted in the request is the ZIP Code that the user typed, and the only data returned in the response is a pair of strings representing a city and state. You won’t see any SOAP or XML (or view state, for that matter). Instead, input and output is encoded using JavaScript Object Notation (JSON), which is much less verbose than XML and easier to process. Rather than SOAP, the request and response use a simple protocol that is basically just HTTP. The combination of HTTP and JSON makes ASP.NET AJAX calls to WebMethods and page methods much more efficient than traditional Web service calls.



Request
POST /Ajax/ZipCodeService.asmx/GetCityAndState HTTP/1.1
Accept: */*
Accept-Language: en-us
Referer: http://localhost:1997/Ajax/ZipCodePage.aspx
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; ...)
Host: localhost:1997
Content-Length: 15
Connection: Keep-Alive
Cache-Control: no-cache

{"zip":"98052"}
Response
HTTP/1.1 200 OK
Server: ASP.NET Development Server/8.0.0.0
Date: Fri, 29 Dec 2006 21:06:17 GMT
X-AspNet-Version: 2.0.50727
Cache-Control: private, max-age=0
Content-Type: application/json; charset=utf-8
Content-Length: 16
Connection: Close

{"REDMOND", "WA"} 

JSON is an up-and-coming industry-standard serialization format. It’s also the native format employed by ASP.NET AJAX. JSON serialization and deserialization support is provided on the client-side by the Microsoft AJAX Library’s Sys.Serialization.JavaScriptSerializer class. On the server, support is provided by the System.Web.Script.Serialization.JavaScriptSerializer class.
Not all types are JSON-compatible. JSON cannot, for example, handle objects with circular references. When you need to return complex data types that aren’t JSON-compatible, you can actually use ASP.NET AJAX’s ScriptMethod attribute to serialize return types into XML. This technique is also useful for methods that return XML data, as demonstrated here:

[ScriptMethod (ResponseFormat=ResponseFormat.Xml)]
public XmlDocument GetData()
{
  ...
}
Alternatively, you can build and register custom JSON converters that allow types that aren’t normally JSON-compatible to be serialized and deserialized. The ASP.NET AJAX January Futures CTP contains three such converters: one for DataSets, one for DataTables, and one for DataRows.

Send your questions and comments for Jeff to  wicked@microsoft.com.

Jeff Prosise is a contributing editor to MSDN Magazine and the author of several books, including Programming Microsoft .NET (Microsoft Press, 2002). He’s also a cofounder of Wintellect (www.wintellect.com), a software consulting and education firm that specializes in Microsoft .NET.

Nhận xét

Bài đăng phổ biến từ blog này

Công ty chứng khoán đón đầu cơ hội (dau tu cong nghe)

The Redback