Adding Design-Time Support to ASP.NET Controls

 

G. Andrew Duthie
Graymad Enterprises, Inc

October 2003

Applies to:
    Microsoft® ASP.NET
    Microsoft Visual Studio® .NET
    Microsoft .NET Framework

Summary: Learn how to build controls that take advantage of Microsoft Visual Studio .NET's design-time support, which makes them as easy to use as the built-in controls that come with Microsoft ASP.NET. (36 printed pages)

MSDNSamples\

Download DesignTimeSupportSample.msi.

This article was adapted from ASP.NET in a Nutshell, 2nd edition (ISBN: 0596001169), by G. Andrew Duthie and Matthew MacDonald, published by O'Reilly & Associates, Inc., 2003.

Contents

Introduction
Types of Design-Time Support
The Blog Control Sample
Adding Design-Time Support
Summary
Code Listings

Introduction

Microsoft® ASP.NET provides developers with one of the most powerful new tools in Web development: server controls. Server controls make it possible to create responsive, robust Web applications in a fraction of the time that it would take to create the equivalent application in classic ASP.

One of the key reasons for the increased productivity enabled by ASP.NET server controls is the rich design-time support for server controls in the Microsoft Visual Studio® .NET development environment. Developers can drag server controls from the Visual Studio .NET toolbox onto a page, access their properties through the Properties window, and take advantage of Microsoft IntelliSense® statement completion, both in the Visual Studio HTML editor, as well as in code-behind classes for ASP.NET pages. These design-time features bring to Web development the rapid application development (RAD) tools that Microsoft Visual Basic® developers have enjoyed for years.

ASP.NET also offers developers the ability to further increase their productivity by building their own custom server controls to encapsulate reusable chunks of user-interface specific code, such as login or registration forms. While developers are beginning to become aware of the power of developing their own custom controls, many developers may not be aware that they can harness the power of Visual Studio's design-time support in their controls as well, making them as easy to use as the built-in controls that come with ASP.NET. In this article, we'll describe the types of design-time support provided by the Microsoft .NET Framework and Visual Studio .NET, and demonstrate how developers can build controls that take advantage of that support.

Types of Design-Time Support

There are 5 distinct areas of design-time support for server controls in Visual Studio .NET. They are:

  • IntelliSense in codebehind classes
  • Property browser support in Design view
  • Toolbox support
  • Property browser support in HTML view
  • IntelliSense in the HTML editor

These areas of design-time support are provided by several different mechanisms. IntelliSense in codebehind is enabled by the IDE reading the metadata for your control to determine the properties and methods exposed by your control, as well as their types and parameters. You do not need to do anything to enable IntelliSense in a code-behind class beyond authoring and compiling your control, and placing its assembly in the bin subdirectory of the application from which it will be used.

