Visual Studio .NET Design View and Custom Web Controls

This article is intended to give you a practical recipe for creating a simple Web Control and extending Visual Studio with support for the control. The control itself is a basic Country Code drop down list such as you would find on many profile entry pages.

Instead of the cut and paste model of ASP, you are presented, by Visual Studio, with the much more elegant Web Control. The Web Control will allow you to write the Country Code dropdown list once, add it to the Toolbox and include it in any of your pages or applications by simply dragging its icon from the Toolbox to the Design View.

Before we start, download the source code of this article here.


1.1. Preliminaries
Topics: Custom Web Controls, Visual Studio Design View, Visual Studio Toolbox
Level: Basic-Intermediate
Language: ASP.NET, C# (C-Sharp)
Required Software: Visual Studio 2003, IIS with .NET Framework 1.1

1.2. Goals
When you have finished this article, you should have an iconic representation of a Country Code DropDownList Web Control in the Toolbox for use in your projects. The Web Control will encapsulate the mundane function of capturing an ISO 3166-1 Country Code and will expose its SelectedItem Text and Value as well as some design time properties for controlling the Design View rendering of the control.

2. Web Control Library
In order to maximize the reusability of the custom Web Control, you need a convenient location for it. There are a couple of reasons for using a Web Control Library for this purpose:

* It makes it easy to add a reference to the control in new projects.
* It gives you a place to find related controls and maintain their code.

I am sure that there are a number of other good reasons, but these will suffice for your discussion.

2.1. New Project
Click the IDE menu item: File=>New=>Project or just click the New Project button from the Start Page. Select Visual C# Projects from the folders on the left. Select Web Control Library from the Templates window. Type a descriptive name into the Name field, something like wclProfileControls. Change the location if you don’t like it. Click OK and take a look at all of the files that the Project wizard has created for you. In particular, you will be focusing most of your attention on the .cs file that contains the code for the web control.

3. Web Control
The Project Wizard has created a template of a Web Control that you will modify and enhance. The name of the .cs file that you start working with is WebCustomControl1.cs.

3.1. Rename .cs File
First things first, WebCustomControl1.cs is not a very good and informative name. Let’s change it to something more applicable.

3.1.1. Solution Explorer
In the Solution Explorer, expand the wclProfileControls Project (or whatever you named your project). Right Click on WebCustomControl1.cs and Click Rename. Name it wcCountryCodeDropDownList.cs or something else that you find to be indicative of its purpose. You will notice that the design view tab now shows wcCountryCodeDropDownList.cs.

3.2. Rename the Web Control Class
While the filename has been changed to wcCountryCodeDropDownList, the name of the class is a generic – WebCustomControl1, not descriptive and not helpful, you will change that as well.

3.2.1. Class View
Find the Class View window, usually in the top right window as a tab behind the Solution Explorer. If you cannot see the window anywhere, click the IDE menu item: View=>Class View. Expand the wclProfileControls Project by clicking the plus sign to the left of the name. Expand the wclProfileControls Namespace below the Project. Right Click on the WebCustomControl1 Class. Click Properties. Find the Properties window, usually the bottom right window. If you cannot see the window anywhere, click the IDE menu item: View=>Properties Window. Make sure that WebCustomControl1 CodeClass is showing in the dropdown list at the top of the properties window. Below that, you will see a Misc property. Expand it. Click the text field to the right of (Name). Type in CountryCodeDropDownList or whatever you feel would be indicative of the control. You will see the changes once the focus changes from the text field, i.e.. Press enter, tab, click on another window, whatever.

3.2.2. Code View
The change of name is not complete until you replace the name WebCustomControl1 in the Comments and ToolBoxData prolog and that means that you need to edit the .cs file directly.

Click over to the wcCountryCodeDropDownList.cs Code View. Type Ctrl-H to bring up the Replace dialog, or from the IDE main menu – select Edit=>Find and Replace=>Replace. Type WebCustomControl1 into the ‘Find what’ edit box. Type CountryCodeDropDownList into the ‘Replace with’ edit box. Click the Replace All button – should result in 3 occurrence(s) replaced.

