#.think.in
learn.create.enjoy

AdRotator: Injecting scripts

June 15, 2008 18:55 by tarn

This article is part of a series of posts about various aspects of writing web controls for ASP.Net using an ad rotator as an example. The AdRotator WebControl Example post has links to related posts and downloads.

The AdRotator needs to insert a class definition script in the output once, even if multiple AdRotator controls are declared on the page. It also needs to insert a script that instantiates that class prototype for every AdRotator control on the page. Fortunately there are static methods on the System.Web.UI.Page object that will do this for us.

Page.ClientScript.RegisterStartupScript(Type type, string key, string script)

The keys here a crucial, there is only one script rendered for each key. If we inject the definition script with a static key it means no matter how many instances control try to register it, only one instance of script will be sent to the output.

To to insert a unique script for every instance of the AdRotator we need to use a unique key. I use the server side controls ClientId property which will be difference for every instance of the control on any page. You could also use a GUID.

This is clearly the weakest post of all the posts about the AdRotator. I just kind of got this working an never really got very inspired to learn anything else about it. I posted it anyway as I felt I did need to cover this topic for completeness of the series. I'll try to update it soon.


AdRotator: Json serialization

June 15, 2008 18:46 by tarn

This article is part of a series of posts about various aspects of writing web controls for ASP.Net using an ad rotator as an example. The AdRotator WebControl Example post has links to related posts and downloads.

The AdRotator needs to pass some data to the client side code so I use a Json serializer. I use the same class we use on the server side. The Json serializer converts an instance of it, or in our case a templated list of it, to a sting on JSON we can emit in the output. Json is JavaScript Object Notation and literally describes an object in Javascript that we can use in our client side Javascript code.

The object is very simple and only contains three strings and constructors. I have removed some of the attributes described in other articles as they are not relevant in this context.

public class ImageItem
{
    public ImageItem()
        : this(string.Empty, string.Empty, string.Empty)
    {
    }

    public ImageItem(string linkUrl, string imageUrl, string displayTime)
    {
        LinkUrl = linkUrl;
        ImageUrl = imageUrl;
        DisplayTime = displayTime;
    }

    public string LinkUrl { get; set; }
    public string ImageUrl { get; set; }
    public string DisplayTime { get; set; }
}

 

The AdRotator has a public property Images that is List of the ImageItem type above.

public List<ImageItem> Images;

 

During the RenderContents event of the AdRotator we can serialize this list into a string of Json.

JavaScriptSerializer serializer = new JavaScriptSerializer(); 
string json = serializer.Serialize(Images); 
 

The Json will look something like this.

[{"ImageUrl":"Images/Winter.jpg","LinkUrl":"#","DisplayTime":"1000"},
{"ImageUrl":"Images/Sunset.jpg","LinkUrl":"#","DisplayTime":"4000"}]

The following script and corresponding output will hopefully demonstrate how we can use the Json on the client side as data.

var imageList = [{"ImageUrl":"Images/Winter.jpg","LinkUrl":"#","DisplayTime":"1000"},
                 {"ImageUrl":"Images/Sunset.jpg","LinkUrl":"#","DisplayTime":"4000"}];

for(var i=0; i<imageList.length; i++)
{
    document.writeln(imageList[0].ImageUrl);
}

Output:

Images/Winter.jpg 
Images/Winter.jpg

 

Well that's all the information I'm going to include in this article. I'd like to go further into how the Json is used in the AdRotator but I've rambled on long enough. I think there is enough here to see that Json is a pretty cool language for communicating data between the server and client side Javascript.

I should also note that the JavaScriptSerializer is marked as depreciated and points towards the DataContractJsonSerializer. I briefly tried to get this working, but I couldn't seem to find the object in my system assemblies. To read more about that, check out DataContractJsonSerializer in .NET 3.5 that discusses using the DataContractJsonSerializer.

I hope you've found the article interesting, if you have you might be interested in reading more article about the AdRotator example or you might want to download and checkout the example project.


AdRotator: Client side code

June 15, 2008 18:23 by tarn

This article is part of a series of posts about various aspects of writing web controls for ASP.Net using an ad rotator as an example. The AdRotator WebControl Example post has links to related posts and downloads.

The Prototype

I wanted the client side code to be as object orientated as possible.  I implemented the behaviour of our AdRotator as  a prototyped Javascript object, equivalent to a class definition in other OO languages. This means every instance of the AdRotator on a page only needs to be an instance of the ad rotator prototype.

I create the anchor and image elements on the fly. I need the client side object to know about these elements to change them when the image changes. I could have created them in the server-side and passed their ids to find them in the DOM, or found them by looking through our containers child controls. In the end creating them on client side was just slightly easier.

