Creating Server Controls





Creating Server Controls

In the previous sections you learned how to create user controls. User controls are a fast and easy way to create reusable user-interface elements in ASP.NET, and they serve as a more structured and richer alternative to server-side includes. However, user controls don't take full advantage of the .NET component model. As a result, they are somewhat limited in terms of functionality.

User controls are compiled automatically in the background, just as ASP.NET pages are. The first time your page is accessed in the browser, the page and any user controls on that page are compiled. After the first compilation, the page and user controls are recompiled only if you change their source code.

In contrast, server controls must be compiled and deployed to the Web server ahead of time. Although this may seem like a bit of a pain, you can actually automate much of the compilation process ahead of time using the command-line compiler and a Windows batch file. This is the tactic we'll use in this chapter.

NOTE

In Visual Studio .NET, when you create a new project and select Web Server Control, VS.NET creates an actual server control project for you (in contrast to a user control). To create a user control in the development environment, you first create an ASP.NET Web Application and then add a user-control file to the project.


Creating a Basic Server Control

You create the most basic kind of compiled server controls by following these steps:

  1. Create a class that inherits from System.Web.UI.Control.

  2. Override the Render method of the inherited Control class.

  3. Optionally, add any properties, methods, or events that are appropriate to your control's functionality.

  4. Optionally, give your control the capability to store and retrieve state using the State object.

As an example, we'll start with a control whose content is completely static— literally, a "Hello, world" example. From there, we'll build more sophisticated and useful controls that have properties, methods, and events you can set programmatically.

NOTE

The properties, methods, and events of the System.Web.UI.Control class are listed at the end of Chapter 2.


The first step is to create a class contained in a namespace that inherits System.Web.UI.Control and overrides the Control class's Render method. Listing 9.7 shows an example.

Listing 9.7 "Hello World" Server Control Example
using System.Web.UI;     // Contains HtmlTextWriter and Control classes

namespace MyExample
{
  public class HelloWorld : Control
  {
    protected override void Render(HtmlTextWriter Output)
    {
      Output.Write("<B>Hello world!</B>");
    }

  }
}

After writing the code, the next step is to compile this code into a .NET component DLL and deploy it into the Web application directory. You can compile this code with the command-line compiler. The command is

Csc  /t:library /out:helloctrl.dll  helloctrl.cs /r:System.dll /r:System.Web.dll

When you compile this control, you must reference the libraries System.dll and System.Web.dll because they contain .NET framework libraries used in your code. The System.Web.dll library is required by every server control because it contains the Control base class from which ASP.NET server controls inherit. This namespace also contains the HtmlTextWriter class that is passed in to the Render method; as you can see from the code listing, this class is used by your code as a way to send HTML data to the browser.

NOTE

For now, we've intentionally glossed over the details of how command-line compilation works in .NET. You can find more detailed information on how to use the command-line compiler to create your components in the next section.


Compiling this code produces a binary, helloctrl.dll, which should be copied to a \bin subdirectory under a Web application directory to be accessible from an ASP.NET application in that directory.

The final step is to create an ASP.NET page that references this control and invokes it. Listing 9.8 shows a minimal example of such a page.

Listing 9.8 ASP.NET Page That References the Custom "Hello World" Server Control
<%@ PAGE language='C#' debug='true' trace='false' %>
<%@ REGISTER TagPrefix='Jeffrey' Namespace='MyExample' Assembly='helloctrl' %>
<HTML>
  <HEAD>
    <TITLE>Hello World Server Control </TITLE>
  </HEAD>
  <BODY>
    <FORM runat='server'>
      <Jeffrey:HelloWorld id='Hello1' runat='server' />
    </FORM>
  </BODY>
</HTML>

The Register directive is crucial here; it's what makes the page aware of the control contained in the external library. Like the user controls we discussed earlier in this chapter, the REGISTER tag contains a TagPrefix attribute; this attribute is an arbitrary text name you give to distinguish your controls from like-named controls on the page. As you can see in the code example, we used the TagPrefix "Jeffrey"; the same tag prefix is used when referencing the control on the page.