3.3. Add Functionality to the Web Control
Most of the custom functionality that you will be adding will be realized through code changes to the Web Control’s .cs file and in particular to the class definition:

	public class CountryCodeDropDownList : System.Web.UI.WebControls.WebControl
	{
		// your code will be added here
	
		...
	
		// you will be modifying the Render method below, as well
	
		/// <summary>
		/// Render this control to the output parameter specified.
		/// </summary>
		/// <param name="output"> The HTML writer to write out to </param>
		protected override void Render(HtmlTextWriter output)
		{
			output.Write(Text);
		}
	}

3.3.1. CreateChildControls
You are going to override the CreateChildControls Method to add and populate your DropDownList with ListItems. I have taken the easy way out and created an array of strings and a for loop to add items. Feel free to do it your way. The constructor below does not include the full ISO 3166-1 list of country codes. The referenced project files, available to download, at the top of the article, contain the complete Method.

	protected override void CreateChildControls()
	{
		//This is a partial list, the project files contain a complete list

		//Country codes are as specified in ISO 3166-1
		//for more information, or to obtain an official list, visit the iso site:
		//http://www.iso.org/iso/en/prods-services/iso3166ma/index.html
		string [] codes = {
			"United Kingdom","GB",
			"United States","US",
			"United States Minor Outlying Islands","UM",
			"Virgin Islands, British","VG",
			"Virgin Islands, U.S.","VI",
		};

		DropDownList ddlCountryCodes = new DropDownList();
		
		for(int i = 0; i < codes.Length; i+=2) 
		{
			ddlCountryCodes.Items.Add(new ListItem(codes[i], codes[i + 1]));
		}

	ddlCountryCodes.SelectedIndexChanged +=
new EventHandler(this.ddlCountryCodes_selectedIndexChanged);
		this.Controls.Add(ddlCountryCodes);
	}

3.3.2. SelectedIndexChange Event
In order to support changes made to your control you will implement an event handler, but it's just a no-op. You can do stuff in here, but it's not necessary for this project.

	private void ddlCountryCodes_selectedIndexChanged(Object sender, EventArgs e) 
	{
		//no need to really do anything here
	}

3.3.3. Remove Unnecessary Code
Before you add properties, remove these lines from the beginning of the class definition:

	private string text;

	[Bindable(true),
		Category("Appearance"),
		DefaultValue("")]
	public string Text
	{
		get
		{
			return text;
		}

		set
		{
			text = value;
		}
	}

Also remove the Render Method:

	/// <summary>
	/// Render this control to the output parameter specified.
	/// </summary>
	/// <param name="output"> The HTML writer to write out to </param>
	protected override void Render(HtmlTextWriter output)
	{
		output.Write(Text);
	}

3.3.4. Expose Some Existing Properties
The Text and Value properties of the DropDownList's SelectedItem are properties that you will want access to in your applications, so you will expose them by adding a couple of public Properties.

3.3.4.1. Value
You want to expose the Value of the currently selected item in the DropDownList.

	public string Value
	{
		get
		{
			this.EnsureChildControls();
			return ((DropDownList)Controls[0]).SelectedItem.Value;
		}

		set
		{
			((DropDownList)Controls[0]).SelectedItem.Value = value;
		}
	}

3.3.4.2. Text
You also want to expose the Text of the currently selected item in the DropDownList.

	public string Text
	{
		get
		{
			this.EnsureChildControls();
			return ((DropDownList)Controls[0]).SelectedItem.Text;
		}

		set
		{
			((DropDownList)Controls[0]).SelectedItem.Text = value;
		}
	}

3.3.5. Add Some New Properties
You are going to add some Design View Properties. These are properties that will effect the Design View of the Web Control.

3.3.5.1. RenderHTML Design View Property
The RenderHTML Property will be used later to control what is rendered in Design View. The implementation of this property consists of a private string and public property. The string will be used when you create a ControlDesigner derived Class in the overridden GetDesignTimeHtml() method.

	//
	//strRender is set to a simple select with Country Code DropDownList to display
	// in Design View. I included the other option, since it's the longest option
	// and represents how wide the control will actually be
	//
	private string strRender = 