The images parameter is an array of objects that have ImageUrl, LinkUrl and DisplayTime properties. I pass it in as Json (Javascript object notion) that I generated on the server using a Json serializer. You'll see the Json later in this article, and I've also added an article about generating Json script on the server side.

You notice the strange way setTimeout is called in the RotateAd function. It is all about Javascript scope and is far to big an issue to go into here. All I will say is that if I had used "this" instead on the "thisObject" which is set to "this" anyway, it might not point to this instance of this class, it might actually point to the document, window or whatever the current "this" actually is when the timeout event occurs.

function AdRotator(id, ads, height, width)
{
   this.id = id;
   this.ads = ads;
   this.index = 0;
   this.container = document.getElementById(id);

   this.anchorElement = document.createElement('a');
   this.imageElement = document.createElement('img');
   this.imageElement.setAttribute('height', height);   
   this.imageElement.setAttribute('width', height);
   this.anchorElement.appendChild(this.imageElement);
   this.container.appendChild(this.anchorElement);
   this.RotateAd();
}

AdRotator.prototype.RotateAd = function()
{
    var currentAd = this.NextAd();
    this.imageElement.setAttribute('src', currentAd.ImageUrl);
    this.anchorElement.setAttribute('href', currentAd.LinkUrl);
    var thisObject = this;
    setTimeout(function() { thisObject.RotateAd(); }, currentAd.DisplayTime);
}

AdRotator.prototype.NextAd = function()
{
    var ad = this.ads[this.index];
    this.index ++;
    if (this.index == this.ads.length) this.index = 0;
    return ad;
}

 

The Instance

For each instance of the ad rotator we simply use this script. You'll immediately notice that the code is filled with place holders. I replace these placed holders on the server-side before I inject it into the output. I'll describe these in more detail and finally I'll include the source of a rendered page to see this as the browser sees.

You might also notice that we are not actually assigning this object to anything. Hopefully because it will always be referenced by a setTimeout event it will always stay in scope. If I ever do have any problem with this I could easily create and array in the definition and push each instance into the array.

(new AdRotator('$ElementId', $Images, $Height, $Width));

 

The $ElementId placeholder is replaced with server control the server controls ClientId property. This means we can reference the DOM object (a span element) that the server outputs as a container for the control. The ClientId property is very importing when creating elements on the server and interacting with them on the client side. Often elements are not output with the ID of the server control. One of the reasons is that its possible in ASP.Net to have two control with the same ID in different placeholders. Or the same ID in a master page and in the content page. I won't go into detail about how this naming works, but its pretty clear when you view the source of the pages rendered by an ASP.Net server. It is a string as it's used by definition to find an element with the getElementById function.

The $Images place holder is replaced with Json. Effectively a Javascript object describing an object which is a an array of objects containing ImageUrl, LinkUrl and DisplayTime properties. I have written an article about how this Json is generated on the server side.

The $Height and $Width place holders are replaced by the corresponding values on the AdRotator server control.

An example of how this script is actually output by the server should hopefully demonstrates how this client side script actually works, and hopefully how it works for multiple controls.

<script type="text/javascript">
(new AdRotator('ctl00_MainContentPlaceHolder_rotator1', 
               [{"ImageUrl":"Images/Winter.jpg","LinkUrl":"Winter.aspx","DisplayTime":"1000"},
                {"ImageUrl":"Images/Sunset.jpg","LinkUrl":"Sunset.aspxd","DisplayTime":"4000"}],
                200, 200))
</script>

 

This is just one article in a series describing various aspects of writing ASP.Net server side controls. You can download the example code or assembly and see how it works.


AdRotator: Declaratively setting a collection property

June 15, 2008 18:22 by tarn

 

This article is part of a series of posts about various aspects of writing web controls for ASP.Net using an ad rotator as an example. The AdRotator WebControl Example post has links to related posts and downloads.

I really wanted to make it possible for the control to be added completely declaratively. Much the same way as you can create a list box with list items declaratively.

<asp:ListBox ID="2" runat="server">
    <asp:ListItem>Option1</asp:ListItem>
    <asp:ListItem>Option1</asp:ListItem>
</asp:ListBox>

 

This actually turned out to be really easy after I found a great example from Microsoft, Web Control Collection Property Example. I'd advise getting straight into that if you are implementing this and need more detail. I will just briefly describe what I had to implement for the AdRotator Control.

Attributes

I regularly set public properties on a web control with the appropriate type converters declaratively. For example where the public properties Height and Width declared as below.

public class AdRotator : WebControl
{
...
public new string Width { get; set; }
public new string Height { get; set; }
...
}

 

They can be set declaratively in ASP.Net web files

