In the preceding chapter, we covered the architecture of the AJAX Control Toolkit, describing at a high level what it has to offer and the attributes, classes, and interfaces that make it all happen.
The enhanced functionality you get in the toolkit, from attribute-based programming to rich animations, provides a compelling alternative to coding against the ASP.NET 2.0 AJAX Extensions and the Microsoft AJAX Library directly. In this chapter, we delve into the details of the toolkit a little further as we develop as series of extender controls that demonstrate the rich features the toolkit provides.
Adding Client-Side Behavior Using the ExtenderControlBase
The ASP.NET AJAX Control Toolkit provides many features to assist in the development of extender controls, such as the automatic creation of $createstatements, the use of attributes to decorate extender control properties that should be included in the $create statement creation, built-in designer support, and many more. In this section, we revisit the Image Rotator extender we created in Chapter 5, “Adding Client Capabilities to Server Controls,” and re-create it using the ASP.NET AJAX Control Toolkit. This approach enables us to compare the alternatives as we build the new extender.
The process of building an extender control using the ASP.NET AJAX Control Toolkit consists of four main steps.
- Create the template classes.
- Provide implementation for the inherited extender control class.
- Attach the extender control to an existing server control.
Visual Studio 2008 Extender Control Library Template
NOTE: Additional Template
The toolkit also comes with a template that generates the same files that can be used when you need to add additional extenders to an existing project, which can be found when you select Add New Item from a project.
The ImageRotatorExtenderclass shown in Listing 11.1 serves as the basis for our ImageRotator extender control. The class inherits from Extender ControlBase and provides a template that contains most of the required entries for us, such as the web resource registration of our associated behavior class, class attributes that associate the designer for the extender, the client script to be downloaded, and the target type for the extender. The template also creates a default property, demonstrating the use of the ExtenderControlProperty and DefaultValue attributes and the use of the GetPropertyValue method inside the property setter and getter.
NOTE: GetPropertyValue Method Version
The version of the GetPropertyValue method used by the template is an outdated one. When building out the class, we will change the implementation to use the GetPropertyValue<T> version instead.
The ImageRotatorDesigner class shown in Listing 11.2 will be the designer class for our ImageRotator extender control. The designer class provides default designer functionality for our extender control during design time. We associate the designer with our ImageRotatorExtender class by using the Designer attribute, which is automatically added when we use the template. The ExtenderControlBaseDesigner<T> class that the ImageRotatorDesigner class inherits from makes it possible for the properties of our extender control to show up in the Properties window while the design-time focus is on the image control we are extending. This default behavior provides a more efficient way of working with extenders and the controls they are extending.
The ImageRotatorBehaviorclass shown in Listing 11.3 will be the client-side behavior class for our ImageRotator extender control. The class consists of the same structure we used in Chapter 5, but now inherits from the AjaxControlToolkit.BehaviorBaseclass, which provides added functionality for working with client state and interacting with the asynchronous request events of the Sys.WebForms.PageRequestManager.
Inheriting from the ExtenderControlBase Class
The ASP.NET AJAX Control Toolkit comes with its own version of the System.Web.UI.ExtenderControl class, which provides additional functionality that supports the development pattern the toolkit is designed to work with. The AjaxControlToolkit.ExtenderControlBaseclass provides the inheritor support for serialization of property values, support for working with the toolkit-based attributes, seamless integration with control-based view state, support for working with client state, and the ability to specify an alternate script path for debugging and working with themes. The ImageRotatorExtender class in Listing 11.4 shows a much different-looking class than we saw in Chapter 5. The class no longer requires overrides for the GetScriptDescriptors and GetScriptReferences methods, it has class-level attributes, it has property-level attributes, and the property setters and getters are referencing their values through a method. So, let’s go over these changes and see how we develop an extender control building on the structure the template provided for us.
The setting of the assembly-based WebResourceattribute in our extender class is a pattern that all the extenders and script controls in the toolkit follow. This pattern helps centralize all the pieces for the component in one location instead of having to add an entry to the assembly when a new control is added to the toolkit. The attributes applied to the class that we cover in this section are the Designer, ClientScriptResource, RequiredScript, and TargetControlType attributes. The Designer attribute is used to specify the class that will provide design-time services to our extender. The ClientScriptResourceattribute is used to include the client-side scripts for our extender and consists of the resource type and the full resource name and should refer to an embedded resource. The RequiredScriptResource attribute brings in the timer script file that is associated with the Timer Script class that we will use in our behavior class. Finally, the Target ControlTypeattribute is used to limit the types of controls our extender can be associated with.
The RotationInterval and ImageList properties of our class have also changed with the use of attributes and the reliance on the GetProperty Value<T> and SetPropertyValue<T> methods to access our property data. The ExtenderControlProperty attribute is used to indicate that the property should be added to the ScriptComponentDescriptoras a property and later included in the $create statement that creates the behavior class on the client. The ClientPropertyName attribute is used to change the name of the property that is used when the property is added to the Script ComponentDescriptor from the default value of the property name to the name provided to the attribute. The DefaultValue attribute, which comes from the System.CompnentModel namespace, is used to indicate to designers and code generators the default value of the property. The Extender ControlBase class provides the GetPropertyValue<T> and GetProperty Value<T>generic methods that get and set the property value directly from the control view state. By using these methods in our property setters and getters, a consumer of our extender can work with it in the designer, declaratively in the HTML editor, or in code and be assured that during a post-back the values will be available.
Creating the AjaxControlToolkit.BehaviorBase Class
The ASP.NET AJAX Control Toolkit comes with its own version of the Sys.UI.Behavior class, which provides additional functionality and supports the development pattern the toolkit is designed to work with. The AjaxControlToolkit.BehaviorBase class provides inheritor support for working with client state and interacting with the asynchronous request events of the Sys.WebForms.PageRequestManager. The support for working with client state is provided by the get_ClientStateand set_ClientState methods that can be used to work with the string-based hidden field associated with your extender. The class also provides two methods tied to the beginRequest and endRequest events of the PageRequestManager, which can be overridden to provide specific functionality in your behavior in situations where an UpdatePanel is being used.
The ImageRotatorBehaviorclass shown in The ASP.NET AJAX Control Toolkit comes with its own version of the Sys.UI.Behavior class, which provides additional functionality and supports the development pattern the toolkit is designed to work with. The AjaxControlToolkit.BehaviorBase class provides inheritor support for working with client state and interacting with the asynchronous request events of the Sys.WebForms.PageRequestManager. The support for working with client state is provided by the get_ClientStateand set_ClientState methods that can be used to work with the string-based hidden field associated with your extender. The class also provides two methods tied to the beginRequest and endRequest events of the PageRequestManager, which can be overridden to provide specific functionality in your behavior in situations where an UpdatePanel is being used.
The ImageRotatorBehaviorclass shown in Listing 11.5 inherits from the BehaviorBase class and provides the client-side behavior for our extender control. The structure of this class is exactly the same as in Chapter 5, with the rotationIntervalproperty used to set the interval at which the images will be swapped out and the imageListproperty containing an array of the images. The one change to the class is in the use of the Sys.Timer class, which is part of the ASP.NET AJAX Control Toolkit. This class, which is contained in the Compat/Timer folder, wraps the window.setIntervalcall, providing a cleaner interface for this timer-specific functionality. The Sys.Timer class is just one of many that come with the toolkit that provide added functionality to the existing Microsoft AJAX Library. If you look in the Compat and Common folders in the toolkit library project, you will find classes for working with dates, drag and drop, and threading, just to name a few. inherits from the BehaviorBase class and provides the client-side behavior for our extender control. The structure of this class is exactly the same as in Chapter 5, with the rotationIntervalproperty used to set the interval at which the images will be swapped out and the imageListproperty containing an array of the images. The one change to the class is in the use of the Sys.Timer class, which is part of the ASP.NET AJAX Control Toolkit. This class, which is contained in the Compat/Timer folder, wraps the window.setIntervalcall, providing a cleaner interface for this timer-specific functionality. The Sys.Timer class is just one of many that come with the toolkit that provide added functionality to the existing Microsoft AJAX Library. If you look in the Compat and Common folders in the toolkit library project, you will find classes for working with dates, drag and drop, and threading, just to name a few.
Attaching the Extender to a Control
You can attach the ImageRotatorextender to an image control by using the new Extender Control Wizard (see Figure 11.3) that comes with Visual Studio 2008 and thus provide the same design-time experience we saw in Chapter 5. The wizard is available from the smart tag of the image control by selecting the Add Extender option, which opens the wizard. The wizard enables the user to select an extender control from a list and associate it with a control. In our case, we would select the ImageRotator extender to associate it with the image control. After we do that, we add values to the RotationInterval property and ImageList property using the Properties window of the image control.
Adding Design-Time Support to Your Extender Control
The introduction of the Extender Wizard in Visual Studio 2008 has enhanced the design-time experience with regard to working with extender controls, and this section explains how to add design-time features of your own to give your controls that professional feel that users have become accustomed to.
Default Design-Time Experience
ImageRotatorDesignerclass shown in Listing 11.2 provides everything we need to get a basic design-time experience for our extender control. The ExenderControlBaseDesigner<T>that it inherits from makes it possible for the properties of our extender control to show up in the Properties window while the design-time focus is on the image control we are extending. Figure 11.4 shows the RotationIntervaland ImageListproperties that appear in the Properties window while the image control has focus in the designer. This default feature addresses one issue, which is being able to work with the ImageRotatorproperties in an integrated way, but still does not address the issue of data entry for the properties themselves and how that experience can be enhanced.
Adding Designers and Editors to Properties
In this section, we look at how to extend the design-time behavior of our ImageRotator ImageList property. The ImageList property that we worked with in Chapter 5 was rudimentary and prone to errors as a user entered in the values. In this version of the extender, we want to extend the functionality to support design-time editing and HTML source editing.
The road to these modifications requires a few steps as we add the functionality:
- Add attributes to the class.
- Add attributes to the property.
- Add editors to assist in assigning values.
- Create a type converter to support serialization.
Add Attributes to the Class
Most users expect when adding multiple entries to a control to be able to add them in the body of the HTML element. This is the experience we have when adding web service references or script references to the Script Manager and one we want to have in our control.
The ParseChildren attribute enables us to add multiple entries inside our ImageRotator HTML tag and treat those entries as a single property assignment. By setting the ChildrenAsPropertiesproperty to trueand the DefaultProperty to ImageList, as in Listing 11.6, we are effectively telling the designer that we want to have all the items contained in the body of our ImageRotator tag parsed and assigned to the ImageList property. The HTML fragment in Listing 11.7 shows what this looks like when the HTML editor is opened and the ImageRotator tag has entries.
NOTE: ASP.NET Server Control Designer References
The addition of designer features to your extenders requires some knowledge of how designers work. MSDN has some great information about this at http://msdn2.microsoft.com/en-us/ library/aa719973%28VS.71%29.aspx that covers adding design-time support to ASP.NET server controls.
Add Attributes to the Property
To fully implement the ability to add nested image entries to our Image Rotator extender, we need to add a couple of attributes, as shown in Listing 11.8, to our ImageListproperty, which provides hooks for the designer to integrate with our property and properly assign the image values.
The DesignerSerializationVisibility attribute is added to the property to ensure that the designer will serialize the contents of the property during design time. The setting of DesignerSerializationVisibility. Content instructs the designer to generate code for the contents of the tag and not the tag itself.
The PersistenceMode attribute is the other piece to this puzzle and is responsible for adding the <ImageUrl .. />entries inside our ImageRotator tag as we add values to the property in the Properties window. The setting of PersistenceMode.InnerProperty specifies that the property is persisted as a nested tag inside the ImageRotator, as shown in Listing 11.7.
Add Editors to Assist in Assigning Values
The use of editors in your extenders can greatly enhance the user experience during design time and in some cases can lead to more accurate entry of data. Recall from Chapter 5 that we entered images to the ImageList property by adding URL entries and separating them using commas. This rudimentary approach would not be expected by a consumer of a professional control. In this version of the ImageRotator, we want to enhance the data entry of the images by providing an editor that can be used to add image URL entries and have those entries placed into the body of our ImageRotator HTML tag. If we go back to the ScriptManager control, this is the experience it provides when adding web service or script references while in the Properties window.
The ImageList property in this version of the ImageRotator uses two editors to provide a rich design-time experience when adding ImageUrl entries. The first editor is a Collection editor, shown in Figure 11.5, and is designed to assist in adding, editing, and removing values that are based on a Collection. The editor is automatically associated with our ImageList property because the type of the property is a Collection. The second editor we will use is the ImageUrlEditor, shown in Figure 11.6, which the ImageUrlentry uses to assist the user in entering a URL. This editor is associated with the Urlproperty of the ImageUrlclass, as shown in Listing 11.9, by adding the Editor attribute to the property. We use the Editor attribute to configure which editor to use when adding values to the property in the designer. In our case, we are using the ImageUrlEditor to provide the user with a clean way to find an image located in a web application and assign the value to the ImageUrl property. The use of the associated UrlProperty attribute provides a filter that identifies specific file types that can be used to filter against the ImageUrl property.
Create a Type Converter to Support Serialization
The ImageListConverter, shown in Listing 11.10, is designed to convert the ImageList to a JSON array of image URLs that are then passed back to the client. The creation of this type converter now enables us to return a data format that the client can use instead of a string that contains the type name of the ImageList. For the type converter to be used, we need to associate it with the ImageList type. We do this by adding the TypeConverter attribute to the ImageListclass, as shown in Listing 11.11, and assigning the type of the ImageList to it. Now when the toolkit performs a Convert ToString on the ImageList, the JSON string representation of the Image List will be returned.
NOTE: Use of the DataContractJsonSerializer
Adding Animations to Your Extender Control
The ImageRotator extender we created earlier provided little in the area of effects as the images switched and resulted in very fast transition from one image to the next, which wouldn’t catch a viewer’s attention. In this section, we create a new version of the ImageRotator, called the Animated ImageRotator, that fades in the image as it switches from one image to the next and provides this feature in addition to the existing functionality of the ImageRotator. As we cover how to add this new animation functionality, we gloss over the topics we have already covered, focusing only on implementing the animation pieces.
To add this functionality to the AnimatedImageRotator, we need to register the animation scripts with the AnimatedImageRotatorExtender class and add logic to the behavior class to call the animation when the image changes.
Registering the Animation Scripts
To register the script files so that they are downloaded to the browser, we need to add the RequiredScript attribute to the AnimatedImageRotator Extender class, as shown in Listing 11.12. We use the RequiredScript attribute in this case to ensure that the animation.js, timer.js, and common.js script files associated with the AnimationScripts type are included with the scripts brought down to the browser for our control. This style of adding scripts associated with a type is a common practice in the toolkit and is clean way to include dependent scripts associated with a type.
Calling Animation APIs
The AnimatedImageRotatorbehavior class, shown in Listing 11.13, takes the ImageRotator behavior and adds a fade animation when the image changes, to fade the image into view. The constructor of the FadeAnimation takes the target of the animation, the duration of the animation, the number of steps per second, the effect, the minimum opacity, the maximum opacity, and whether to adjust for layout in Internet Explorer. In our case, the BannerImage image control will be the target of our animation, and the duration of our animation will be hard coded to 20% of the time the image is visible. To provide a clean animation, we will set the animation steps to 150, and combine that with a fade-in effect that will cause the image to transition in when the image changes. During this transition, we will start off with an opacity of 0, which will give us a full view of the image background, and then through the 150 steps work our way to a full view of the image with an opacity of 1. Table 11.1 lists some of the FadeAnimationproperties and provides a little more information about what they do.
After we associate the animation to the element, starting, stopping, and pausing the animation is just a method call away, making it simple to manipulate the animation. In the AnimatedImageRotator, the load event of the image is used to trigger the animation to play because it will be fired each time our Sys.Timer calls the _rotateImage method. To do this, we associated the _onLoadImage event handler with the onLoad event of the image and called the play method on the animation inside the function. Now each time the load event occurs, the animation plays, transitioning the image into view. One of the side effects of working with an animation in a situation like this is a potential race condition if the duration was set too long. When working with transition-type animations like the FadAnimation, pay close attention to how you are using it to ensure the animation will work in all cases.
Animations Using the Declarative Method
Overview of Declarative Syntax
To get started, let’s look at the HTML source we will be working toward being able to work with in our ImageAnimationextender. The source in Listing 11.14 contains an ImageAnimationExtendertag that contains in its body an Animationstag. As you might guess, the approach here is to add various animations that are driven by events raised by the image control we are extending. In our case, we are working with the OnLoad event and adding a Sequenceanimation that will call a child Fadeanimation. A Sequenceanimation is designed to run all its child animations one at a time until all have finished. So, what this source tells us is that our extender will have an animation that will be tied to the OnLoad event of the image control and will run the child Fade animation whenever the OnLoad event occurs.
Providing Declarative Support in Your Extender Class
The AnimationExtenderControlBase class provides most of the functionality we need to parse the Animationtag and all its contents. This class provides internal methods that convert the XML representation of the animation into JSON format, which our behavior will then use to run the animation, and also provides the Animation property that we see in Listing 11.15. The following sections cover the steps needed to ensure the extender will work correctly.
- Add attributes to the class.
- Create a property for the event.
- Add attributes to the property.
Add Attributes to the Class
This type of extender has a couple of added class attribute entries of interest to us. The first is the inclusion of the RequiredScript attribute for the AnimationExtender type. The AnimationExtender class provides a lot of the client-side functionality we will be using in our extender control, and by using this type in our RequiredScripts attribute, we are guaranteed that the scripts will be present on the client for us to use. The second attribute is the System.Web.UI.Design.ToolboxItem attribute, which enables our control to show up in the toolbox of Visual Studio. It might seem strange that we have to add this because all our other extenders didn’t. If we look at the attributes on the AnimationExtenderControlBaseclass, however, the support for viewing in the toolbox has been turned off. Therefore, we must reset this value on our control so that it will show up in the toolbox.
Create a Property for the Event
The pattern when creating extenders of this type is to add a property for each event you want to interact with. In our case, we are working with the OnLoad event, so we create a property named OnLoad (to make it easy to understand what the event is). If we were to choose other events, we would name them based on the DOM event they represent. The property accessor for these events must use the GetAnimation and SetAnimation methods to ensure proper data conversion into JSON as the data is stored and retrieved out of the extender’s view state.
Add Attributes to the Event Property
The event property must have the Browsable, DefaultValue, Extender ControlProperty, and DesignerSerializationVisibility attributes applied to it. The Browsableattribute stops the property from showing up in the Properties window and therefore excludes the property from being assigned in the Properties window. This is needed because no editor is associated with this property, and we don’t want users to try to add anything into the Properties window that would corrupt the values. The Designer SerializationVisibilityattribute with a value of DesignerSerialization Visibility.Hiddenis used to indicate that the property value should not be persisted by the designer because the Animation property will take care of that for us. The DefaultValue attribute indicates to the designer that the default value will be null, and the ExtenderControlProperty attribute is used to register the property with the ScriptComponentDescriptor.
Adding Declarative Support to Your Behavior Class
The ImageAnimationBehavior class, shown Listing 11.16, provides all the client-side functionality for our extender with support from the animation script files associated with the AutomationExtenderclass. These associated scripts provide support for converting the JSON representation of the FadeIn animation that was captured on the server to an actual animation, support for associating the animation with the high-level OnLoadevent, and support for playing the animation when the OnLoad event occurs.
You need to complete a few steps for each event you plan to work with:
- Add variables to the class.
- Create functions.
- Add handlers.
Add Variables to the Class
Each event that your behavior will work with needs a variable that references the GenericAnimationBehaviorfor the event and a delegate that will be called for the event that will be processed. In the ImageAnimation Behavior class, we use the _onLoad variable to store a reference to the GenericAnimationBehaviorclass and the _onLoadHandlervariable to store a reference to the delegate that will handle the onLoadevent. The guidelines established so far in the toolkit use a naming convention that includes the event name in all the variable names.
The behavior needs a series of functions for each event you will work with. The get_OnLoadand set_OnLoadfunctions in our case take care of working with the JSON-based data for the FadeIn animation and utilize the functionality provided by the GenericAnimationBehavior class to store and retrieve that data. The get_OnLoadBehavior function returns a reference to the GenericAnimationBehavior instance that was created for our FadeIn animation, providing the ability to work with the behavior that directly exposes the play, stop, and quit methods common to all animations.
Handlers must be added for each event the behavior will process and should correspond to the events exposed on the extender control. In our case, we are working with the onLoad event, so we need to create the _onLoadHandler delegate and associate it with the onLoad event of the image using the $addHandlershortcut. The opposite of this must happen in the dispose of our behavior, when we use the $removeHandler shortcut to ensure proper memory cleanup.
The AJAX Control Toolkit comes with quite a bit of functionality that you can use to create truly interactive extenders that require much less coding than if you were to use the ASP.NET 2.0 AJAX Extensions directly. As you learned in this chapter, the toolkit provides a much richer environment for creating extender controls than using the ASP.NET 2.0 AJAX Extensions alone. In addition, the toolkit includes myriad controls you can either use or build on, making the toolkit a compelling alternative.