[Back]

/// Denis Bauer, http://www.denisbauer.com
/// (c) 2002-2006 by Denis Bauer. All rights reserved.

using System;
using System.Collections;
using System.Reflection;
using System.Web;
using System.Web.UI;
using System.Web.UI.Design;
using System.Web.UI.Design.WebControls;
using System.Web.UI.WebControls;
using System.ComponentModel;

namespace AdefWebserver.Modules.SimpleSurvey
{
    /// <summary>
    /// DynamicControlsPlaceholder solves the problem that dynamically added controls are not automatically recreated on subsequent requests.
    /// The control uses the ViewState to store the types of the child controls recursively and recreates them automatically.
    /// 
    /// Please note that property values that are set before "TrackViewState" is called (usually in Controls.Add) are not persisted
    /// </summary>
    [ControlBuilder(typeof(System.Web.UI.WebControls.PlaceHolderControlBuilder)),
    Designer("System.Web.UI.Design.ControlDesigner"),
    DefaultProperty("ID"),
    ToolboxData("<{0}:DynamicControlsPlaceholder runat=server></{0}:DynamicControlsPlaceholder>")]
    public class DynamicControlsPlaceholder : PlaceHolder
    {
        #region custom events
        /// <summary>
        /// Occurs when a control has been restored from ViewState
        /// </summary>
        public event DynamicControlEventHandler ControlRestored;
        /// <summary>
        /// Occurs when the DynamicControlsPlaceholder is about to restore the child controls from ViewState
        /// </summary>
        public event EventHandler PreRestore;
        /// <summary>
        /// Occurs after the DynamicControlsPlaceholder has restored the child controls from ViewState
        /// </summary>
        public event EventHandler PostRestore;

        /// <summary>
        /// Raises the <see cref="ControlRestored">ControlRestored</see> event.
        /// </summary>
        /// <param name="e">The <see cref="DynamicControlEventArgs">DynamicControlEventArgs</see> object that contains the event data.</param>
        protected virtual void OnControlRestored(DynamicControlEventArgs e)
        {
            if (ControlRestored != null)
                ControlRestored(this, e);
        }

        /// <summary>
        /// Raises the <see cref="PreRestore">PreRestore</see> event.
        /// </summary>
        /// <param name="e">The <see cref="System.EventArgs">EventArgs</see> object that contains the event data.</param>
        protected virtual void OnPreRestore(EventArgs e)
        {
            if (PreRestore != null)
                PreRestore(this, e);
        }

        /// <summary>
        /// Raises the <see cref="PostRestore">PostRestore</see> event.
        /// </summary>
        /// <param name="e">The <see cref="System.EventArgs">EventArgs</see> object that contains the event data.</param>
        protected virtual void OnPostRestore(EventArgs e)
        {
            if (PostRestore != null)
                PostRestore(this, e);
        }
        #endregion custom events

        #region custom propterties
        /// <summary>
        /// Specifies whether Controls without IDs shall be persisted or if an exception shall be thrown
        /// </summary>
        [DefaultValue(HandleDynamicControls.DontPersist)]
        public HandleDynamicControls ControlsWithoutIDs
        {
            get
            {
                if (ViewState["ControlsWithoutIDs"] == null)
                    return HandleDynamicControls.DontPersist;
                else
                    return (HandleDynamicControls)ViewState["ControlsWithoutIDs"];
            }
            set { ViewState["ControlsWithoutIDs"] = value; }
        }
        #endregion custom propterties

        #region ViewState management
        /// <summary>
        /// Recreates all dynamically added child controls of the Placeholder and then calls the default 
        /// LoadViewState mechanism
        /// </summary>
        /// <param name="savedState">Array of objects that contains the child structure in the first item, 
        /// and the base ViewState in the second item</param>
        protected override void LoadViewState(object savedState)
        {
            object[] viewState = (object[])savedState;

            //Raise PreRestore event
            OnPreRestore(EventArgs.Empty);

            //recreate the child controls recursively
            Pair persistInfo = (Pair)viewState[0];
            foreach (Pair pair in (ArrayList)persistInfo.Second)
            {
                RestoreChildStructure(pair, this);
            }

            //Raise PostRestore event
            OnPostRestore(EventArgs.Empty);

            base.LoadViewState(viewState[1]);
        }

