Microsoft Most Valuable Professional

Chris Pietschmann

An MVP From Wisconsin

ASP.NET: Fix for WebParts + UrlRewriting/UrlMapping Issue

The problem with using WebParts with UrlRewriting (or UrlMapping) is the WebPart PersonalizationProvider uses the path of the page being rendered to save/load the personalization state, not the path that was rewritten that the user sees. So, to fix this you just need to create a custom PersonalizationProvider and override all the methods that reference path, and call the base methods but pass in the path that was requested instead of the path being rendered.

Here's a SqlPersonalizationProvider I wrote that does this:

public class SqlPersonalizationProvider : System.Web.UI.WebControls.WebParts.SqlPersonalizationProvider
{
    protected override void LoadPersonalizationBlobs(WebPartManager webPartManager, string path, string userName, ref byte[] sharedDataBlob, ref byte[] userDataBlob)
    {
        base.LoadPersonalizationBlobs(webPartManager, this.GetActualPath(), userName, ref sharedDataBlob, ref userDataBlob);
    }

    protected override void ResetPersonalizationBlob(WebPartManager webPartManager, string path, string userName)
    {
        base.ResetPersonalizationBlob(webPartManager, this.GetActualPath(), userName);
    }

    public override int ResetState(PersonalizationScope scope, string[] paths, string[] usernames)
    {
        return base.ResetState(scope, paths, usernames);
    }

    public override int ResetUserState(string path, DateTime userInactiveSinceDate)
    {
        return base.ResetUserState(this.GetActualPath(), userInactiveSinceDate);
    }

    protected override void SavePersonalizationBlob(WebPartManager webPartManager, string path, string userName, byte[] dataBlob)
    {
        base.SavePersonalizationBlob(webPartManager, this.GetActualPath(), userName, dataBlob);
    }

    protected string GetActualPath()
    {
        HttpApplication app = HttpContext.Current.ApplicationInstance;
        string p = app.Request.RawUrl;

        // Convert Absolute to Relative
        if (p.ToLowerInvariant().StartsWith(VirtualPathUtility.ToAbsolute("~/").ToLowerInvariant()))
        {
            p = "~/" + p.Substring(VirtualPathUtility.ToAbsolute("~/").Length);
        }

        // Remove QueryString Parameters
        if (p.Contains("?"))
            p = p.Substring(0, p.IndexOf("?"));

        return p;
    }   
}

Another implementation (possibly a better one) would be to create a custom WebPartManager and change the path that is sent to the PersonalizationProvider, but the way shown above was simplier for me to implement and it works.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: asp.net
Posted by crpietschmann on Wednesday, June 25, 2008 11:16 PM
Permalink | Comments (0) | Post RSSRSS comment feed


ASP.NET 3.5: Create AJAX Extender Controls using the ExtenderControl base class

As a followup to the "Create AJAX Server Controls using the ScriptControl base class" post I wrote a couple weeks ago, I've decided to write on how to create ASP.NET AJAX Extender Controls using the ExtenderControl base class in ASP.NET 3.5. There is already an article titled "Adding ASP.NET AJAX Client Behaviors to Web Server Controls" in MSDN, but it doesn't show all the code involved in creating Extender Controls, especially the JavaScript code.

What is an Extender Control?

First what exactly is an Extender Control? An Extender Control is basically a non-visual control that adds some sort of AJAX behavior to another control within the page. The majority of the controls within the AjaxControlToolkit are built as Extender Controls. It is a common misconception that you can only create Extender Controls by using the base classes within the AjaxControlToolkit, but in fact ASP.NET 3.5 contains the required ExtenderControl base class to create Extender Controls.

The base class I'm referring to in this article is the System.Web.UI.ExtenderControl base class located within the System.Web.Extensions.dll assembly.

According to MSDN the ExtenderControl class is:

The ExtenderControl class enables you to programmatically add AJAX functionality to an ASP.NET server control. The ExtenderControl inherits from the Control class and implements the IExtenderControl interface. The Control class defines the properties, methods, and events that are shared by all ASP.NET server controls. The IExtenderControl interface is an abstract class, which you cannot instantiate directly. Instead, you create a derived type.

Basics of created Extender Controls that Inherit from ExtenderControl

When first creating your custom extender control you need to inherit from the ExtenderControl base class. The first thing you'll see is the ExtenderControl requires you to implement to methods: GetScriptDescriptors and GetScriptReferences. The GetScriptDescriptors method is used to get an IEnumerable collection of ScriptDescriptor objects that basically define any of the controls client-side AJAX properties. The GetScriptReferences method is used to get an IEnumerable collection of ECMAScript (JavaScript) files that will be loaded on the client-side once the page loads; this is used to basically define any client-side scripts the control will require to run on the client.

[TargetControlType(typeof(Control))]
public class FocusExtender : System.Web.UI.ExtenderControl
{
    protected override System.Collections.Generic.IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl)
    {
        throw new NotImplementedException();
    }

    protected override System.Collections.Generic.IEnumerable<ScriptReference> GetScriptReferences()
    {
        throw new NotImplementedException();
    }
}

You may have noticed the TargetControlType attribute applied to the FocusExtender control; this attribute is required, and is used to define the type of control that the extender explicitly works with. In the case of this example, I'm setting it to work with any thing that inherits from the Control class, which is all server controls.

Define ScriptReferences

In the GetScriptReferences method, return an IEnumerable collection of the ScriptReference objects to include in the page on the client-side.

Here's a basic example of setting up a ScriptReference that includes a script resource that is embedded within the assembly that FocusExtender is contained in:

protected override System.Collections.Generic.IEnumerable<ScriptReference> GetScriptReferences()
{
    yield return new ScriptReference("CustomExtenderControl.FocusExtender.js", "CustomExtenderControl");;
}

When inheriting from ExtenderControl you'll always have at least one ScriptReference, because you need to reference the script that creates the client-side JavaScript representation of your extender control.