Property browser support in the Design view of the Visual Studio .NET editor is provided through two means: the type associated with a property and/or any Metadata Attributes associated with the property. You add Metadata Attributes (from here on, we'll just refer to them as attributes) to your code, for example, to identify the category of the property, provide a description for the property, and specify a preferred editor, if desired. Properties with certain types, such as System.Drawing.Color, are automatically mapped to the appropriate editor in Visual Studio .NET.

IntelliSense and Property browser support in the HTML view of Visual Studio .NET are provided through the use of an XSD schema that describes the types associated with the control, and which uses text decorations called Visual Studio Annotations to specify preferred editor and other preferences for the control.

Finally, you can provide support for dragging and dropping your control from the Visual Studio .NET toolbox through a combination of attributes and a custom bitmap with specific properties.

The Blog Control Sample

The control that we'll use to demonstrate the design-time features in Visual Studio .NET is called the Blog control, and is shown in Listing 1 at the end of this article. The control provides simple Web log functionality using XML as a storage medium. A Web log—or blog, as they're commonly called—is essentially a Web page where a person posts regular observations or commentary about their lives, the world, politics, or anything else on their mind. Blog entries are added through a Web browser.

The Blog control is fairly straightforward, and uses control composition to render output to the browser. In compositional controls, the CreateChildControls method (which is called automatically by the ASP.NET runtime) is overridden, and in that method, we create the controls that will constitute the UI of the custom control, and add them to the Controls collection of the control. In addition, the control contains logic for displaying and adding blogs, as well as for creating an XML blog storage file if none exists. The control exposes several public properties that developers will set at design-time, including the URL for the page that the control will redirect to when a new blog is added, the email address to be associated with new blogs, the mode of the control (display or add), and the color of the separator line between each blog entry. Figure 1 shows the blog control in action. The Add Blog hyperlinks are provided by ASP.NET Hyperlink controls, and are separate from the Blog control. The code for BlogClient.aspx is shown in Listing 2. The codebehind class for BlogClient.aspx is shown in Listing 3, and provides the logic for changing the blog mode when the "Add Blog" link is clicked.

Aa478960.aspnet-adddesigntimesupport-01(en-us,MSDN.10).gif

Figure 1. The Blog control at runtime

Figure 2 shows the appearance of the basic Blog control at design time. Note that the properties are listed, but not categorized.

Aa478960.aspnet-adddesigntimesupport-02(en-us,MSDN.10).gif

Figure 2. The Blog control at design time

Adding Design-Time Support

While it's fairly simple to use the Blog control in a Web forms page, it's still not 100% intuitive. For example, without documentation, there's no way for someone using the Blog control to know that the only appropriate values for the Mode property are Display or Add. Without explicitly telling developers using the control about the Add mode, it would be difficult for them to discover and make use of this mode on their own.

For developers using Visual Studio .NET (or another IDE that supports IntelliSense), you can solve this problem by adding design-time support to the control. This is done using a combination of the techniques described earlier in this article. Part of the challenge of providing design-time support for custom server controls is the variety of techniques necessary to fully support design-time functionality in a custom control. The simplest, requiring no additional coding, is IntelliSense statement completion in codebehind, which is shown in Figure 3 for BlogClient.aspx.vb.

Aa478960.aspnet-adddesigntimesupport-03(en-us,MSDN.10).gif

Figure 3. IntelliSense in code-behind

Unfortunately, automatic support for statement completion does not extend to the Design or HTML views when editing Web forms pages, nor does Visual Studio provide built-in support for viewing and editing properties in the Property browser without some additional work in your control. To complicate things further, one technique is necessary for supporting IntelliSense in the Property browser and Design view of the Web forms editor, while another is necessary for supporting it in the HTML view of the Web forms editor.

The technique required for supporting property browsing in Design view uses attributes to inform Visual Studio .NET about how to handle the properties. Supporting statement completion and property browsing in HTML view requires creating a custom XSD schema that describes the types in your control. We'll discuss both these techniques in the next sections.

Design View and Metadata Attributes

Visual Studio .NET provides rich support for designing and modifying controls visually using drag-and-drop techniques, as well as tools, such as the Property browser, and related designers, such as the color picker. Support for these tools is provided by a series of attributes that you can add to your control. These attributes tell the Visual Studio IDE whether to display any properties your control exposes in the Properties browser, what type the properties are, and which designer should be used to set the properties' values.

For the version of the control that will provide design-time support, we'll make a copy of the control file Blog.vb named Blog_DT.vb, and make our modifications to this file. This allows us to create a design-time version of the control, and still have the original control for comparison.

To support editing of the AddRedirect property in the Property browser, we would add the following attributes before the Property procedure, as shown in the following code snippet:

<Browsable(True), _
Category("Behavior"), _
Description("URL to which the page should " & _
   "redirect after successful submission of a " & _
   "new Blog entry."), _
Editor("System.Web.UI.Design.UrlEditor", _
   GetType(UITypeEditor))> _
Public Property AddRedirect() As String
   'Property procedure code
End Property

These attribute declarations allow the property to be displayed in the Property browser, set the desired category for the property (when properties are sorted by category), provide a description of the property, and tell Visual Studio .NET to use the UrlEditor class to edit the property's value, as shown in Figure 4.

Aa478960.aspnet-adddesigntimesupport-04(en-us,MSDN.10).gif

Figure 4. Property support in Design view

The attribute syntax shown in this section is for Visual Basic .NET. In Visual Basic .NET, attributes are declared with the following syntax:

<AttributeName(AttributeParams)>

In C#, attributes take the form:

[AttributeName(AttributeParams)]

Visual Basic .NET requires that the attribute declaration appear on the same line as the member it's modifying, so it's usually a good idea to follow the attribute with a Visual Basic line continuation character to improve readability:

<AttributeName(AttributeParams)> _
Public Membername()

In both C# and Visual Basic, you can declare multiple attributes within a single set of [ ] or <> brackets by separating multiple attributes with commas, although in Visual Basic .NET, if they appear on separate lines, you must use the Visual Basic line continuation character to continue the attributes as a single statement.

Adding Toolbox Support

In addition to setting attributes at the property level, you can also set certain attributes at the class and assembly levels. For example, you can use the assembly-level attribute TagPrefix to specify the tag prefix to use for any controls contained in the assembly. Visual Studio .NET then automatically inserts this tag prefix when you add an instance of the control to a Web forms page from the Visual Studio toolbox. The following code snippet shows the syntax for the TagPrefix attribute. This attribute should be placed within the class module that defines the control, but outside the class and namespace declarations (note that in Visual Basic .NET projects, the namespace is defined at the project level, so you do not need to worry about placing the Assembly attributes outside of the Namespace declaration). In the following attribute the first parameter of the TagPrefix attribute is the namespace of the control, and the second is the text you wish to use for the tag prefix itself.

<Assembly: TagPrefix("BlogControl", "BlogControl")>

To complete the integration of a control in the Visual Studio .NET environment, add the ToolBoxData attribute (which tells Visual Studio .NET your preferred tag name for controls inserted from the toolbox) to the class that implements the control:

<ToolboxData("<{0}:Blog_DT runat=server></{0}:Blog_DT>")> _
Public Class Blog_DT
   Inherits Panel
   Implements INamingContainer
   'control implementation
End Class

When the control is inserted into a page from the toolbox, the{0}placeholders will be replaced by the tag prefix specified by the TagPrefix attribute, while the rest of the text will be inserted literally.

You can also provide your own custom icon for the control to be displayed in the toolbox. To do so, create a 16 x 16 pixel bitmap (the color of the lower left pixel is used as the transparency color) with the same name as the class containing the control (in other words, classname.bmp). Add the bitmap to the project using the Add Existing Item command, and use the property browser to set its Build Action to "Embedded Resource" as shown in Figure 5.

Aa478960.aspnet-adddesigntimesupport-05(en-us,MSDN.10).gif

Figure 5. Setting the Build Action

Once compiled, the control will support automatic insertion of the @Register directive, tag prefix, and tag name for the Blog control when the control is added to a page from the toolbox, and will display the custom icon in the toolbox, as shown in Figure 6. To add the control to the Visual Studio .NET toolbox, follow these simple steps:

  1. In Design view, select the Web forms tab of the Visual Studio .NET toolbox.
  2. Right-click anywhere in the tab and select Add/Remove Items (Customize Toolbox in Visual Studio .NET 2002).
  3. Select the .NET Framework Components tab, and then click Browse.
  4. Browse to the location of the compiled control assembly, select it, and click Open.
  5. Click OK.

Aa478960.aspnet-adddesigntimesupport-06(en-us,MSDN.10).gif

Figure 6. Custom control in the Toolbox

Once the control has been added to the toolbox, you can add it to a Web forms page by either double-clicking the control, or dragging it from the Toolbox onto the Web forms page. In either case, Visual Studio .NET will automatically insert the correct @Register directive, including setting the TagPrefix based on the assembly-level attribute, and will also create a set of tags for the control with the tag name specified in the ToolBoxData attribute.

Adding a Designer

As written, the Blog control will not have any visible interface in the Design view of the Web forms editor. This can make it more difficult to select the control on the page, and also may make it more difficult to understand what the control will look like at runtime. To correct this, we can add support for a designer that will render HTML at design time that approximates the look of the Blog control at runtime. Note that you can also create designers that completely reproduce the runtime output of a control, but this is more involved and beyond the scope of this article.

All server control designers derive from the class System.Web.UI.Design.ControlDesigner, which exposes a number of methods you can override to provide design-time rendering for your control. The following code simply overrides the GetDesignTimeHtml method to return some simple HTML to be displayed at design time. Note that the example shows the entire designer class for the Blog control, which you can simply add to the existing Blog_DT.vb class file.

Public Class BlogDesigner
   Inherits ControlDesigner

   Public Overrides Function GetDesignTimeHtml() As String
      Return "<h1>Blog</h1><hr/><hr/>"
   End Function

End Class

To tie this designer into the Blog_DT class, we use the Designer attribute, as shown in the following snippet. Note that this code also adds a Description attribute that describes what the control does.

<Description("Simple Blog control. Supports display " & _
   "of Web log / news items from an XML file."), _
Designer("BlogControl.BlogDesigner"), _
ToolboxData("<{0}:Blog_DT runat=server></{0}:Blog_DT>")> _
Public Class Blog_DT
   Inherits Panel
   Implements INamingContainer

As you can see, the BlogDesigner class is extremely simple, but it adds a lot to the control's design-time appearance on a Web forms page, as shown in Figure 7.

Aa478960.aspnet-adddesigntimesupport-07(en-us,MSDN.10).gif

Figure 7. Adding design-time rendering

Listing 4 shows the code for the Blog control, updated with attributes to enable design-time support for the control in Design view and the Property browser. Note that the example adds several using directives to import the namespaces needed to support the attributes and designer classes we've used. The new listing also adds an enumeration to be used for the value of the Mode property.

HTML View Support: Custom Schemas and Visual Studio Annotations

As much as the attributes described in the previous section help in providing support for the Blog control at design-time, they're missing one important piece: IntelliSense support for adding tags and attributes in the HTML view of the Web forms editor. For developers who are more comfortable working in HTML rather than in WYSIWYG style, this is a significant oversight.

Since the HTML view of the Web forms editor uses XSD schemas to determine what elements and attributes to make available in a Web forms page, in order to correct the oversight, we need to provide an XSD schema that describes the Blog control and the attributes that it supports. Optionally, we can add annotations to the schema that tell Visual Studio .NET about the various elements and how we'd like them to behave.

Listing 5 contains the portion of the XSD schema specific to the Blog control. The actual schema file (which is available in the sample code for this article) also contains type definitions for the Panel control, from which the Blog_DT control is derived, as well as other necessary attribute and type definitions. These definitions were copied from the asp.xsd schema file created for the built-in ASP.NET server controls.

Note that you should never modify the asp.xsd schema file directly, but rather copy any necessary type or attribute definitions to your custom schema file. While this may seem redundant, if you edit asp.xsd directly and a later installation or service pack for the .NET Framework overwrites this file, your custom schema entries would be lost.

In Listing 5, note the targetNamespace and xmlns attributes on the root schema element, which define the XML namespace for the control's schema. The value of the targetNamespace and xmlns attributes will also be used as an attribute in your Web forms page to "wire up" the schema. The <xsd:element> tag defines the root Blog_DT element. The <xsd:complexType> tag defines the attributes for the Blog_DT element, which includes the Web control attributes referenced by the <xsd:attributeGroup> tag. Finally, the <xsd:simpleType> tag defines the enumeration for the BlogMode type used as one of the attributes for the Blog_DT element.

Note that Listing 5 uses the vs:builder annotation to tell Visual Studio .NET to use the URL builder for the AddRedirect attribute and the Color builder for the SeparatorColor attribute. The vs:builder annotation is one of a number of annotations available to modify schemas. The most commonly-used annotations are listed in Table 1.

Table 1. Common Visual Studio .NET Annotations

Annotation Purpose Valid Values
vs:absolutepositioning Used at the root <schema> element to determine whether Visual Studio may insert style attributes for positioning. true or false
vs:blockformatted Indicates whether leading white space may be added to the element during automatic formatting. true or false
vs:builder Specifies the builder to be used for editing the related property's value. color, style, or url
vs:deprecated Allows a related property to be marked as "deprecated," which prevents it from showing up in the Properties browser and in statement completion. true or false
vs:empty Used at the element level to indicate that Visual Studio .NET should use single tag syntax for the related tag (no end tag). true or false
vs:friendlyname Used at the root level to provide a display name for the schema.  
vs:iscasesensitive Used at the root level, specifies whether Visual Studio .NET will treat the related tags in a case-sensitive manner. true or false
vs:ishtmlschema Used at the root level, specifies whether the schema is an HTML document schema. true or false
vs:nonbrowseable Used at the attribute level, specifies that the attribute should not appear in statement completion. true or false
vs:readonly Used at the attribute level, specifies that the attribute may not be modified in the Properties window. true or false
vs:requireattributequotes Used at the root level, specifies that the attribute values must have quotes. true or false

Once you've built your XSD schema, save it to the same location as the asp.xsd file (which defaults to C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Packages\schemas\xml\ for Visual Studio .NET 2003).

To allow Visual Studio .NET to read your custom schema, you'll need to add an xmlns attribute to the <body> tag of the page in which you wish to use the schema, as shown in the following snippet:

<body xmlns:BlogControl="urn:http://www.aspnetian.com/schemas">

Notice that this code uses the BlogControl prefix with the xmlns attribute to specify that the schema is for controls prefixed with the BlogControl tag prefix, which, you may recall, is set up for us by the TagPrefix attribute (described in the previous section on "Metadata attributes"). The value of the xmlns attribute should be the same as the targetNamespace attribute defined at the root of the schema.

Once you've wired up your schema via the xmlns attribute, you should be able to type an opening "<" character, and have the Blog control appear as one of the options for statement completion, as shown in Figure 8. At this point, you should also get statement completion for the defined properties as well, including the allowed values for the Mode property, and the builders specified by the annotations in the XSD file.

Aa478960.aspnet-adddesigntimesupport-08(en-us,MSDN.10).gif

Figure 8. Statement completion in HTML view

Summary

In this article, we've seen the design-time support available in Visual Studio .NET for ASP.NET server controls, and demonstrated how developers can take advantage of this support in their own custom controls. While it's relatively straightforward to add design-time support to your controls, it does require mastering several different techniques to fully take advantage of these features. One area that is particularly lacking is that of wiring up custom XSD schemas to the page. At the time of this writing, there is no built-in support for automatically adding the xmlns attribute necessary for connecting the page to the XSD schema for the control. As a result, it is still necessary to add this attribute manually. Hopefully in future releases of Visual Studio .NET, this process will be automated.

The sample code for this article contains a Visual Studio .NET project for both the basic and design-time support versions of the Blog control, as well as a client project that demonstrates the use of each control. To run the BlogControlClient project, you will need to create a new virtual directory in IIS named BlogControlClient, and map it to the location on your hard drive where you save the BlogControlClient project folder.

My thanks to Rob Caron from the Microsoft Visual Studio .NET team for his assistance in working out the process of creating and wiring up the custom XSD schema.

About the author

G. Andrew Duthie is the founder and principal of Graymad Enterprises, Inc., providing training and consulting in Microsoft Web development technologies. Andrew has been developing multi-tier Web applications since the introduction of Active Server Pages. He has written numerous books on ASP.NET including: Microsoft ASP.NET Step By Step, Microsoft ASP.NET Programming with Microsoft Visual Basic, and ASP.NET in a Nutshell. Andrew is a frequent speaker at events including Software Development, the Dev-Connections family of conferences, Microsoft Developer Days, and VSLive! He also speaks at .NET user groups as a member of the International .NET Association (INETA) Speaker's Bureau. You can find out more about Andrew at his company's Web site, Graymad Enterprises, Inc..

This article was adapted from ASP.NET in a Nutshell, 2nd edition (ISBN: 0596001169), by G. Andrew Duthie and Matthew MacDonald, published by O'Reilly & Associates, Inc., 2003.

Code Listings

Listing 1. Blog.vb

'supports Color structure
Imports System.Drawing
'supports StreamWriter type
Imports System.IO
Imports System.Web.UI
'supports use of HTML Controls
Imports System.Web.UI.HtmlControls
'supports use of Web Controls
Imports System.Web.UI.WebControls

Public Class Blog
   Inherits Panel
   Implements INamingContainer

   Protected BlogDS As DataSet
   Protected TitleTB As TextBox
   Protected BlogText As TextBox

   Private _addRedirect As String
   Private _email As String
   Private _mode As String
   Private _separatorColor As Color = Color.Black

   Public Property AddRedirect() As String
      Get
         Return Me._addRedirect
      End Get
      Set(ByVal Value As String)
         Me._addRedirect = Value
      End Set
   End Property

   Public Property Email() As String
      Get
         Return Me._email
      End Get
      Set(ByVal Value As String)
         Me._email = Value
      End Set
   End Property

   Public Property Mode() As String
      Get
         Return Me._mode
      End Get
      Set(ByVal Value As String)
         Me._mode = Value
      End Set
   End Property

   Public Property SeparatorColor() As Color
      Get
         Return Me._separatorColor
      End Get
      Set(ByVal Value As Color)
         Me._separatorColor = Value
      End Set
   End Property

   Protected Overrides Sub OnInit(ByVal e As EventArgs)
      LoadData()
      MyBase.OnInit(e)
   End Sub

   Protected Overrides Sub CreateChildControls()
      If Not Me._mode = "Add" Then
         DisplayBlogs()
      Else
         NewBlog()
      End If
   End Sub

   Protected Sub LoadData()
      BlogDS = New DataSet()

      Try
         BlogDS.ReadXml(Page.Server.MapPath("Blog.xml"))
      Catch fnfEx As FileNotFoundException
         CreateBlankFile()
         LoadData()
      End Try
   End Sub

   Protected Sub DisplayBlogs()
      Dim BlogDate As DateTime
      Dim CurrentDate As DateTime = New DateTime()
      Dim BlogRows As DataRowCollection = _
         BlogDS.Tables(0).Rows
      Dim BlogDR As DataRow
      For Each BlogDR In BlogRows
         Dim BDate As String = BlogDR("date").ToString()
         BlogDate = New DateTime _
            (Convert.ToInt32(BDate.Substring(4, 4)), _
            Convert.ToInt32(BDate.Substring(0, 2)), _
            Convert.ToInt32(BDate.Substring(2, 2)))

         If Not CurrentDate = BlogDate Then
            Dim TempDate As Label = New Label()
            TempDate.Text = BlogDate.ToLongDateString()
            TempDate.Font.Size = FontUnit.Large
            TempDate.Font.Bold = True
            Me.Controls.Add(TempDate)
            Me.Controls.Add _
               (New LiteralControl("<br/><br/>"))
            CurrentDate = BlogDate
         End If

         Dim Anchor As HtmlAnchor = New HtmlAnchor()
         Anchor.Name = "#" & BlogDR("anchorID").ToString()
         Me.Controls.Add(Anchor)

         Dim Title As Label = New Label()
         Title.Text = BlogDR("title").ToString()
         Title.Font.Size = FontUnit.Larger
         Title.Font.Bold = True
         Me.Controls.Add(Title)

         Me.Controls.Add(New LiteralControl("<p>"))
         Dim BlogText As LiteralControl = _
            New LiteralControl("<div>" & _
            BlogDR("text").ToString() & "</div>")
         Me.Controls.Add(BlogText)
         Me.Controls.Add(New LiteralControl("</p>"))

         Dim Email As HyperLink = New HyperLink()
         Email.NavigateUrl = "mailto:" & _
            BlogDR("email").ToString()
         Email.Text = "E-mail me"
         Me.Controls.Add(Email)

         Me.Controls.Add(New LiteralControl(" | "))
         Dim AnchorLink As HyperLink = New HyperLink()
         AnchorLink.NavigateUrl = _
            Page.Request.Url.ToString() & "#" & _
            BlogDR("anchorID").ToString()
         AnchorLink.Text = "Link"
         Me.Controls.Add(AnchorLink)

         Me.Controls.Add(New _
            LiteralControl("<hr color='" & _
            ColorTranslator.ToHtml(_separatorColor) & _
            "' width='100%'/><br/>"))
      Next
   End Sub

   Protected Sub NewBlog()
      Dim Title As Label = New Label()
      Title.Text = "Create New Blog"
      Title.Font.Size = FontUnit.Larger
      Title.Font.Bold = True
      Me.Controls.Add(Title)

      Me.Controls.Add(New LiteralControl("<br/><br/>"))

      Dim TitleLabel As Label = New Label()
      TitleLabel.Text = "Title: "
      TitleLabel.Font.Bold = True
      Me.Controls.Add(TitleLabel)
      TitleTB = New TextBox()
      Me.Controls.Add(TitleTB)

      Me.Controls.Add(New LiteralControl("<br/>"))

      Dim BlogTextLabel As Label = New Label()
      BlogTextLabel.Text = "Text: "
      BlogTextLabel.Font.Bold = True
      Me.Controls.Add(BlogTextLabel)
      BlogText = New TextBox()
      BlogText.TextMode = TextBoxMode.MultiLine
      BlogText.Rows = 10
      BlogText.Columns = 40
      Me.Controls.Add(BlogText)

      Me.Controls.Add(New LiteralControl("<br/>"))

      Dim Submit As Button = New Button()
      Submit.Text = "Submit"
      AddHandler Submit.Click, AddressOf Me.Submit_Click
      Me.Controls.Add(Submit)
   End Sub

   Protected Sub Submit_Click(ByVal Sender As Object, _
      ByVal e As EventArgs)
      EnsureChildControls()
      AddBlog()
   End Sub

   Protected Sub AddBlog()
      Dim NewBlogDR As DataRow
      NewBlogDR = BlogDS.Tables(0).NewRow()
      NewBlogDR("date") = FormatDate(DateTime.Today)
      NewBlogDR("title") = TitleTB.Text
      NewBlogDR("text") = BlogText.Text
      NewBlogDR("anchorID") = Guid.NewGuid().ToString()
      NewBlogDR("email") = _email
      BlogDS.Tables(0).Rows.InsertAt(NewBlogDR, 0)
      BlogDS.WriteXml(Page.Server.MapPath("Blog.xml"))
      Page.Response.Redirect(_addRedirect)
   End Sub

   Protected Function FormatDate(ByVal dt As DateTime) _
      As String
      Dim retString As String
      retString = String.Format("{0:D2}", dt.Month)
      retString &= String.Format("{0:D2}", dt.Day)
      retString &= String.Format("{0:D2}", dt.Year)
      Return retString
   End Function

   Public Sub CreateBlankFile()
      Dim NewXml As StreamWriter = _
         File.CreateText(Page.Server.MapPath("Blog.xml"))

      NewXml.WriteLine("<blogs>")
      NewXml.WriteLine _
         ("   <!-- blog field describes a single blog -->")
      NewXml.WriteLine("   <blog>")
      NewXml.WriteLine("      <!-- date field contains" & _
         " the creation date of the blog -->")
      NewXml.WriteLine("      <date>" & _
         FormatDate(DateTime.Today) & "</date>")
      NewXml.WriteLine _
         ("      <title>Temporary Blog</title>")
      NewXml.WriteLine("      <!-- text field " & _
         "should contain the blog text, including any " & _
         "desired HTML tags -->")
      NewXml.WriteLine("      <text>This entry " & _
         "indicates that the file blog.xml was not " & _
         "found. A default version of this file has " & _
         "been created for you. You can modify the " & _
         "fields in this file as desired. If you set " & _
         "the Blog control to add mode (add the " & _
         "attribute mode='add' to the control's " & _
         "declaration), the control will " & _
         "automatically populate the XML file when " & _
         "you submit the form.</text>")
      NewXml.WriteLine("      <!-- anchorID field " & _
         "will be autopopulated by the control -->")
      NewXml.WriteLine("      <anchorID></anchorID>")
      NewXml.WriteLine("      <!-- email field should" & _
         " contain the email address for feedback -->")
      NewXml.WriteLine("      <email>change this to a " & _
         "valid email address</email>")
      NewXml.WriteLine("   </blog>")
      NewXml.WriteLine("</blogs>")
      NewXml.Close()
   End Sub

End Class

Listing 2. BlogClient.aspx

<%@ Register TagPrefix="cc1" Namespace="BlogControl"
   Assembly="BlogControl" %>
<%@ Page Language="vb" AutoEventWireup="false"
   Codebehind="BlogClient.aspx.vb"
   Inherits="BlogControlClient.WebForm1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0
   Transitional//EN">
<html>
  <head>
   <title>Blog Client</title>
  </head>
<body>
   <form id=Form1 method=post runat="server">
      <p><asp:hyperlink id=Link1 
            navigateurl="BlogClient.aspx?mode=add"
            runat="server">Add Blog</asp:hyperlink></p>
      <cc1:blog 
         id=Blog1 
         Email="andrew@graymad.com" 
         AddRedirect="BlogClient.aspx" 
         SeparatorColor="LawnGreen"
         runat="server"></cc1:blog>
      <p><asp:hyperlink id=Link2 
            navigateurl="BlogClient.aspx?mode=add"
            runat="server">Add Blog</asp:hyperlink></p>
   </form>
</body>
</html>

Listing 3. BlogClient.aspx.vb

Imports BlogControl

Public Class WebForm1
    Inherits System.Web.UI.Page
      Protected WithEvents Link1 As _
         System.Web.UI.WebControls.HyperLink
      Protected WithEvents Link2 As _
         System.Web.UI.WebControls.HyperLink
      Protected WithEvents Blog1 As BlogControl.Blog

   Private Sub Page_Load(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Load
      If Request.QueryString("mode") = "add" Then
         Blog1.Mode = "Add"
         Link1.Visible = False
         Link2.Visible = False
      Else
         Blog1.Mode = "Display"
         Link1.Visible = True
         Link2.Visible = True
      End If
   End Sub

End Class

Listing 4. Blog_DT.vb

'supports design-time attributes
Imports System.ComponentModel
'supports Color structure
Imports System.Drawing
'supports UITypeEditor type
Imports System.Drawing.Design
'supports StreamWriter type
Imports System.IO
Imports System.Web.UI
'supports ControlDesigner type
'  note that you must add a reference to the 
'  Assembly System.Design to import this namespace
Imports System.Web.UI.Design
'supports use of HTML Controls
Imports System.Web.UI.HtmlControls
'supports use of Web Controls
Imports System.Web.UI.WebControls

<Assembly: TagPrefix("BlogControl", "BlogControl")> 

Public Enum BlogMode
   Add
   Display
End Enum

<Description("Simple Blog control. Supports display " & _
   "of Web log / news items from an XML file."), _
Designer("BlogControl.BlogDesigner"), _
ToolboxData("<{0}:Blog_DT runat=server></{0}:Blog_DT>")> _
Public Class Blog_DT
   Inherits Panel
   Implements INamingContainer

   Protected BlogDS As DataSet
   Protected TitleTB As TextBox
   Protected BlogText As TextBox

   Private _addRedirect As String
   Private _email As String
   Private _mode As BlogMode
   Private _separatorColor As Color = Color.Black

   <Browsable(True), _
   Category("Behavior"), _
   Description("URL to which the page should " & _
      "redirect after successful submission of a " & _
      "new Blog entry."), _
   Editor("System.Web.UI.Design.UrlEditor", _
      GetType(UITypeEditor))> _
   Public Property AddRedirect() As String
      Get
         Return Me._addRedirect
      End Get
      Set(ByVal Value As String)
         Me._addRedirect = Value
      End Set
   End Property

   <Browsable(True), _
   Category("Behavior"), _
   Description("Email address the control will use " & _
      "for listing in new Blog entries.")> _
   Public Property Email() As String
      Get
         Return Me._email
      End Get
      Set(ByVal Value As String)
         Me._email = Value
      End Set
   End Property

   <Browsable(True), _
   Category("Behavior"), _
   Description("Controls whether existing Blogs are " & _
      "displayed, or fields for creating a new Blog " & _
      "entry.")> _
   Public Property Mode() As BlogMode
      Get
         Return Me._mode
      End Get
      Set(ByVal Value As BlogMode)
         Me._mode = Value
      End Set
   End Property

   <Browsable(True), _
   Category("Appearance"), _
   Description("Controls the color of the line that " & _
      "separates Blog entries when in display mode.")> _
   Public Property SeparatorColor() As Color
      Get
         Return Me._separatorColor
      End Get
      Set(ByVal Value As Color)
         Me._separatorColor = Value
      End Set
   End Property

   Protected Overrides Sub OnInit(ByVal e As EventArgs)
      LoadData()
      MyBase.OnInit(e)
   End Sub

   Protected Overrides Sub CreateChildControls()
      If Not Me._mode = BlogMode.Add Then
         DisplayBlogs()
      Else
         NewBlog()
      End If
   End Sub

   Protected Sub LoadData()
      BlogDS = New DataSet()

      Try
         BlogDS.ReadXml(Page.Server.MapPath("Blog.xml"))
      Catch fnfEx As FileNotFoundException
         CreateBlankFile()
         LoadData()
      End Try
   End Sub

   Protected Sub DisplayBlogs()
      Dim BlogDate As DateTime
      Dim CurrentDate As DateTime = New DateTime()

      Dim BlogRows As DataRowCollection = _
         BlogDS.Tables(0).Rows
      Dim BlogDR As DataRow
      For Each BlogDR In BlogRows
         Dim BDate As String = BlogDR("date").ToString()
         BlogDate = New DateTime _
            (Convert.ToInt32(BDate.Substring(4, 4)), _
            Convert.ToInt32(BDate.Substring(0, 2)), _
            Convert.ToInt32(BDate.Substring(2, 2)))

         If Not CurrentDate = BlogDate Then
            Dim TempDate As Label = New Label()
            TempDate.Text = BlogDate.ToLongDateString()
            TempDate.Font.Size = FontUnit.Large
            TempDate.Font.Bold = True
            Me.Controls.Add(TempDate)
            Me.Controls.Add _
               (New LiteralControl("<br/><br/>"))
            CurrentDate = BlogDate
         End If

         Dim Anchor As HtmlAnchor = New HtmlAnchor()
         Anchor.Name = "#" + BlogDR("anchorID").ToString()
         Me.Controls.Add(Anchor)

         Dim Title As Label = New Label()
         Title.Text = BlogDR("title").ToString()
         Title.Font.Size = FontUnit.Larger
         Title.Font.Bold = True
         Me.Controls.Add(Title)

         Me.Controls.Add(New LiteralControl("<p>"))
         Dim BlogText As LiteralControl = _
            New LiteralControl("<div>" & _
            BlogDR("text").ToString() & "</div>")
         Me.Controls.Add(BlogText)
         Me.Controls.Add(New LiteralControl("</p>"))

         Dim Email As HyperLink = New HyperLink()
         Email.NavigateUrl = "mailto:" & _
            BlogDR("email").ToString()
         Email.Text = "E-mail me"
         Me.Controls.Add(Email)

         Me.Controls.Add(New LiteralControl(" | "))
         Dim AnchorLink As HyperLink = New HyperLink()
         AnchorLink.NavigateUrl = _
            Page.Request.Url.ToString() & "#" & _
            BlogDR("anchorID").ToString()
         AnchorLink.Text = "Link"
         Me.Controls.Add(AnchorLink)

         Me.Controls.Add _
            (New LiteralControl("<hr color='" & _
            ColorTranslator.ToHtml(_separatorColor) & _
            "' width='100%'/><br/>"))
      Next
   End Sub

   Protected Sub NewBlog()
      Dim Title As Label = New Label()
      Title.Text = "Create New Blog"
      Title.Font.Size = FontUnit.Larger
      Title.Font.Bold = True
      Me.Controls.Add(Title)

      Me.Controls.Add(New LiteralControl("<br/><br/>"))

      Dim TitleLabel As Label = New Label()
      TitleLabel.Text = "Title: "
      TitleLabel.Font.Bold = True
      Me.Controls.Add(TitleLabel)
      TitleTB = New TextBox()
      Me.Controls.Add(TitleTB)

      Me.Controls.Add(New LiteralControl("<br/>"))

      Dim BlogTextLabel As Label = New Label()
      BlogTextLabel.Text = "Text: "
      BlogTextLabel.Font.Bold = True
      Me.Controls.Add(BlogTextLabel)
      BlogText = New TextBox()
      BlogText.TextMode = TextBoxMode.MultiLine
      BlogText.Rows = 10
      BlogText.Columns = 40
      Me.Controls.Add(BlogText)

      Me.Controls.Add(New LiteralControl("<br/>"))

      Dim Submit As Button = New Button()
      Submit.Text = "Submit"
      AddHandler Submit.Click, AddressOf Me.Submit_Click
      Me.Controls.Add(Submit)
   End Sub

   Protected Sub Submit_Click(ByVal Sender As Object, _
      ByVal e As EventArgs)
      EnsureChildControls()
      AddBlog()
   End Sub

   Protected Sub AddBlog()
      Dim NewBlogDR As DataRow
      NewBlogDR = BlogDS.Tables(0).NewRow()
      NewBlogDR("date") = FormatDate(DateTime.Today)
      NewBlogDR("title") = TitleTB.Text
      NewBlogDR("text") = BlogText.Text
      NewBlogDR("anchorID") = Guid.NewGuid().ToString()
      NewBlogDR("email") = _email
      BlogDS.Tables(0).Rows.InsertAt(NewBlogDR, 0)
      BlogDS.WriteXml(Page.Server.MapPath("Blog.xml"))
      Page.Response.Redirect(_addRedirect)
   End Sub

   Protected Function FormatDate(ByVal dt As DateTime) As String
      Dim retString As String
      retString = String.Format("{0:D2}", dt.Month)
      retString &= String.Format("{0:D2}", dt.Day)
      retString &= String.Format("{0:D2}", dt.Year)
      Return retString
   End Function

   Public Sub CreateBlankFile()
      Dim NewXml As StreamWriter = _
         File.CreateText(Page.Server.MapPath("Blog.xml"))

      NewXml.WriteLine("<blogs>")
      NewXml.WriteLine _
         ("   <!-- blog field describes a single blog -->")
      NewXml.WriteLine("   <blog>")
      NewXml.WriteLine("      <!-- date field contains" & _
         " the creation date of the blog -->")
      NewXml.WriteLine("      <date>" & _
         FormatDate(DateTime.Today) & "</date>")
      NewXml.WriteLine _
         ("      <title>Temporary Blog</title>")
      NewXml.WriteLine("      <!-- text field " & _
         "should contain the blog text, including any " & _
         "desired HTML tags -->")
      NewXml.WriteLine("      <text>This entry " & _
         "indicates that the file blog.xml was not " & _
         "found. A default version of this file has " & _
         "been created for you. You can modify the " & _
         "fields in this file as desired. If you set " & _
         "the Blog control to add mode (add the " & _
         "attribute mode='add' to the control's " & _
         "declaration), the control will " & _
         "automatically populate the XML file when " & _
         "you submit the form.</text>")
      NewXml.WriteLine("      <!-- anchorID field " & _
         "will be autopopulated by the control -->")
      NewXml.WriteLine("      <anchorID></anchorID>")
      NewXml.WriteLine("      <!-- email field should" & _
         " contain the email address for feedback -->")
      NewXml.WriteLine("      <email>change this to a " & _
         "valid email address</email>")
      NewXml.WriteLine("   </blog>")
      NewXml.WriteLine("</blogs>")
      NewXml.Close()
   End Sub

End Class

Public Class BlogDesigner
   Inherits ControlDesigner

   Public Overrides Function GetDesignTimeHtml() As String
      Return "<h1>Blog</h1><hr/><hr/>"
   End Function

End Class

Listing 5. Blog.xsd

<?xml version="1.0" encoding="utf-8" ?> 
<xsd:schema
   targetNamespace="urn:http://www.aspnetian.com/schemas"
   elementFormDefault="qualified"
   xmlns="urn:http://www.aspnetian.com/schemas"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
   xmlns:vs="https://schemas.microsoft.com/Visual-Studio-Intellisense"
   vs:friendlyname="Blog Control Schema"
   vs:ishtmlschema="false" 
   vs:iscasesensitive="false" 
   vs:requireattributequotes="true" >
   <xsd:annotation>
      <xsd:documentation>
         Blog Control schema.
      </xsd:documentation>
   </xsd:annotation>

   <xsd:element name="Blog_DT" type="BlogDef" />

   <!-- <aspnetian:Blog> -->
   <xsd:complexType name="BlogDef">
      <!-- <aspnetian:Blog>-specific attributes -->
      <xsd:attribute name="AddRedirect" type="xsd:string"
         vs:builder="url"/>
      <xsd:attribute name="Email" type="xsd:string"/>
      <xsd:attribute name="Mode" type="BlogMode"/>
      <xsd:attribute name="SeparatorColor"
         type="xsd:string" 
         vs:builder="color"/>
      <!-- <asp:Panel>-specific attributes -->
      <xsd:attribute name="BackImageUrl" 
         type="xsd:anyURI" />
      <xsd:attribute name="HorizontalAlign"
         type="HorizontalAlign" />
      <xsd:attribute name="Wrap" type="xsd:boolean" />
      <xsd:attribute name="Enabled" type="xsd:boolean" />
      <xsd:attribute name="BorderWidth" type="ui4" />
      <xsd:attribute name="BorderColor" type="xsd:string" 
         vs:builder="color" />
      <xsd:attribute name="BorderStyle" 
         type="BorderStyle" />
      <xsd:attributeGroup ref="WebControlAttributes" />
   </xsd:complexType>

   <!-- DataTypes -->
   <xsd:simpleType name="BlogMode">
      <xsd:restriction base="xsd:string">
         <xsd:enumeration value="Add" />
         <xsd:enumeration value="Display" />
      </xsd:restriction>
   </xsd:simpleType>
</xsd:schema>

This article was adapted from ASP.NET in a Nutshell, 2nd edition (ISBN: 0596001169), by G. Andrew Duthie and Matthew MacDonald, published by O'Reilly & Associates, Inc., 2003.

© Microsoft Corporation. All rights reserved.