Source code location: ./weather/weather.cs

using System;
using System.ComponentModel;
using System.Web.UI;

namespace anrControls
{
    #region Enumerations
    /// <summary>
    /// Specifies the type of <see cref="Weather.Units" />.
    /// </summary>
    public enum WeatherUnits 
    { 
        /// <summary>
        /// Standard units (Fahrenheit, inches, miles, etc).
        /// </summary>
        Standard, 

        /// <summary>
        /// Metric units (Celsius, millimeters, kilometers, etc).
        /// </summary>
        Metric 
    };

    /// <summary>
    /// Specifies the type of <see cref="Weather.IconSize" />.
    /// </summary>
    /// <remarks>The images provided by Weather.com are PNGs.</remarks>
    public enum WeatherIconSize { 
        /// <summary>
        /// Small-size icons, 32x32 pixels.
        /// </summary>
        Small,
        /// <summary>
        /// Medium-size icons, 64x64 pixels.
        /// </summary>
        Medium, 
        /// <summary>
        /// Large-size icons, 128x128 pixels.
        /// </summary>
        Large 
    }; 
    #endregion

	/// <summary>
	/// The Weather class is an ASP.NET custom server control which displays up-to-date
	/// information about weather conditions at a specified location. It has no design-time rendering.
	/// </summary>
	/// <remarks>The control is powered by Weather.com services. You need to obtain a license key and a
	/// partner ID from Weather.com for FREE to use their services. Go to 
	/// <a href="http://registration.weather.com/registration/xmloap/step1">Weather.com</a>
    /// and apply. At the end of the registration process you will receive an e-mail message containing 
    /// your <see cref="Weather.PartnerID">Partner ID</see>, unique <see cref="Weather.LicenseKey">License Key</see>.
	/// </remarks>
	/// <example>
	/// This sample shows how to declare a weather control and display local time, temperature,
	/// wind and pressure information, as well as an icon.<br/>
	/// <strong>Note:</strong> you need to obtain your
	/// own <see cref="Weather.PartnerID">Partner ID</see> and <see cref="Weather.LicenseKey">License Key</see> to run
	/// this sample.
	/// <code>
    /// &lt;%@ Register TagPrefix="anr" Namespace="anrControls" Assembly="anrControls.Weather" %&gt;
    ///
    /// &lt;h4&gt;Local weather:&lt;/h4&gt;
    ///
    /// &lt;anr:Weather id="Weather2" runat="server" 
    ///              PartnerID="xxxxxxxxxx" 
    ///              LicenseKey="xxxxxxxxxxxxxxxx" 
    ///              LocationID="USNY1348"
    ///              IconSize="Medium"&gt;
    ///    
    ///  &lt;WeatherTemplate&gt;
    ///    &lt;p&gt;&lt;%# Container.LocalTime %&gt;&lt;/p&gt;
    ///    &lt;p&gt;&lt;%# Container.Temperature %&gt;&lt;/p&gt;
    ///    &lt;p&gt;&lt;%# Container.Wind %&gt;&lt;/p&gt;
    ///    &lt;p&gt;&lt;%# Container.Pressure %&gt;&lt;/p&gt;
    ///    &lt;p&gt;&lt;%# Container.IconUrl %&gt;&lt;/p&gt;
    ///  &lt;/WeatherTemplate&gt;
    ///  
    /// &lt;/anr:Weather&gt;
	/// </code>
	/// 
	/// Since the weather server control does not emit any HTML itself you can build
	/// any markup, style it with CSS, make sure it comforms to XHTML specs, etc.
	/// </example>
	[ToolboxData("&lt;{0}:Weather runat=server></{0}:Weather>"),
	    Designer("anrControls.WeatherDesigner"),
	    ParseChildren(true)]
	public class Weather : Control, INamingContainer
	{
	    #region Class members

	    private WeatherData     data = null;
	    private ITemplate       template = null;

	    #endregion

        #region Events