Define ScriptDescriptors

In the GetScriptDescriptors method, return an IEnumerable collection of ScriptDescriptor objects that define what properties will be passed down to the client-side JavaScript representation of your control.

The only ScriptDescriptor you need to return in the IEnumerable collection is a  ScriptBehaviorDescriptor instance that contains all the descriptors for your extender control.

Here's a basic example of returning a ScriptBehaviorDescriptor without any properties being defined:

protected override System.Collections.Generic.IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl)
{
    ScriptBehaviorDescriptor descriptor = new ScriptBehaviorDescriptor(this.GetType().FullName, targetControl.ClientID)

    yield return descriptor;
}

What ECMAScript (JavaScript) code is needed?

There is a basic block of JavaScript code that you'll need to include that will define the client-side JavaScript representation of your control. This basic block of code is the same base code that you'll start with when creating all extender controls.

Here's the basic JavaScript that will define the client-side JavaScript representation of the ForucExtender control we're using in this example. One thing to remember is that the namespaces and object name in the JavaScript file need to be the same as they are in the server-side .NET code. In case you're wonderin, there is a way to make the client-side and server-side namespaces different, but that's beyond the scope of this article.

Type.registerNamespace("CustomExtenderControl");

CustomExtenderControl.FocusExtender = function(element) {
    CustomExtenderControl.FocusExtender.initializeBase(this, [element]);
};

CustomExtenderControl.FocusExtender.prototype = {
    initialize:function(){
        CustomExtenderControl.FocusExtender.callBaseMethod(this, "initialize");
    },
    dispose:function(){
        CustomExtenderControl.FocusExtender.callBaseMethod(this, "dispose");
    }
};

CustomExtenderControl.FocusExtender.registerClass("CustomExtenderControl.FocusExtender", Sys.UI.Behavior);

Sys.Application.notifyScriptLoaded();

Also, in order for the ExtenderControl to correctly load the "CustomExtenderControl.FocusExtender.js" script reference that we're using in this example, we must not forget to define the assemblies embedded resource as a Web Resource, like so:

[assembly: System.Web.UI.WebResource("CustomExtenderControl.FocusExtender.js", "text/javascript")]

[/code]

 

Let's Add a Little "Rich" AJAX Functionality to the Extender Control

Now that we have a basic ExtenderControl created, we can start adding out "rich" AJAX functionality that we will use to extend our target server control in the page.

In this article, I'm going to keep things extemely simple, and we're going to do the following:

  1. Pass HighlightCssClass and NoHighlightCssClass properties down to the client, that will determin the CSS class's used to highlight out server control (in this article a TextBox).

Add the HighlightCssClass and NoHighlightCssClass Properties

public string HighlightCssClass { get; set; }
public string NoHighlightCssClass { get; set; }

Add the ScriptDescriptor for the HighlightCssClass and NoHighlightCssClass Properties

Just modify the GetScriptDescriptor method above to look like the following:

protected override System.Collections.Generic.IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl)
{
    ScriptBehaviorDescriptor descriptor = new ScriptBehaviorDescriptor(this.GetType().FullName, targetControl.ClientID)

    descriptor.AddProperty("HighlightCssClass", this.HighlightCssClass);
    descriptor.AddProperty("NoHighlightCssClass", this.NoHighlightCssClass);

    yield return descriptor;
}

Add the Client-Side Name Property that will receive the value

To do this we need to add a "private" like variable for each CSS property and property accessor methods for each to our JavaScript.

Here's the above JavaScript file with the "private" variables and property accessors added for the CSS properties:

Type.registerNamespace("CustomExtenderControl");

CustomExtenderControl.FocusExtender = function(element) {
    CustomExtenderControl.FocusExtender.initializeBase(this, [element]);
    this._HighlightCssClass = null;
    this._NoHighlightCssClass = null;
};

CustomExtenderControl.FocusExtender.prototype = {
    initialize:function(){
        CustomExtenderControl.FocusExtender.callBaseMethod(this, "initialize");
    },
    dispose:function(){
        CustomExtenderControl.FocusExtender.callBaseMethod(this, "dispose");
    },
   
    get_HighlightCssClass:function() {
        return this._HighlightCssClass;
    },
    set_HighlightCssClass:function(v) {
        this._HighlightCssClass = v;
    },
    get_NoHighlightCssClass:function() {
        return this._NoHighlightCssClass;
    },
    set_NoHighlightCssClass:function(v) {
        this._NoHighlightCssClass = v;
    }
};

CustomExtenderControl.FocusExtender.registerClass("CustomExtenderControl.FocusExtender", Sys.UI.Behavior);

Sys.Application.notifyScriptLoaded();

Set the TargetControls CSS according to the Focus

In this example we're going to attach event handlers to the TargetControl's focus and blur events that will change the controls CSS.

Here's the above JavaScript code with the event handlers added:

Type.registerNamespace("CustomExtenderControl");
CustomExtenderControl.FocusExtender = function(element) {
    CustomExtenderControl.FocusExtender.initializeBase(this, [element]);
    this._HighlightCssClass = null;
    this._NoHighlightCssClass = null;
   
    this._focusHandler = Function.createDelegate(this, this._onFocus);
    this._blurHandler = Function.createDelegate(this, this._onBlur);
};
CustomExtenderControl.FocusExtender.prototype = {
    initialize:function(){
        CustomExtenderControl.FocusExtender.callBaseMethod(this, "initialize");
       
        var targetElement = this.get_element();
       
        $addHandler(targetElement, "focus", this._focusHandler);
        $addHandler(targetElement, "blur", this._blurHandler);       
    },
    dispose:function(){
        var targetElement = this.get_element();
       
        $removeHandler(targetElement, "focus", this._focusHandler);   
        $removeHandler(targetElement, "blur", this._blurHandler);
   
        CustomExtenderControl.FocusExtender.callBaseMethod(this, "dispose");
    },
   
    get_HighlightCssClass:function() {
        return this._HighlightCssClass;
    },
    set_HighlightCssClass:function(v) {
        this._HighlightCssClass = v;
    },
    get_NoHighlightCssClass:function() {
        return this._NoHighlightCssClass;
    },
    set_NoHighlightCssClass:function(v) {
        this._NoHighlightCssClass = v;
    },
   
    //Event Handler Methods
    _onFocus:function(eventArgs) {
        var targetElement = this.get_element();
        if (targetElement != null)
        {
            targetElement.className = this.get_HighlightCssClass();
        }
    },
    _onBlur:function(eventArgs) {
        var targetElement = this.get_element();
        if (targetElement != null)
        {
            targetElement.className = this.get_NoHighlightCssClass();
        }
    }
};
CustomExtenderControl.FocusExtender.registerClass("CustomExtenderControl.FocusExtender", Sys.UI.Behavior);
Sys.Application.notifyScriptLoaded();

 

