Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Developing Silverlight-enabled ASP.NET Controls

0.00/5 (No votes)
4 Jan 2008 2  
This article shows you how to bring revolutionary UI provided by MS Silverlight to the world of ASP.NET control development with the help of the recently released ASP.NET 3.5 Extensions CTP.

Screenshot - button.jpg

Contents

Introduction

In this article, I'll show you how to create a custom ASP.NET control with new generation UI provided by Microsoft Silverlight. So our objective is to create a custom control similar to standard ASP.NET controls such as Button in functionality, but the presentation will be customized not with a CSS class but with Silverlight. I'll call such controls Silverlight-enabled. The basis for this fantastic concept of ASP.NET control development is the Silverlight control (don't mix it up with the name of the technology) provided by the ASP.NET 3.5 Extension CTP, which was released a few days ago. Also, the Silverlight control may be regarded as an updated version of the Xaml control provided by ASP.NET Futures, but there are some key differences between them which make my samples hard to get to work with ASP.NET Futures, so I strongly recommend you to use ASP.NET 3.5 Extensions.

Architecture of an ASP.NET Silverlight-enabled control

Screenshot - arhitecture.jpg

A Silverlight-enabled control is a custom AJAX-enabled control with integrated ASP.NET 3.5 Extensions Silverlight control within. (If you aren't familiar with the ASP.NET AJAX-enabled control development process, you'd better read about it in the wonderful book "ASP.NET AJAX in Action" before you start working with this article.)

Like an AJAX-enabled control, a Silverlight-enabled control consists of server and client parts which interact with each other through ScriptDesriptors. On the server side, we inherit from the WebControl class, implement the interface IScriptControl to connect our control with its client side part, register our control in ScriptManager, and finally add the Silverlight control to its control collection. But on the client side, unlike an AJAX-enabled control, we inherit from Sys.UI.Silverlight.Control instead of Sys.UI.Control, and the elementID passed to the constructor of the ScriptDescriptor class should be the ClientID of the integrated Silverlight control instead of this.Client.ID.

So as a result, in our ASP.NET page, we will have the following tag:

<MyNamespace:MySilverlightEnabledControl ID="Control1" 
  runat=""server"" SkinUrl="MySkin.xaml" Width="200" 
  Height="100" Click="Control1_Clicked" />

As you can see from this sample, the Silverlight-enabled control tag looks like a standard ASP.NET control, but instead of the CssClass property, we have the SkinUrl.

To show this ASP.NET control development concept in practice, I've chosen the Button control. Its functionality and properties (such as width, height, text, and click event, which can be handled by the ASP.NET engine) will be similar to the standard ASP.NET Button control, but its UI will be customized by the Silverlight skin.

Screenshot - button.jpg

Note: In this article, I won't show you how to create XAML skin (you will find two in the demo project). All you need to know is that it has a pulsation animation (PulseStoryBoard), a root element (RootCanvas), and a text field (TextBlock). So your custom skins must include these elements with such names. Their names will be used during the initialization process.

Creating a custom ASP.NET Silverlight-enabled button control

First of all, we should create an ASP.NET server control project and add a Web.Extensions 3.6.0.0 reference.

Screenshot - addproject.jpg

As I said earlier, there is a client and server part of our control. Let's start creating the server part step by step:

  1. Create a .cs file with an empty class Button with the following namespaces:
  2. using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.SilverlightControls;
    
    namespace SilverlightUIControls {
    
        public class Button: WebControl, IPostBackEventHandler, IScriptControl
        {
            /// <summary>
            /// Constructor for Button where we specify root element for our control
            /// 
            public Button() : base(HtmlTextWriterTag.Div)
            {
                
            }
        }
  3. Now we should add the necessary properties to our control to let the user customize the design for his needs.
  4. /// <summary>
    /// Source of xaml file which is used for skinning of our control
    /// 
    public string SkinUrl
    {
        get { return (string)ViewState["SkinUrl"]; }
        set { ViewState["SkinUrl"] = value; }
    }
    
    /// <summary>
    /// Width of our button control
    /// ASP.NET WebControl has its own width property, but we need to overload
    /// 'cause its format is unsuitable in our case(e.g 100px)
    /// 
    public new int Width
    {
        get { return (int)ViewState["Width"]; }
        set { ViewState["Width"] = value; }
    }
    
    /// <summary>
    /// Height of our button control
    /// 
    public new int Height
    {
        get { return (int)ViewState["Height"]; }
        set { ViewState["Height"] = value; }
    }
    
    /// <summary>
    /// Text(Name) within our button control
    /// 
    public string Text
    {
        get {
            if (ViewState["Text"] != null)
            {
                return (string)ViewState["Text"];
            }
            else return "Button";
        }
        set { ViewState["Text"] = value; }
    }
    
    /// <summary>
    /// Font family of the text
    /// 
    public string FontFamily
    {
        get
        {
            if (ViewState["FontFamily"] != null)
            {
                return (string)ViewState["FontFamily"];
            }
            else return "Comic Sans MS";
        }
        set { ViewState["FontFamily"] = value; }
    }
    
    /// <summary>
    /// Font size of the text
    /// 
    public int FontSize
    {
        get
        {
            if (ViewState["FontSize"] != null)
            {
                return (int)ViewState["FontSize"];
            }
            else return 20;
        }
        set { ViewState["FontSize"] = value; }
    }
  5. Create a private method where you will initialize ASP.NET 3.5 Extensions Silverlight control (it is just an initialization of the Silverlight plug-in; resizing the setting text, its font family, and size will be done on the client side), and add it to the Controls collection of your custom control. This method should be called during the pre-render stage of the ASP.NET control life cycle.
  6. private void CreateSilverlightControl()
    {
        _silverlightControl = new Silverlight();
        _silverlightControl.Source = SkinUrl;
        _silverlightControl.Width = Width;
        _silverlightControl.Height = Height;
        _silverlightControl.Windowless = true;
        _silverlightControl.ID = this.ID + "Silverlight";
        _silverlightControl.ClientType = "SilverlightUIControls.Button";
        _silverlightControl.PluginBackColor = System.Drawing.Color.Transparent;
    
        Controls.Add(_silverlightControl);
    }
  7. To connect the server and client parts of our control, we should do several things. First of all, we should register our script control at the script manager during the prerender stage, and its script descriptors during the render stage of the control lifecycle. After that, we should implement the IScriptConrol interface to specify a reference to the .js file with the client side part of our control and properties, which need to be sent to the client side for initializing of our Silverlight UI.
  8. protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);
    
        if (_silverlightControl == null)
        {
            CreateSilverlightControl();
        }
    
        ScriptManager manager;
        manager = ScriptManager.GetCurrent(this.Page);
    
        if (manager == null)
            throw new InvalidOperationException(
                        "A ScriptManager is required on the page.");
    
        manager.RegisterScriptControl(this);
    }
    
    protected override void Render(HtmlTextWriter writer)
    {
        base.Render(writer);
    
        Page.ClientScript.GetPostBackEventReference(this, "");
        ScriptManager.GetCurrent(this.Page).RegisterScriptDescriptors(this);
    }
    
    public IEnumerable<scriptdescriptor> GetScriptDescriptors()
    {
        ScriptControlDescriptor descriptor = 
           new ScriptControlDescriptor("SilverlightUIControls.Button", 
                                       _silverlightControl.ClientID);
        descriptor.AddProperty("text", this.Text);
        descriptor.AddProperty("width", this.Width);
        descriptor.AddProperty("height", this.Height);
        descriptor.AddProperty("fontSize", this.FontSize);
        descriptor.AddProperty("fontFamily", this.FontFamily);
        descriptor.AddProperty("buttonId", this.ID);
    
        yield return descriptor;
    }
    
    public IEnumerable<scriptreference> GetScriptReferences()
    {
        yield return new ScriptReference(
            Page.ClientScript.GetWebResourceUrl(typeof(Button), 
            "SilverlightUIControls.Button.js"));
    }
  9. And the last thing we should do on the server side is to implement the IPostBackEventHandler interface to make our button do postback after clicking.
  10. public event EventHandler Click;
    
    public void RaisePostBackEvent(string eventArgument)
    {
        OnClick(new EventArgs());
        
    }
    protected virtual void OnClick(EventArgs e)
    {
        if (Click != null)
            Click(this, e);
    }