        /// <summary>
        /// Walks recursively through all child controls and stores their type in ViewState and then calls the default 
        /// SaveViewState mechanism
        /// </summary>
        /// <returns>Array of objects that contains the child structure in the first item, 
        /// and the base ViewState in the second item</returns>
        protected override object SaveViewState()
        {
            if (HttpContext.Current == null)
                return null;

            object[] viewState = new object[2];
            viewState[0] = PersistChildStructure(this, "C");
            viewState[1] = base.SaveViewState();
            return viewState;
        }

        /// <summary>
        /// Recreates a single control and recursively calls itself for all child controls
        /// </summary>
        /// <param name="persistInfo">A pair that contains the controls persisted information in the first property,
        /// and an ArrayList with the child's persisted information in the second property</param>
        /// <param name="parent">The parent control to which Controls collection it is added</param>
        private void RestoreChildStructure(Pair persistInfo, Control parent)
        {
            Control control;

            string[] persistedString = persistInfo.First.ToString().Split(';');

            string[] typeName = persistedString[1].Split(':');
            switch (typeName[0])
            {
                //restore the UserControl by calling Page.LoadControl
                case "UC":
                    //when running under ASP.NET >= 2.0 load the user control based on its type
                    if (Environment.Version.Major > 1)
                    {
                        Type ucType = Type.GetType(typeName[1], true, true);
                        try
                        {
                            //calling the overload Page.LoadControl(ucType, null) via reflection (which is not very nice but necessary when compiled against 1.0)
                            MethodInfo mi = typeof(Page).GetMethod("LoadControl", new Type[2] { typeof(Type), typeof(object[]) });
                            control = (Control)mi.Invoke(this.Page, new object[2] { ucType, null });
                        }
                        catch (Exception e)
                        {
                            throw new ArgumentException(String.Format("The type '{0}' cannot be recreated from ViewState", ucType.ToString()), e);
                        }
                    }
                    else //in ASP.NET 1.0/1.1 load the user control based on the file
                    {
                        //recreate the Filename from the Typename
                        string ucFilename = typeName[2] + "/" + typeName[1].Split('.')[1].Replace("_", ".");
                        if (!System.IO.File.Exists(Context.Server.MapPath(ucFilename))) //original filename must have contained a "_"
                        {
                            string filePattern = typeName[1].Split('.')[1].Replace("_", "*");
                            //due to some strange behaviour of windows you can't use the '?' wildcard to find a '.'... We'll use the * instead,
                            string[] files = System.IO.Directory.GetFiles(Context.Server.MapPath(typeName[2]), filePattern);
                            if (files.Length == 1)
                                ucFilename = typeName[2] + "/" + System.IO.Path.GetFileName(files[0]);
                            else
                                throw new ApplicationException(string.Format("Could not load UserControl '{2}' from VRoot '{0}' with PersistenceString: {1}. Found {3} files that match the pattern {4}",
                                    this.Context.Request.ApplicationPath, persistedString[1], ucFilename, files.Length.ToString(), Context.Server.MapPath(typeName[2]) + "\\" + filePattern));
                        }
                        control = Page.LoadControl(ucFilename);
                    }
                    break;
                case "C":
                    //create a new instance of the control's type
                    Type type = Type.GetType(typeName[1], true, true);
                    try
                    {
                        control = (Control)Activator.CreateInstance(type);
                    }
                    catch (Exception e)
                    {
                        throw new ArgumentException(String.Format("The type '{0}' cannot be recreated from ViewState", type.ToString()), e);
                    }
                    break;
                default:
                    throw new ArgumentException("Unknown type - cannot recreate from ViewState");
            }

            control.ID = persistedString[2];

            switch (persistedString[0])
            {
                //adding control to "Controls" collection
                case "C":
                    parent.Controls.Add(control);
                    break;
            }

            //Raise OnControlRestoredEvent
            OnControlRestored(new DynamicControlEventArgs(control));

            //recreate all the child controls
            foreach (Pair pair in (ArrayList)persistInfo.Second)
            {
                RestoreChildStructure(pair, control);
            }
        }