"<select name><option value="">Country Code DropDownList</option><option value="UM">
United States Minor Outlying Islands</option></select>";

	public string RenderHTML 
	{
		get 
		{
			return strRender;
		}

		set
		{
			strRender = value;
		}
	}

3.3.5.2. NoRenderHTML Design View Property
The NoRenderHTML Property will be used later to control what is rendered in Design View when the Design View thinks that there will not be anything rendered at run time. This control will always render at runtime, by it's very nature. However, there are times when you will want to create controls that don't, for instance, a label control. It is convenient to add a "This control will not be rendered (visible) at runtime with the current settings" render string and you will add it here, simply to provide a consistent template to work from for future controls. The implementation of this property consists of a private string and public property. The string will be used when you create a ControlDesigner derived Class in the overridden GetEmptyDesignTimeHtml() method.

	private string strNoRender = "Not Visible at Runtime with Current Settings";

	public string NoRenderHTML 
	{
		get 
		{
			return strNoRender;
		}

		set
		{
			strNoRender = value;
		}
	}

3.3.5.3. Property Grid Extensions
The Property Grid is the Visual Studio Property Window and you can control how the Web Control is represented in it, when you view its properties. This is accomplished by a square bracketed prolog immediately prior to the public Properties themselves.

Add a Property Grid control prolog to each of the new Properties (The RenderHTML Property is shown, use the same procedure for the NoRenderHTML, Text and Value Properties).

	[
		Bindable(true),
		Category("Custom"),
		DefaultValue(""),
		Description("Set the Design View Render HTML String")
	]
	public string RenderHTML 
	{
		...
	}

The prolog will add a Custom Category to the Property Grid and add the property RenderHTML to the Grid. If you select the RenderHTML property in the Property Grid, it will display the Description, "Set the Design View Render HTML String", in the Description Box below the Properties as a sort of help text.

4. Custom Designer
The Custom Designer Class is where you will control what gets displayed in Design View for the Web Control. You will leverage Microsoft's System.Web.UI.Design.ControlDesigner. All you are going to do is override the two methods GetDesignTimeHtml and GetEmptyDesignTimeHtml.

4.1. System.Design Assembly
In order to use the System.Web.UI.Design.ControlDesigner Microsoft class, you will need to add a reference to the assembly to the project.

4.1.1. Add Reference
In the Solution Explorer, Right Click the wclProfileControls Project(or whatever you named your project). Click Add Reference. Or Select the IDE menu item: Project=>Add Reference. Select the .NET tab, Scroll down to System.Design.dll, Click the Select button and Click the OK button to add the reference to the Project.

4.2. New Class
In the Solution Explorer, Right Click the wclProfileControls Project (or whatever you named your project). Select Add. Select Add Class. An Add New Item dialog will pop up with Class preselected (if not, select Class). The name class.cs will be pre filled into the Name text box. Change the name to something more meaningful, such as CountryCodeControlDesigner.cs and Click Open.

4.2.1. Derive from System.Web.UI.Design.ControlDesigner
It is all about leverage, the ControlDesigner will provide a lot of base functionality that you will use. In order to acquire the leverage, change the class definition to derive the class from ControlDesigner:

	public class CountryCodeControlDesigner : System.Web.UI.Design.ControlDesigner

4.2.2. GetDesignTimeHtml()
This method is overridden to allow you to change the way the control is rendered in Design View. Add the following to the Class Definition (just after the Constructor):

	public override string GetDesignTimeHtml()
	{
		//cast the underlying Component to a CountryCodeDropDownList
		//so that you have access to the design time Property - RenderHTML
		CountryCodeDropDownList dl = (CountryCodeDropDownList) Component;
		return dl.RenderHTML;
	}

4.2.3. GetEmptyDesignTimeHtml()
This method is overridden to allow you to change the way the control is rendered in Design View when it thinks that nothing will be rendered at runtime. It is included here for completeness. This control will always be rendered at runtime. However, if you created a control composed of a Label and did not supply any Text at design time, it is possible that it would be empty at run time. Since this is specified, it will be clear when you look at the control in design view that this is the case. Add the following just after the GetDesignTimeHtml method:

	protected override string GetEmptyDesignTimeHtml()
	{
		//cast the underlying Component to a CountryCodeDropDownList
		//so that you have access to the design time Property - NoRenderHTML
		CountryCodeDropDownList dl = (CountryCodeDropDownList) Component;
		return CreatePlaceHolderDesignTimeHtml(dl.NoRenderHTML);
	}