        /// <summary>
        /// This event is raised when the control throws an exception.
        /// </summary>
        /// <remarks>
        /// You might want to subscribe to this event so you have a change to display a friendly
        /// message to an end-user, yet handle the geeky one, with a call stack, in some meaninful
        /// way (such as emailing it).
        /// </remarks>
        /// <example>
        /// The following example demonstrates how to specify and code a handler for the <strong>Error</strong>
        /// event.
        /// <code>
        /// 	public class defaultTest : Page
        ///     {
        ///        protected Weather Weather1;
        ///
        ///        /// &lt;summary&gt;
        ///        /// Handle error(s) from the weather control
        ///        /// &lt;/summary&gt;
        ///        private void WeatherError (object sender, WeatherErrorEventArgs e)
        ///        {
        ///            // Refer to e.OriginalException member to extract the error stack, etc
        ///        }
        ///
        ///        override protected void OnInit(EventArgs e)
        ///        {
        ///            base.OnInit(e);
        ///
        ///            Weather1.Error += new WeatherErrorHandler (WeatherError);
        ///        }
        ///    }
        ///      
        /// </code>
        /// </example>
	    public event WeatherErrorHandler Error;

        /// <exclude />
        protected virtual void OnError (WeatherErrorEventArgs e)
        {
            if (Error != null)
                Error (this, e);
        }
	    #endregion

	    #region Properties

	    /// <summary>
	    /// Gets or sets the location identifier. 
	    /// </summary>
	    /// <remarks>The location identifier is mandatory. You use it for all weather requests.</remarks>
	    /// <value>
	    /// <p>Weather data must be requested for a specific location, so before your application 
	    /// can request weather data it must determine a Location ID.</p>
	    /// <p>If the location is within the United States a valid US zip code may be used as the Location ID. 
	    /// If you are not sure what the zip code is, or if the location is outside of the United States,
	    /// you may run a search at <a href="http://www.aspnetresources.com/tools/locid.aspx">www.AspNetResources.com</a> 
	    /// to determine the correct Location ID.</p>
	    /// </value>
	    [Bindable(true), Category ("License")]
	    public string LocationID
	    {
	        get {
	            return (string)ViewState ["LocID"];
	        }

	        set {
	            ViewState ["LocID"] = value;
	        }
	    }

	    /// <summary>
	    /// Gets or sets your partner identifier.
	    /// </summary>
	    /// <remarks>The partner identifier is mandatory. You use it for all weather requests.</remarks>
	    /// <value>A unique identifier issues by Weather.com.</value>
	    [Bindable(true), Category ("License")]
	    public string PartnerID
	    {
	        get {
	            return (string)ViewState ["PartnerID"];
	        }

	        set {
	            ViewState ["PartnerID"] = value;
	        }
	    }

	    /// <summary>
	    /// Gets or sets the license key you received from Weather.com.
	    /// </summary>
	    /// <remarks>The license key is mandatory.</remarks>
	    /// <value>A unique key issued by Weather.com. <b>Make sure you do not reveal it!</b></value>
	    [Bindable(true), Category ("License")]
	    public string LicenseKey
	    {
	        get {
	            return (string)ViewState ["LicKey"];
	        }

	        set {
	            ViewState ["LicKey"] = value;
	        }
	    }

	    /// <summary>
	    /// Specifies which set of units to use to represent weather data.
	    /// </summary>
	    /// <value>One of the <see cref="WeatherUnits"/> enumeration values. The default is <b>Standard</b>.</value>
	    [Bindable(true), Category("Appearance"), DefaultValue (WeatherUnits.Standard)]
	    public WeatherUnits Units
	    {
	        set  {
	            ViewState ["Units"] = value;
	        }

	        get {
	            if (ViewState ["Units"] == null)
	                return WeatherUnits.Standard;
	            else
	                return (WeatherUnits) ViewState ["Units"];
	        }
	    }

	    /// <summary>
	    /// Gets or sets the location of weather icons in the application tree.
	    /// </summary>
	    /// <value>An existing folder in your application tree. The default is <b>ccimages</b>.</value>
	    [Bindable(true), Category("Appearance"), DefaultValue("ccimages")]
	    public string IconLocation
	    {
	        get {
	            if (ViewState ["IconLocation"] == null)
	                return "ccimages";
	            else
	                return (string)ViewState ["IconLocation"];
	        }

	        set {
	            ViewState ["IconLocation"] = value;
	        }
	    }

