ASP.NET server controls are great. They allow for building reusable components, and are easy to distribute because they are entirely contained in an assembly (no .ascx files needed). One of the drawbacks of server controls is the lack of a designer/HTML editor for creating your layout. The HTML is generated in the code and generally looks messy. It can also be a headache to tweak/debug because your controls are being added in a mess of LiteralControls for the HTML.

Simplify the construction of HTML inside ASP.NET server controls using ControlInjector

 

Introduction:

ASP.NET server controls are great. They allow for building reusable components, and are easy to distribute because they are entirely contained in an assembly (no .ascx files needed). One of the drawbacks of server controls is the lack of a designer/HTML editor for creating your layout. The HTML is generated in the code and generally looks messy. It can also be a headache to tweak/debug because your controls are being added in a mess of LiteralControls for the HTML.

Approach:

After creating a server control with some very complex HTML, I realized that the method of mixing HTML literal content with my private controls is not ideal. What if there was a way to accomplish something similar to String.Format()? We could parse a string and look for blocks like {xxx} and inject our control there... all HTML content before and after those blocks would be added to the Control collection as LiteralContent.

Doing this will allow us to separate layout from content, and we can easily use a designer to construct and view the HTML, then copy it into a string to place in the code.
The Implementation

The HTML can be stored as a single string, and with C#, we can use string literals @"string" to allow for multi-line strings without any concatenation code. It will be relatively simple to cut-n-paste from another designer. We will need to parse the HTML string and have placeholders for each control we want to inject. I chose to use "{controlID}" as the placeholder text. Some might argue that a use of {0} {1} would fit the String.Format pattern a little better, but I chose to be more verbose here because clarity of code is what I am going for here.

Our private controls will need to be contained inside of a hashtable. We can use the control ID as the key, so it will be easy to work with.


Update -- I decided to follow some advice and keep the Hashtable internal to the class. The ControlInjector will no longer hold static methods, so you will need to instantiate it before use. I like this design a lot better.

We will parse the HTML string, find the indices of the { and }, add the text to the left as a literal control, replace the {xxx} text with the actual control, and continue. The end result will be one single control that the client code can add to the control tree.
The Code

The ControlInjector class contains three useful methods:

public void AddControl(Control c)
{
//make sure that the ID is valid
if(c.ID == null || c.ID == "")
throw new ArgumentException("Control" +
c.ToString() + " must have a valid ID", "c");

//add it to our private hashtable
_hashControls.Add(c.ID, c);
}

public Control Inject(string html)
{
//create a container for our controls
PlaceHolder phContent = new PlaceHolder();

//loop while we still have content to render
while( html.Length > 0 )
{
//find the next { character
int iLBrace = html.IndexOf("{", 0);
int iRBrace = html.IndexOf("}", 0);

//make sure the order is correct
if( iRBrace < iLBrace )
throw new FormatException("your html string " +
"is not properly formatted -- a } was found before a {");

//process control (if any)
if( iLBrace >= 0 )
{
//add the literal content first
phContent.Controls.Add( new
LiteralControl( html.Substring(0, iLBrace) ) );

//get the inner content of the { }
string controlID = html.Substring(iLBrace+1, iRBrace-iLBrace-1);

//make sure that the control exists in the hash table
if( ! _hashControls.ContainsKey(controlID) )
throw new ArgumentNullException("hashControls[" +
controlID + "]",
"hashControls must contain the control to add");

//add the control
phContent.Controls.Add(_hashControls[controlID] as Control);

//strip the part of the string that we have dealt with
html = html.Substring(iRBrace + 1);
}
else
{
//not more controls, just add the rest
//of the html and break out of the loop
phContent.Controls.Add( new LiteralControl( html ) );
break;
}
}

return phContent;
}


Update -- After using this utility for a while, I realized quickly that I was still not getting any kind of designer support, and for large, complex controls, it was still a bit unreadable. I wrote a method to allow for getting the HTML string from an embedded resource. This way I can create an HTML page inside my class library, set it as an embedded resource and retain HTML designer support. Still, the designer support is limited - you cannot design the individual controls, only the layout. Another drawback is that I cannot edit the inner HTML of one of the controls, I must use separate HTML files for this.

The best part about this is that the HTML file is included in your DLL, so you do not introduce any file dependencies at all, which is ideal.

