If you are trying to develop a reusable component, for example a DualListBox, which encapsulates some child controls and their interactions, ASP.NET gives you two choices: a user control or a custom server controls. A user control is easier to develop, but harder to deploy or use. A server control is the opposite. Can we have the best of both worlds?
Server Control vs. User Control
A user control's presentation layer (.ascx) is separated from its business logic (code-behind). This is the most important factor underlying the major differences between a server control and a user control.
Table 1: Comparison between user and server controls
Can be designed declaratively in Visual Studio with the ease of drag-n-drop and WYSWYG.
Have to be painstakingly hand-coded in
Deploy design files (ascx) along with the DLL. If you have a library of 10 user controls, you need to deploy at least 11 files (10 ascx + 1 DLL).
Many server controls can be compiled into and deployed as a single DLL.
Create new instance at Run-time
Can only be loaded at runtime using
Page.LoadControl(), which returns a reference of Type
System.Web.Control. Since the control's code-behind class is partial, you cannot directly cast the reference to the class to access its properties or methods.
new to construct an instance at runtime, and access its properties and methods.
Declare new instance at Design-time
Limited design-time support. Only primitive properties can be set at design-time. Even if you expose child controls as public properties, they can only be accessed at runtime. You cannot set them up at design time.
Full design-time support. Can expose child controls as inner properties, and set them up declaratively at design-time.
In order to have the best of both worlds, we need a userver (user-server) control, which can be
- designed declaratively using drag-n-drop like a user control,
- compiled and deployed in a single DLL like a server control,
- created and accessed at runtime like a server control, and
- setup declaratively at design-time like a server control.
In his article, Load WebForms and UserControls from Embedded Resources, Kurt Harriger described a method to compile and deploy user controls in a single DLL. Ben Allfree took it one step further in his article, Embedded UserControls: Revisited. He wrapped user controls in light-weight server controls, such that the child controls can be exposed and accessed at runtime through the wrapper server controls.
Three pieces of the puzzle have already been out there. The only piece missing is the ability to setup child controls declaratively at design time. The light-weight wrapper solution fell just short on this one. This was because the host of the child controls was the user control. They were not available from within the wrapper server control's constructor, where declared properties were to be set. Therefore, in order to declaratively set child controls' properties, we need to make the wrapper server control the host. In other words, instead of putting the child controls in a user control, designing the layout first and then wrapping it into a server control, we first put the child controls in a server control, and then "outsource" the layout to a user control.
The implementation consists of three components: AssemblyPathProvider, UserControlWrapper, and WrappedUserControl. Their responsibilities are summarized below.
Table 2: Responsibilities of Userver Components
Serve embedded aspx or ascx files from assembly DLLs.
Abstract base class of all userver controls. Define business logic in subclasses. Outsource layout rendering to a user control.
Base class of userver controls' layout providers.
ASP.NET 2.0 introduced
System.Web.Hosting.VirtualPathProvider, enabling web applications to access files from virtual file systems. AssemblyPathProvider is a subclass of VirtualPathProvidor, serving files embedded in assembly DLLs. For example, you can load an embedded user control with the following virtual path:
This is the server-control side of a userver control. Its subclass serves as a conceptual container of interacting child controls that carry out the business logic of the userver control. The presentation layer is outsourced to a user control, which has PlaceHolders for the child controls. The base class does the plumbing work of outsourcing. Its
VirtualPath property defines how to find the corresponding user control:
- in the same namespace as the server control,
- in a sub folder named
- in a file named
xxxLayout.ascx, where xxx is the name of the userver class.
If you would like to put it somewhere else, or name it something else, you may override the property in subclass. The user control is called upon during
Init event. Once loaded, it is passed with a one-to-one mapping from PlaceHolder IDs to child controls.
It is subclasses' reponsibility to create the mapping by implementing the abstract
CreateLayoutMap method. The base class provides two overloaded
AddLayoutControl methods. If the child controls and their corresponding PlaceHolders are named accordingly, i.e.
PlaceHolder.ID = ChildControl.ID + “PlaceHolder", then the mapping can be created in one line of code.
Since the base class has taken care of the presentation layer, subclasses can focus on their business logic.
- Create child controls that carry out the functionality of the control.
- Expose the child controls as properties that need to configured at design time or accessed at runtime.
- Define interactions among the child controls.
This is the user control side of a userver control. The base class can find all PlaceHolders at runtime, and put right controls into right places. Subclasses only need to layout the PlaceHolders in place of the real controls.
Put It Together: DualListBox
Let's try it out by deveploying a DualListBox control. A DualListBox is a component for making selections, as shown below.
Figure 1: Screenshot of DualListBox
To develop a userver control or a library of userver controls, start with a new "Web Application" project, not a "Server Control" project. This is because you need to add user controls to the library, which is not available in "Server Control" project template. After the project is created, delete automatically generated web.config and Default.aspx files. I added reference to the userver classes by including them as an existing project. Alternatively, you might simply drop the dll to the bin folder. I also created a DemoSite project to test the component.
Figure 2: Project setup
Next, I added a new server control DualListBox inheriting from UserControlWrapper, and a new user control DualListBoxLayout inheriting from WrappedUser. The DualListBox contained 8 child controls: 2 ListBoxes, 2 Literals (as list titles), and 4 buttons. They were initiated in the constructor. The two ListBoxes were exposed as inner properties; and the Literals' Text were exposed as title properties. The four buttons' actions were encapsulated within the component, so it's not necessary to expose them as properties. I left out the buttons' click event handlers for your practice.
Listing 1: DualListBox class
The DualListBoxLayout was put under subfolder
layout to confirm with the naming pattern. It contained 8 PlaceHolders in a table-based layout. Don't forget to set its build action to "Embedded resource".
Listing 2: DualListBoxLayout.ascx
I also added an internal DualListBoxDesigner class in DualListBox.cs to provide design-time preview. Using the "outsourcing" trick one more time, I mocked it up in a static HTML page, then cut-n-pasted the HTML into the designer's
Figure 3: DualListBoxDesigner mockup
Now DualListBox is a first-class server control with full design-time support, as illustrated below in VS designer:
- Drag-n-drop from Toolbox.
- Configure child controls in source window or property window.
- Design-time preview.
Figure 4: Full design-time support
Finally, this is how the DLL you are going to deploy looks like in Reflector.
Figure 5: Userver control in Reflector
If you are going to develop and deploy a reusable component encapsulating several controls interacting with each other, now you have a third option, userver control, combining the best of both worlds from ASP.NET's built-in options, user control and server control.
Please login to rate or to leave a comment.