Introduction
Have you ever wanted to build a custom web control, but lost confidence once you started to navigate the MSDN information ocean? Ever wanted to build a web control only to find out that you don't know where to start and worse yet there are no comprehensive examples available on the internet? If you have faced any or all of these situations, sit back and relax! Throughout this article I will detail the major "gotchas" relating to building a custom web control and deploy it to your web application.
About Web Controls
Web Controls should be designed with the following considerations in mind; alleviate repetitive, complex tasks and add new functionality to your web pages (web forms). After wading through numerous resources, I discovered that .NET controls for the web can come in many different varieties! In short, web controls can be broken down into 6 logical types (classes):
- HTML Server Control
- Web Server Control
- Web Validation Control
- Web User Control
- Composite Control
- Custom Control
Since we are going to focus on custom controls, I will limit discussion on all other types to a basic definition.
Web Controls Defined
HTML Server Controls: HTML Server Controls are HTML elements containing attributes which make them visible to, and programmable on the web server. Additionally, HTML Controls map closely to HTML elements via public properties and styles.
Web Server Control: Web Server Controls are designed to provide a quick, easy way to add functionality to your web pages. These controls provide for up and down level browser support. The core requirements for Up-level Browsers are; ECMAScript (JScript, JavaScript) support, HTML version 4.0 compliance, Supports Microsoft Document Object Model (MSDOM) and Cascading style sheets (CSS) support. Additionally, these controls can be used to build more complex server controls.
Web Validation Control: Validation controls provide you with a way to check user input in Web or HTML server controls. Attach validation controls to input controls to test what the user enters for that input control.
Web User Control: Web User Controls are created like Web Forms pages. These controls can be embedded into other Web Forms pages and are an easy way to create menus, toolbars, and other reusable elements. These types of controls always end with the extension of .ascx and must be included separately in each project that uses the control. Web User Controls are dynamically compiled at run time.
Composite Control: Composite Controls combine the functionality of two or more existing controls and can expose custom properties and events. Basic requirements for implementation: must override the CreateChildControls method inherited from Control and must implement System.Web.UI.INamingContainer interface for unique naming convention.
Custom Control: Custom Controls are fully authored, pre-compiled code (.dll) and are a good choice for dynamic web page layout. The major advantage of Custom Controls is full design-time support, to include: Toolbox Integration, Visual Designers support and interfacing with the Property Grid. These controls can be installed to the Global Assembly Cache (GAC) which will allow multiple applications to share a single control.
Classy Choices - enter System.Web.UI
The System.Web.UI
namespace provides classes and interfaces that allow you to create ASP.NET server controls and pages that will appear in your Web applications as user interface elements. This namespace includes the Control class, which provides all server controls, whether HTML server controls, Web server controls, or user controls, with a common set of functionality. It also includes the Page class, which is generated automatically whenever a request is made for an .aspx file contained in your Web application. You can inherit from both of these classes. Also provided are classes which provide the server controls with data binding functionality, the ability to save the view state of a given control or page, and parsing functionality for both programmable and literal controls."
The two classes that we will focus on for the purpose of this article are UserControl
and WebControls
. User controls are contained in ASP.NET Web Forms pages, and offer Web developers an easy way to capture commonly used Web UI. They are instantiated and cached in ways similar to Page objects. Unlike pages, however, user controls cannot be called independently. They can only be called from the page or other user control that contains them.
The WebControl
class provides the properties, methods, and events that are common to all Web server controls. You can control the appearance and behavior of a Web server control by setting properties defined in this class. For example, the background color and font color of a control are controlled by using the BackColor and ForeColor properties, respectively. On controls that can display a border, you can control the border width, the border style, and the border color by setting the BorderWidth, BorderStyle, and BorderColor properties. The size of a Web server control can be specified by using the Height and Width properties.
The behavior of the control can be specified by setting certain properties. You can enable and disable a control by setting the Enabled property. The place of the control in the tab order is controlled by setting the TabIndex property. You can specify a ToolTip for the control by setting the ToolTip property.
Enough Already! How does it work?
Now that all the basics are out of the way, lets examine the class I implemented for WebChart
. The WebChart
control is inherited from System.Web.UI.WebControls.WebControl
. By inheriting this class I have achieved "free functionality" such as Height, Width, BackColor and ForeColor. Not bad considering this is one line of code! Additionally, I will override the Render method of MyBase to display my user drawn GDI+ chart. The following code example shows how to inherit from System.Web.UI.WebControls.WebControl:
<DefaultProperty("Type"), _
ToolboxData("<{0}:WebChart runat="server"></{0}:WEBCHART>"), _
ParseChildren(True, "WebChartItems"), ToolboxBitmapAttribute(GetType(Bitmap))> _
Public Class WebChart : Inherits System.Web.UI.WebControls.WebControl
...
End Class
The above code block is relatively easy to understand, but what's all the stuff above the class statement? These are known as attributes. When you compile your code for the runtime, it is converted into Microsoft intermediate language (MSIL) and placed inside a portable executable (PE) file along with metadata generated by the compiler. Attributes allow you to place extra descriptive information into metadata that can be extracted using runtime reflection services. The compiler creates attributes when you declare instances of special classes that derive from System.Attribute. The ToolBoxData Attribute specifies how the control will be named when placed on an ASP.NET web form. The ParseChildren Attribute must identify the indexed property you will hold your collection within. The ToolboxBitmap Attribute is used to supply a custom icon for your control. The icon must be 16 x 16 and be restricted to 16 colors. The bottom left corner of the bitmap is used as the transparency color. Lastly, the icon for this project is set as an embedded resource and is named the same as the Root Namespace.
Delving deeper into the WebChart
Class, we will look at enumerations and properties:
Public Enum ChartType
Pie = 0
Bar = 1
End Enum
...
Private mChartType As ChartType = ChartType.Bar
<DefaultValue(GetType(ChartType), "Bar"), Category("Chart Common"), _
Description("Type of chart to generate.")> _
Public Property Type() As ChartType
Get
Return mChartType
End Get
Set(ByVal Value As ChartType)
mChartType = Value
End Set
End Property
Scalar Property (above): Notice that the public property Type is declared as the type ChartType. This will allow the Property Type to contain a drop down box populated with the 'English' values to be displayed in the property grid. Also, by initializing the mChartType property to ChartType = ChartType.Bar you are effectively assigning a default value to your property! The Attribute DefaultValue is set to get the type ChartType via Reflection and set the value to "Bar". In the property grid default values are not bolded, values which have changed from their default property will be displayed as bold text. The Category Attribute determines which category this property will be associated with on the PropertyGrid. Last, the Description Attribute is used to give the developer an idea of what the property does.
<DefaultProperty("Type"), _
ToolboxData("<{0}:WebChart runat="server"></{0}:WEBCHART>"), _
ParseChildren(True, "WebChartItems"), ToolboxBitmapAttribute(GetType(Bitmap))> _
Public Class WebChart : Inherits System.Web.UI.WebControls.WebControl
Private mWebChartItems As New WebChartItemCollection(Me)
<Category("Chart Common"), _
DesignerSerializationVisibility(DesignerSerializationVisibility.Content), _
PersistenceMode(PersistenceMode.InnerDefaultProperty), _
Description("Supply data values for selected chart.")> _
Public ReadOnly Property WebChartItems() As WebChartItemCollection
Get
If mWebChartItems Is Nothing Then
mWebChartItems = New WebChartItemCollection(Me)
End If
Return mWebChartItems
End Get
End Property
<Serializable()> _
Public Class WebChartItemCollection : _
Inherits System.Collections.CollectionBase
Private mOwner As WebChart
Public Sub New()
MyBase.New()
End Sub
Public Sub New(ByVal Owner As WebChart)
MyBase.New()
mOwner = Owner
End Sub
Private Property Owner() As WebChart
Get
Return CType(mOwner, WebChart)
End Get
Set(ByVal Value As WebChart)
mOwner = Value
End Set
End Property
Default Public ReadOnly Property Item(ByVal index As Int32) As WebChartItem
Get
Return CType(List.Item(index), WebChartItem)
End Get
End Property
Public Function Add(ByVal Item As WebChartItem) As Integer
Return List.Add(Item)
End Function
End Class
End Class
Indexed Property: Our indexed property WebChartItems is ReadOnly because it only controls the return of our collection object. Keep in mind that a WebChartItem
is added to the WebChartCollection
either through the PropertyGrid or code. This allows for static values to be added to the control or using code both charts can be dynamically populated with a DataSet or XML source. Two new Attributes can be examined on this property: DesignerSerializationVisibility - This property will control how data from our indexed property is persisted, PersistenceMode - InnerDefaultProperty specifies that the persisted data will be stored as nested elements instead of attributes of the control.
Adding WebChart to the Toolbox
In order to use the WebChart
control you must first build it. After the control has been compiled, you must reference it in you ASP.NET Web Application Project. Once the control has been referenced, go to your Toolbox and right-click. Select Customize Toolbox and go to the .NET Tab and browse for the .dll you compiled. After selecting the .dll you will see an icon (see picture at top of article). If you are not sure how to do any of the above, please look in VS.NET help documentation.
Example use
Adding the control at design-time is straight forward, use it like any of the native ASP.NET Controls. However, you must insert the following XML tags into your Web.config file directly underneath the opening <SYSTEM.WEB> tag (see below). This tag allows ASP.NET to track WebChart images via Application Variable. The <httpModules> tag is required for proper WebChart operation. The following code block shows how to create a pie type WebChart
dynamically with an ADO.NET DataReader Object:
REQUIRED: Place the following httpModules XML in your local Web.config.
<SYSTEM.WEB>
<httpModules>
<add name= "WebChartImageStream" type= "blong.WebControls.WebChartImageStream, blong" />
</httpModules>
<SYSTEM.WEB />
Imports System.Data.SqlClient
Imports blong.WebControls
Protected Sub MakeChart()
Dim Chart As WebChart
Dim i As Int32
Dim cn As New SqlConnection(Session.Item("strConn"))
Dim cmd As New SqlCommand("WebHitsPerMonth", cn)
Dim dReader As SqlDataReader
cn.Open()
cmd.CommandType = CommandType.StoredProcedure
dReader = cmd.ExecuteReader(CommandBehavior.CloseConnection)
Chart = New WebChart()
With Chart
.ID = "CodeChart"
.Type = WebChart.ChartType.Pie
Dim highVal As Single
Dim ExplodeIndex As Int32
While dReader.Read
If Not IsDBNull(dReader("HitYear")) Then
.WebChartItems.Add(New WebChartItem(dReader("HitMonth") & _
" " & dReader("HitYear"), dReader("HitMonthCount"), False))
If dReader("HitMonthCount") > highVal Then
highVal = dReader("HitMonthCount")
ExplodeIndex = i - 1
End If
End If
i += 1
End While
.WebChartItems(ExplodeIndex).Explode = True
.ShowLegend = True
.Diameter = WebChart.PieDiameter.Larger
.ExplodeOffset = 25
.Rotate = 70
.Thickness = WebChart.PieThickness.Medium
.Title = "My Run Time Chart"
.Format = WebChart.ChartFormat.Png
End With
Me.Controls.Add(Chart)
End Sub
WebChart Credits
Special thanks to Mohammed Banat at eSense Software, Development and Consulting, for help on HTTP streaming which allows WebChart to draw charts directly to the Output.Response stream with no additional external web pages.
WebChart History
- Version 1.1 (1/1/2003)
- No longer rely on implicit type casting.
- Option Explicit/Strict enabled.
- Removed unecessary code.
- Added code to support image streaming, no longer writes images to disk.
- Version 1.0 (10/24/2002)
- Initial Release