The Register directive for a precompiled server control also contains two attributes not found in user-control registrations. The Namespace attribute references the control's namespace; it must be the same as the namespace you declared when you constructed the server control.

Finally, the Assembly attribute points the page to the name of the assembly in which the component class resides. This should be the filename of the compiled DLL, without the .DLL filename extension. Remember that for the page to find the DLL, the DLL must be copied to a \bin subdirectory beneath the Web application directory.

Assuming everything is in place, when you navigate to this page in the browser, you should be able to see the "Hello World" text emitted by the overridden Render method of the HelloWorld control. You can, of course, replace this simple HTML with any text you want by altering the output of the Render function and then recompiling and redeploying the server control.

Before we go into more detail on the more sophisticated features of server controls, we'll take a brief detour and cover the general steps involved in creating and deploying .NET components. Because server controls are a type of .NET component, it's important to have a basic understanding of how components are built in .NET.

Compiling Your Control as a .NET Component

Because server controls are a type of .NET component, they must be compiled separately from the ASP.NET page that hosts them. You can do this in a number of ways. You may choose to build and compile your control in Visual Studio .NET, which is certainly okay. However, we prefer to compile our .NET components manually, using the command-line compiler. Although this takes a bit longer to set up, you may find that it's faster in the long run. It certainly gives you more control over what's going on, at any rate, and will probably prove to be more enlightening in terms of how your source code gets turned into a .NET component.

You may have created COM components in previous versions of Visual Studio or Visual Basic. The theory behind components in .NET is similar, although the implementation is different (in our opinion, component-based development is easier in many ways in .NET than it was in COM). Any .NET code can be packaged as an independently compiled component as long as the code contains a namespace and at least one public class.

Creating .NET Components Using the Command-Line Compiler

The first step to compiling a component using the command line is to add the location of your compiler to your computer's PATH variable. In Windows 2000 you do this by opening the System control panel, clicking the Advanced tab, clicking the Environment Variables button, and altering the PATH variable located in the System variables panel. A number of semicolon-delimited paths should already be in the PATH variable; you'll need to add something like

%SystemRoot%\Microsoft.NET\Framework\v1.0.2914

to the end of whatever's there. Ensure that you separate the original path and the Microsoft .NET directory string with a semicolon. Note that the location and name of the .NET framework compilers will almost certainly be different on your machine, depending on which version and build of .NET you're using. If you're in doubt as to which PATH setting to append, use Windows Explorer to do a search for the file csc.exe and use whatever directory that file is located in. The objective here is simply to provide a way for you to get to csc.exe from a command line without having to type the path to it every time you compile.

NOTE

You should be able to find compilers for at least three languages in the .NET directory; vbc.exe is the Visual Basic compiler, csc.exe is the one for C#, and jsc.exe is used for applications created in JScript, Microsoft's implementation of JavaScript. You can also see a number of DLLs that compose the .NET framework in this directory—files such as System.dll, System.Web.dll, System.Data.dll, and so forth.


After you've appended the location of the .NET compilers to your PATH setting, you can open a command window by selecting Start, Run, and then typing cmd into the Run dialog box. After the command window appears, you can test to make sure everything works by typing csc /help | more into the command window. If everything worked correctly, the numerous parameters for the C# compiler will appear in the window.

To compile a class to a component using the command-line compiler, you must specify that the compiled output (the "target") should be a component rather than a conventional executable. When compiling with csc, you do this using the command-line switch /target:library. The output of a build that uses the switch /target:library is a .NET component contained in a familiar DLL file.

Listing 9.9 shows an example of one of the smallest possible chunks of code that can be compiled into a .NET component.

Listing 9.9 Example of a Namespace and Class That Can Be Compiled into a .NET Component
using System;
namespace HelloComponent
{

  public class Hello
  {

    public String SayHello()
    {
      return "Hello, world!!!";
    }

  }

}

Note that this component is contained by a namespace and contains a single public function, SayHello, which returns a hard-wired text string.

Assuming that this code is contained in a file called hello.cs, you could compile this class to a .NET component called hello.dll by using the following command line:

csc /target:library /out:hello.dll hello.cs

