Explaining the Toolkit's Extender classes

All of the controls in the preview release of the Toolkit are built upon a set of classes that really help streamline the process of writing control extenders. These classes form a thin "glue" layer between the Tookit controls and the "Atlas" classes as they exist today. I'm really happy with how this stuff turned out and we enabled a bunch of things that take some work when doing control extenders by hand. It's certainly made our lives easier, made the process considerably less error prone, and we'll continue to make improvements. In fact, we've already made several great improvements that you'll see in the next release. I'll highlight those changes here in italics.

To get the most out of this entry, you should have written a control with the toolkit, or at least read this walkthrough.

The Extender Classes automate a couple of major things:

· The generation of XML script that hooks up your behavior to an element on the page

· The loading of your script file reference

· The management of any scripts that your behavior or control may be dependant on

· The mapping of properties and their valeus from the managed side to the client script

· Help in developing and debugging your script

· A mechanism to move state back-and-forth between your extender and the behaviors in the browser

The main class is called Microsoft.AtlasControlExtender.ExtenderControlBase (we'll call it ECB here). ECB is a generic, abstract class, which means you can't instantiate it directly; you have to create a derived type. The main job of this control is to hook up an HTML element with some behaviors on the page, as XML script. This class takes two generic parameters. One is a TargetPropertiesBase-derived type (see below), and the other is a Control type (e.g. Panel). This type specifies which kind of controls in the web designer that your extender will be available for.

The other class that you'll spend time working with is Microsoft.AtlasControlExtender.TargetPropertiesBase (we'll call this TPB), which is another abstract, generic class. ECB and TPB are the Yin and Yang of our Extender universe. An ECB instance has a collection of TPB's - one for each association between a server control and a behavior. So in the case of CascadingDropDown for example, there will be a TBP instance for each DropDownList. This class has one generic parameter, which is (again) the type of controls (e.g. Panel) that you want your extender to be available for.

On this class is where you declare your managed object model. A simple one would look like this:

[DefaultProperty("SomeValue")]

public class MyExtenderProperties : TargetControlPropertiesBase<WebControl>

    {

   [DisplayName("someValue")]

        public string SomeValue

        {

            get

            {

                return GetPropertyStringValue("SomeValue");

            }

            set

            {

                SetPropertyStringValue("SomeValue", value);

            }

        }

    }

The general lifecycle works like this. The developer adds an extender to their ASPX page:

<ns:MyExtender ID="myExtender1" runat="server">

  <ns:MyExtenderProperties TargetControlID="Button1" SomeValue="Foo"/>

</ns:<yExtender>

When the page loads, this instantiates a server control that derives from ECB above. It then instantiates a properties class (a TPB instance) and adds it to the extenders TargetProperties collection.

When the page renders, the ECB walks the properties of each TPB instance and generates XML for them. So the page payload will get something like:

<script type="text/xml-script">

<references>

<add src="WebResource.axd?blahblahblahblahblahblah&blah"/>

</references>

<page xmlns:script="https://schemas.microsoft.com/xml-script/2005" xmlns:myns="myns">

  <components>

    <control id="Button1">

      <behaviors>

        <myns:myBehavior someValue="Foo"/>

      </behaviors>

    </control>

  </components>

</page></script>

If you look at this XML and compare it to the above one, you'll see some similarities. Both declare a connection to Button1, and a property called "SomeValue". If you weren’t using the toolkit, you’d have to write all of this by hand. The JS gets down to the client via the WebResource.axd line. The ECB does that for you automatically as well!

Let’s take a closer look at some of the features of the ECB:

RequiredScripts.   This is a ScriptReferenceCollection that lets you add a list of scripts that you want loaded before your behavior runs. So if you’re writing a behavior that relies on something in the Atlas “UI Glitz” library, you’d add this code to your constructor:

public MyExtender(){

RequiredScripts.AddScriptReference(FrameworkScript.AtlasUIGlitz);

}