public Control InjectFromResource(string resourceName)
{
//get assembly from the code that called us
System.Reflection.Assembly dll =
System.Reflection.Assembly.GetCallingAssembly();

//make sure resource exist in the assembly
string[] resourceNames = dll.GetManifestResourceNames();
bool found=false;
foreach(string rName in resourceNames)
{
if(rName == resourceName)
{
found = true;
break; //no need to continue searching
}
}

if(!found)
throw new ArgumentException("The resource did not exist" +
" in this assembly. Resource name:" +
resourceName, "resourceName");

//get the resource from the dll
System.IO.Stream streamResource =
dll.GetManifestResourceStream(resourceName);

//read the resource data into a byte array, then convert to string
byte[] resourceData = new byte[streamResource.Length];
streamResource.Read(resourceData, 0, (int)streamResource.Length);

System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
string html = enc.GetString(resourceData, 0, resourceData.Length);

//now pass this string to the original method to parse
return Inject(html);
}



Nothing really exciting here, the code uses reflection to get the current assembly object, then checks to see if the resource exists. The only requirement is that the caller assembly contains the embedded resource and they supply the fully qualified name of the resource.



The Advantage:

What does this code give you? Well, here's a snippet from a server control with mixed HTML and controls:

protected override void CreateChildControls()
{
base.CreateChildControls ();

label1 = new Label();
label1.ID = "label1";
label1.Text = "i am a label!";

textBox1 = new TextBox();
textBox1.ID = "textBox1";
textBox1.Text = "i am a textbox!";

dropdown1 = new DropDownList();
dropdown1.ID = "dropdown1";
dropdown1.Items.Add ( new ListItem("i am a dropdown", "") );


string html = @"
<table border=2>
<tr>
<td align=center colspan=2>Title!</td>
</tr>
<tr>
<td>label</td>
<td>{label1}</td>
</tr>
<tr>
<td>textBox</td>
<td>{textBox1}<td>
</tr>
<tr>
<td>dropdown1</td>
<td>{dropdown1}</td>
</tr>

</table>";


using(ControlInjector injector = new ControlInjector())
{
injector.AddControl(dropdown1);
injector.AddControl(textBox1);
injector.AddControl(label1);
this.Controls.Add( injector.Inject(html) );
}
}


To demonstrate the resource example, go to Visual Studio and add an HTML page to your webcontrols project. Design the above HTML using the Visual Studio HTML editor. Make sure and right-click the file in Solution Explorer and choose Embedded Resource. If you don't do this, the file will not be found at runtime. Once you have done this, the above method can be reduced to the following:

protected override void CreateChildControls()
{
base.CreateChildControls ();

label1 = new Label();
label1.ID = "label1";
label1.Text = "i am a label!";

textBox1 = new TextBox();
textBox1.ID = "textBox1";
textBox1.Text = "i am a textbox!";

dropdown1 = new DropDownList();
dropdown1.ID = "dropdown1";
dropdown1.Items.Add ( new ListItem("i am a dropdown", "") );

using(ControlInjector injector = new ControlInjector())
{
injector.AddControl(dropdown1);
injector.AddControl(textBox1);
injector.AddControl(label1);
this.Controls.Add( injector.InjectFromResource("MyWeb" +
"Controls.MyControl.html" ) );
}
}

This will get the HTML from the resource located in the MyWebControls namespace, entitled MyControl.html.

Without the injector, we would have something like this:

Controls.Add( new LiteralControl("<table><tr><td>label1</td><td>") );
Controls.Add( label1 );
Controls.Add( new LiteralControl("</td></tr><tr><td>textbox1</td></tr>") );
Controls.Add( textbox1 );

... you get the idea... multiply this mess by 100 lines of HTML mixed with controls and it's not very easy to see what's going on.
What's next?

This method has very minimal error checking, and that would be the first place I would expand, but it is out of the scope of this article. (I wanted to keep it clean and easy to understand.)

The next thing I would like to add would be the ability to nest content inside of the controls, something like {control1}nested html{/control1}, but that introduces a large level of complexity to the parser, so I haven't decided if the benefit will be worth it.
Conclusion

Hopefully this will be of some use, and I am sure that there are other methods of accomplishing the same task. I welcome any comments or criticisms.
Updates

* June 30, 2005 - I removed the static methods in favor of an instantiated use of the control. This allowed me to contain the hashtable as an internal reference only, making the calling code a lot simpler. I also added a method to read from an embedded resource to get the HTML string. These two additions make the class 100% more readable in my server controls. I hope it helps you as well!

About Subdigital:

Ben Scheirman is a professional web developer providing enterprise level ASP.NET solutions.

He started programming in QBASIC in 1993, learning from examples downloaded online. He migrated to Pascal in 1996, C/C++ ruled his spare time during the late 90's, and for the past 2 years he has been developing using C# & VB.NET.

He enjoys programming in his spare time, especially toying around with Managed DirectX.

Read his blog here