Tips and Tricks

Tip #1: Simplify Creating ExtenderControls using a custom ExtenderControlBase class

One trick that I've learned, that proves useful when creating custom Extender Controls, is to create your own base class that inherits from ExtenderControl and extends it to use some custom attributes to make adding ScriptReferences and ScriptDescriptors easier. This is the same trick that I learned from creating ScriptControls.

Here's what the final server-side code for the FocusExtender object would look like when using custom attributes and our very own ExtenderControl base class to add ScriptReferences and ScriptDescriptors:

[TargetControlType(typeof(Control)),
ScriptReference("CustomExtenderControl.FocusExtender.js", "CustomExtenderControl")]
public class FocusExtender : ExtenderControlBase
{
    [ExtenderControlProperty]
    public string HighlightCssClass { get; set; }

    [ExtenderControlProperty]
    public string NoHighlightCssClass { get; set; }
}

As you can see, the new code for the FocusExtender using this ExtenderControlBase class with custom attributes is much cleaner and easier to read.

Here's the complete code for the ExtenderControlBase, ScriptReferenceAttribute and ExtenderControlPropertyAttribute objects:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI;

public class ExtenderControlBase : ExtenderControl
{
    protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl)
    {
        ScriptControlDescriptor descriptor = new ScriptControlDescriptor(this.GetType().FullName, targetControl.ClientID);

        // Add all the ExtenderControls Client-Side Object Properties
        PropertyDescriptorCollection props = TypeDescriptor.GetProperties(this);
        foreach (PropertyDescriptor prop in props)
        {
            ExtenderControlPropertyAttribute propAttr = prop.Attributes[typeof(ExtenderControlPropertyAttribute)] as ExtenderControlPropertyAttribute;
            if (propAttr != null)
            {
                object value = prop.GetValue(this);
                string name = (propAttr.Name != null) ? propAttr.Name : prop.Name;
                if (value != null)
                {
                    descriptor.AddProperty(name, value);
                }
            }
        }

        yield return descriptor;
    }

    protected override IEnumerable<ScriptReference> GetScriptReferences()
    {
        // Add all the ExtenderControls Client-Side JavaScript References
        object[] scriptReferences = Attribute.GetCustomAttributes(this.GetType(), typeof(ScriptReferenceAttribute), false);
        foreach (ScriptReferenceAttribute r in scriptReferences)
        {
            yield return r.GetScriptReference();
        }
    }
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class ExtenderControlPropertyAttribute : Attribute
{
    public ExtenderControlPropertyAttribute() { }

    public ExtenderControlPropertyAttribute(string name)
    {
        this.Name = name;
    }

    public string Name { get; set; }
}

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ScriptReferenceAttribute : Attribute
{
    public ScriptReferenceAttribute(string path)
    {
        this.Path = path;
    }

    public ScriptReferenceAttribute(string name, string assembly)
    {
        this.Name = name;
        this.Assembly = assembly;
    }

    private string _path = null;
    public string Path
    {
        get { return _path; }
        set { _path = value; }
    }

    private string _name = null;
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    private string _assembly = null;
    public string Assembly
    {
        get { return _assembly; }
        set { _assembly = value; }
    }

    public ScriptReference GetScriptReference()
    {
        ScriptReference r = null;

        if (this.Path == null)
        {
            r = new ScriptReference(this.Name, this.Assembly);
        }
        else
        {
            r = new ScriptReference(this.Path);
        }

        return r;
    }
}

Tip #2: Access ExtenderControl via JavaScript

Another trick that I figured out allows you to be able to reference the custom extender control from within JavaScript. The issue this solves is the fact that the ExtenderControl base class doesn't send the control ID down to the client, so there is no way to get a reference to it for manipulation via JavaScript.

To do this all we need to do is add a property named "id" to the ScriptBehaviorDescriptor within the GetScriptDescriptor method. Here's an example of doing this:

protected override System.Collections.Generic.IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl)
{
    ScriptBehaviorDescriptor descriptor = new ScriptBehaviorDescriptor(this.GetType().FullName, targetControl.ClientID);

    descriptor.AddProperty("id", this.ClientID);

    descriptor.AddProperty("HighlightCssClass", this.HighlightCssClass);
    descriptor.AddProperty("NoHighlightCssClass", this.NoHighlightCssClass);

    yield return descriptor;
}

Now with the following FocusExtender control in the page:

<cec:FocusExtender runat="server" id="FocusExtender1"
    TargetControlID="TextBox1"
    HighlightCssClass="Highlight" NoHighlightCssClass="NoHightlight">
</cec:FocusExtender>

We can access it from JavaScript like so:

<input type="button" value="Show HighlightCssClass" onclick="ShowHighlightCssClass();" />
<script type="text/javascript">
    function ShowHighlightCssClass()
    {
        var focusExtender = $find("<%=FocusExtender1.ClientID%>");
        alert(focusExtender.get_HighlightCssClass());
    }
</script>

