By Solomon Shaffer
In my first installment, we explored the creation and implementation of user controls. These controls encapsulate code and HTML into smaller more functional units. User controls are built very similar to ASP.NET Web forms but are placed on the hosting page as objects. The controls can then interact with each other. It is important to note that user controls are created and edited just like any other page in ASP.NET. The HTML and code-behind files are freely viewable and editable. But what if you want to sell your controls, or for some other reason, limit access to the code the control contains? You may also want to encapsulate the control in a more concise manner. You may want to distribute only one file, not the three files created with user controls, to the other developers on your team. Or, you may want to distribute the control as a single file that can be added to the IDE toolbox just like the built-in ASP.NET server controls. Since user controls must contain an HTML based ascx file, and the codebehind must be compiled in the assembly, user controls are not a viable option. Enter custom controls. While more complex and time consuming to create, custom controls (or sometimes called composite controls) afford you greater flexibility with all the advantages mentioned above.
What Is An ASP.NET Custom Control
In contrast to a user control, a custom control is boiled down into one compiled class. The interface (HTML and JavaScript) is written out programmatically instead of being visually created in an IDE. You can blend the richness of the .NET programming model in your code with as complex of an interface as need be. Because a custom control is contained in a class, you have access to all the functionality of the .NET Framework, properties can be defined, and functions can be implemented. In fact, the server controls that ship with ASP.NET and Visual Studio.NET are in essence, custom controls. They are compiled classes that when placed on a page, interact with each other, interact with the page (take advantage of events like OnLoad), emit HTML and script, and provide the power behind the ASP.NET technology. The only difference between Microsoft’s pre-built server controls and our custom controls is that they are pre-registered in the IDE for use (the System.Web.dll file is registered in the toolbox), and references in the HTML are implied (as discussed with user controls). Obviously, Microsoft’s controls are very thought out and are designed to work together efficiently. For instance, the SQLDataAdapter, DataSet, and DataGrid controls work very closely with each other. Your custom controls are more likely to focus on one particular piece of functionality and not worry about interaction to this degree. So let’s get down to building our control, registering it with the IDE for use (we will even define an icon for use in the toolbox), and placing the control on a page for rendering.
Creating a Sample Custom Control
In this article, we will create one custom control. I will try to make the control broad enough to cover a number of different areas and issues. For this example, we will create a visual HTML container that could be used to surround a piece of functionality (possibly even another custom or server control) in a page. The container will have a title on top, borders surrounding the left, right, and bottom sides, and a collapse button placed in the header. The collapse button will expand or collapse the control with some JavaScript. This type of control would be useful where the page is broken down into portal-like chunks of data or functionality. By using this container control when you have multiple controls on a page, you could promote a consistent look and feel to your site. For clarity, both examples are written in C# and the full source code is available for download. First, we need to create a new project. Open the VS.NET IDE, click the New Project button, and select a Web Control Library in the Visual C# Projects folder. Name the project “BuildingCustomControls” and click OK. The IDE will create a new project (remember that this is not an ASP.NET project so an IIS Web folder will not be created) and a very bare-bones project is created with only two files and three references. A skeleton file is created and displayed for you. Close and delete this file. Right-click on the project and click Add – Add New Item and select a Web Custom Control. Name the control “CCContainer” and click Open. The new file contains our new class name (which needs to be descriptive and will be referenced later) and is within our namespace (also should be descriptive and will be referenced later – in our case, BuildingCustomControls). The file you just created should be open in code view. You now have a clean slate with which to build. Notice that our control inherits from System.Web.UI.WebControls.WebControl. This allows us to tap into the page’s event model and all the goodies associated with the creation and destruction of a Web control. Also, notice the explicit attributes specified above the class declaration. The DefaultProperty attribute specifies the default property of the component (as the name implies). This is the property that is set when a value is applied to the component and no specific property is defined. We also have an attribute called “ToolboxData.” Since we must place these controls on the page and register them in the HTML (registration is not implied as with ASP.NET server controls), default tag prefixes and names may be defined. This is very similar to adding a user control to the page (See Building ASP.NET User and Custom Controls, Part 1). These tag values will be automatically registered and placed in the HTML when the control is dragged onto the page from the toolbox. The “{0}” tag prefix denotes that the tags prefixes will be automatically created unique by the IDE. It is best to leave this prefix tag alone and let the system handle it. We also have the “runat=server” text to ensure that this control renders server side (we will get a runtime error if it is not present). We will also add an attribute called “ToolboxBitmap.” This value will specify the icon bitmap to use in the IDE toolbox. The bitmap should be 16×16 pixels in size and somehow represent your control in graphical format. Also, the path to the icon must be an absolute path with a drive letter specified. Relative paths to the bitmap (i.e. \images\icon.bmp) will produce a runtime error. Next, we will declare our public properties of the component. We will have three public properties, all of which will describe how the control will appear rendered. The first property is the container’s contents. This is a text property that will be displayed within the body that lies between the borders of our container. This property could very well be plain text or HTML (perhaps an <iframe> tag pointing to another page). The second property is the title text displayed in the container header. The final property in this example is the container width. This property’s datatype is a “struct” declared in the System.Web.UI.WebControls.WebControl namespace. A struct is a user defined value type tailored to suit a specific need. In this case, we can specify “px,” “%,” etc. as our unit of measure, and the control will understand how to use these measurements. The properties are accessed through the traditional get and set routines. In our example, we are not using default values for our properties, although this could be easily implemented. We now can concentrate on the output of the control. The idea for a custom control is to emit HTML and script to the page in an encapsulated unit. This means that the JavaScript must be written inline with the control (as opposed to within the header). All HTML will be place inline within the page as well. It is up the creator to emit the correct HTML and script to run properly in the browser. You could very well have two sets of HTML and output only one of them based on the browser the user is running. Microsoft takes this into account with their server controls. First, they query the user’s browser for capabilities and type. Types include IE, Netscape, PocketPC, cellphone, Palm, etc. Based off the capabilities and the browser type, the server control will emit the proper HTML for that machine. If the browser does not support client-side JavaScript, the control will not emit JavaScript and deal with interactivity another way. If the HTML renders differently on browser type A than on browser type B, the control will account for these differences. One great example of this is the MobileControls shipped with the Mobile Internet SDK. These controls are capable of emitting an entirely different interface based off the brand or type of cell phone or PDA they are targeting. The developer must take into account all these scenarios for a robust control. For our control, we will create two functions. Both of these functions will take the properties we declared above as parameters and will return a StringBuilder object that contains HTML. The first function will build the header HTML, JavaScript, and define the styling. The second function will build the HTML for the borders and footer. To ensure cross-browser HTML, the developer could build the HTML programmatically with HTML objects. While this could ensure correct cross-browser HTML, the code to create the HTML DOM could get quite cumbersome with a sizeable control. In our example, the HTML will be manually written out and appended to a StringBuilder object. When dealing with large strings, it is recommend that you not concatenate the text with the “x = x + y” approach. This is especially true when you have a number of text files that you are combining. Each new string that is created holds a memory space and must wait for garbage collection. This can be very expensive and can deteriorate performance substantially in our case where we are creating a large HTML string. Instead, we will use a StringBuilder object. With the StringBuilder, we allocate one chunk of memory for our object and add text to it. The StringBuilder object is located in the System.Text namespace, so it will have to be referenced in your code. Remember to use single quotes within your JavaScript so you do not confuse the compiler. Next, we need to call these functions. Since our control inherits from System.Web.UI.WebControls.WebControl, we can tap into the event lifecycle of the page (see my article on The ASP.NET Page Life Cycle for more information). Since we have this luxury, we will override the Render event and call our functions from there. We will use the “output.Write” function to send our markup to the browser. We call the first function (passing in our parameters set with our properties and getting our HTML back in a StringBuilder object), insert the sContextText variable set in our ContentText property, and call the second function to build the borders and footer. The overridden Render event takes a single argument called “output.” Our control should now be complete and compiled successfully. Click the Build Solution button and the code should compile into a single file called BuildingCustomControls.dll (based off our namespace that we defined).
Figure 1 – The attributes above the class declaration indicate how the control will behave.
Figure 2 – The properties are declared using the proper data types and accessed through get and set routines.
Figure 3 – HTML created programmatically.
Figure 4 – Our two functions build the HTML string and return StringBuilder objects. Notice the style sheet and JavaScript is inline with the HTML of our control.
Figure 5 – The Render event is overridden and our HTML building functions are called and outputted.
Registering and Using the Control
Now we can use the control on a page. Open a new instance of the VS.NET IDE, click the New Project button, and select an ASP.NET Web Application in the Visual C# Projects folder. Name the project “UsingCustomControls” and click OK. The IDE will create a new Web project. We now want to register our control in the toolbox, so we can simply drag it onto the designer. Right-click on the toolbox and select Customize Toolbox. Select the .NET Components tab (since our control is a .NET assembly, not a COM component) and click the Browse button. Navigate to the BuildingCustomControls project that we created and drill down into the bin/debug folder (if you built our custom control project in “release” configuration, you will see a “release” folder also). There you should find our BuildingCustomControls.dll component. Select the BuildingCustomControls.dll file and click OK. You should now see our control with our custom icon in the toolbox. Drag the control on the body of the open Web form and click View In Browser. You should get a parser error stating that one of the dependencies was not found. This is because while we added the control to the toolbox, the code is unreachable by the CLR. We must set a reference to the component in our project and import the class. To do this, right-click on the References folder in the solution explorer and click Add Reference. The displayed list shows .NET components that are recognized by the IDE. Our component is not in this list yet, so click on the Browse button. Again, find the .NET component called BuildingCustomControls.dll that we discussed earlier. Click Open, click OK. We now have a reference to our control in the References folder. Refresh the browser see if the control renders (there is no need to recompile since the control is already compiled into a dll file). You should now get some rendered HTML with broken images. This is because we reference several images in our control and specify relative paths to those images (i.e. images/ CCContainer_arrow_open.gif). We now have the control in a context that leaves these paths unresolved. There are three fixes to this problem. One is to import the images folder into the Web application so that the paths in the HTML are valid. Another would be to make the paths fully qualified and absolute in the control code. This would ensure that paths are valid regardless of where the images were located. Third would be not to use images at all in our custom control at all. Images in our control were included for example, but it is recommended that developers keep their controls self contained and not dependent on any outside resources that they do not have control over (such as images or external style sheets not created and placed in a setup routine). Microsoft has opted to included icon images for their controls, but the HTML rendered from these controls contains no images. The entire presentation is created with markup. For our example, I have included the images folder in the example project. Next, we set the control properties. This topic is covered in Building ASP.NET User and Custom Controls, Part 1. Insert Width=”80%” Title=”This is the title” into the control declaration in the HTML. This will set the width and title properties. You can also set properties at runtime in the code behind file by inserting CCContainer1.ContentText = “This is the body”; into the Page_Load event. Build the project and click View in Browser. The control should render and behave correctly now. Click on the arrow to expand and collapse the control. Right-click on the page and view the source of the document. The JavaScript and styling is rendered inline with the HTML in the control.
Figure 6 – The CCContainer (derived from the CCContainer class) component is now located in the toolbox.
Figure 7 – The reference to our control is added. Now we can access the code in the BuildingCustomControls namespace.
Figure 8 – The Width and Title properties set in the HTML.
Figure 9 – The ContentText property is set in the code.
Figure 10 – The custom control rendered in IE.
Conclusion
While this control is quite simple, it is a good example of how custom controls can be bundled up into tidy components that can be distributed and used with ease. We will see a large commercial market open up with a variety of custom controls in the future. It may also be a common practice for developers to package their own custom controls for distribution within their own team. The ease of use and versatility make custom controls a very powerful tool.
Appreciation
I would like to thank my wife for her support through the years.
About the Author
Solomon Shaffer has been in the information technology industry for over five years. Recently, he founded Cynthetic, Inc., a software-development practice focused on .NET and Microsoft Internet technologies. Solomon’s areas of expertise include COM development, ASP, SQL Server, and UI layout / design. At press time, Solomon is working on a contract basis in the Atlanta area building the next-generation Web applications using the .NET platform. He has over 1 year of enterprise .NET development experience, developing enterprise applications with the platform since its Beta 1 release as part of the Early Adopters Program. Solomon and his wife reside in Alpharetta, GA. He can be reached at solomon.shaffer@cynthetic.com.