Walkthrough: Developing and Using a Custom Web 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.

Prerequisites

In order to complete this walkthrough, you will need:

  • Visual Studio 2008 or Visual Web Developer

Creating the Server Control

The control you will create, WelcomeLabel, is a simple control that derives from the standard Label control. The WelcomeLabel class overrides some of the behavior of the Label control 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 example, if the page developer sets "Hello" as the value of the Text property and the user's name is present in the header, WelcomeLabel renders "Hello, userName". The WelcomeLabel class provides a NameForAnonymousUser property that enables a page developer to customize the message if the user's name is not present. For example, if the page developer sets "Guest" as the value of the NameForAnonymousUser property and the user's name is not present, WelcomeLabel displays "Hello, Guest". For more information about how to retrieve the user name, see User.

To create the code for the custom server control

  1. In Visual Studio or Visual Web Developer Express Edition, create a Web site and name it ServerControlsTest.

  2. Create an App_Code directory directly under the root directory of your Web site.

    ASP.NET dynamically compiles code included in the App_Code directory under an ASP.NET Web site's root. Classes in source files in App_Code can therefore be accessed from pages without being manually compiled into assemblies.

  3. In the App_Code directory, create a class named WelcomeLabel.cs or WelcomeLabel.vb.

  4. 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 Label
            < _
            Bindable(False), _
            Category("Appearance"), _
            Description("The text to use if the user is not authenticated."), _
            DefaultValue(""), _
            Localizable(True) _
            > _
            Public Property NameForAnonymousUser() As String 
                Get 
                    Dim s As String = CStr(ViewState("NameForAnonymousUser"))
                    If s Is Nothing Then s = String.Empty
                    Return s
                End Get 
                Set(ByVal value As String)
                    ViewState("NameForAnonymousUser") = 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 
                    Else 
                        If (NameForAnonymousUser <> String.Empty) Then
                            writer.Write(", ")
                            writer.Write(NameForAnonymousUser)
                        Else
                            writer.Write("!")
                        End If 
    
                    End If 
                End If 
    
            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 : Label
        {
    
            [
            Bindable(false),
            Category("Appearance"),
            Description("The text to use if the user is not authenticated."),
            DefaultValue(""),
            Localizable(true)
            ]
            public string NameForAnonymousUser
            {
                get
                {
                    string s = (string)ViewState["NameForAnonymousUser"];
                    return (s == null) ? String.Empty : s;
                }
                set
                {
                    ViewState["NameForAnonymousUser"] = 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]);
                        }
                    }
                    else
                    {
                        if (NameForAnonymousUser != String.Empty)
                        {
                            writer.Write(", ");
                            writer.Write(NameForAnonymousUser);
                        }
                        else
                        {
                            writer.Write("!");
                        }
                    }
                }
            }
        }
    }
    

Code Discussion

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 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.

From Label, WelcomeLabel inherits the Text property. WelcomeLabel overrides the RenderContents method to provide the logic for creating the text to display. 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.

The WelcomeLabel uses view state to store the value for the NameForAnonymousUser property. Using view state persists the value of NameForAnonymousUser across postbacks. On each postback, the page is recreated and the value is restored from view state. If the value was 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 by using a String key. In this case, "NameForAnonymousUser" 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.

