Walkthrough: Developing and Using a Custom Server Control
This walkthrough shows you how to create and compile a custom ASP.NET server control and use it in a page.
During this walkthrough you will learn how to:
Create an ASP.NET server control.
Add metadata to the control and its members to control security and design-time behavior.
Use the App_Code directory in an ASP.NET Web site to test your control without manual compilation steps.
Specify a tag prefix in a configuration file and in the control's assembly.
Compile the control into an assembly and add it to the Bin directory.
Embed a bitmap into the control's assembly as the toolbox icon for a visual designer.
Use the compiled control in a page.
A visual design tool such as Microsoft Visual Studio 2005 simplifies control development but is not essential for creating or building custom controls. You can create your controls using any text editor and build them from the command line using the compilers that are part of the .NET Framework SDK. Regardless of how you create your control, the control's design-time appearance and behavior in a visual designer will be the same. A page developer can add your control to the visual designer's toolbox, drag it onto the design surface, and access its properties and events in the property browser. In some visual designers such as Visual Studio 2005, a custom control also has IntelliSense support without any work on your part.
Creating the Server Control
The control you will create, WelcomeLabel
, is a simple control similar to the standard Label control. The WelcomeLabel
class derives from WebControl and defines a Text
property that allows a page developer to provide a text string to welcome a user to a site. WelcomeLabel
appends the user's name to the text string if the user's name is present in the header sent by the user's browser. For more information on retrieving the user name, see User. For example, if the page developer sets "Hello" as the value of the Text
property, WelcomeLabel
renders "Hello, userName!" or "Hello!" based on whether the user name is present in the header.
To create the code for the custom server control
Create a file named WelcomeLabel.cs or WelcomeLabel.vb.
Add the following code to the source file for the control:
' WelcomeLabel.vb Option Strict On Imports System Imports System.ComponentModel Imports System.Security.Permissions Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls Namespace Samples.AspNet.VB.Controls < _ AspNetHostingPermission(SecurityAction.Demand, _ Level:=AspNetHostingPermissionLevel.Minimal), _ AspNetHostingPermission(SecurityAction.InheritanceDemand, _ Level:=AspNetHostingPermissionLevel.Minimal), _ DefaultProperty("Text"), _ ToolboxData( _ "<{0}:WelcomeLabel runat=""server""> </{0}:WelcomeLabel>") _ > _ Public Class WelcomeLabel Inherits WebControl < _ Bindable(True), _ Category("Appearance"), _ DefaultValue(""), _ Description("The welcome message text."), _ Localizable(True) _ > _ Public Overridable Property Text() As String Get Dim s As String = CStr(ViewState("Text")) If s Is Nothing Then s = String.Empty Return s End Get Set(ByVal value As String) ViewState("Text") = value End Set End Property Protected Overrides Sub RenderContents( _ ByVal writer As HtmlTextWriter) writer.WriteEncodedText(Text) If Context IsNot Nothing Then Dim s As String = Context.User.Identity.Name If (s IsNot Nothing) AndAlso (s <> String.Empty) Then Dim split() As String = s.Split("\".ToCharArray) Dim n As Integer = split.Length - 1 If (split(n) <> String.Empty) Then writer.Write(", ") writer.Write(split(n)) End If End If End If writer.Write("!") End Sub End Class End Namespace
// WelcomeLabel.cs using System; using System.ComponentModel; using System.Security.Permissions; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace Samples.AspNet.CS.Controls { [ AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal), DefaultProperty("Text"), ToolboxData("<{0}:WelcomeLabel runat=\"server\"> </{0}:WelcomeLabel>") ] public class WelcomeLabel : WebControl { [ Bindable(true), Category("Appearance"), DefaultValue(""), Description("The welcome message text."), Localizable(true) ] public virtual string Text { get { string s = (string)ViewState["Text"]; return (s == null) ? String.Empty : s; } set { ViewState["Text"] = value; } } protected override void RenderContents(HtmlTextWriter writer) { writer.WriteEncodedText(Text); if (Context != null) { string s = Context.User.Identity.Name; if (s != null && s != String.Empty) { string[] split = s.Split('\\'); int n = split.Length - 1; if (split[n] != String.Empty) { writer.Write(", "); writer.Write(split[n]); } } } writer.Write("!"); } } }
Code Discussion
The code discussion that follows is not essential for performing the steps in this walkthrough and you can skip it initially. However, if you are new to control authoring, we recommend that you read it at least after completing the walkthrough.
If your control renders a user interface (UI) element or any other visible element on the client, you should derive your control from System.Web.UI.WebControls.WebControl (or a derived class). If your control renders an element that is not visible in the client browser, such as a hidden element or a meta element, derive your control from System.Web.UI.Control. The WebControl class derives from Control and adds style-related properties such as Font, ForeColor, and BackColor. In addition, a control that derives from WebControl participates in the themes features of ASP.NET without any extra work on your part.
If your control extends the functionality of an existing control, such as the Button, Label, or Image controls, you can derive from that control. Because WelcomeLabel
extends the functionality of the Label control, it could derive from Label. However, this walkthrough derives WelcomeLabel
from WebControl to show how to define a property and define property metadata.
The WelcomeLabel
defines one property, Text
, and uses view state to store the property value. Using view state persists the value of Text
across postbacks. On each postback, the page is recreated and values are restored from view state. If the Text
value were not stored in view state, the value would be set to its default, Empty, on each postback. The ViewState property inherited from WebControl is a dictionary that saves data values. Values are entered and retrieved using a String key. In this case, "Text" is used as the key. Items in the dictionary are typed as Object, which you must then cast to the property type. For more information, see ASP.NET State Management Overview.
The WelcomeLabel
control renders its Text
property by overriding the inherited RenderContents method. The parameter passed into the RenderContents method is an object of type HtmlTextWriter, which is a utility class that has methods for rendering tags and other HTML (and HTML-variant) markup.
Notice that WelcomeLabel
makes successive calls to the HtmlTextWriter object's Write method instead of performing string concatenation and then invoking the Write method. This improves performance because the HtmlTextWriter object writes directly to the output stream. String concatenation requires time and memory to create the string, and then writes to the stream. When you implement rendering in your controls, you should follow the pattern illustrated in this walkthrough.
Note |
---|
In general, when your control derives from WebControl and renders a single element, you should override the RenderContents method (and not the Render method) to render content within the control's tags. The Render method of WebControl invokes RenderContents after rendering the opening tag for the control and its style attributes. If you override the Render method to write contents, your control will lose the style-rendering logic that is built into the Render method of WebControl. For more information about rendering a control that derives from WebControl, see Web Control Rendering Example. |
The attributes applied to WelcomeLabel
contain metadata that is used by the common language runtime and by design-time tools.
At the class level, WelcomeLabel
is marked with the following attributes:
AspNetHostingPermissionAttribute is a code-access security attribute. It causes the JIT compiler to check that code that links to
WelcomeLabel
is granted the AspNetHostingPermission permission. All public ASP.NET classes are marked with this attribute. You should apply AspNetHostingPermissionAttribute to your controls as a security check against partially trusted callers.DefaultPropertyAttribute is a design-time attribute that specifies the default property of a control. In visual designers, the property browser typically highlights the default property when a page developer clicks the control on the design surface.
ToolboxDataAttribute specifies the format string for the element. The string becomes the control's markup when the control is double-clicked in the toolbox or dragged from the toolbox onto the design surface. For
WelcomeLabel
, the string creates this element:<aspSample:WelcomeLabel runat="server"> </aspSample:WelcomeLabel>
The WelcomeLabel
control also inherits two attributes from the WebControl base class, ParseChildrenAttribute and PersistChildrenAttribute. They are applied as ParseChildren(true)
and PersistChildren(false)
. These two attributes work together and with the ToolboxDataAttribute attribute so that child elements are interpreted as properties and properties are persisted as attributes.
The following attributes applied to the Text
property of WelcomeLabel
are standard design-time attributes that you will generally apply to all public properties of your controls:
BindableAttribute, specified as true or false, specifies for visual designers whether it is meaningful to bind the property to data. For example, in Visual Studio 2005, if a property is marked with
Bindable(true)
, the property is displayed in the DataBindings dialog box. If a property is not marked with this attribute, the property browser infers the value to beBindable(false)
.CategoryAttribute specifies how to categorize the property in the visual designer's property browser. For example,
Category("Appearance")
tells the property browser to display the property in the Appearance category when the page developer uses the category view of the property browser. You can specify a string argument corresponding to an existing category in the property browser or create your own category.DescriptionAttribute specifies a brief description of the property. In Visual Studio 2005, the property browser displays the description of the selected property at the bottom of the Properties window.
DefaultValueAttribute specifies a default value for the property. This value should be the same as the default value you return from the property accessor (getter). In Visual Studio 2005, the DefaultValueAttribute allows a page developer to reset a property value to its default by bringing up the shortcut menu in the Properties window and clicking the Reset button.
LocalizableAttribute, specified as true or false, specifies for visual designers whether it is meaningful to localize the property. When a property is marked
Localizable(true)
, the visual designer includes it when serializing localized resources. The designer will persist the property value to the culture-neutral resource file or other localization source when the control is polled for localizable properties.Note You could not apply LocalizableAttribute to custom server controls in ASP.NET versions 1.0 and 1.1 because the ASP.NET localization model was different in those versions.
Design-time attributes applied to a control and its members do not affect the functioning of the control at run time, but enhance the developer experience when the control is used in a visual designer. You can see a complete listing of design-time, parse-time, and run-time attributes for server controls in Metadata Attributes for Custom Server Controls.
Using the App_Code Directory to Test Controls Without Compilation
You can use the ASP.NET dynamic compilation feature to test your control in a page without compiling the control into an assembly. ASP.NET dynamically compiles code placed in the App_Code directory under an ASP.NET Web site's root. Classes in source files in App_Code can thus be accessed from pages without being manually compiled into assemblies. If you place the source files for your controls in the App_Code directory, changes that you make to the controls' code are immediately reflected in pages that use the controls.
Note |
---|
The App_Code directory is a new feature that was not available in ASP.NET 1.0 and 1.1. Using the App_Code directory for initial control testing is optional. The main steps in building a server control are the same as in earlier versions, as you will see in the next section, "Compiling the Control into an Assembly." |
To create the ASP.NET Web site and the App_Code directory
Create a Web site named
ServerControlsTest
. You can create the site in IIS as a virtual directory namedServerControlsTest
. For details about creating and configuring an IIS virtual directory, see How to: Create and Configure Virtual Directories in IIS.Create an App_Code directory directly under the root directory of your Web site (also called Web application root).
Copy the source file for the control (WelcomeLabel.cs or WelcomeLabel.vb) to the App_Code directory.
Creating a Tag Prefix
A tag prefix is the prefix, such as "asp" in <asp:Table />, that appears before a control's type name when the control is created declaratively in a page. To enable your control to be used declaratively in a page, ASP.NET needs a tag prefix that is mapped to your control's namespace. A page developer can provide a tag prefix/namespace mapping by adding a @ Register directive on each page that uses the custom control, as in the following example:
<%@ Register TagPrefix="aspSample"
Namespace="Samples.AspNet.CS.Controls"%>
[Visual Basic]
<%@ Register TagPrefix="aspSample"
Namespace="Samples.AspNet.VB.Controls"%>
Note |
---|
The @ Register directive in ASP.NET 2.0 is the same as that in ASP.NET 1.0 and ASP.NET 1.1. If you are familiar with the Register directive in earlier versions of ASP.NET, you might notice that the assembly attribute that specifies the name of the control assembly is missing in the preceding Register directive. When the assembly attribute is missing, ASP.NET infers that the assembly is dynamically compiled from source files in the App_Code directory. |
As an alternative to using the @ Register directive in each .aspx page, the page developer can specify the tag prefix/namespace mapping in the Web.config file. This is useful if the custom control will be used in multiple pages in a Web application. The following procedure describes how to specify the tag prefix mapping in the Web.config file.
To add a tag prefix mapping in the Web.config file
Create a text file named Web.config under your Web site's root directory, if such a file does not already exist.
If you created a new (empty) Web.config file, copy the following code to the file and save the file.
<?xml version="1.0"?> <configuration> <system.web> <pages> <controls> <add tagPrefix="aspSample" namespace="Samples.AspNet.CS.Controls"> </add> </controls> </pages> </system.web> </configuration>
<?xml version="1.0"?> <configuration> <system.web> <pages> <controls> <add tagPrefix="aspSample" namespace="Samples.AspNet.VB.Controls"> </add> </controls> </pages> </system.web> </configuration>
The highlighted section shows a tag prefix entry, which maps the tag prefix "aspSample" to the namespace
Samples.AspNet.CS.Controls
orSamples.AspNet.VB.Controls
.If a Web.config file already exists, add the highlighted text from the preceding step as a child of the controls element of the configuration file. If the controls and pages elements do not exist in the Web.config file, create those elements as illustrated in the preceding step.
Note The tag prefix entry must be a child of the controls section, which must be under the pages section, which in turn must be a child of system.web.
After you have specified the tag prefix mapping in the configuration file, you can use the WelcomeLabel
control declaratively (as <aspSample:WelcomeLabel />
) in any page in the Web site.
Note |
---|
A configuration entry for the tag prefix is new feature of ASP.NET 2.0. In ASP.NET 1.0 and 1.1 the tag prefix mapping was specified in the @ Register directive in each page that used the custom control. |
Creating a Page to Use the Control
To create a page that uses the custom control
Create a text file named WelcomeLabelTest.aspx in your Web site.
Copy the following markup into the WelcomeLabelTest.aspx file and save the file.
<%@ Page Language="VB"%> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>WelcomeLabel Test</title> </head> <body> <form id="form1" runat="server"> <div> <aspSample:WelcomeLabel Text="Hello" ID="WelcomeLabel1" runat="server" BackColor="Wheat" ForeColor="SaddleBrown" /> </div> </form> </body> </html>
<%@ Page Language="C#"%> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>WelcomeLabel Test</title> </head> <body> <form id="form1" runat="server"> <div> <aspSample:WelcomeLabel Text="Hello" ID="WelcomeLabel1" runat="server" BackColor="Wheat" ForeColor="SaddleBrown" /> </div> </form> </body> </html>
Display WelcomeLabelTest.aspx in your browser by entering the following URL in the address bar:
https://localhost/ServerControlsTest/WelcomeLabelTest.aspx
Make some change to the source code for the control. For example, write an additional string by adding this line of code at the end of the RenderContents method:
writer.Write("Testing how the App_Code directory works.");
writer.Write("Testing how the App_Code directory works.")
Refresh the WelcomeLabelTest.aspx page in your browser.
You will see that changes in the control are reflected in the page even though you did not compile the control.
In addition to the WelcomeLabel
control's Text
property that you explicitly defined, you can see from the control instance in the page that it has BackColor and ForeColor properties that you did not define. The WelcomeLabel
control gets these and other style-related properties by inheritance from the WebControl base class. In addition, WelcomeLabel
can be assigned a skin and be part of a theme without any work on your part.
Compiling the Control into an Assembly
Although the App_Code directory enables you to test your control without compiling it, if you want to distribute your control as object code to other developers, you must compile it. In addition, a control cannot be added to the toolbox of a visual designer unless it is compiled into an assembly.
To compile the control into an assembly
Set the Windows environment PATH variable of your computer to include the path to your .NET Framework installation by following these steps:
In Windows, right-click My Computer, select Properties, click the Advanced tab, and click the Environment Variables button.
In the System variables list, double-click the Path variable.
In the Variable value text box, add a semicolon (;) to the end of the existing values in the text box, and then type the path of your .NET Framework installation. The .NET Framework is generally installed in the Windows installation directory at \Microsoft.NET\Framework\versionNumber.
Click OK to close each dialog box.
Run the following command from the directory you created for source files in the first procedure of this walkthrough.
Note This is different from the App_Code directory where you copied the control's source file for testing.
csc /t:library /out:Samples.AspNet.CS.Controls.dll /r:System.dll /r:System.Web.dll *.cs
vbc /t:library /out:Samples.AspNet.VB.Controls.dll /r:System.dll /r:System.Web.dll *.vb
The /t:library compiler option tells the compiler to create a library instead of an executable assembly. The /out option provides a name for the assembly and the /r option lists the assemblies that are linked to your assembly.
To keep the example self-contained, this walkthrough asks you to create an assembly with a single control. In general, the .NET Framework design guidelines recommend that you do not create assemblies that contain only a few classes. For ease of deployment, you should create as few assemblies as possible.
Embedding an Icon in the Control Assembly
A visual designer such as Visual Studio 2005 typically uses a default icon (such as an image of a gear) to display a control in the toolbox. As an option for your control, you can customize the appearance of your control in the toolbox by embedding a 16-by-16-pixel bitmap in your control's assembly. By convention, visual designers use the lowermost left pixel of the bitmap as the transparent color.
To embed an icon in the control's assembly
Create or obtain a 16-by-16-pixel bitmap as the toolbox icon for your control.
Name the bitmap WelcomeLabel.bmp.
Add the bitmap file to the directory (CustomControlsCS or CustomControlsVB) where you have the source files for the
WelcomeLabel
control.Run the following command from the directory that contains the source files:
csc /res:WelcomeLabel.bmp,Samples.AspNet.CS.Controls.WelcomeLabel.bmp /t:library /out:Samples.AspNet.CS.Controls.dll /r:System.dll /r:System.Web.dll *.cs
vbc /res:WelcomeLabel.bmp,Samples.AspNet.VB.Controls.WelcomeLabel.bmp /t:library /out:Samples.AspNet.VB.Controls.dll /r:System.dll /r:System.Web.dll *.vb
This command compiles the control and embeds the bitmap in the assembly as a resource. You must name the embedded bitmap resource exactly the same as the namespace-qualified name of the control with which it is associated. For example, if the name of the control is
Samples.AspNet.CS.Controls.WelcomeLabel
, the name of the embedded bitmap must beSamples.AspNet.CS.Controls.WelcomeLabel.bmp
. This naming convention causes a visual designer to automatically use the bitmap as your control's toolbox icon. If you do not use the naming convention, you must apply the ToolboxBitmapAttribute to the control to specify the name of the embedded bitmap resource.
Using the TagPrefixAttribute to Provide a Tag Prefix/Namespace Mapping
Earlier in this walkthrough, when using the App_Code directory, you saw how a page developer can specify a tag prefix in the page or in the Web.config file. You can optionally suggest a default tag prefix that a visual designer should use for your control by including the assembly-level System.Web.UI.TagPrefixAttribute attribute. The TagPrefixAttribute attribute is useful because it provides a tag prefix for a visual designer to use if the designer does not find a tag prefix mapping in the Web.config file or in a Register directive in the page. The tag prefix is registered with the page the first time the control is double-clicked in the toolbox or dragged from the toolbox onto the page.
If you decide to use the TagPrefixAttribute attribute, you can specify it in a separate file that is compiled with your controls. By convention, the file is named AssemblyInfo.languageExtension, such as AssemblyInfo.cs or AssembyInfo.vb. The following procedure describes how to specify the TagPrefixAttribute metadata.
Note |
---|
If you do not specify the TagPrefixAttribute in the control's assembly, and the page developer does not specify the tag prefix/namespace mapping in the page or in the Web.config file, the visual designer might create a default tag prefix. For example, Visual Studio 2005 will create its own tag (such as |
To add a namespace/tag prefix mapping using the TagPrefixAttribute
Create a file named AssemblyInfo.cs or AssemblyInfo.vb in your source code directory and add the following code to the file.
using System; using System.Web.UI; [assembly: TagPrefix("Samples.AspNet.CS.Controls", "aspSample")]
Imports System Imports System.Web.UI <Assembly: TagPrefix("Samples.AspNet.VB.Controls", "aspSample")>
The tag prefix attribute creates a mapping between the namespace
Samples.AspNet.CS.Controls
orSamples.AspNet.VB.Controls
and the prefixaspSample
.Recompile all the source files using the compilation command you used earlier (with or without the embedded resource).
Using the Compiled Custom Control in an ASP.NET Page
To test the compiled version of your custom control, you must make your control's assembly accessible to pages in the Web site.
To make your control's assembly accessible to the Web site
Create a Bin directory under the root of the Web site.
Copy the control assembly (Samples.AspNet.CS.Controls.dll or Samples.AspNet.VB.Controls.dll) to the Bin directory.
Delete the control's source file from the App_Code directory.
If you do not delete the source files, your control's type will exist in both the compiled assembly and in the dynamically generated assembly created by ASP.NET. This will create an ambiguous reference when loading your control, and any page in which the control is used will generate a compiler error.
The assembly that you created in this walkthrough is called a private assembly because it must be placed in an ASP.NET Web site's Bin directory to enable pages in the Web site to use your control. The assembly cannot be accessed from other applications unless a copy is also installed with those applications. If you are creating controls for shared Web hosting applications, you will typically package your controls in a private assembly. However, if you create controls for use in a dedicated hosting environment or you create a suite of controls that an ISP makes available to all its customers, you might need to package your controls in a shared (strongly named) assembly that is installed in the global assembly cache. For more information, see Working with Assemblies and the Global Assembly Cache.
Next, you must modify the tag prefix mapping you created in Web.config to specify your control's assembly name.
To modify the tag prefix mapping in Web.config
Edit the Web.config file to add an assembly attribute to the an add tagPrefix element:
<controls> <add tagPrefix="aspSample" namespace="Samples.AspNet.CS.Controls" assembly="Samples.AspNet.CS.Controls"> </add> </controls>
<controls> <add tagPrefix="aspSample" namespace="Samples.AspNet.VB.Controls" assembly="Samples.AspNet.VB.Controls"> </add> </controls>
The assembly attribute specifies the name of the assembly the control is in. An add tagPrefix element maps a tag prefix to a namespace and assembly combination. When the assembly is dynamically generated by ASP.NET from source files in the App_Code directory, the assembly attribute is not necessary. When the assembly attribute is not used, ASP.NET loads the control's type from the assemblies dynamically generated from the App_Code directory.
To view the page that uses the custom control
Display the WelcomeLabelTest.aspx page in your browser by entering the following URL in the address bar:
https://localhost/ServerControlsTest/WelcomeLabelTest.aspx
If you use your control in a visual designer such as Visual Studio 2005, you will be able to add your control to the toolbox, drag it from the toolbox to the design surface, and access properties and events in the property browser. In addition, in Visual Studio 2005, your control has full IntelliSense support in Source view of the page designer and in the code editor. This includes statement completion in a script block as well as property browser support when a page developer clicks the control's tag.
Note |
---|
In many visual designers, you can add custom controls to the designer's toolbox. For details, consult the documentation for your designer. |
Next Steps
This walkthrough showed you how to develop a simple custom ASP.NET server control and use it in a page. You saw how to define a property and how to compile your control into an assembly. For more information, including information about rendering, defining properties, maintaining state, and implementing composite controls, see Developing Custom ASP.NET Server Controls.
In the walkthrough, you saw that you can provide a custom toolbox icon for your control. You also learned how you can add design-time metadata to customize property browser support for your control. Complex controls such as the GridView control further enhance their design-time experience by using visual designer classes that provide a different user interface at design time and at run time. The ASP.NET 2.0 designer object model for server controls is significantly different from the model provided in ASP.NET 1.0 and 1.1. To learn about implementing custom designer classes for your controls in ASP.NET 2.0, see ASP.NET Control Designers Overview.
In ASP.NET 2.0, a server control can define different behaviors for client browsers or devices by using adapter classes. For more information, see Developing Adapters for ASP.NET Server Controls
Different browsers, and different versions of the same browser, support different features. ASP.NET server controls will automatically determine the browser that has requested the .aspx page and format the HTML markup it generates correctly for that browser. However, some control features cannot be rendered on older browsers, so it is a good idea to look at the output of your pages on as many browser types as you can to make sure that the pages are presented to all browsers the way you want them to be. For more information, see ASP.NET Web Server Controls and Browser Capabilities.