        /// <summary>
        /// Saves a single control and recursively calls itself to save all child controls
        /// </summary>
        /// <param name="control">reference to the control</param>
        /// <param name="controlCollectionName">contains an abbreviation to indicate to which control collection the control belongs</param>
        /// <returns>A pair that contains the controls persisted information in the first property,
        /// and an ArrayList with the child's persisted information in the second property</returns>
        private Pair PersistChildStructure(Control control, string controlCollectionName)
        {
            string typeName;
            ArrayList childPersistInfo = new ArrayList();

            //check if the control has an ID
            if (control.ID == null)
            {
                if (ControlsWithoutIDs == HandleDynamicControls.ThrowException)
                    throw new NotSupportedException("DynamicControlsPlaceholder does not support child controls whose ID is not set, as this may have unintended side effects: " + control.GetType().ToString());
                else if (ControlsWithoutIDs == HandleDynamicControls.DontPersist)
                    return null;
            }

            if (control is UserControl)
            {
                if (Environment.Version.Major > 1)
                    typeName = "UC:" + control.GetType().AssemblyQualifiedName; //in ASP.NET >= 2.0 save the full type name
                else
                    typeName = "UC:" + ((UserControl)control).GetType().ToString() + ":" + control.TemplateSourceDirectory; //otherwise get the directory
            }
            else
                typeName = "C:" + control.GetType().AssemblyQualifiedName;

            string persistedString = controlCollectionName + ";" + typeName + ";" + control.ID;

            //childs of a UserControl need not be saved as they are recreated on Page.LoadControl, same for CheckBoxList
            if (!(control is UserControl) && !(control is CheckBoxList))
            {
                //saving all child controls from "Controls" collection
                for (int counter = 0; counter < control.Controls.Count; counter++)
                {
                    Control child = control.Controls[counter];
                    Pair pair = PersistChildStructure(child, "C");
                    if (pair != null)
                        childPersistInfo.Add(pair);
                }
            }

            return new Pair(persistedString, childPersistInfo);
        }
        #endregion ViewState management

        #region Render method
        /// <summary>
        /// Renders a copyright box in design mode and calls the base method at runtime
        /// </summary>
        /// <param name="writer"></param>
        protected override void Render(System.Web.UI.HtmlTextWriter writer)
        {
            if (HttpContext.Current == null)
            {
                //removed the following line, because it causes the assembly to require full trust (thanks to Willem Schroers)
                //System.Web.UI.Design.ControlDesigner cd = new System.Web.UI.Design.ControlDesigner(); //create a dummy instance that System.Design.DLL is included as a reference

                string designTimeHTML = "<p style=\"font-family: verdana; color: #FFFF99; font-size: xx-small; border: outset 1px #000000; padding: 10 10 10 10; background-color: #5a7ab8\">" +
                    "<b>DynamicControlsPlaceholder: \"" + this.ID + "\"</b><br>(c) 2002-2006 by Denis Bauer<br>http://www.denisbauer.com</p>";
                writer.Write(designTimeHTML);
            }
            else
                base.Render(writer);
        }
        #endregion Render method
    }

    /// <summary>
    /// Specifies the possibilities if controls shall be persisted or not
    /// </summary>
    public enum HandleDynamicControls
    {
        /// <summary>
        /// DynamicControl shall not be persisted
        /// </summary>
        DontPersist,
        /// <summary>
        /// DynamicControl shall be persisted
        /// </summary>
        Persist,
        /// <summary>
        /// An Exception shall be thrown
        /// </summary>
        ThrowException
    }

    /// <summary>
    /// Represents the method that will handle any DynamicControl event.
    /// </summary>
    [Serializable]
    public delegate void DynamicControlEventHandler(object sender, DynamicControlEventArgs e);

    /// <summary>
    /// Provides data for the ControlRestored event
    /// </summary>
    public class DynamicControlEventArgs : EventArgs
    {
        private Control _dynamicControl;

        /// <summary>
        /// Gets the referenced Control when the event is raised
        /// </summary>
        public Control DynamicControl
        {
            get { return _dynamicControl; }
        }

        /// <summary>
        /// Initializes a new instance of DynamicControlEventArgs class.
        /// </summary>
        /// <param name="dynamicControl">The control that was just restored.</param>
        public DynamicControlEventArgs(Control dynamicControl)
        {
            _dynamicControl = dynamicControl;
        }
    }
}