Building ASP.NET AJAX Controls (Pt 7 - Server control implementation)

Links to the other posts in this series

This really is turning into a bit of a marathon - sorry about that. Here's the full source for my implementation of the VEMapControl (without any embedded resources - we'll get to that!):

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

namespace ServerControls
{
    public class VEMapControl : CompositeControl, IScriptControl
    {
        protected Image i = new Image();
        protected Panel p = new Panel();

        public VEMapControl()
        {
            this.Height = 150;
            this.Width = 150;
            this.Style.Add(HtmlTextWriterStyle.Position, "Relative");
            this.Style.Add(HtmlTextWriterStyle.Margin, "5px");
            this.Style.Add(HtmlTextWriterStyle.Padding, "1px");
        }

        protected override HtmlTextWriterTag TagKey
        {
            get { return HtmlTextWriterTag.Div; }
        }

        public string ImageUrl
        {
            get
            {
                EnsureChildControls();
                return i.ImageUrl;
            }
            set
            {
                EnsureChildControls();
                i.ImageUrl = value;
            }
        }

        protected override void CreateChildControls()
        {
            p.ID = this.ID;
            p.Style.Add(HtmlTextWriterStyle.Position, "Relative");
            p.Style.Add(HtmlTextWriterStyle.Width, "80%");
            p.Style.Add(HtmlTextWriterStyle.Height, "80%");
            p.Style.Add(HtmlTextWriterStyle.Margin, "10% 10%");
            p.Style.Add(HtmlTextWriterStyle.Left, "0");
            p.Style.Add(HtmlTextWriterStyle.Top, "0");

            i.Style.Add(HtmlTextWriterStyle.Position, "Absolute");
            i.Style.Add(HtmlTextWriterStyle.Width, "100%");
            i.Style.Add(HtmlTextWriterStyle.Height, "100%");
            i.Style.Add(HtmlTextWriterStyle.Left, "0");
            i.Style.Add(HtmlTextWriterStyle.Top, "0");

            this.Controls.Add(p);
            this.Controls.Add(i);
        }

        public double Lat
        {
            get { return (double)(ViewState["Lat"] ?? 0.0); }
            set { ViewState["Lat"] = value; }
        }

        public double Lon
        {
            get { return (double)(ViewState["Lon"] ?? 0.0); }
            set { ViewState["Lon"] = value; }
        }

        public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
        {
            ScriptControlDescriptor scd =
                new ScriptControlDescriptor("ServerControls.VEMapControl", this.ClientID);

            scd.AddProperty("lat", this.Lat);
            scd.AddProperty("lon", this.Lon);

            yield return scd;
        }

        public IEnumerable<ScriptReference> GetScriptReferences()
        {
            yield return new ScriptReference("~/JavaScript/VEMapControl.js");
            yield return new ScriptReference("https://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6");
        }

        #region Extra Stuff Because We Implement IScriptControl / IExtenderControl
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            ScriptManager sm = ScriptManager.GetCurrent(this.Page);

            if (sm == null)
            {
                throw new InvalidOperationException("A ScriptManager is required");
            }

            sm.RegisterScriptControl(this);
        }

        protected override void Render(HtmlTextWriter writer)
        {
            base.Render(writer);

            ScriptManager.GetCurrent(this.Page).RegisterScriptDescriptors(this);
        }
        #endregion
    }
}

Let's take a look at a few aspects:

  • I set a default width and height and also set some styles for the rendered control
  • I want the control to render as a <div> as opposed to the default <span> so I override the TagKey property
  • The ImageUrl property I can just delegate to the Image control I'm rendering as part of my control
  • In CreateChildControls() I render a Panel and an Image setting some CSS properties to make them look the way I want
  • Lat and Lon are properties on my server control mapped to properties on the client control. The server control properties are backed by ViewState and return 0 if the ViewState value is null
  • GetScriptDescriptors() should look familiar. Not the mapping of client properties to server properties
  • GetScriptReferences() should also look familiar
  • The detail of the implementation of OnPreRender() / Render() is slightly different here but it essentially amounts to the same thing as the implementation in my previous post

The above in conjunction with the appropriate VEMapControl.js script allows me to use it in a page:

 <%@ Page Language="C#" %>

<%@ Register Assembly="ServerControls" Namespace="ServerControls" TagPrefix="cc1" %>


<html xmlns="https://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    
            <asp:ScriptManager ID="ScriptManager1" runat="server">
            </asp:ScriptManager>
            
            <cc1:VEMapControl ID="VEMapControl1" runat="server"
            Lat="51.52514" Lon="-0.733681" ImageUrl="~/Photos/Image1.png" Height="250" Width="250" />
    
    </div>
    </form>
</body>
</html>

Which generates the following result:

Next we'll look at embedding resources in the assembly so we're not referencing "loose" scripts on the server. We can also include our own custom pushpin icon in this fashion.

Technorati Tags: asp.net,visual studio,windows live,virtual earth