GetEmptyDesignTimeHtml is a protected override and not public like its cousin GetDesignTimeHtml, but it is, so be careful copying and pasting.

5. Designer Attribute
The Designer Attribute is a part of the prolog to the Web Control class definition and tells Visual Studio about the ControlDesigner that is used for the control. It is the link between the Web Control and the Designer. Edit the prolog that appears just before the class definition, CountryCodeDropDownList, in wcCountryCodeDropDownList.cs and add a Designer Attribute.

	[
		DefaultProperty("Text"),
		ToolboxData("<{0}:CountryCodeDropDownList runat=server>"),
		Designer("wclProfileControls.CountryCodeControlDesigner, wclProfileControls")
	]
		
	public class CountryCodeDropDownList : System.Web.UI.WebControls.WebControl
	{
		...
	}

6. Icon
Adding an Icon to the project is completely optional. In order to add an icon to the project, you first have to have an icon. The icon must be 16 pixels by 16 pixels and no more than 16 bit color. I downloaded a free 32x32x16 icon from the web and resized it to 16x16 in PaintShop Pro. Do what you must. Once you have an icon, copy it into the Project directory and name it CountryCodeDropDownList.bmp. The filename for the icon is the same as the name of the Web Control Class.

Here is the icon that I have chosen:

6.1. Solution Explorer
In the Solution Explorer, Right Click the wclProfileControls Project (or whatever you named your project). Select Add. Select Add Existing Item. Change the Files of Type Dropdown List to "All Files" (you may need to scroll down in the list) and Browse to the CountryCodeDropDownList.bmp file and select it. Click Open to add it to the project.

Right Click the CountryCodeDropDownList.bmp file in the Solution Explorer and Select Properties. Click the word Content in Build Action and Select Embedded Resource from the Dropdown List (the down arrow to the right of the word Content).

7. Build the Web Control Library
This is a good checkpoint for building the Web Control Library.

7.1. Solution Explorer
In the Solution Explorer, Right Click on the wclProfileControls Project and Select Build. You should not get any errors and the output will be similar to this:

	------ Build started: Project: wclProfileControls, Configuration: Debug .NET ------
	
	Preparing resources...
	Updating references...
	Performing main compilation...
	
	The project is up-to-date.
	Building satellite assemblies...
	
	
	
	---------------------- Done ----------------------
	
	    Build: 1 succeeded, 0 failed, 0 skipped

8. ASP .NET Web Application
The Web Control is complete and is ready to be consumed by an Application. You will create a simple Web Project to exercise the Web Control.

8.1. New Project
Create a new Project within the Solution for the Web Application.

8.1.1. Solution Explorer
In the Solution Explorer, Right Click the Solution wclProfileControls (top level, not the Project). Select Add. Select New Project. Select Visual C# Projects from the folders on the left. Select ASP .NET Web Application from the Templates window. Change the location to something like http://localhost/ccTest. Click OK and take a look at all of the files that the Project wizard has created for you. In particular, you will be focusing most of your attention on the .aspx and .aspx.cs files.

8.2. Rename .aspx File
Change the generated WebForm1.aspx to default.aspx.

8.2.1. Solution Explorer
In the Solution Explorer, expand the ccTest Project(or whatever you named your project). Right Click on WebForm1.aspx and Click Rename. Name it default.aspx. You will notice that the design view tab now shows default.aspx and that the WebForm1.aspx.cs file has automatically been renamed default.aspx.cs.

8.2.2. Code View
The change of name is not complete until you replace the name WebForm1 in the code itself.

First, make sure that you only have default.aspx (HTML View) and possibly default.aspx.cs open in the Designer. Then, open default.aspx.cs in Code View by Right Clicking on default.aspx and selecting View Code. Check the Option on the Search Panel that says All open documents and Replace all occurences of WebForm1 with wf_ccTest. There should be a grand total of 4 occurences in the 2 files.