Notice that WelcomeLabel makes successive calls to the Write method of the HtmlTextWriter object, 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 in 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 just-in-time (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. For more information, see Code Access Security. For more information about the JIT compiler, see Compiling MSIL to Native Code.

  • 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 attribute 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 so that child elements are interpreted as properties and properties are persisted as attributes.

The following attributes applied to the NameForAnonymousUser property of WelcomeLabel are standard design-time attributes that you will generally apply to 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, 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 be Bindable(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, 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 that you return from the property accessor (getter). In Visual Studio, the DefaultValueAttribute allows a page developer to reset a property value to its default by displaying 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 localized resources are being serialized. 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.

Design-time attributes applied to a control and its members do not affect the functionality of the control at run time. They 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.

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 an @ Register directive on each page that uses the custom control, or by specifying the tag prefix and namespace in the Web.config file. Adding the tag prefix and namespace in the Web.config is useful when the custom control is used in multiple Web pages in the Web site. In this walkthrough topic, you will add the tag prefix in the Web page. For more information about how to add the tag prefix in the Web.config file, see controls Element for pages (ASP.NET Settings Schema).

Note

When you do not provide a value for the assembly attribute in the @ Register directive, ASP.NET infers that the control is dynamically compiled from source files in the App_Code directory.

The following procedure shows how to register the custom control in an individual Web page.

To add a tag prefix mapping in a Web page

  • In the Default.aspx page, add the following code after the @ Page directive:

    [C#]

    <%@ Register TagPrefix="aspSample" 
            Namespace="Samples.AspNet.CS.Controls"%>
    

    [Visual Basic]

    <%@ Register TagPrefix="aspSample" 
            Namespace="Samples.AspNet.VB.Controls"%>
    

Creating a Page to Use the Control

You can now use the custom control in an ASP.NET Web page.

To create a page that uses the custom control

  1. Copy the following markup into the Default.aspx file and save the file.

    <head id="Head1" runat="server">
      <title>WelcomeLabel Test</title>
    </head>
    <body>
      <form id="form1" runat="server">
        <div>
          <aspSample:WelcomeLabel Text="Hello" NameForAnonymousUser="Guest" ID="WelcomeLabel1" 
            runat="server" BackColor="Wheat" ForeColor="SaddleBrown" />
        </div>
      </form>
    </body>
    
    <head id="Head1" runat="server">
      <title>WelcomeLabel Test</title>
    </head>
    <body>
      <form id="form1" runat="server">
        <div>
          <aspSample:WelcomeLabel Text="Hello" NameForAnonymousUser="Guest" ID="WelcomeLabel1" 
            runat="server" BackColor="Wheat" ForeColor="SaddleBrown" />
        </div>
      </form>
    </body>
    
  2. View Default.aspx in your browser.

In addition to the WelcomeLabel control's Text property that was explicitly defined in the markup that you copied, 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

The App_Code directory enables you to test your control without compiling it. However, 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.

You can provide a default tag prefix that a visual designer should use for your control by including the assembly-level System.Web.UI.TagPrefixAttribute attribute. 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 will create its own tag (such as cc1) for your control when the control is dragged from the toolbox.

To prepare the control for compiling and provide a default tag prefix

  1. Create a new directory named CustomControls that is not in the Web site.

  2. Create a file that is named AssemblyInfo.cs or AssemblyInfo.vb in the 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 TagPrefix attribute creates a mapping between the namespace Samples.AspNet.CS.Controls or Samples.AspNet.VB.Controls and the prefix aspSample.

  3. Copy the WelcomeLabel.cs or WelcomeLabel.vb file from App_Code to the CustomControls directory.

  4. Delete the WelcomeLabel.cs or WelcomeLabel.vb file in the App_Code folder of the Web site.

    If you do not delete the source files from the App_Code folder, your control's type will occur in both the compiled assembly and in the dynamically generated assembly created by ASP.NET. This will create an ambiguous reference when your control is loaded, and any page in which the control is used will generate a compiler error.

A visual designer such as Visual Studio 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 create a toolbox icon

  1. Create or obtain a 16-by-16-pixel bitmap as the toolbox icon for your control.

  2. Name the bitmap WelcomeLabel.bmp.

  3. Add the bitmap file to the CustomControls directory where you have the source files for the WelcomeLabel control.

When compiling the code, you embed the bitmap as a resource in the assembly.

To compile the control into an assembly and embed the icon

  1. Open the Visual Studio Command Prompt window. For more information, see Command-line Building With csc.exe.

  2. At the command line, switch to the directory that contains the custom control class files.

  3. Run the following command.

    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
    

    The /res compiler option embeds a resource in the assembly. In this example, the bitmap is embedded as a resource. You specify a name for the resource in the second parameter of the /res option. You must name the embedded bitmap resource 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 be Samples.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.

    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.

    Note

    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.

  4. When you have finished compiling the class files, close the Command line window.

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 available to pages in the Web site.

To make your control's assembly available to the Web site and add it to the toolbox

  1. Return to Visual Studio.

  2. Create a Bin directory under the root of the Web site.

  3. Right-click the Bin directory and select Add Existing Item.

  4. Navigate to the directory that contains the control assembly (Samples.AspNet.CS.Controls.dll or Samples.AspNet.VB.Controls.dll) and select the assembly.

  5. Right-click anywhere in the Toolbox and then click Add Tab.

    Add new tab.

  6. Name the tab "CustomControl".

    Name new tab.

  7. Right-click the CustomControl tab and then click Choose Items.

    Choose items for tab.

  8. Click Browse and select the assembly that you copied to the Bin folder.

    Select assembly to add.

    The WelcomeLabel control now appears in the Toolbox with the icon you created.

The assembly that you created in this walkthrough is referred to as a private assembly because it must be included 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 have 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.

The custom control is now available from the Toolbox.

To use the custom control from the Toolbox

  1. Delete Default.aspx to remove any code related to the custom control from the previous procedures.

  2. Create a new Web page called Default2.aspx.

  3. In Design view, drag the WelcomeLabel control from the Toolbox onto Default2.aspx.

  4. Set the Text property to "Hello".

  5. In Source view, notice that the @ Register directive was automatically added. The assembly attribute is set because the control no longer is in the App_Code folder.

  6. View Default.aspx in a Web browser.

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.

You saw that you can provide a custom toolbox icon for your control. You also learned how you can add attributes to customize property browser support for your control. Complex controls such as the GridView control additionally enhance their design-time experience by using visual designer classes that provide a different user interface at design time and at run time. To learn about how to implement custom designer classes for your controls, see ASP.NET Control Designers Overview.

See Also

Other Resources

Developing Custom ASP.NET Server Controls