ASP.NET's extensive server control library makes building dynamic forms relatively easy. It's object model keeps HTML encapsulated allowing developers to focus on a higher level in the design process. On the surface, ASP.NET MVC Framework's server control free environment seemingly negates these advantages forcing developers to get down in the mud with handwritten HTML in it's Views.  But don't give up hope on that MVC powered wufoo knock-off just yet.

 

Builder to the Rescue

Our design goal is pretty clear here.  We want a way to programmatically create HTML so that we can store form layout information in a database table or XML file and then use a little Reflection to build the form at runtime.

We want to:

  1. Leverage pre-existing classes that generate HTML (ASP.NET Server side controls, third party controls, etc) whenever possible.
  2. Have the flexibility to get dirty and write our own classes that generate HTML if needed.
  3. Classes in both cases need to share the same interface so client code isn't bogged down with the details of how controls get rendered.

So how do we go about accomplishing these goals?  I'd suggest a look at the builder pattern.

Separate the construction of a complex object from its representation so that the same construction process can create different representations.

 

Builder encapsulates the process of construction and delegates details to derived classes.

Let's see how its works:

[code:c#]

namespace DynamicForm.Model
{
    //Encapsulates construction of control..delegates details to derived classes.
    public abstract class ControlBuilder
    {
        public string BuildControl()
        {
            this.OpenTag();
            this.SetAttributes();
            this.SetContents();
            this.CloseTag();
            return this.RenderHTML();
        }

        public abstract void OpenTag();
        public abstract void SetAttributes();
        public abstract void SetContents();
        public abstract void CloseTag();
        public abstract string RenderHTML();
    }
}

[/code]

ControlBuilder is the base class that encapsulates the process for construction of the control.

[code:c#]

namespace DynamicForm.Model
{
    public abstract class ThirdPartyControlBuilder:ControlBuilder
    {
        protected HTMLControl formControl;
        private HTMLControl labelControl;

        public HTMLControl Form
        {
            get { return formControl; }
        }

        public HTMLControl Label
        {
            get { return labelControl; }
        }

        public ThirdPartyControlBuilder(string labelName)
        {
            Label label = new Label();
            label.Text = labelName;
            labelControl = new HTMLControl(label);
        }

        public override string RenderHTML()
        {
            return labelControl.RenderHTML() + ": " + formControl.RenderHTML();
        }

        // Simplify interface for control builders.  Third Party Server Side Control Builders do not have to worry about
        // opening and closing tags because the control handles that for them. We'll simplify the interface here
        // to make it easier for developers to recognize that all they have to do is set the formControl
        public override void OpenTag()
        {
            this.SetControl();

        }

        public override void CloseTag()
        {
        }

        public override void SetContents()
        {
        }

        public override void SetAttributes()
        {
            this.SetProperties();
        }

        protected abstract void SetProperties();

        protected abstract void SetControl();
    }
}

[/code]

ThirdPartyControlBuilder inherits from ControlBuilder.  It provides a facade for its derived classes so that they operate with an interface that makes sense for leveraging ASP.NET server controls and third party controls.

[code:c#]

namespace DynamicForm.Model
{
    public class HTMLControl
    {
        private WebControl control;

        public HTMLControl(WebControl control)
        {
            this.control = control;
        }

        public string RenderHTML()
        {
            HtmlTextWriter htmlWriter = new HtmlTextWriter(new StringWriter());
            this.control.RenderControl(htmlWriter);
            return htmlWriter.InnerWriter.ToString();
        }
    }
}

[/code]

HTMLControl accepts a ASP.NET Server Control in its constructor and uses WebControl's RenderControl method to output HTML.  This is how we get the HTML out of server controls.

[code:c#]

namespace DynamicForm.Model
{
    public class DynamicForm
    {
        private IList<ControlBuilder> controls = new List<ControlBuilder>();

        public void AddControl(ControlBuilder control)
        {
            controls.Add(control);
        }

        public string Display()
        {
            string html = string.Empty;

            foreach (ControlBuilder builder in controls)
            {
                html += builder.BuildControl();
                html += "<br /><br />";
            }

            return html;
        }
    }
}

[/code]

The DynamicForm class contains a list of Controls.  It deals with these controls in a unified way regardless of how the HTML is created.

Now let's look at some control classes.

[code:c#]

namespace DynamicForm.Model
{
    public class TextBoxBuilder : ThirdPartyControlBuilder
    {
        public TextBoxBuilder(string labelName) : base(labelName) { }

        protected override void SetProperties()
        {
        }

        protected override void SetControl()
        {
            this.formControl = new HTMLControl(new TextBox());
        }
    }
}

[/code]

TextBoxBuilder inherits from ThirdPartyControlBuilder.  It uses ASP.NET's textbox server side control.

[code:c#]

namespace DynamicForm.Model
{
    public class GridViewBuilder:ThirdPartyControlBuilder
    {
        private IEnumerable dataSource;
        private GridView gridView; 

        public GridViewBuilder(string labelName, IEnumerable dataSource)
            : base(labelName)
        {
            this.dataSource = dataSource;
            this.gridView = new GridView();
        }

        protected override void SetProperties()
        {
            this.gridView.DataSource = this.dataSource;
            this.gridView.DataBind();
        }

        protected override void SetControl()
        {
            this.formControl = new HTMLControl(this.gridView);
        }
    }
}

[/code]

GridViewBuilder uses ASP.NET's gridview server side control

[code:c#]

namespace DynamicForm.Model
{
    public class ParagraphBlockBuilder:ControlBuilder
    {
        private string html;
        private string paragraph;

        public ParagraphBlockBuilder(string paragraph)
        {
            this.paragraph = paragraph;
        }
        public override void OpenTag()
        {
            this.html += "<p";
        }

        public override void SetAttributes()
        {
            this.html += ">";
        }

        public override void SetContents()
        {
            this.html += this.paragraph;
        }

        public override void CloseTag()
        {
            this.html += "</p>";
        }

        public override string RenderHTML()
        {
            return this.html;
        }
    }
}

[/code]

ParagraphBuilder generates HTML on its own without the use of server controls.

[code:c#]

public ActionResult About()
  {
      DynamicForm form = new DynamicForm();

      string paragraph = "Welcome to my sample form. This is a completely dynamically built form. This is an example of a Paragraph Block.  The Paragraph block uses HTML. <br />";
      paragraph += "You will also find examples of the TextBox and of the CheckBox below. The TextBox and CheckBox controls lets the .NET framework generate HTML. <br /> This system is configurable.  And so the possibilities are endless.";

      form.AddControl(new ParagraphBlockBuilder(paragraph));
      form.AddControl(new TextBoxBuilder("Test Label For TextBox"));
      form.AddControl(new TextBoxBuilder("Test Label For TextBox1"));
      List<ControlItem> testItems = new List<ControlItem>();
      testItems.Add(new ControlItem("test", "test"));
      testItems.Add(new ControlItem("test1", "test1"));
      testItems.Add(new ControlItem("test2", "test2"));

      List<ControlItem> policyItems = new List<ControlItem>();
      policyItems.Add(new ControlItem("Policy", "policy"));
      policyItems.Add(new ControlItem("Quote", "quote"));

      form.AddControl(new CheckBoxBuilder("Test", testItems));
      form.AddControl(new CheckBoxBuilder("Policy", policyItems));

      IList<string> data = new List<string>();
      data.Add("sample_file.xls");
      data.Add("rating_work_sheet_file.xls");
      data.Add("sample_pdf.pdf");
      data.Add("policy_pdf.pdf");
      data.Add("invoice_pdf.pdf");

      form.AddControl(new GridViewBuilder("Files", data));

      return View(form);
  }

[/code]

Our About Controller uses our DynamicForm object to create a form.  Of course, you'll probably never use Dynamic Form like this (it defeats the purpose).  Ideally, you'd want to store which controls are used on the page somewhere (a database or XML file) then generate the DynamicForm at runtime.

Let's look at the view:

[code:c#]

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="About.aspx.cs" Inherits="DynameForm.UI.Views.Home.About" %>

<asp:Content ID="aboutContent" ContentPlaceHolderID="MainContent" runat="server">
    <h2>About Us</h2>
    <p>
        <%= ViewData.Model.Display() %>
    </p>
</asp:Content>

[/code]

There you have it:

screenshot