So now we are ready to move on to the development of the client side part:

  1. Add a JavaScript file to your project and mark it as an embedded resource.
  2. Screenshot - addjs.jpg

    Screenshot - mark.jpg

  3. Describe this JavaScript file as a WebResource in the AssemblyInfo.cs of your project.
  4. [assembly: WebResource("SilverlightUIControls.Button.js", "text/javascript")]
  5. In the .js file, create a JavaScript class inherited from Sys.UI.Silverlight.Control which will initialize our Silverlight button skin (set the text and its font size and family, resizing, text centering), and handle its events.
  6. Note: There are no classes in JavaScript, so they are simulated by prototypes and functions. The Microsoft AJAX library standardized the process of class simulation in JavaScript. You can read more about this process in the book I've mentioned at the introduction to my article.

  7. Initializing: all you need to know about this step you will find in the code comments.
  8. _initializeFields: function(slhost) 
    {
        var host = slhost.content;
        
        this._rootCanvas = host.findName('RootCanvas');
        this._pulseStoryBoard = host.findName('PulseStoryBoard');
        this._textBlock = host.findName('TextBlock');
               
        this._textBlock.text = this._text;
        this._textBlock.fontFamily = this._fontFamily;
        this._textBlock.fontSize = this._fontSize;  
        
        // before we call xaml resizing function, 
        // we should calculate X and Y resizing coefficients
        // and resize the root canvas
        
        var horizontalResizePercent = parseFloat(this._width) / this._rootCanvas.width;
        var verticalResizePercent = parseFloat(this._height) / this._rootCanvas.height;
    
        this._rootCanvas.width = this._rootCanvas.width * horizontalResizePercent;
        this._rootCanvas.height = this._rootCanvas.height * verticalResizePercent;
        
        this._rootCanvas.setValue("Canvas.Left", 
          this._rootCanvas.getValue("Canvas.Left") * horizontalResizePercent);
        this._rootCanvas.setValue("Canvas.Top", 
          this._rootCanvas.getValue("Canvas.Top") * verticalResizePercent);          
        
        this._Resize(this._rootCanvas, horizontalResizePercent, verticalResizePercent);
        
        this._centerText(this._rootCanvas, this._textBlock);      
    
    },
    
    // function for centering textblock in the canvas
    
    _centerText: function (canvas, textBlock)
    {
        var left = (canvas.Width - textBlock.ActualWidth) / 2;
        var top = (canvas.Height - textBlock.ActualHeight) / 2;
        textBlock.SetValue("Canvas.Left", left);
        textBlock.SetValue("Canvas.Top", top);   
    },
    
    // recursive function for xaml resizing
        
    _Resize: function (root, horizontalResizePercent, verticalResizePercent)
    {
        for (var i = 0; i < root.children.count - 1; i++)
        {
    
            var child = root.children.getItem(i);
            if (child.toString() != 'TextBox')
            {
                child.width = child.width * horizontalResizePercent;
                child.height = child.height * verticalResizePercent;
                
                child.setValue("Canvas.Left", 
                  child.getValue("Canvas.Left") * horizontalResizePercent);
                child.setValue("Canvas.Top", 
                  child.getValue("Canvas.Top") * verticalResizePercent);  
                              
                if (child.toString() == 'Rectangle')
                {               
                    child.RadiusX = child.RadiusX * horizontalResizePercent;
                    child.RadiusY = child.RadiusY * verticalResizePercent;
                    if (horizontalResizePercent > verticalResizePercent)
                    {
                        child.strokeThickness = 
                          child.strokeThickness * horizontalResizePercent;
                    }
                    else
                    {
                        child.strokeThickness = 
                          child.strokeThickness * verticalResizePercent;                    
                    } 
                }
                
                if (child.toString() == 'Canvas')
                {
                    this._Resize(child,horizontalResizePercent, 
                                 verticalResizePercent);
                }
            }
        }           
    },
  9. The last thing we should do is handle the events. All events are invoked by the root canvas of our skin. Here are its handlers:
    • _onButtonMouseEnter and _onButtonMouseLeave handlers are used to start and stop button pulsation animation.
    • _onButtonClicked calls the __doPostBack function to tell the ASP.NET engine to invoke the button click event. __doPostBack needs the control ID, that's why we send it to the client side through the script descriptor.
    _onButtonMouseEnter: function(sender, e)
    {
        this._pulseStoryBoard.begin(); 
    },    
    _onButtonMouseLeave: function(sender, e)
    {
        this._pulseStoryBoard.stop(); 
    },
    _onButtonClick: function(sender, e)
    {    
        __doPostBack(this._buttonId,'');
    },

Software requirements

  1. Visual Studio 2008
  2. ASP.NET 3.5 Extensions CTP

Conclusion

I hope that you enjoyed reading my article and now you are ready to create your own controls. Using this concept of control development, you can create amazing grids, menus, and other complex controls too. There is also a way to use managed code instead of JavaScript, and I'll certainly write about it in my future articles.

If you haven't understood something, write about it in the comments section and I'll be glad to answer your questions. And don't forget to vote for the article if you find it useful. ;-)

References

  1. Nikhil Kothari "Developing ASP.NET AJAX Controls with Silverlight", MIX07 video.
  2. Manning, Allesandro Gallo, ASP.NET AJAX in Action.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here