November 2006 - Posts

ASP.NET AJAX JavaScript Class Browser (take 2)

Updated on 2//22/2007:
New version available here: ASP.NET AJAX JavaScript Class Browser (take 3) 

I've read all the great feedback on the first release of the class browser and I've made some updates and fixes. First, I've added FireFox support. It should probably work in Safari and Opera now as well but I don't have those on my laptop to test. It snowed last night in the Seattle area so much of the area is closed down and we were told to work from home today. That, and my car is at the shop getting serviced, so I'm not going to work to test it out :) Second, I added support for viewing the Microsot AJAX Futures (formerly known as "Preview" or "CTP") types. To install the new class browser just overwrite the old files with the new attached ZIP.

To view the Futures types, follow these steps:

  1. Add the Futures DLL to your app's Bin directory.
  2. Open up the Master page and uncomment the ScriptReference items to the Futures scripts.

Technically this support was always in there; you can add ScriptReferences to any script libraries you have and they will automatically show up in the class browser.

Some of you may be curious about the "All Types" view. This view shows a flat list of all the types available to the class browser without the TreeView. Our test team and documentation team sometimes use this to "diff" builds of Microsoft AJAX to see what got added or removed. It's a great way for them to see if they need to add new test automation or additional documentation topics.

Posted by Eilon with 2 comment(s)
Filed under: , , ,

ASP.NET AJAX JavaScript Class Browser

Updated on 2//22/2007:
New version available here: ASP.NET AJAX JavaScript Class Browser (take 3) 

As part of an exercise in app building, as well as an exercise in doing useful stuff, I wrote a class browser to inspect the ASP.NET AJAX client libraries. It's a simple ASP.NET-based app, though most of the code is client-side. I wrote a simple reflection layer (inspired in part by the .NET Reflection classes) and then a UI layer that uses it. It's about 1,000 lines of JavaScript and no managed code at all.

To run it, download the attachment and place it in a virtual directory on your web server. You'll need to have ASP.NET AJAX Beta 2 installed on the machine.

Feedback and bug reports are always welcome. Or, of course, just enjoy the app itself!

Posted by Eilon with 15 comment(s)
Filed under: , , ,

HOWTO: Write controls compatible with UpdatePanel without linking to the ASP.NET AJAX DLL

This is another repost from a post I made to the ASP.NET Forums:

Buying Into Microsoft ASP.NET AJAX without Necessarily Paying For It

By Eilon Lipton, Software Design Engineer on Microsoft ASP.NET AJAX

Abstract

Without the release of Microsoft ASP.NET AJAX just around the corner, taking advantage of the new AJAX features is all the buzz. However, what if some of your customers are not yet upgrading to ASP.NET AJAX? This article discusses a technique to allow your ASP.NET server control to function without ASP.NET AJAX installed, while taking advantages of new features such as the UpdatePanel control’s asynchronous postbacks when ASP.NET AJAX is available.

Introduction

The problem being solved here is how to access ASP.NET AJAX functionality when you can’t link against the Microsoft.Web.Extensions.dll assembly installed in the GAC. The primary technique we will use is the .NET Framework’s Reflection feature, which allows us to dynamically call functions without taking a compile-time dependency on them.

The intent of the sample control is neither to show how to write an ASP.NET control, nor the best practices of doing such. The sample control is just to demonstrate patterns and practices for using Microsoft ASP.NET AJAX to write compatible controls.

The techniques demonstrated here are typically only required for controls that do not take advantage of the Microsoft AJAX client libraries and the server interfaces, such as IScriptControl. Controls that use those features typically have to link against the Microsoft.Web.Extensions.dll assembly anyway. Rather, these techniques are intended for controls that were built against ASP.NET 2.0 and wish to merely “play nicely” with ASP.NET AJAX.

The full source code is available here.

Why Write an ASP.NET AJAX-Aware Control?