You can see that compiling components is fairly straightforward, as long as you keep things simple. The tricky part with command-line compilation of components has to do with ensuring that you've included references to all the external libraries used by your component. For example, if your code uses classes found in the System.Data namespace (including subnamespaces such as System.Data.SqlClient), the command you use to build your component must contain a reference to the component System.Data.dll, or the build will fail because the compiler won't be able to find the external libraries your code refers to.

Therefore, when compiling, you must include references to external libraries using the /r switch. Your command line can have as many /r switches as it needs. For example, if your component references the System.Data and System.Xml namespaces, the command line you use to compile your component might look like this:

csc /out:mycomp.dll /target:library mysrc.cs
/r:System.Data.dll /r:System.XML.dll

More options are available in the command-line compiler, but these are all you'll need to know to get your server controls to compile.

Sooner or later you'll notice that your command-line compilation statements will become long and unwieldy. This is particularly the case when your project comprises many source files or when your project contains more than one or two references to external libraries. In this case, you'll probably want to automate the build process. A common way to accomplish this is by creating a batch file. Listing 9.10 shows an example of a batch file that compiles a .NET component project.

Listing 9.10 Batch File That Compiles and Deploys a .NET Component Project Automatically
Csc /out:mycomp.dll /t:library mysrc1.cs mysrc2.cs /r:System.Data.dll
copy mycomp.dll c:\inetpub\wwwroot\myapp\bin
pause

This set of commands, all contained within the text file build.bat, is all that's needed to create and deploy the fictitious mycomp.dll component. Note that two source files are in this project (mysrc1.cs and mysrc2.cs), and we're using the /t abbreviation instead of /target in this example. When the compilation is complete, the batch file copies the newly compiled DLL to c:\inetpub\wwwroot\myapp\bin.

Remember that batch files must end in a .bat or .cmd extension. You can use any text editor (such as the onerous Notepad or the extremely whizzy shareware TextPad) to create them.

Deploying a Component in ASP.NET

Deploying components is much easier in .NET than it was using COM. In .NET, you need only to deploy the component to a \bin directory located beneath your application directory. That's it! No REGSVR32, no shutting down and restarting IIS—none of that jive.

The batch file demonstrated in the previous section also copied the DLL to the Web application's \bin subdirectory after the compiler ran. Copying the file to a directory where it can be tested immediately after compilation is, of course, optional, but it's a handy trick that you'll probably want to use often.

No difference exists between a server control and any other kind of component in .NET, so the technique you use to develop, compile, and deploy server controls is essentially the same that you would use to create middle-tier business-logic components that have no representation in the user interface.

Creating Composite Controls

In the previous section, you saw a simple example of how to create a simple, HTML-based control by overriding the Render method of System.Web.UI.Control. This is a straightforward way to emit a chunk of static HTML.

A composite control, on the other hand, is a control that comprises other controls (either HTML controls or server controls). A Search control comprising a text label, a text box, and a button is an example of a composite control. This control would be similar to the Search control described in the section on user controls earlier in this chapter. We will create a new version of this control as a server control in our example.

Composite server controls are a type of ASP.NET server control. You create them essentially the same way as you create server controls, starting by creating a class that inherits from the System.Web.UI.Control class. The technique to create a composite control is slightly different from creating normal server controls, however. Instead of overriding the Render method of System.Web.UI.Control, as you do with normal server controls, you instead override the CreateChildControls method of the Control class. In this method, you add child controls to your control in two ways: by adding instances of existing server control objects to the Controls collection contained by the control object (inherited from the Control base class) and by inserting literal HTML into the control by way of the LiteralControl object. The LiteralControl object is used by ASP.NET for HTML elements that don't require server processing, so make sure that you don't plan on programmatically accessing any of the HTML you create using a LiteralControl.

For example, to build a composite server control similar to the Search user control described earlier in this chapter, you need at least three elements: a text box, a button to submit the search, and a separator to go between the text box and the button. In the user control examples, we used a table for this; for this example, we'll use an HTML nonbreaking space.

Listing 9.11 shows an example.