It's kind of strange that the ExtenderControl base class doesn't automatically link it up to be able to access it from within JavaScript, but at least it's pretty simple to add.

Conclusion

As you can see it's fairly simple to get started creating your own custom extender controls that inherit from the ExtenderControl base class, especially if you use the above ExtenderControlBase class along with it's ExtenderControlPropertyAttribute and ScriptReferenceAttribute attributes. It's also very simple to enable the ability to access the ExtenderControl from within JavaScript to enable much richer AJAX functionality to be built.

Download Full Source Code: CreateAJAXExtenderControlUsingExtenderControlBaseClass.zip (21.36 kb)

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: asp.net
Posted by crpietschmann on Friday, May 30, 2008 11:22 PM
Permalink | Comments (1) | Post RSSRSS comment feed


Virtual Earth + ASP.NET AJAX Now Fixed in Safari

A couple months ago I blogged about the fact that using Virtual Earth and ASP.NET AJAX together on the same page caused errors in Safari. There is good new to report back, the latest update to Virtual Earth has fixed this issue. In fact Virtual Earth v6.1 actually includes much better Safari support that previous versions; it actually works correctly in Safari. I found this to be especially helpful since I can now support Safari with my Web.Maps.VE product.

What's the catch? I can't work perfectly now can it. Well, as far as I can tell Virtual Earth works great. However, the code example in my post calling attention to the error actually still has a little bug in it. The bug is the calendar that is automatically shown on hover over by the AjaxControlToolkit CalendarExtender get hidden by the Map below the textbox. This is actually caused by a layering issue related to the z-index of the elements on the page. An easy way to fix this is to just set the z-index of the VEMap's DIV element to a lower value so all other elements on the page will be rendered over the top of it.

Here's the full example code showing example what I mean above:

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>
<!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 id="Head1" runat="server">
<title>Untitled Page</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.1"></script>
<script type="text/javascript">
    var map = null;
    function GetMap()
    {
        map = new VEMap('myMap');
        map.LoadMap();
    }
</script>
</head>
<body onload="GetMap();">
<form id="form1" runat="server">
<asp:ScriptManager runat="server" ID="ScriptManager1"></asp:ScriptManager>
<div>
<asp:TextBox runat="server" id="txtDate"></asp:TextBox>
<ajaxToolkit:CalendarExtender runat="server" ID="CalendarExtender1" TargetControlID="txtDate"></ajaxToolkit:CalendarExtender>
<br /><br />
<div id='myMap' style="position:relative; width:400px; height:400px; z-index: -100;"></div>
</div>
</form>
</body>
</html>

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by crpietschmann on Saturday, May 17, 2008 1:13 PM
Permalink | Comments (0) | Post RSSRSS comment feed


ASP.NET: Create AJAX Server Controls using the ScriptControl base class

I've been doing a lot with ASP.NET AJAX for almost 2 years now, including my Web.Maps.VE product. So, I've decided to spread some of my knowledge in this area by writing up some articles on things that I've learned and figured out.

As a note, since .NET 3.5 is the "latest and greatest" version of the framework out at the time of this writing, this article targets .NET 3.5. This article also assumes you have a basic understanding of ASP.NET AJAX and JavaScript.

What is ScriptControl?

First, what exactly is ScriptControl? Well, the base class I'm referring to is the System.Web.UI.ScriptControl base class located within the System.Web.Extensions.dll assembly.

The ScriptControl is basically the first building block you want to use to start building out a WebControl (server control) that will have some kind of rich AJAX functionality in the browser. You may have heard of ASP.NET AJAX Control Extenders, especially if you're familiar with the AJAX Control Toolkit, so why would we use ScriptControl instead of creating an ExtenderControl in order to give a control some rich AJAX functionality within the browser? Well, you want to create an ExtenderControl to extend or enhance an existing control with some rich AJAX functionality, and you want to inherit from the ScriptControl class when you are creating a full control that will have some rich AJAX functionality.

According to MSDN the ScriptControl class is:

ScriptControl inherits from the WebControl class and implements the IScriptControl interface. The WebControl class is a base class for ASP.NET Web server controls. The ScriptControl is an abstract class, which you cannot instantiate directly. Instead, you derive a class based on this abstract class.

The ScriptControl base class tests the page for a ScriptManager control during the PreRender stage of the derived control. The ScriptControl base class also makes sure that the derived control calls methods of the ScriptManager control to register script during the Render event. This includes registering ScriptDescriptor objects for the ScriptControl when the Render method is called. The Render method makes sure that ScriptDescriptor objects are not rendered unless the ScriptControl itself is rendered. This enables a ScriptControl to work inside a closed WebPart control.

You can add as many descriptors and types as necessary to any class that you derive from ScriptControl.

 

Basics of Creating Server Controls that Inherit from ScriptControl

When first creating your custom server control you inherit from ScriptControl instead of WebControl like usual. The first thing you'll see is ScriptControl requires you to implement two methods: GetScriptDescriptors and GetScriptReferences. The GetScriptDescriptors method is used to get an enumeration of ScriptDescriptor objects that basically define any of the controls client-side AJAX properties. The GetScriptReferences method is used to get an enumeration of ECMAScript (JavaScript) files that will be loaded on the client-side once the page loads; this is used to basically define any client-side scripts the control will require to run on the client.

Here's a basic stub of a control that inherits from ScriptControl:

public class CustomScriptControl : ScriptControl
{
    protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
    {
        throw new NotImplementedException();
    }

    protected override IEnumerable<ScriptReference> GetScriptReferences()
    {
        throw new NotImplementedException();
    }
}

Define ScriptReferences

In the GetScriptReferences method, return an enumeration of the ScriptReference objects to include in the page on the client-side.

Here's a basic example of setting up a ScriptReference that includes a script resource that is embedded within the assembly the CustomScriptControl is contained in:

protected override IEnumerable<ScriptReference> GetScriptReferences()
{
    List<ScriptReference> references = new List<ScriptReference>();
    references.Add(new ScriptReference("CustomScriptControl.CustomScriptControl.js", "CustomScriptControl"));
    return references;
}

When inheriting from ScriptControl you'll generally always have at least one ScriptReference because you need to reference the script that creates the client-side JavaScript representation of your control.

Define ScriptDescriptors

In the GetScriptDescriptors method, return an enumeration of ScriptDescriptor objects that define what properties will be passed down to the client-side JavaScript representation of your control.

The only ScriptDescriptor you need to return in the enumeration is a ScriptControlDescriptor instance that contains all the descriptors for your control.

Here's a basic example of returning a ScriptControlDescriptor without any properties being defined:

protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
    ScriptControlDescriptor descriptor = new ScriptControlDescriptor(this.GetType().FullName, this.ClientID);
    return new ScriptDescriptor[] { descriptor };
}

What ECMAScript (JavaScript) code is needed?

There is a basic block of JavaScript code that you'll need to include in the ScriptReference that's being defined by the GetScriptReferences method that will define the client-side JavaScript representation of your control. This basic block of code that you include in this script file is the same for all ScriptControls that you'll create.

Here's the basic JavaScript that will define the client-side JavaScript representation of the CustomScriptControl we're using in this example. One thing to remember is that the namespaces and object name in the JavaScript file need to be the same as they are in the server-side .NET code.

Type.registerNamespace("CustomScriptControl");
CustomScriptControl.CustomScriptControl = function(element) {
    CustomScriptControl.CustomScriptControl.initializeBase(this, [element]);
};
CustomScriptControl.CustomScriptControl.prototype = {
    initialize:function() {
        CustomScriptControl.CustomScriptControl.callBaseMethod(this, "initialize");
    },
    dispose:function() {
        CustomScriptControl.CustomScriptControl.callBaseMethod(this, "dispose");
    }
};
CustomScriptControl.CustomScriptControl.registerClass("CustomScriptControl.CustomScriptControl", Sys.UI.Control);
if(typeof(Sys)!=="undefined")Sys.Application.notifyScriptLoaded();

 

Also, in order for the ScriptControl to correctly load the "CustomScriptControl.CustomScriptControl.js" script reference that we're using in this example, we must not forget to define the assemblies embedded resource as a Web Resource, like so:

[assembly: System.Web.UI.WebResource("CustomScriptControl.CustomScriptControl.js", "text/javascript")]

Let's Add a Little "Rich" AJAX Functionality to the Control

Now that we have a basic ScriptControl created, we can start to add out "rich" AJAX functionality to it.

In this article, I'm going to keep things extremely simple, and we're going to do the following:

  1. Pass a Name (type string) property to the client by defining it as a ScriptDescriptor, and displaying it to the user via a JavaScript Alert dialog.

Add the Name Property to the Control

public string Name { get; set; }

Add the ScriptDescriptor for the Name Property

Just modify the GetScriptDescriptors method above to look like the following:

protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
    ScriptControlDescriptor descriptor = new ScriptControlDescriptor(this.GetType().FullName, this.ClientID);

    // Add our Name property to be passed down to the client
    descriptor.AddProperty("Name", this.Name);

    return new ScriptDescriptor[] { descriptor };
}

Add the Client-Side Name Property that will receive the value

To do this we need to add a "private" like variable to hold the value and property accessor methods to our JavaScript file.

Here's the above JavaScript file with the "private" variable and property accessors added for the Name property:

Type.registerNamespace("CustomScriptControl");
CustomScriptControl.CustomScriptControl = function(element) {
    CustomScriptControl.CustomScriptControl.initializeBase(this, [element]);
    // "private" variable to hold the Name properties value
    this._name = null;
};
CustomScriptControl.CustomScriptControl.prototype = {
    initialize:function() {
        CustomScriptControl.CustomScriptControl.callBaseMethod(this, "initialize");
    },
    dispose:function() {
        CustomScriptControl.CustomScriptControl.callBaseMethod(this, "dispose");
    },
    // Name Property Accessors
    get_Name:function() {
        return this._name;
    },
    set_Name:function(value) {
        this._name = value;
    }   
};
CustomScriptControl.CustomScriptControl.registerClass("CustomScriptControl.CustomScriptControl", Sys.UI.Control);
if(typeof(Sys)!=="undefined")Sys.Application.notifyScriptLoaded();

 

Display the Name to the User

In this example we're going to display the Name property to the user using a JavaScript Alert within the objects client-side initialize property.

Here's the initialize method modified to include the Alert:

initialize:function() {
    CustomScriptControl.CustomScriptControl.callBaseMethod(this, "initialize");

    // Display Name property to user
    alert(this.get_Name());

},

Tips and Tricks

One trick that I've learned that proves useful when creating custom ScriptControls, is to create your own base class that inherits from ScriptControl and extends it to use some custom attributes to make adding ScriptReferences and ScriptDescriptors easier.

Here's what the final server-side code of the CustomScriptControl object would look like when using custom attributes to add the ScriptReferences and ScriptDescriptors:

[ScriptReference("CustomScriptControl.CustomScriptControl.js", "CustomScriptControl")]
public class CustomScriptControl : ScriptControlBase
{
    [ScriptControlProperty]
    public string Name { get; set; }
}

As you can see, the new code for the CustomScriptControl using this base class with custom attributes is much cleaner and easier to read.

Here's the complete code for the ScriptControlBase, ScriptReferenceAttribute and ScriptControlPropertyAttribute objects:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI;