8.3. FlowLayout Positioning
Visual Studio defaults to GridLayout for .aspx files. Change it to FlowLayout. The difference is that GridLayout uses absolution pixel positioning (Grid) and FlowLayout uses relative positioning (Flow, things get put next to each other). To change the layout, Click over to the HTML view of default.aspx and change the MS_POSITIONING body attribute to FlowLayout:

	<body MS_POSITIONING="FlowLayout">

8.4. Add Web Control to the Toolbox
Now it is time to add the Web Control, that you created earlier, to the Toolbox.

8.4.1. Design View
Click back to the Design View of the default.aspx file.

8.4.2. Toolbox
Find the Toolbox, usually to the left of the workspace. If you cannot see the window anywhere it might be unpinned. If this is the case, you will see a button labeled Toolbox somewhere on screen, simply click it and it will expand. If you want it to stay expanded, you will need to pin it by clicking on the push-pin at the top right of the window. If you cannot see the window or the button anywhere, Click the IDE menu item: View=>Toolbox.

8.4.2.1. My User Controls
The My User Controls Toolbox Tab is where you are going to add the newly created control. To do so, Click on the My User Controls Tab. Right Click in the empty area below the name and Select Add/Remove Items... The Customize Toolbox dialog will appear. Make sure that the .NET Framework Components Tab is selected and Click the Browse... button on the bottom right. Navigate to your project directory - typically in My Documents\Visual Studio Projects\wclProfileControls\bin\debug, which is accessible by the My Projects button to the left of the folder list. Select the dll, wclProfileControls.dll Click the Open Button, this will create an entry in the .NET Framework Components Tab that will be preselected - CountryCodeDropDownList. Click the OK Button to generate an icon and text for a CountryCodeDropDownList control in the Toolbox.

8.5. Using the Web Control
If you used the icon from the project files I generated, you will see a World Map icon next to the Caption CountryCodeDropDownList in the Toolbox, otherwise you will see the default Gear icon - that is fine. It is ready to use. In order to add the control to your Web Project, simply switch to Design View for default.aspx and double click the item in the Toolbox. Alternatively, you can drag it from the Toolbox to the Design View. The IDE will automatically add the correct references to the Project for you to be able to access the Web Control.

8.5.1. Programmatic Access
The Control is only useful because it allows you to drag and drop it into your application and have it do it's country code selection magic, giving you back the Text/Value pair of it's selected item when it gets submitted.

8.5.1.1. Label
Add a label to the form for displaying the values of the DropDownList after the form is submitted. I know kludgy, write me with improvements.

Click just after the DropDownList control in Design View and Press Enter to drop down a line. Drag an ASP Label control from the Web Forms Tab of the Toolbox to the default.aspx Design View. Right Click the Label and view its Properties in the Property Grid. Change the Id to lblKludge and delete its Text.

8.5.1.2. Submit Button
Add a submit button to the form.

Click just after the Label control in Design View and Press Enter to drop down a line. Drag an ASP Button control from the Web Forms Tab of the Toolbox to the default.aspx Design View. Right Click the Button and view its Properties in the Property Grid. Change its text to Submit. No need to write a handler for it, it will automatically submit the form.

8.5.1.3. Page_Load
To actually access the values of the DropDownList from the Web Form, add the following to default.aspx.cs in the Page_Load method:

this.lblKludge.Text = this.CountryCodeDropDownList1.Text + ", " +this.CountryCodeDropDownList1.Value;

8.5.1.4. PreRender Event Handler
First you need to add this line of code to the InitializeComponent Method:

this.PreRender +=new EventHandler(WebForm1_PreRender);

Next you need to create the PreRender Event Handler, add these lines:

private void WebForm1_PreRender(object sender, EventArgs e)
	{
this.lblKludge.Text = this.CountryCodeDropDownList1.Text + ", " +this.CountryCodeDropDownList1.Value;
	}

8.6. Startup Project
Set the ccTest Project as the Startup Project.