<x:AdRotator ID="AdRotator1" runat="server" Height="100" Width="100" />
 

Children

When it came to adding the child data, I had already implemented a strongly typed public data source property on the AdRotator control, so all I really needed to do to get it working was add attribute meta data to some classes and properties.

On the actual control I had to add meta data about the type of child elements we the control will support.

[DefaultProperty("Images")]
[ParseChildren(true, "Images")]
...
public class AdRotator : WebControl

 

On the property "Images" I added the following metadata. I didn't need it all, the "Category" for example is used when the object is displayed in a property list control. The attribute classes and CollectionEditor are all in the System.ComponentModel.Design namespace and the UITypeEditor is in System.Drawing.Design namespace. As the collection we are exposing is strongly typed we don't need to extend the CollectionEditor class.

[Category("Behavior")]
[Description("Image URL collection")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Editor(typeof(CollectionEditor), typeof(UITypeEditor))]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]public List<ImageItem> Images
public List<ImageItem> Images

 

The ImageItem class also need some additional meta data.

[TypeConverter(typeof(ExpandableObjectConverter))]
public class ImageItem
{
public ImageItem()
: this(string.Empty, string.Empty, string.Empty)
{
}
public ImageItem(string linkUrl, string imageUrl, string displayTime)
{
LinkUrl = linkUrl;
ImageUrl = imageUrl;
DisplayTime = displayTime;
}
[Category("Behavior")]
[DefaultValue("")]
[Description("Anchor Href Url")]
[NotifyParentProperty(true)]
public string LinkUrl { get; set; }
...
}
}

 

Finally

By the time the RenderContents method is fired, the Images property is fully populated with all the items we declaratively added. When we are declaring the image items we are getting nice intellisense. That's everything we set out to do.

ad_rotator_intellisense_example1

Although I didn't go into much detail here we did get everything I wanted in the AdRotator example. I find this quite interesting as the concept of XML based object declarations was build into the XAML SilverLight and WPF technologies.


AdRotator: Embedding resources in an assembly

June 15, 2008 18:20 by tarn

This article is part of a series of posts about various aspects of writing web controls for ASP.Net using an ad rotator as an example. The AdRotator WebControl Example post has links to related posts and downloads.

The AdRotator has several scripts which it emits in the output. I wanted the AdRotator completely encapsulated in a single "dll" file. I choose to embed the scripts in the assembly and read them back at run time. I could have included the scripts in the code as a string but I feel having escaped script declarations in code is more difficult to read and maintain. It also makes it difficult for the IDE to provide intellisense.

Its easy to embed a file included in a project into the projects output assembly. The setting can be found by selecting properties of the file you want embed and selecting "Embedded Resource" as the "Build Action".

embed_resource_example 

The file can be read back as a stream by reflecting the assembly and getting the resource by name. Below is an example of reading a resource by name into a string.

private string GetResourceString(string resourceName)
{
   Assembly assembly = GetType().Assembly;
   string resourceFullName = string.Format("{0}.{1}", 
   assembly.GetName().Name, resourceName);
   string resourceString;
   using (Stream stream = assembly.GetManifestResourceStream(resourceFullName))
   {
      TextReader reader = new StreamReader(stream);
      resourceString = reader.ReadToEnd();
      reader.Close();
   }
   return resourceString;
} 
  

As far as the code above goes, if it was a more professional application the method would be protected by a unit tested wrapper with application specific exception handling.

There is a free tool created by the Sells Brothers you can use to view the resources embedded in an assembly that may be useful.


AdRotator WebControl Example

June 15, 2008 18:01 by tarn

I didn't really actually want to write another ad rotator, but I did think it would be a good example control to implement. It has the scope to cover some aspects of writing web controls I wanted to learn more about and discuss. I decided to build an ASP.Net web control that cycles through a list of images on the client side. There are a few areas I want to discuss.

I wanted the control completely encapsulated within an assembly. So all you'd need to use the control would be a copy of the assembly. To do this I embedded some resources in the assembly and read them out at runtime. I discuss this in the article.

I wanted the control to be able to be created completely declaratively, and I thought providing the sort of intellisense you get when using standard ASP.Net controls would be nice.

I wanted the server-side control to inject Javascript into the output. I wanted the control to be smart enough to only output the Javascript prototype, or class definition once, even if there were multiple controls on the page. I wanted the client side code to be as object orientated as possible. 

You can download the source code which includes an example website in the solution.

I would like to add a post about supporting difference storage mechanisms and using the control, but I have other projects demanding my attention. Hopefully I'll get back to it sometime.

I Hope you've found some of these articles useful or interesting. I'll keep and eye on the comments, contribute to further discussion, and update the posts where errors or omissions are noted.

Cheers