Listing 9.11 Creating a Compositional Control by Overriding the CreateChildControls Method of the Control Object
using System;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace MyServerControl
{

  public class Search : Control
  {

    protected override void CreateChildControls()
    {
      TextBox txt =  new TextBox();
      Button btn = new Button();
      btn.Text = "Search";
      this.Controls.Add(txt);
      this.Controls.Add(new LiteralControl("&nbsp;"));
      this.Controls.Add(btn);
    }

  }

}

You can compile this code to a file called search.dll by using the following command in a command prompt window:

csc /t:library /out:search.dll search.cs  /r:System.dll /r:System.Web.dll

By navigating to the page shown in Listing 9.12, you can test how the control appears in the page.

Listing 9.12 Page Created to Host an Instance of the Composite Search Server Control
<%@ Page Language="C#"%>
<%@ Register TagPrefix="demo" Namespace="MyServerControl" Assembly="search" %>
<HTML>
  <HEAD>
    <TITLE>ASP.NET Page</TITLE>
  </HEAD>
  <BODY>
    <FORM runat='server' ID="Form1">
      <demo:Search id='Search1' runat='server' />
    </FORM>
  </BODY>
</HTML>

If you copy the file search.dll to the Web application's \bin directory and navigate to this page, you should be able to see an instance of the Search control on the page, with a text box and command button.

Notice that within the control code, you can access the properties and methods of the contained controls. In this example, we specified a default text property for the Command button (using the assignment statement btn.Text = "Search").

Adding properties and methods to this control is done in the same way you add properties and methods to any class. To do this, you create a public variable or property procedure, or (for methods) a public subroutine or function.

In this example, we'll provide access to the Text property of the Search control. In this version of the control, the public Text property of your Search control just provides access to the Text property contained in the child TextBox control. The TextBox control does the work of storing and retrieving the text. (In object-oriented programming, the term for handing off an operation to a related or contained control is delegation.)

Listing 9.13 shows an example of using delegation to store and retrieve the Text property of the Search control within the Text property of the child TextBox control.

Listing 9.13 Using Delegation to Provide Access to the Text Property of aChild Control
using System;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace MyServerControl
{

  public class Search1:Control
  {
    private TextBox txt;
    private String Text
    {
      get
      {
        EnsureChildControls();
        return txt.Text;
      }
      set
      {
        EnsureChildControls();
        txt.Text = value;
      }

    }

    protected override void  CreateChildControls()
    {
      TextBox txt = new TextBox();
      Button btn = new Button();

      btn.Text = "Search";
      txt.Text = "Testing this.";

      this.Controls.Add(txt);
      this.Controls.Add(new LiteralControl("&nbsp;"));
      this.Controls.Add(btn);
    }


  }

}

In this version of the Search control, the TextBox control is defined at the class level rather than inside the CreateChildControls subroutine, as in the previous example. This extends the child control's lifetime to that of the class so we have an opportunity to access its Text property after it's created. You can see in the Text property procedure that the Text property of the contained TextBox control can be stored and retrieved. But in each case, there's a call to a function called EnsureChildControls() in there. A call to this function (actually a method of the Control base class) is required whenever you reference a property of a child control in a composite server control. This must be done because the page is loaded asynchronously; without an explicit call to EnsureChildControls, it's possible that your code will attempt to access a property of a child control that has not yet been loaded by the server.

Delegating to child controls is useful when your control is a composite control comprising two or more types of controls or when the number of properties of a child control you want to expose is limited. But what happens when you want to create a specialized type of existing server control? You may want your control to have most of or all the properties and methods of the existing control, plus a few additional members (or overridden members). You certainly would not want to write custom accessors for the 70+ members of the base TextBox class. In this case, you'll instead want to use inheritance to create your server control, subclassing an existing control to use its functionality.

Subclassing Existing Server Controls

In the previous section you saw an example of a composite control built from several existing controls. But we identified a problem—if you want to expose a large number of properties of a child control, you have to write a large number of property accessor functions that enable the page to get to the properties of the contained control.

Inheritance provides an alternative to containing an instance of a control and delegating to its members using accessor functions. A control that inherits from an existing control is said to be subclassed from that control.