public class ScriptControlBase : ScriptControl
{
    protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
    {
        ScriptControlDescriptor descriptor = new ScriptControlDescriptor(this.GetType().FullName, this.ClientID);

        // Add all the ScriptControls Client-Side Object Properties
        PropertyDescriptorCollection props = TypeDescriptor.GetProperties(this);
        foreach (PropertyDescriptor prop in props)
        {
            ScriptControlPropertyAttribute propAttr = prop.Attributes[typeof(ScriptControlPropertyAttribute)] as ScriptControlPropertyAttribute;
            if (propAttr != null)
            {
                object value = prop.GetValue(this);
                string name = (propAttr.Name != null) ? propAttr.Name : prop.Name;
                if (value != null)
                {
                    descriptor.AddProperty(name, value);
                }
            }
        }

        return new ScriptDescriptor[] { descriptor };
    }

    protected override IEnumerable<ScriptReference> GetScriptReferences()
    {
        List<ScriptReference> references = new List<ScriptReference>();

        // Add all the ScriptControls Client-Side JavaScript References
        object[] scriptReferences = Attribute.GetCustomAttributes(this.GetType(), typeof(ScriptReferenceAttribute), false);
        foreach (ScriptReferenceAttribute r in scriptReferences)
        {
            references.Add(r.GetScriptReference());
        }

        return references;
    }
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class ScriptControlPropertyAttribute : Attribute
{
    public ScriptControlPropertyAttribute() { }

    public ScriptControlPropertyAttribute(string name)
    {
        this.Name = name;
    }

    public string Name { get; set; }
}

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ScriptReferenceAttribute : Attribute
{
    public ScriptReferenceAttribute(string path)
    {
        this.Path = path;
    }

    public ScriptReferenceAttribute(string name, string assembly)
    {
        this.Name = name;
        this.Assembly = assembly;
    }

    private string _path = null;
    public string Path
    {
        get { return _path; }
        set { _path = value; }
    }

    private string _name = null;
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    private string _assembly = null;
    public string Assembly
    {
        get { return _assembly; }
        set { _assembly = value; }
    }

    public ScriptReference GetScriptReference()
    {
        ScriptReference r = null;

        if (this.Path == null)
        {
            r = new ScriptReference(this.Name, this.Assembly);
        }
        else
        {
            r = new ScriptReference(this.Path);
        }

        return r;
    }
}

Conclusion

As you can see it's fairly simple to get started creating your own custom server controls that inherit from ScriptControl, especially if you use the above ScriptControlBase class along with its ScriptControlPropertyAttribute and ScriptReferenceAttribute attributes.

Download Full Source Code: CreateAJAXServerControlUsingScriptControlBaseClass.zip (33.31 kb)

Update 5/31/2008: I recently posted an article titled "Create AJAX Extender Controls using the ExtenderControl base class" on how to create Extender Controls in ASP.NET using the System.Web.UI.ExtenderControl base class.

Currently rated 4.3 by 12 people

  • Currently 4.333333/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: asp.net
Posted by crpietschmann on Thursday, May 15, 2008 12:52 AM
Permalink | Comments (6) | Post RSSRSS comment feed

Some ASP.NET AJAX Tips and Tricks

I've been using ASP.NET AJAX alot for a while now, so I thought I'd share some of the Tips and Tricks that I've learned though my adventures. Lately, all the buzz has been around Silverlight, but I know there are still a lot of devs out there that can benefit from these tips and tricks.

UpdatePanel Tips

Use UpdateMode=Conditional

By setting the UpdatePanel's UpdateMode property to Conditional, it will only update its contents when one of its triggers occurs or a control within the UpdatePanel itself raises a Postback.

To do this just do the following:

{code:html}

<asp:UpdatePanel runat="server" ID="UpdatePanel_List" UpdateMode="Conditional">

{/code}

Programmatically Tell the ScriptManager to handle a specific controls postbacks asynchronously

Programmatically telling the ScriptManager to raise a Postbacks Asynchronously can be usefull in some scenarios; such as when you may want to optionally enable ajax for certain things on the page.

To do this you just use the ScriptManagers RegisterAsyncPostBackControl method and pass it the reference to the control you want to make asynchronous.

Here's an example page that does this:

Default.aspx

{code:html}

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="UpdatePanel_01_Default" %>
<!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>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
   
        <asp:ScriptManager runat="server" ID="ScriptManager1"></asp:ScriptManager>
        <asp:Button runat="server" ID="btnShowTime" Text="Show Time" OnClick="btnShowTime_Click" />
        <br />
        <asp:UpdatePanel runat="server" ID="UpdatePanel1">
            <ContentTemplate>
                <asp:Label runat="server" ID="lblTime"></asp:Label>
            </ContentTemplate>
        </asp:UpdatePanel>
  
    </div>
    </form>
</body>
</html>

{/code}

Default.aspx.cs

public partial class UpdatePanel_01_Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        /// Make btnShowTime postback asynchronously
        this.ScriptManager1.RegisterAsyncPostBackControl(btnShowTime);
    }

    protected void btnShowTime_Click(object sender, EventArgs e)
    {
        lblTime.Text = DateTime.Now.ToLongTimeString();
    }
}

WebMethods and PageMethods

Using WebMethods

By attaching the ScriptService and ScriptMethod attributes to your WebServices you can enable them to be accessed from within your pages via JavaScript.

Here's a sample WebService with the ScriptService and ScriptMethod attributes attached:

{code:c#}

[System.Web.Script.Services.ScriptService]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class WSTime : System.Web.Services.WebService
{
    [WebMethod]
    [System.Web.Script.Services.ScriptMethod]
    public string GetServerTime()
    {
        return DateTime.Now.ToLongTimeString();
    }

}

{/code}

Then to access the WebService from within JavaScript you need to first tell the ScriptManager about the WebService by giving it a ServiceReference. This can be done declaratively within the page, like so:

{code:html}

<asp:ScriptManager runat="server" ID="ScriptManager1">
    <Services>
        <asp:ServiceReference Path="~/WebMethods/WSTime.asmx" />
    </Services>
</asp:ScriptManager>

{/code}

Now to acess the WebServices methods you just use the namespace that ASP.NET Ajax creates in the page for the WebService and access its methods like normal JavaScript methods. The namespace that is created is the same name as the WebService, in this case it's WSTime. The whole Ajaxiness of calling the server and getting the results back are handled for you, and all you have to worry about is processing the results. Remember, WebMethods can only be called Asynchronously.

Here's an example of using the WSTime.GetServerTime WebService method from within JavaScript:

{code:js}

function btnGetServerTime_Click()
{
    // call the WebMethod and pass it the method
    // to call once it gets the response from
    // the server.
    WSTime.GetServerTime(SucceededCallback);
}
function SucceededCallback(result, eventArgs)
{
    alert(result);
}

{/code}

Using PageMethods

PageMethods are accessed in the same fashion as WebMethods, except they do not require the creation of a WebService. PageMethods are actually WebMethods that are added to the page itself. Once thing to note is there is No way to embed PageMethods within Custom User Controls or Server Controls, they must be added to the Page; hence the name PageMethods.

Here's the C# code of an example page with a PageMethod defined:

{code:c#}

public partial class PageMethods_Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
    }

    [WebMethod]
    public static string GetServerTime()
    {
        return DateTime.Now.ToLongTimeString();
    }
}

{/code}

To enable the PageMethods you must first set the ScriptManagers EnablePageMethods property to True.

{code:html}

<asp:ScriptManager runat="server" ID="ScriptManager1" EnablePageMethods="true"></asp:ScriptManager>

{/code}

Then you can call the PageMethods in the same way as WebMethods, except the namespace you use to access the methods is named PageMethods.

Here's the example code of calling the above PageMethod from JavaScript:

{code:js}

function btnGetServerTime_Click()
{
    PageMethods.GetServerTime(SucceededCallback);
}
function SucceededCallback(result, eventArgs)
{
    alert(result);
}

{/code}

Conclusion

I hope these couple of tips help in your endeavors with ASP.NET AJAX. When I get time (in between all the other million things) maybe I'll post some more tips.

Also, if you have your own tips, please post them in the comments.

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,
Categories: asp.net
Posted by crpietschmann on Thursday, April 03, 2008 9:56 PM
Permalink | Comments (0) | Post RSSRSS comment feed

.NET 3.5: JSON Serialization using the DataContractJsonSerializer

In ASP.NET AJAX Extensions v1.0 for ASP.NET 2.0 there is the JavaScriptSerializer class that provides JSON serialization and deserialization functionality. However, in .NET 3.5 the JavaScriptSerializer has been marked obsolete. The new object to use for JSON serialization in .NET 3.5 is the DataContractJsonSerliaizer object. I'm still new to the DataContractJsonSerializer, but here's a summary of what I've learned so far...

Object to Serialize

Here's a simple Person object with First Name and Last Name properties.

public class Person
{
    public Person() { }
    public Person(string firstname, string lastname)
    {
        this.FirstName = firstname;
        this.LastName = lastname;
    }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Now in order to serialize our object to JSON using the DataContractJsonSerializer we must either mark it with the Serializable attribute or the DataContract attribute. If we mark the class with the DataContract attribute, then we must mark each property we want serialized with the DataMember attribute.

/// Marked with the Serializable Attribute
[Serializable]
public class Person
{
    public Person() { }
    public Person(string firstname, string lastname)
    {
        this.FirstName = firstname;
        this.LastName = lastname;
    }
    public string FirstName { get; set; }
    public string LastName { get; set; }

}

/// Marked with the DataContact Attribute
[DataContract]
public class Person
{
    public Person() { }
    public Person(string firstname, string lastname)
    {
        this.FirstName = firstname;
        this.LastName = lastname;
    }

    [DataMember]
    public string FirstName { get; set; }

    [DataMember]
    public string LastName { get; set; }
}

[/code]

 

Serialization Code

Jere's the most basic code to serialize our object to JSON:

Person myPerson = new Person("Chris", "Pietschmann");

/// Serialize to JSON
System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(myPerson.GetType());
MemoryStream ms = new MemoryStream();
serializer.WriteObject(ms, myPerson);
string json = Encoding.Default.GetString(ms.ToArray());

Our resulting JSON looks like this:

/// Result of Person class marked as Serializable
{"<FirstName>k__BackingField":"Chris","<LastName>k__BackingField":"Pietschmann"}


/// Result of Person class marked as DataContract with
/// each Property marked as DataMember

{"FirstName":"Chris","LastName":"Pietschmann"}

As you can see the first serialization with the class marked with the Serializable attribute isn't quite what we were expecting, but is still JSON. This serialization actually isn't compatible with the client-side JSON Serializer in ASP.NET AJAX.

As you can see the second serialization with the class marked with the DataContract attribute is exactly what we were expecting, and is the same JSON that the old JavaScriptSerializer object would have generated. This is the method of JSON serialization using the DataContractJsonSerializer that you'll need to do if you are going to pass the resulting JSON down to the client to be consumed with ASP.NET AJAX.

 

Deserialization Code

Here's the most basic code to deserialize our object from JSON:

Person myPerson = new Person();
MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json));
System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(myPerson.GetType());
myPerson = serializer.ReadObject(ms) as Person;
ms.Close();

 

Controlling the property names in the resulting JSON

When using the DataContract and DataMember attributes, you can tell the DataMember attribute the specific name you want that property to have within the JSON serialization by setting its "Name" named parameter.

Here's an example that will give the name of "First" to the "FirstName" property within the JSON serialization:

[DataMember(Name = "First")]
public string FirstName { get; set; }

The resulting JSON looks like this:

{"First":"Chris","LastName":"Pietschmann"}

 

Here's the code for some Helper methods using Generics to do all the dirty work for you

using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;

public class JSONHelper
{
    public static string Serialize<T>(T obj)
    {
        System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(obj.GetType());
        MemoryStream ms = new MemoryStream();
        serializer.WriteObject(ms, obj);
        string retVal = Encoding.Default.GetString(ms.ToArray());
        return retVal;
    }

    public static T Deserialize<T>(string json)
    {
        T obj = Activator.CreateInstance<T>();
        MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json));
        System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(obj.GetType());
        obj = (T)serializer.ReadObject(ms);
        ms.Close();
        return obj;
    }
}

/// Our Person object to Serialize/Deserialize to JSON
[DataContract]
public class Person
{
    public Person() { }
    public Person(string firstname, string lastname)
    {
        this.FirstName = firstname;
        this.LastName = lastname;
    }

    [DataMember]
    public string FirstName { get; set; }

    [DataMember]
    public string LastName { get; set; }
}


/// Sample code using the above helper methods
/// to serialize and deserialize the Person object

Person myPerson = new Person("Chris", "Pietschmann");

// Serialize
string json = JSONHelper.Serialize<Person>(myPerson);

// Deserialize
myPerson = JSONHelper.Deserialize<Person>(json);

 

What Assembly References does my application need for this?

From the namespace that contains DataContractJsonSerializer you can probably tell that you need to add a reference to the System.Runtime.Serialization assembly. However, you also need to add a reference to the System.ServiceModel.Web assembly.

 

Currently rated 5.0 by 6 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: asp.net
Posted by crpietschmann on Wednesday, February 27, 2008 6:15 PM
Permalink | Comments (9) | Post RSSRSS comment feed

Virtual Earth Plus ASP.NET AJAX Breaks In Safari!

Update 5/17/2008: This issue has since been fixed with the release of the Virtual Earth v6.1 release. There is still a small issue related to the CalendarExtender and Virtual Earth that only occurs in Safari. You can read more about it here.

 

I do alot of development with ASP.NET AJAX and Virtual Earth, and each of them work fine by themselves in Safari 3 on Windows. However, if you use them both on the same page, it will cause ASP.NET AJAX to break and stop working in Safari.

This issue comes up when you include the Virtual Earth API JavaScript file in the page. You don't even need to create an instance of VE on the page to cause the issue to occur.

I don't have a Mac, so keep in mind that I haven't tested this in Safari on the Mac. I've only verified this issue in Safari 3 on Windows.

Below is a code example using the CalendarExtender in the Ajax Control Toolkit to demonstrate the issue. If you run the following code, you will notice that when you click on the textbox in Safari, the calendar never pops up, and the JavaScript console will register a couple exceptions in the ASP.NET AJAX Extensions JavaScript code.

[code:html]
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="SafariTest.aspx.cs" Inherits="SafariTest" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>
<!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>Untitled Page</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6" mce_src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6"></script>
<script type="text/javascript"> var map = null; function GetMap() { map = new VEMap('myMap'); map.LoadMap(); } </script>
</head>
<body onload="GetMap();">
<form id="form1" runat="server">
<asp:ScriptManager runat="server" ID="ScriptManager1"></asp:ScriptManager>
<div>
<asp:TextBox runat="server" id="txtDate"></asp:TextBox>
<ajaxToolkit:CalendarExtender runat="server" ID="CalendarExtender1" TargetControlID="txtDate"></ajaxToolkit:CalendarExtender>
<br /><br />
<div id='myMap' style="position:relative; width:400px; height:400px;"></div>
</div>
</form>
</body>
</html>
[/code]

This is a real issue for me, since I can not add Safari support to my Web.Maps.VE product because of this issue. I know it's probably an issue with Safari, since everything works perfect in IE and Firefox, but I hope it gets fixed soon.

Currently rated 4.0 by 1 people

  • Currently 4/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by crpietschmann on Thursday, February 07, 2008 12:51 AM
Permalink | Comments (1) | Post RSSRSS comment feed

Virtual Earth: Dynamically Load InfoBox Using ASP.NET AJAX

Loading alot of pushpins on the map can slow down your page in two ways: 1) Page load times can be slowed down, and 2) loading pushpins via ajax can be slow. To improve the performance (as in download and database query times) of loading pushpins on the map, you can load the Pushpin Shapes Title and Description on the fly. This allows you to only load the Pushpins Title and Description as it's needed, thus reducing the amount of data you need to send down to the client when initially loading your pushpins.

Dynamically Load InfoBox Title and Description using ASP.NET AJAX

Step 1: When adding Pushpins to the map, use the following format when setting thier Titles: "LOAD:{0}". Replace the "{0}" with a unique ID representing that Pushpin.

var s = new VEShape(VEShapeType.Pushpin, map.GetCenter());
s.Title =
"LOAD:15";
map.AddShape(s);

Step 2: Create the GetInfoBoxData Page Method in the page that we'll call using Ajax. Also create a simple InfoBoxData class in the page; we'll use this class to more easily pass both the Title and Description down to the client.

[WebMethod]
public static InfoBoxData GetInfoBoxData(int id)
{
    // Pass back the InfoBoxData
    InfoBoxData data = new InfoBoxData();
    data.Title =
"Item " + id.ToString();
    // Get the HTML to be displayed within the Description field
    data.Description = ViewManager.RenderView("~/App_Views/InfoBoxDescription.ascx", id);
    return data;
}

Step 3: Add the ViewManager class to the site's App_Code folder. This class will be used within the GetInfoBoxData Page Method to render the Description from a UserControl Template. This class was written by Scott Guthrie in his post titled "Tip/Trick: Cool UI Templating Technique to use with ASP.NET AJAX for non-UpdatePanel scenarios", and it works perfect in this scenario.

public class ViewManager
{
    public static string RenderView(string path)
    {
        return RenderView(path, null);
    }
    public static string RenderView(string path, object data)
    {
        Page pageHolder = new Page();
        UserControl viewControl = (UserControl)pageHolder.LoadControl(path);
        if (data != null)
        {
            Type viewControlType = viewControl.GetType();
            FieldInfo field = viewControlType.GetField("Data");
            if (field != null)
            {
                field.SetValue(viewControl, data);
            }
            else
            {
                throw