The ASP.NET AJAX UpdatePanel control places restrictions on what the controls placed inside it are allowed to do. If the control is not intended to be placed inside an UpdatePanel control then are no restrictions on what it may do. The key restrictions for a control inside an UpdatePanel are:

  1. It must perform script registration through the ScriptManager’s registration APIs instead of through the Page.ClientScript APIs. There is a simple one-to-one mapping from the old APIs to the new APIs.
  2. If the control attaches event handlers it must implement dispose functionality. The “dispose” expando technique will be shown, although there are several other ways to do this.
  3. The scripts registered by the control need to be divided into two: 1. the script library code, which contains only function and class declarations, and is shared by all instances of the control; and 2. the initialization code for the control, which is unique to each control instance.

If your customers want to use your control inside an UpdatePanel, you will have to abide by these restrictions.

For more information on how UpdatePanels work, please see this blog post.

The Sample MathWidget Control

The sample control used for demonstration purposes is a simple ASP.NET composite control that performs math operations. Rather than posting back to the server to do the calculations, it includes a client script library that does the work in the browser.

Math Widget

If you’re been following so far, you’re probably thinking to yourself, “oh no, it’s registering client script – that will never work inside an UpdatePanel!” Here’s an outline of the control and the code it uses to perform its script registration:

namespace MathWizard {


public class MathWidget : CompositeControl {
protected override void CreateChildControls() {

}

protected override void OnPreRender(EventArgs e) {
base.OnPreRender(e);

// Register script library
Page.ClientScript.RegisterClientScriptResource(
typeof(MathWidget),
"MathWizard.MathWidget.js");

// Register initialization
Page.ClientScript.RegisterStartupScript(
typeof(MathWidget),
ClientID + "_KeyStuff",
"MathWidget_Initialize('" + ClientID + "');",
true);
}
}
}

  

 

Of the restrictions mentioned earlier only the third one is being followed: separation of script library and initialization code. Clearly this control will not work when placed inside an UpdatePanel since the async postback processing will be unaware of the script registrations performed during that postback. Only registrations performed through the ScriptManager APIs will work during an async postback.

Here’s the initial version of the client-side library for the MathWidget control:

 

 

function MathWidget_Add(widgetID) {
// Perform add button operation
var leftOperand = parseFloat(document.getElementById(widgetID + "_LeftOperandTextBox").value);
var rightOperand = parseFloat(document.getElementById(widgetID + "_RightOperandTextBox").value);
document.getElementById(widgetID + "_ResultTextBox").value = leftOperand + rightOperand;
}

function MathWidget_Multiply(widgetID) {
// Perform multiply button operation
var leftOperand = parseFloat(document.getElementById(widgetID + "_LeftOperandTextBox").value);
var rightOperand = parseFloat(document.getElementById(widgetID + "_RightOperandTextBox").value);
document.getElementById(widgetID + "_ResultTextBox").value = leftOperand * rightOperand;
}

function MathWidget_Initialize(widgetID) {
// Initialize a client instance of the MathWidget control
var leftOperand = document.getElementById(widgetID + "_LeftOperandTextBox");
var rightOperand = document.getElementById(widgetID + "_RightOperandTextBox");
leftOperand.attachEvent('onkeypress', MathWidget_KeyPressHandler);
rightOperand.attachEvent('onkeypress', MathWidget_KeyPressHandler);
}

function MathWidget_KeyPressHandler() {
// Allow only numeric keys to go through
if (window.event.keyCode < 48 ||
window.event.keyCode > 57) {
window.event.returnValue = false;
}
}

As you can tell, the MathWidget_Initialize() method hooks up event handlers to DOM elements. This will also break during an async post since we’ll be leaving some old event handlers attached to DOM events, as mentioned in the second restriction. Although this usually doesn’t produce disastrous results, it’s a great way to cause a memory leak. To avoid the memory leak we’ll need to implement dispose functionality.

Making the Control Register Its Scripts

The first step in getting the control to work with UpdatePanels is to write a compatibility layer that hides some of the implementation details. This is especially important since the compatibility layer will use the .NET Framework’s Reflection feature to do the method calls, and we’d rather hide that code from the control’s code.

The compatibility layer’s job is to detect whether ASP.NET AJAX is available. When it is available, it uses the new ScriptManager APIs to perform the script registration. When it isn’t available, it calls the ASP.NET 2.0 Page.ClientScript APIs. This fulfils the first restriction. Here’s an outline of the compatibility layer:

namespace MathWizard {


internal static class ScriptManagerHelper {
private static readonly object ReflectionLock = new object();
private static bool MethodsInitialized;
private static MethodInfo RegisterClientScriptResourceMethod;


private static void InitializeReflection() {
if (!MethodsInitialized) {
lock (ReflectionLock) {
if (!MethodsInitialized) {
Type scriptManagerType = Type.GetType("Microsoft.Web.UI.ScriptManager, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", false);
if (scriptManagerType != null) {
RegisterClientScriptResourceMethod = scriptManagerType.GetMethod("RegisterClientScriptResource");

}
MethodsInitialized = true;
}
}
}
}

public static void RegisterClientScriptResource(Control control, Type type, string resourceName) {

InitializeReflection();
if (RegisterClientScriptResourceMethod != null) {
// ASP.NET AJAX exists, so we use the ScriptManager
RegisterClientScriptResourceMethod.Invoke(null, new object[] { control, type, resourceName });
}
else {
// No ASP.NET AJAX, so we just call to the ASP.NET 2.0 method
control.Page.ClientScript.RegisterClientScriptResource(type, resourceName);
}
}

}
}

 

The basic technique is to try locating the ScriptManager type in the Microsoft.Web.Extensions.dll assembly. If it can’t be found, we know that ASP.NET AJAX is not available. If it was found then we locate specific methods of interest and hold on to them so that we can call them later.

We then change the code in our control to call the new methods:

 

        protected override void OnPreRender(EventArgs e) {
base.OnPreRender(e);
// Register script library
ScriptManagerHelper.RegisterClientScriptResource(
this,
typeof(MathWidget),
"MathWizard.MathWidget.js");

// Register initialization
ScriptManagerHelper.RegisterStartupScript(
this,
typeof(MathWidget),
ClientID + "_KeyStuff",
"MathWidget_Initialize('" + ClientID + "');",
true);
}

 

That’s two restrictions down, and one to go.

Implementing Dispose

Fortunately, implementing dispose functionality is rather easy. Typically to implement dispose functionality I look at what my initialization functionality does and do it backwards. In the case of the MathWidget script library the initialize function attaches event handlers, so our dispose method must detach those handlers.

Here’s what the client script dispose function looks like:

function MathWidget_Dispose(widgetID) {
    // Initialize a client instance of the MathWidget control
    var leftOperand = document.getElementById(widgetID + "_LeftOperandTextBox");
    var rightOperand = document.getElementById(widgetID + "_RightOperandTextBox");
    leftOperand.detachEvent('onkeypress', MathWidget_KeyPressHandler);
    rightOperand.detachEvent('onkeypress', MathWidget_KeyPressHandler);
}

 

The next problem is how we get the ASP.NET AJAX async postback processing to call the dispose function when the UpdatePanel gets updated. As mentioned earlier, there are several techniques. This technique is the easiest to implement for existing controls, and it involves adding a “dispose” expando to one of our DOM elements, in this case the main <span> tag surrounding our control. When an async postback returns from the server and the UpdatePanels’ contents need to be updated, ASP.NET AJAX will search the contents of the UpdatePanel for DOM elements with a “dispose” expando. If it finds it and the expando is a function, it will call it. Here’s how we register the expando:

 

        protected override void OnPreRender(EventArgs e) {
            …
            // Register dispose if Microsoft ASP.NET AJAX is available
            if (ScriptManagerHelper.IsMicrosoftAjaxAvailable()) {
                ScriptManagerHelper.RegisterStartupScript(
                    this,
                    typeof(MathWidget),
                    ClientID + "_Dispose",
                    @"
document.getElementById('" + ClientID + @"').dispose = function() {{
    MathWidget_Dispose('" + ClientID + @"');
}}
",
                    true);
            }
        }

  

 

Note that we only bother registering the expando if ASP.NET AJAX is available. It doesn’t hurt to always register it, but there’s no point cluttering the page with script that won’t get used.

Summary

We fulfilled all three requirements to be fully compatible with UpdatePanels and we barely had to modify our control. You can also extend the compatibility layer to call other methods on ScriptManager, as necessary.

All the source code here is free for any purposes you have. I’d love to hear your feedback on it too!

Posted by Eilon with 8 comment(s)
Filed under: , , ,