You can create customized subclassed versions of existing ASP.NET server controls using inheritance, without writing tons of code. Through inheritance, you can use the existing control as a base class, adding new members or overriding existing members to provide enhanced functionality.

The technique for doing this is similar to creating any server control. But instead of inheriting from System.Web.UI.Control, you instead inherit from whichever control you're interested in subclassing. Because all server controls ultimately inherit from System.Web.UI.Control, your subclassed control still satisfies the requirement that all server controls inherit from the abstract Control class.

For example, suppose you want to provide a custom text box that provides a large number of default values. You don't want to have to code these defaults every time you use the control, and you know you're going to use this kind of control again and again in the construction of your site, so creating a custom control that inherits from the basic TextBox control makes sense.

To accomplish this, you create a class that inherits from the System.Web.UI.WebControls.TextBox control, overriding the property of the text box, supplying your own formatting defaults in the object constructor. Listing 9.14 shows an example.

Listing 9.14 CustomTextBox Control That Contains a Set of Custom Formatting Defaults
using System;
using System.Drawing;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace MyServerControl
{

  public class CustomText : TextBox
  {

    public CustomText()
    {

      this.BackColor = Color.CornflowerBlue;
      this.Text = "Can I get this one in cornflower blue?";
      this.Font.Name = "Verdana";
      this.Font.Size = FontUnit.XSmall;
      this.Width = Unit.Pixel(300);
    }
  }

}

You can compile this control using the command line:

csc /t:library /out:CustomText.dll CustomText.cs /r:System.dll
/r:System.Web.dll /r:System.Drawing.dll

You can see that this code imports a namespace we haven't used before; the System.Drawing namespace is used to set a background color for the control. As a result, the assembly System.Drawing.dll must also be included in your compilation command.

The customized default properties for the text box are set in the control's constructor; you can specify any properties you want by using MyBase to indicate that you want to change a property derived from the base TextBox class. (The quotations are all from the movie Fight Club; I got sick of using "Hello, world" all the time.)

Because it inherits from the standard Web forms TextBox control, the subclassed CustomText control has all the properties, methods, and events of the normal TextBox control. In addition to using inherited properties, you could override properties, as well.

To demonstrate how the inherited Text property works, you can deploy the compiled control assembly to the Web server's \bin directory and navigate to a page that instantiates the control. Listing 9.15 shows a page that puts the control through its paces.

Listing 9.15 ASP.NET Page That Uses an Instance of the Subclassed CustomText Control
<%@ Page language='C#' debug='true' trace='false' %>
<%@ Register TagPrefix="demo" Namespace="MyServerControl"
Assembly="CustomText" %>
<SCRIPT runat='server'>

  void Change_Click( Object Sender,EventArgs e)
  {
    CustomText1.Text = "...because waste is a thief.";
  }

</SCRIPT>
<HTML>
  <HEAD>
    <TITLE>ASP.NET Page</TITLE>
  </HEAD>
  <BODY>
    <FORM runat='server' ID="Form1">
      <demo:CustomText id='CustomText1' runat='server' /><BR>
      <asp:button runat='server' text='The First Rule of Fight Club'
            id='Change' onClick='Change_Click' />
    </FORM>
  </BODY>
</HTML>

You should be able to see when this page loads that the text box is an unappealing cornflower blue color with the font and text properties set as specified in the control's constructor code. By clicking the button, the control's Text property is set to the value specified in the Change_Click event procedure.

Events in the Life of a Server Control

A number of events are fired by every ASP.NET server control. These events are primarily inherited from the server control's base class, System.Web.UI.Control; you typically handle these events to perform initialization tasks related to the control and the data it displays.

The sequence of events raised by a server control are listed in Figure.

Figure Events Raised by System.Web.UI.Control in Custom Server Controls
Phase Description Found In
DataBinding The control has been bound to a data source. Control class
Init Instructs the control to set its default property values. These values are retained for the life of the HTTP request. Control class
LoadViewState View state information is loaded into the appropriate properties of the control. Control class
LoadPostData Process information from the incoming form. IPostBackDataHandler interface
Load Triggered when the control is loaded into the Page object. Control class
RaisePostDataChangedEvent Informs the page that the control's state has changed. IPostBackDataHandler interface
RaisePostBackEvent Enables the control to process postback events. IPostBackEventHandler interface
PreRender Triggered just before the control is sent to the client. Control class
Unload The server control is unloaded from memory. Control class

