If you write components, you have an obligation to ensure your components are well-designed, easy to deploy, and above all, easy to use.
- Technology: .NET (C#)
- Difficulty: Advanced
- Estimated Completion Time: 1 hour
The term "component" used throughout this article refers to a single reusable program building block. While it is entirely possible to group multiple components together to form one complex component, this article discusses the design of simple, single-purpose components. This article focuses on, and uses, C# and .NET for examples, but you can apply the same principles to any language and platform.
There are three aspects that component authors must address in order to write usable components.
- Identifying the type of component
- Designing the API
- Documentation and Packaging
Identifying the Component's Type
Identifying your component's type is the crucial stepping off point of a writing a usable component. It is impossible to write a clean and simple API, much less package it for distribution, without adequately identifying its type. Thankfully, this first step is easy.
The two primary component types are utility components to perform a specific, non-visual task, and UI components (visual) to either enhance the user's experience or make development simpler for the developer. Consider the following component as an example:
This is a simple component that only contains this HtmlRetriever class. This class has a static method called GetPageSource() which accepts a string parameter containing the URL of a page. This component contains no visual elements ñ it is simply a component that performs some sort of function. As such, you can easily identify this component as a utility component.
Now consider the next component, called LabelTextBox, that inherits UserControl and contains markup in an ASCX file:
<asp:Label ID="lblLabel" runat="server"
<asp:TextBox ID="txtTextBox" runat="server" />
The fact that this component inherits UserControl, directly incorporates ASP.NET controls, and outputs HTML are obvious clues that this component is a visual component. Therefore, it should be used and thought of as a control, the UI component ofASP.NET WebForms.
As stated earlier, identification is the easiest step in writing a usable component, but it has to be done before you can adequately start writing code. Follow these rules of thumb when identifying a component's type:
- If your component simply works with data and is independent from the UI, it is better served as a utility class or reference type.
Properly identifying the type of component you need to write determines how you approach its API and packaging.
The API and Why it is Important
A well-designed API naturally leads to productivity due to its simplicity ñ developers spend less time acquainting themselves with the API and more time writing code.
From this simplicity also comes maintainability, as designing your API with simplicity in mind helps keep the developer's code simple.
Several key factors play into a simple API, and you can achieve one by adhering to the following guidelines:
- Follow naming conventions and general practices for your language and platform.
- Emulate an API from a similar component.
- Design for yourself.
- Design for flexibility and cleanliness.
Naming Conventions and General Practices
No matter what language you write in or platform you target, the most important rule to follow when designing your API is to follow the naming conventions and general practices specific to your language and platform. Doing so promotes uniformity and
thus simplicity. In the realm of .NET, these conventions are centered on two types of casing:
- Pascal case ñ The first letter of an identifier is capitalized. If the identifier
consists of multiple words, then each word should start with a capital letter. Example:
MyClass is a proper name for a class.
- Camel case ñ The first letter is lower-case, and any subsequent word in the
identifier begins with a capital letter. Example: myVariable.
Pascal case should be used for all class names, enumerations and their values, events, interfaces, methods, namespaces, and properties.
Camel case should be used for all parameters and local variables.
Emulate Another API
When starting from scratch, it may be a good idea to look for components with a similar utility and emulate their API if it makes since to do so. Consider text-based ASP.NET controls as an example. The Label and TextBox controls' primary function is to present text to the user. As such, their default property is the Text property. If you build another text-based control, it too should implement a Text property to get and set the text-based content of the control.
The API for UI components are typically much easier to design and implement than that of non-visual components. Your target platform's UI components will more than likely have a common set of properties and methods. For .NET components, this is easily achieved by simply inheriting the System.Web.UI.Control ,System.Web.UI.WebControls.WebControl, or System.Web.UI.UserControl classes.
It is absolutely crucial that your UI component's API matches that of other UI components in your target platform. It promotes uniformity and familiarity for the developer cutting the amount of time needed to learn a new API.
Build an API for Yourself
Chances are you are building a component for your own needs, and you later decided to release it to the world. After emulating the API of another utility or control, the next step is to add the properties and methods you want to add. After all, you build it for a specific purpose. Not only are you the first consumer of your own component, you are also the best resource to supply it with an API. Remember to keep it as simple and logical as possible. Also remember to provide your properties and methods with descriptive and clear names.
Make it Flexible
When designing the API, be sure to make it flexible to handle multiple scenarios. Use the API you built for yourself as a basis, and start planning for different scenarios that other developers may want your component to handle.
Making HtmlRetriever Flexible
Consider the HtmlRetriever class from the previous section. Its GetPageSource() method may work for your original intended purpose, but also assume consumers of your class might want a page's source code without element tags. Make it flexible by refactoring the method into two overloads, like in the following code:
You will notice quite a few changes from this iteration of the HtmlRetriever class from what was presented in the previous section. As you read this code, notice how the method names, and the names of their parameters, are descriptive. There is no guesswork here; consumers of this class know exactly what the method does and how the parameters change its behavior simply by how they are named.
The first overload serves as the default behavior of GetPageSource() ñ it returns the HTML source code of a page with the tags intact. The second overload of GetPageSource() is the workhorse of the GetPageSource() overloads. It accepts a URL and a Boolean value as its parameters. It gets the HTML source and returns it with or without the element tags depending on what value was passed to the stripTags parameter.
Overloading is a fantastic means of adding flexibility to your API. It also has the added benefit of keeping your API clean and free of clutter.
Adding an API to LabelTextBox
Now refer back to the LabelTextBox. It inherits the System.Web.UI.UserControlclass, so the basis of the control's API in place from the base class. LabelTextBox is essentially a wrapper around a Label and TextBox, so some properties should be written to wrap the underlying controls' properties. The first is the Text property, which wraps around the TextBox's Text property:
The decision to make LabelTextBox's Text property wrap that of the TextBox is a design choice. The TextBox receives the most amount of attention from the user; it therefore makes sense to make the Text property wrap around the TextBox's Text property. To set the Label control's Text property, create a property called LabelText:
These two properties fit the primary need of this component ñ to get and set values. However, a developer consuming this component may have use for a TextChanged event. This can easily be done with the following code that wraps the TextBox's TextChanged event:
The first line of this code specifies an EventHandler delegate called TextChanged, allowing developers to add event handlers to handle the TextChanged event. Next, in Page_Load(), the TextBox's TextChanged event is handled by the txtTextBox_TextChanged() method. This allows you to check if TextChanged has any subscribers, and if so, execute those event handlers.
You could continue emulating the TextBox properties and methods for this control. For the sake of brevity, this is as far as the API emulation will go in this article.
So now this component's API consists of familiar members, such as the Text property and the TextChanged event, as well as custom properties (LabelText) to fit your needs. You could add more customizability, such as providing functionality to dynamically add a validation control to validate the text within the TextBox. The possibilities, and thus API, are limited only by your imagination. Just ensure that yourself, as well as anyone else, has the appropriate tools to get the most out of your component.
Final Touches: Documentation and Packaging
How you document and package your component is just as crucial to its usability as its API. All the hard work you put into your component is for naught if its documentation is poor and it is difficult to add to a project. Follow the guidelines in this section, and your component can easily be added to a project.
Use XML Comments
Chances are good that you rely on Intellisense when experimenting with pieces of the .NET Framework you have never worked with before. The majority of developers do the same thing. Intellisense is a fantastic tool, and your component can support Intellisense if you use XML comments and generate an XML documentation file. The following code adds an XML comment to the Text property of LabelTextBox:
The triple-slash is not a typo. Simply typing the slash three times above a class, property, or method automatically inserts the XML comments for that identifier. The only information you need to provide is what that identifier does.
Follow these steps to enable XML documentation file generation:
- Open your project's property pages.
- Click on the Build tab.
- Scroll down and check the box next to XML documentation file.
Non-Visual Component Distribution
Utility components are best distributed as class libraries. Doing so gives the developer a quick and easy means to add your component to their project. All they have to do is add a reference to the DLL to their project and they can begin using it.
It is also a feasible solution to release your component as code files, as long as the code in the files pertains only to your component. Embedding the code within a code-behind, or some other source file type, is unprofessional and causes consumers of your component to copy and paste the code into separate code files.
The best of both worlds: release the DLL and the code files together. If you want to sell your component, some marketplaces may require you to include the source code. Providing a precompiled DLL is a bonus for developers since they can just drop it into their project.
UI Component Distribution
Distributing UI components can follow one of two scenarios. The LabelTextBox component discussed in this article is a UserControl (ASCX). Distributing this component as an ASCX is fine. It gives developers the ability to easily augment the control to fit their needs thanks to the markup portion of ASCX files.
The alternative is to derive from Control or WebControl, in which case you have to manually code the control without using markup. For simple controls, like LabelTextBox, this is not much of an issue. However, more complex controls require more code to write the output HTML with Render() and RenderControl(). This approach makes distribution much easier, as the only addition to a project is a DLL file (or a code file) as
opposed to the ASCX and its code-behind file.
Never, ever, ever release your code without packaging it.
Few things are more frustrating to a developer than having to copy and paste several lines of code from an ASPX code-behind, or worse, an ASPX markup file. As a component author, it is your responsibility to ensure consumers of your code can use your component as quickly as possible. Anything more than adding a file or reference to a project quickly detracts the developer from your component. Keep it simple!
Writing components is a noble undertaking. You spend (countless) hours writing code to make another developer's work easier. Take pride in writing usable components. The more professional and usable you make them, the more developers will want to use them.