What's up with UpdatePanels and how come nothing works? Or: A brief explanation of how UpdatePanel works by the guy who wrote the feature. (Long!)

This is a repost from a post I made to the ASP.NET Forums:

Intro:

With the recent Microsoft ASP.NET AJAX Extensions release there has been a lot of feedback, both positive and negative, about the changes made to UpdatePanels. The feedback has ranged from “wow, finally validators work” all the way to “what the $%*@, my custom control doesn’t work!”

 

The biggest change we made was related to script registration during async posts. We used to do some extremely hacky parsing work to detect the scripts rendered in a page and try to execute them on the client.

 

 

 

Atlas CTPs (pre-beta):

In the CTPs we had a special response writer that would parse the rendered contents of the page. It would look for specific items such as <script> tags, <input> tags, and a few other things. It worked really well for trivial scenarios such as a GridView with a textbox and a validator on the page. Unfortunately for us it turned out that most pages are significantly more complex.

 

A classic example that didn’t work was a page that had an UpdatePanel with a wizard. Each step of the wizard contained a validator and a textbox. As you go through the wizard steps validators conceptually get removed and added to the page but nowhere was there any JavaScript code to remove the validator from the page. We would just keep adding more and more validators. Busted!

 

 

 

Microsoft ASP.NET AJAX Extensions 1.0 beta:

To overcome the problem of blindly reexecuting scripts that we parsed from the page we came up with an explicit registration model for scripts. This way the UpdatePanel can known exactly which scripts to execute, and when. The ScriptManager now has a set of static script registration methods. They are static so that they work on pages that don’t use partial rendering, including those that don’t even have a ScriptManager. You do, however, have to link to the Microsoft.Web.Extensions.dll to be able to call these.

 

If you used to call:

            Page.ClientScript.RegisterClientScriptBlock(typeof(Foo), "key", "alert('hello'); ", true);

You would now call:

      ScriptManager.RegisterClientScriptBlock(this, typeof(Foo), "key", "alert('hello');", true);

 

If you look closely, there’s a new parameter for the method of type Control. This parameter is the precise reason why we needed the new set of registration methods. We use that parameter to detect whether the control registering the script is inside an UpdatePanel, and if so, whether that UpdatePanel is being refreshed during a given async post.

 

We also require that controls implement dispose logic so that when an UpdatePanel is cleared out during an update the scripts inside it can tear down anything they need. This is done through a number of techniques, which I won’t go in too much depth here. The technique the validators use is to have a “dispose” expando on their rendered elements and the UpdatePanel client code will execute all the “dispose” expandos inside a panel that is getting cleared.

 

Implications of having new registration APIs:

One of the biggest implications of this is that existing controls that register scripts don’t work inside UpdatePanels. We are aware that this is a problem but we saw that it was necessary in order to make UpdatePanels work at all.

 

Controls that shipped in ASP.NET 2.0:

We have updated versions of the ASP.NET 2.0 validators that are tag-mapped to the old versions (this means that if you have an <asp:RequiredFieldValidator> tag it will use the updated one and not the old one. These updated validators use the new registration APIs.

 

Other controls, such as WebParts, TreeView, and Menu were not updated to use the new registration APIs primarily due to time constraints. We felt that it was far less common to have these controls inside UpdatePanels compared to the validators. The TreeView control has built-in AJAX functionality anyway. In a future version of Atlas or ASP.NET we will have updated versions of these controls, however.

 

3rd party controls:

We have been working closely with custom control vendors to have them learn about the new APIs and many of them will be releasing updated versions of their controls that are compatible with UpdatePanel.

 

 

 

Issues:

We’ve still got a few things to work out for custom control vendors. And there are also some issues remaining for page developers. We love hearing this feedback from you since it makes a huge difference in how we build this awesome framework.

 

 

I hope this helps explain some of our decisions in one place instead of scattering it in varied responses to several posts.

 

Posted by Eilon with 17 comment(s)
Filed under: , , ,

TechEd 2006 Barcelona slides and code

Slides and code from our TechEd talk on Building a Complete Web Application are available from Matt's blog post.

Thanks to everyone who attended our talk!

Posted by Eilon with 1 comment(s)
Filed under: , ,
More Posts