You will often find it useful to write code to respond to these events in your server control's lifetime. To handle these events, you override the corresponding "On" method (OnInit, OnLoad, OnDataBinding, and so forth) provided by the Control base class.

Binding Controls to Data

The ASP.NET controls you create can be bound to data. By enabling data binding, you can make it easy for application developers to use your control in database applications.

You provide support for data binding in your application by overriding the OnDataBinding method provided by System.Web.UI.Control, the base class of your control.

You set the data source consumed by your control by assigning a data object to the DataSource property of the control. If you're working with relational data, the data source can be a DataSet or DataReader object (introduced in Chapter 11); but you can also bind your control to a .NET array or collection object.

Generating Postback in Server Controls

As we discussed in Chapter 2, postback is the process of sending a form to the Web server for processing. When this happens, server script can process the contents of the form and perform useful tasks with the form data; this is the basis of Web applications.

A number of server controls provided with ASP.NET generate postback, including the Button and ImageButton controls, the LinkButton, HtmlButton, HtmlInputButton, HtmlImageButton, and DropDownList, CheckBoxList, or RadioButtonList (with AutoPostBack set to true). To generate a postback, your control must emit a chunk of JavaScript code because the process of submitting a form to the server must always be initiated on the client side; a client-side JavaScript is used to perform this in ASP.NET.

But when the control you're using is one you've created yourself, you are responsible for programmatically generating the client-side JavaScript code that kicks off a form postback. To do this, the method that renders your control must include a reference to the GetPostBackEventReference method of the ASP.NET Page object. This method returns the name of a function (generated internally by ASP.NET) that is responsible for submitting the form to the page.

Listing 9.16 shows an example of a simple hyperlink server control that generates a client postback function through a call to GetPostBackEventReference.

Listing 9.16 Hyperlink Control That Can Generate Client-Side Postback of a Form
using System.Web.UI;
using System.Collections;
using System;

namespace CustomControls
{
  public class MyLinkButton : Control, IPostBackEventHandler
  {
    // Defines the Click event.
    public event EventHandler Click;

    // Invokes delegates registered with the Click event.

    protected virtual void  OnClick(EventArgs e)
    {
      if (Click != null)
      {
        Click(this, e);
      }

    }

    // Method of IPostBackEventHandler that raises change events.

    public void RaisePostBackEvent(String eventArgument)
    {
       OnClick(new EventArgs());
    }

    protected override void Render(HtmlTextWriter output)
    {
      output.Write(("<a  id ='" + this.UniqueID +
        "' href=\"javascript:" +
        Page.GetPostBackEventReference(this) + "\">"));
      output.Write((" " + this.UniqueID + "</a>"));
    }
  }
}


<%@ Register TagPrefix="Custom" Namespace="CustomControls"
Assembly = "CustomControls" %>

<script language="C#" runat="server">
  private void Button_Click(Object sender, EventArgs e)
  {
    TextBox.BackColor = System.Drawing.Color.LightGreen;
    TextBox.Text = "The link button caused postback.";
  }
</script>

<html>
<body>
  <form runat=server>
    Here is the custom link button.<br>
    <Custom:MyLinkButton Id = "Link"  OnClick = "Button_Click"
            runat=server/>
    <br><br>
    <asp:TextBox id = "TextBox" Text = "Click the link" Width = "200"
      BackColor = "Cyan" runat=server/>
    <br>
  </form>
</body>
</html>

Persistence Support

Your control has the capability to store state information that is posted back across round trips to the server. In English, this means that even though the page is destroyed and re-created each time the user submits a form to the server, your controls can maintain the values that users enter into them.