	    /// <summary>
	    /// Specifies the size of weather icons.
	    /// </summary>
	    /// <value>One of the <see cref="WeatherIconSize"/> enumeration values. 
	    /// The default is <b>Medium</b>, i.e. 32x32 pixel icons.</value>
	    [Bindable(true), Category("Appearance"), DefaultValue (WeatherIconSize.Medium)]
	    public WeatherIconSize IconSize
	    {
	        set { 
	            ViewState ["IconSize"] = value; 
	        }

	        get {
	            if (ViewState ["IconSize"] == null)
	                return WeatherIconSize.Medium;
	            else
	            {
	                return (WeatherIconSize) ViewState ["IconSize"];
	            }
	        }

	    }

	    /// <summary>
	    /// You define your own layout in a &lt;WeatherTemplate&gt; element.
	    /// </summary>
	    /// <remarks>The content of this template is created within an instance of 
	    /// <see cref="Weather.WeatherData"/>.
	    /// </remarks>
	    [Browsable(false), 
	        DefaultValue(null), 
	        TemplateContainer(typeof (WeatherData)), 
	        PersistenceMode(PersistenceMode.InnerProperty)]
	    public virtual ITemplate WeatherTemplate
	    {
	        get { return template; }
	        set { template = value; } 
	    }

	    /// <summary>
	    /// Private member which represents information about current weather conditions.
	    /// </summary>
	    [Browsable(false),
	        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
	        ]
	    protected WeatherData WeatherData
	    {
	        get {
	            this.EnsureChildControls ();
	            return data;
	        }
	    }

	    /// <summary>
	    /// Not intended for developer use.
	    /// </summary>
	    public override ControlCollection Controls
	    {
	        get
	        {
	            this.EnsureChildControls();
	            return base.Controls;
	        }
	    }

	    #endregion

	    #region Overridden Control Members
        /// <summary>
        /// Notifies server controls that use composition-based implementation to 
        /// create any child controls they contain in preparation for posting back or rendering.
        /// </summary>
        protected override void CreateChildControls()
        {
            Controls.Clear();

            // --------------------------------------------------------------
            // Make sure all mandatory properties were set
            // --------------------------------------------------------------
            if (this.LocationID == null)
                throw new MissingMemberException ("Please set a correct LocationID");

            if (this.PartnerID == null)
                throw new MissingMemberException ("Please set a correct PartnerID");

            if (this.LicenseKey == null)
                throw new MissingMemberException ("Please set a correct LicenseKey");

            // ----------------------------------------------------------------
            // Load and make available weather data
            // ----------------------------------------------------------------
            try 
            {
                this.data = new WeatherData (this.LocationID, this.PartnerID, this.LicenseKey, 
                                             this.Units, this.IconSize, this.IconLocation);

                ITemplate template = this.WeatherTemplate;
                if (template == null)
                    throw new InvalidOperationException ("The Weather control should contain a WeatherTemplate element");


                template.InstantiateIn (this.data);
                data.DataBind ();
          
                Controls.Add (this.data);
            }
            catch (Exception ex)
            {
                // We're not going to display a geeky message to the user, but will simply say
                // we're unable to read weather conditions.
                Controls.Add (new LiteralControl ("Weather forecast is not available at this time."));

                // Package up the exception and raise our event.
                // NOTRE: We can't just re-throw the exception or let it propagate because the developer
                // won't have a change to trap it. That's why an Error event is made evailable so you
                // can wire it in a designer.
                OnError (new WeatherErrorEventArgs (ex));
            }

            // Indicate that we've already created child controls
            ChildControlsCreated = true;
        }

        /// <exclude />
        public override void DataBind()
        {
            base.OnDataBinding (EventArgs.Empty);

            CreateChildControls ();
            this.ChildControlsCreated = true;

            base.DataBind ();
        }

        #endregion
	}
}