8.6.1. Solution Explorer
In the Solution Explorer, Right Click on the ccTest Project and Select Set as Startup Project.

8.7. Execute Web Form
Run the Web Form from Visual Studio and marvel at your ingenuity.

Click the IDE menu item: Debug=>Start or Click the Play VCR Button to the left of the Solution Configurations DropDownList (usually says Debug or Release)

This is a very simple application that is intended to serve as a testing platform for the Web Control. To test that it is performing properly, simply change the selection of the DropDownList and click the Submit Button. The Label will change to reflect your choice. I leave it as an exercize for the reader to trap the SelectedIndexChanged Event of the DropDownList and modify the Label in that method.

8.8. Browse
Please spend an hour or so, poking around all the code that Microsoft gratuitously generated for you. There is a lot of cruft and if you are intermediate-advanced, you should be able to recognize it as such. Questions to ask yourself, are; Why is it there? Can I leverage the fact that it's there? Should I remove it?

9. Next Steps
Some other things that you may want to do with your Web Control, that you either did not cover or covered minimally are:

Extend Property Grid Support
Specify a Default XML Tag for the Web Control
Specify a TagPrefix
Add Intellisense Support
Package the Web Control for Deployment

More information on many of these topics is included with the MSDN Library. Just open up help in Visual Studio and navigate to the topic:
MSDN Library/.NET Development/Visual Studio .NET/Product Documentation/Visual Basic and Visual C#/Programming with Components

If you think tree walking the MSDN Library is painful, searching is just as bad. I highly recommend that you become proficient with Google, or better yet, Copernic.

10. Conclusion
The Design Time environment of Visual Studio .NET is powerful yet mysterious, poorly documented yet accessible, attractive yet aggravating - a true study in contrasts. If you start down the road of extending the Design View further, be prepared for headaches galore, but it will be worth it if you can master it. Very few developers tap into these features and if you resell your components or even work in a large company where other developers will consume your work, you will stand out from the crowd.

I hope that you have enjoyed reading this article and if you have worked through the recipe, I hope your Web Control turned out well. Let me know if there are errors or omissions and I will try to update the Recipe.

11. Resources

F1 and Shift-F1 should be your first line of defense

Copernic Agent Basic (free) is the most powerful search engine I have ever used
Google is the place on the web where you will have the most luck
GotDotNet remains a decent place to visit
CodeProject is a great resource
Microsoft MSDN .NET Framework Home Page Too often, this is a last resort
Download the source
Email the author Will Senn

12. Acknowledgements
Thanks to my Wife for putting up with my 40+ hour focus on writing this.
Thanks to Randy Geyer for assisting me with some of the trickier bits of the IDE.
Kudos to Susan Warren of Microsoft ASP.NET Team fame for her work with Custom Controls.
GotDotNet for the Microsoft ASP.NET QuickStarts Tutorial


If you would like to see your thoughts or experiences with technology published, please consider writing an article for OSNews.

27 Comments

  1. 2004-04-22 1:30 pm
  2. 2004-04-22 2:29 pm
  3. 2004-04-22 2:34 pm
  4. 2004-04-22 3:29 pm
  5. 2004-04-22 3:54 pm
  6. 2004-04-22 4:21 pm
  7. 2004-04-22 5:04 pm
  8. 2004-04-22 5:59 pm
  9. 2004-04-22 6:10 pm
  10. 2004-04-22 6:43 pm
  11. 2004-04-22 6:55 pm
  12. 2004-04-22 7:28 pm
  13. 2004-04-22 11:26 pm
  14. 2004-04-22 11:28 pm
  15. 2004-04-22 11:33 pm
  16. 2004-04-22 11:35 pm
  17. 2004-04-22 11:40 pm
  18. 2004-04-23 2:15 am
  19. 2004-04-23 3:13 am
  20. 2004-04-23 4:30 am
  21. 2004-04-23 10:16 am
  22. 2004-04-23 1:07 pm
  23. 2004-04-23 7:31 pm
  24. 2004-04-23 11:05 pm
  25. 2004-04-23 11:39 pm
  26. 2004-04-24 5:08 pm
  27. 2004-04-24 10:43 pm