This is accomplished through the encoded postback data. You see this in ASP.NET Web forms programming all the time; if you set up a Web form with a bunch of text boxes and a button, and then fill in the text boxes and click the button to submit the form to the server, you should see that the contents of the text box remain the same, even though the page has been completely torn down and re-created as a result of its round trip to the server.

If the value of one or more of the properties of your control needs to stay the same, you can store the value of that property in the ViewState property of the control. Having a generic way to store state is useful because Web forms controls are created and destroyed each time a page is accessed.

The ViewState property is an object of type StateBag, found in the namespace System.Web.UI. It is a typical collection type, although it doesn't derive from any of the .NET framework collection types. It does, however, implement the interfaces IDictionary, ICollection, and IEnumerable, so you can use it as you would many other types of .NET collection objects.

To demonstrate this property, we'll create a simple counter control that has the capability to store, retrieve, and display a single numeric value. To provide the capability to store and retrieve the value consistently across page reloads, the control will store its property in the state bag collection provided by the Control base class.

Listing 9.17 provides the code for this server control.

Listing 9.17 Example of a Basic Server Control That Stores Property State
using System;
using System.Web;
using System.Web.UI;

namespace MyExamples
{
  public class Counter : Control
  {
    public Int32 CurrentValue
    {
      get
      {
        return (Int32)ViewState["CurrentValue"];
      }
      set
      {
        ViewState["CurrentValue"] = value;
      }
    }

    protected override void Render(HtmlTextWriter Output)
    {
      Output.Write("<table border='1' width='200'><tr>" +
        "<td align='center' bgcolor='#FFFF99'>" +
          this.CurrentValue + "</td></tr></table>");
    }

  }

}

Simple, simple, simple. You can see from the code example that the ViewState object is a simple key/value pair; the key can be whatever you want, although you'll probably want to give it the same name as the property it stores for simplicity's sake.

To test this control, compile it using the command line:

csc /t:library /out:counter.dll /r:System.dll /r:System.Web.dll counter.cs

After it's compiled and copied to the \bin directory, you can test it in a page similar to that shown in Listing 9.18. This page adds two ASP.NET Button controls to increment and decrement the value of your counter control.

Listing 9.18 Page to Contain the Counter Control
<%@ REGISTER TagPrefix="demo" Namespace="MyExamples" Assembly="counter" %>
<SCRIPT runat='server'>

  void UpButton_Click(Object Sender, EventArgs e)
  {
    Counter1.CurrentValue++;
  }

  void DownButton_Click(Object Sender, EventArgs e)
  {
   Counter1.CurrentValue--;
  }

</SCRIPT>
<HTML>
  <HEAD>
    <TITLE>ASP.NET Page</TITLE>
  </HEAD>
  <BODY>
    <FORM runat='server'>
      <demo:counter id='Counter1' runat='server' />
      <asp:button id='DownButton' OnClick='DownButton_Click'
          text='Down' runat='server' />&nbsp;
      <asp:button id='UpButton' OnClick='UpButton_Click'
          text='Up' runat='server' />
    </FORM>
  </BODY>
</HTML>

When you load this page, you should be able to see that the Counter control's value is stored each time it's incremented. More importantly, it's displayed properly even if you hit the Refresh button on the browser—and the page designer didn't have to write any code to make that state retrieval happen.

You can see that this is the case if you rewrite the CurrentValue property, commenting out the lines of code that persist data to the state bag and instead storing the data in a private variable, as classes normally would be stored. Listing 9.19 shows an example of this.

Listing 9.19 Rewritten CurrentValue Property, Demonstrating Lack of State Persistence
private Int32 _CurrentValue;
public Int32 CurrentValue
{
    get
    {
      return _CurrentValue;
    }
    set
    {
    _CurrentValue = value;
    }
}

If you try this, don't forget to recompile counter.dll using the command line shown in the previous example. You should not have to make any changes to your page to use the new version of this control.