This can either be a FrameworkScript enum value or a string to URL of a JS file . Note we’ve simplified this. In the next drop, you’ll do this via a “RequiredScriptAttribute”, so rather than defining a constructor, you’ll just put “[RequiredScript(FrameworkScript.AtlasUIGlitz)]” on your class. Moreover, you can pass the type of another extender here to cause the scripts for that extender to be loaded. So say you want to write a behavior that leverages the ConfirmButton, you can add “[RequiredScript(typeof(ConfirmButtonExtender)]”. What's super cool about this is that it's recursive - you'll all the required scripts that ConfirmButtonExtender specifies as well. Or else it wouldn't work.

 

ScriptPath.   This is a debugging helper property that really simplifies your script development. Once you’ve got your ECB and TPB classes created, create an ASPX page and add them. Copy your “MyBehavior.js” file next to that ASPX page, and then add the following (in bold) to your extender declaration:

<ns:MyExtender ID="myExtender1" ScriptPath="MyBehavior.js" runat="server">

  <ns:MyExtenderProperties TargetControlID="Button1" SomeValue="Foo"/>

</ns:<yExtender>

This will cause the extender to load your script from the URL you give it instead of from the DLL, which is the default. This allows you to debug and modify your script file without having to rebuild your extender. When you’re done, don’t forget to copy this file back into your project.

RenderInnerScript.   This allows you to render extra script inside your behavior declaration. Some behaviors allow setting of complex properties as sub-elements. By overriding this method, you’ll get an opportunity to do that for each TargetProperties object associated with your extender:

protected override void

  RenderInnerScript(Microsoft.Web.Script.ScriptTextWriter writer,

MyExtenderProperties props) {

    writer.WriteStartElement("someElement");

    writer.WriteAttributeString("someValue");

    writer.WriteEndElement();

}

ClientScriptResourceAttribute. This isn’t a method on ECB but it’s related to it. This is what associates your ECB instance with a script file. Normally, it looks like this:

 

[ClientScriptResource("myns", "myBehavior", "MyProject.MyBehavior.js")]

 

But if you want to wrap an existing behavior in Atlas so that you can easily use it on the server-side, you just pass null for the namespace and drop the resource name:

 

[ClientScriptResource(null, "opacity")]

 

It’s very handy to do this so that you’ll never have to write any XML Script for extenders. In this case you now have an extender which will hook an element up with the AtlasUIGlitz OpacityBehavior (note you’d need to add this to the RequiedScripts as well).

 

Now let’s look at what’s available on the TPB. The class is pretty simple but it has several metadata (attribute) options that are very powerful:

 

EnsureValid.   When you create your TPB instance, you override this to make sure that the user has set all of the appropriate values. See below for some improvements we’ve made for the next drop.

 

EnableClientState. This enables client state transport between your extender and the class running on the client. See the ClientState property below. This defaults to false, so if you don’t set it, ClientState will do nothing.

 

ClientState. This is a simple string property that will be made available to your running behavior instance on the client side. So you could do something like this on the server side:

protected void Page_Load(object sender, EventArgs e)

    {

        MyExtender1.TargetProperties[0].ClientState = "hello";

    }

And on the client side, (in Javascript):

 

var state = this.getClientState(); // returns "hello"

 

It’s that simple. There is a corresponding setClientState method on the Javascript side that you can call as well to change the value that the server-side receives. These values persist only for the life of the page. If the user refreshes or navigates away and back to the page, they start from their initial state again.

Now on to the attributes:

 

DefaultValueAttribute. Put this on a TPB property to tell the framework what the default value is for a property. Note this does not set that value, but is a comparison. So, of you’ve got a string value, you would normally put [DefaultValue(null)] or [DefaultValue(0)] for an int. The benefit of this is that it prevents the ECB from serializing out default values. It sounds simple but it’s pretty important to get things working right.

 

DisplayNameAttribute. This allows you to declare a different property name in your managed code from your client script code. For example, you may want to have your managed class names start with an upper-case character, and your client script ones start with a lower case character. Or you may be wrapping an existing behavior and prefer other names. Note this attribute will be replaced by the ClientPropertyNameAttribute in the next drop.

  [DisplayName("someValue")]

  public string SomeValue { … }

IDReferenceAttribute. This attribute tells the ECB that it references the ID of a control on the page. So during rendering, the ECB will replace this value with the Client ID of the control. Note this also tells the designer what kind of controls can be set into this property:

[IDReferenceProperty(typeof(WebControl))]

[DefaultValue(null)]

public string PopupControlID {…}

ResolveClientUrlAttribute. Will be available in second release. This tells the framework the property value for a given property is a client URL (e.g. “~/MyApp/SomeFile.jpg”) that should be mapped to a real URL during rendering. This turns out to be handy for anything that specifies an image or a web service path.

[ResolveClientUrl()]

public string UncheckedImageUrl {…}

RequiredPropertyAttribute.   Will be available in second release. This specifies that a given property declared on a TPB class is required. I mentioned EnsureValid above. Well this means you usually don’t need to write that code anymore. If you just put these attributes on the properties that your behavior requires, the ECB serializer will check to see that each has a value and throw an exception if it doesn’t.

 

One last thing is on the Microsoft.AtlasControlExtender.Design.ExtenderControlBaseDesigner

class. In most cases you won’t need to do anything with this class but there is one virtual member:

 

ExtenderPropertyName. Override this to control the name of the property that shows up in the property grid for your extender when you click on the item that you want to extend.

protected override string ExtenderPropertyName{

   get {

   "TheNameToShowInThePropertyGrid";

   }

}

Wow, that got long. If you’re writing components, hopefully that will help. Post comments for questions.

 

I’m sure this will come up: when is the “second release” going to happen? Well we’re currently trying to incorporate as much of the feedback we’ve gotten from customers, get Safari support working, and add a few new controls. So with some luck you’ll see a release by the end of the month or thereabouts.