When your page uses this version of the control, the control does not have the capability to increment and decrement as it did previously. It can increment or decrement only once. This means the control can display only the values of 0 (the first time the page is loaded), 1 (when the Increment button is clicked), or -1 (when the Decrement button is clicked). This problem occurs because, in the absence of state persistence, the control's CurrentValue property is reinitialized to 0 every time the page is loaded. So, for example, when you click the Increment button, the form is first submitted to the server, destroying the page and the control along with it. The side effect is that the control's CurrentValue property is set to 0. Then the event procedure for the Increment button is run, setting the property to 1. But in the absence of persistent state, the control's property can never be incremented to 2 again, because when the page reloads, the control will be destroyed and reinitialized to 0 before the incrementing code has a chance to run.

As you take advantage of persistent property state, remember that a cost is associated with storing and retrieving state. Every time your control stores state information, that information is encoded and sent over the Internet from the client to the server and back again. The encoding process makes it easier for ASP.NET to handle your data, but it also has the side effect of making the postback data itself larger. Hence, postback will slow down your application significantly if you overuse it.

Also remember that state information that is passed back and forth between client and server isn't encrypted, so it isn't secure. The information is encoded, but that isn't the same as encryption; it won't keep hackers' grimy mitts off your data.

NOTE

Ultimately, if your Web application passes sensitive information from client to server in any form, you should consider transferring the information using a Secure Socket Layer (SSL) connection. This way, it doesn't matter whether the postback information is encrypted, because under SSL, all the information that passes between client and server is encrypted. (There is a cost in terms of performance and configuration hassle associated with this, however, so plan carefully when you want to secure specific forms or pages.)


Building Validation Controls

You can build controls that have the capability to validate user input in Web forms. Such controls, called validators, are simply a type of ASP.NET server control.

NOTE

ASP.NET comes with a set of validation controls you can use to ensure that user input in Web forms controls is valid. Among these is a CustomValidator control that enables you to use any validation function you want. Before embarking on creating a new validation control from scratch, you may want to first determine whether the CustomValidator control will suit your needs.

Validation controls are discussed in Chapter 11.


Taking Advantage of Rich Clients

ASP.NET server controls have the capability to tailor their output to the capabilities of the client. In the case of present-day Web applications, this capability enables you to write applications that take advantage of features, such as Dynamic HTML, that are supported by only the most advanced browsers, without your having to either write your pages twice or restrict your user base to a particular browser (usually Internet Explorer). This is called uplevel/downlevel rendering.

Uplevel/downlevel rendering is a nice feature that can result in performance increases in situations such as client-side data validation (discussed in Chapter 11), and the server controls you design yourself can also take advantage of uplevel/downlevel rendering functionality in ASP.NET the same way that ASP.NET's own server controls do.

NOTE

When Microsoft documentation refers to a "rich client," it's really talking about Internet Explorer 5.0 running on Windows. When it talks about an "uplevel browser," it's talking about the same thing. Conversely, when it's talking about a "downlevel browser," it's talking about every browser except Internet Explorer (including, but not limited to, Netscape Navigator).

I don't like using Netscape if I don't have to, particularly to test Web sites I'm developing, because it doesn't seem to run as fast as Internet Explorer. If you want to test uplevel/downlevel functionality but don't want to use Netscape, you have an alternative. The Opera browser is fast, lightweight, aggressively W3C standards–compliant, and free, if you don't mind looking at a few advertisements while you browse (you can get an ad-free version of Opera by paying a registration fee). It's great for testing Web applications when you need an alternative to Internet Explorer. Download Opera from http://www.opera.com.


One way ASP.NET provides uplevel/downlevel rendering is through HtmlTextWriter object, which is passed as an argument to the Render method of the Control class. This object normally renders HTML 4.0, but is swapped out by the Page object in favor of the System.Web.UI.Html32TextWriter class, which automatically renders HTML 3.2 where appropriate.

Supporting Designers in Custom Server Controls

If you want developers to be able to work with your server controls in visual development environments such as Visual Studio .NET, you should add support for visual designers to your control. A designer is a package of information that determines how your control interacts with the development environment when a developer is working with it.

An important distinction exists between the way a control behaves when it's being used by a developer in a tool such as Visual Studio .NET—known as design time—and the way a control executes in a running application, known as runtime. This distinction will be familiar to you if you've created ActiveX controls in previous versions of Visual Basic or Visual C++.


 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows