|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis article is mainly for developers who have not much experience with ASP.NET custom controls. In this article, I will explain the concept behind custom controls, with some useful guidelines. In the source code, there are a few useful controls like Email link control that generate an email link that displays the name, email subject, and body of an email. There is another useful custom control, a custom edit box that has a bound label, so once you click the label, it will jump to the associated edit box and it is accessible as well. I will also try to focus on the benefits of creating custom controls for programmers who have not used custom controls at all, or for those who rely only on using user controls. How to use the codeAll the code is in one project library, so you can compile and reuse it, but the website is a simple tutorial that has links for testing, with a full description of all the features for each test. This solution was made with Visual Studio 2008 and C#. BackgroundIn my experience as a .NET programmer, I realised that after ASP.NET 2 was released, it minimized the need for creating custom controls. And, user controls have more great features compared with what they had in ASP.NET 1.0. For example, it was not easy to reference a user control and manipulate its state via code (there was a workaround), or to invoke the intellisense in code without casting (there was a workaround too). But more than that, the design view was very bad, you just see a gray box with the user control ID. I have seen a few programmers at work doing simple custom controls in ASP.NET 1.0, but most of the controls that I saw in ASP.NET 2.0 were user controls, not custom controls. One day, I made a quick technical session about data-bindable custom controls, and after the session, there was a lot of argument about why we should create complex code if user controls can do all that we need. And, in my work, I realised that it is essential to know how to create your own custom controls to solve many problems that you can’t solve using other methods. When I was learning to develop some AJAX enabled controls, I found myself dragged again to custom controls more than user controls, so I hope that this article will be useful as I expect, for non-custom-control developers. Content
What is user control, and what is a custom control?User control, is a container for markup (HTML and Server controls) that you may use in any web page as a unit of code; you can add properties, events, and methods to make it fully customisable. User control creation is very simple compared with custom control creation, and normally, you build it visually, via drag and drop of controls from the Toolbox. Custom control, is a class extending the Reasons for favouring custom controls over user controlsSure, we don’t have to create a custom control in all scenarios, but I will list a few scenarios that will make us think about custom controls as the first option.
Toolbox, icon, and tag registration
When you create your project using the Visual Studio 8 ASP.NET Server Control template, it will generate this code for you: [DefaultProperty("Text")]
[ToolboxData("<{0}:WebCustomControl1 runat="server">")]
public class WebCustomControl1 : WebControl {
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public string Text {
get {
String s = (String)ViewState["Text"];
return ((s == null) ? String.Empty : s);
}
set {
ViewState["Text"] = value;
}
}
protected override void RenderContents(HtmlTextWriter output) {
output.Write(Text);
}
}
It is all that you want to create a basic custom control. It defines the [ToolboxData("<{0}:CustomEditBox runat="\""server\" Label=\"[Label]\" Text=\"[Text]\">")]
I replaced the generated tag with a custom element. Notice that I added more attributes to be generated in the container ASPX page. For the component’s icon, all that you need is to create a BMP image with the same name as the custom control (you may have other controls in the same project, so you may have to create more than one icon image). The icon image is optional.
Finally, this icon will not appear in the tool box in the automatic registration tab. You should register the toolbox item through toolbox's Choose Item context menu, browse the assembly, then register the component. Now, you will see the icon in the toolbox.
Almost everything is ready except this code: [ToolboxData("<{0}:CustomEditBox runat="\""server\" Label=\"[Label]\" Text=\"[Text]\">")]
How does {0} get the value, and from where? Do not worry about it; you can register your own [assembly: TagPrefix("FirstCustomControl", "FC")]
You should add this line in your AssemblyInfo.cs in the class library project, and you will see that the <FC:CustomEmailLink ID="CustomEmailLink1" runat="server" />
ViewState round trip managementThis topic is very important, and I believe it is the most important topic in custom controls. Although there is a lot of documentation about ViewState, I can tell that many programmers do the same mistakes (like me:)) when they try to save child controls or internal controls in the ViewState, and they end up with the error: “this [object] is not serializable”. First, let us understand what the ViewState is. It is an internal ASP.NET server object that maintains the state of the page and its child controls, so it is very distinct from the [DefaultProperty("Text")]
[ToolboxData("<{0}:CustomEmailLink runat="server">")]
public class CustomEmailLink : WebControl {
//.... other code
//.... other code
[Bindable(true)]
[Category("Appearance")]
[Localizable(true)]
public string Email {
get {
String s = (String)ViewState["Email"];
return ((s == null) ? "[Email]" : s);
}
set {
ViewState["Email"] = value;
}
}
This [DefaultProperty("Text")]
[ToolboxData("<{0}:CustomEditBoxPrimitive runat="\""server\"
Label=\"[Label]\" Text=\"[Text]\">")]
public class CustomEditBoxPrimitive : WebControl {
public string Text {
get {
String s = ViewState["Text"] as string;
return ((s == null) ? "[Text]" : s);
}
set {
ViewState["Text"] = value;
}
}
Now, the Simple custom controls vs. composite custom controlsIt is not a bad idea to begin creating all custom controls as composite (extend the
The simple control (extend public class CustomEditBoxPrimitive : WebControl {
//...... code
//..... code
[Bindable(true)]
protected override void CreateChildControls() {
BuildControl();
base.CreateChildControls();
}
private void RenderDesign(HtmlTextWriter output) {
output.Write("Text:{0} Label:{1}", this.Text, this.Label);
}
private void InitControls() {
_innerTextControl.EnableViewState = true;
_innerTextControl.ID = "Inner_TextBox";
_innerLabel.EnableViewState = true;
_innerLabel.ID = "Inner_Label";
LoadDataState();
}
As you can see, the control is inherit from Restore posted dataRestoring posted data is the most interesting part in any custom control that has an input data from user, may be text, or selected index, or others. I was thinking how the Framework knows the changes that happen to any text box rendered to the client. I was thinking that maybe there is a JavaScript generated at the client to capture any changes happened to the client controls. But, I did not see anything like this. I realised that any server button in the page already posts the data to the page form, so all data is already there in the public class CustomEditBoxPrimitive : WebControl {
//.... code
//.... code
private void LoadDataState() {
if (this.Context != null) {
foreach (string key in this.Page.Request.Form.AllKeys) {
if (key.ToLower().Contains(this._innerTextControl.UniqueID.ToLower())) {
this.Text = this.Page.Request.Form[key];
}
}
}
}
This code is for tutorial purposes only, but sure there is other useful code that we need to add like, for raising an event with any change that has happened. Another way to get posted data is to use the custom control APIs that are supported by the Framework, like implementing the
[DefaultProperty("Text")]
[ToolboxData("<{0}:CustomEditBox runat="\""server\" Label=\"[Label]\" Text=\"[Text]\">")]
public class CustomEditBox : CompositeControl,IPostBackDataHandler {
//... code
// .. code
public bool LoadPostData(string postDataKey,
System.Collections.Specialized.NameValueCollection postCollection) {
if (postDataKey == this.UniqueID) {
foreach (string key in postCollection.Keys) {
if (key.ToLower().Contains(this._innerTextControl.UniqueID.ToLower())) {
this.Text = postCollection[key];
}
}
}
return false;
}
When I tried to use it, this method never fired. I thought there was something wrong in the debugging configuration, until I realised there is a relation between the object client name and the object private void BuildControl() {
//.. code
// .. code
hidControl.Text=String.Format("<input type="hidden" name="{0}" />", this.UniqueID);
this.Controls.Add(hidControl);
ClearChildViewState();
}
With this workaround, your event will be fired and the magic will happen. Sure, we can make the custom control name itself the same as the Managing simple eventsManaging simple events is very simple and straightforward. You can create your own custom event and raise this event. You may use one of two scenarios: either raise the event corresponding to a custom action, like raising the [Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public string Text {
get {
String s = ViewState["Text"] as string;
return ((s == null) ? "[Text]" : s);
}
set {
if (ViewState["Text"] != null && (ViewState["Text"].ToString() != value))
if (this.Text != this._innerTextControl.Text)
OnTextChanged(this, EventArgs.Empty);
ViewState["Text"] = value;
}
}
Or you may in other scenarios hook to an internal event of any internal control, may be Extending a custom controlOne of the major features of custom controls is the ability to extend them, or to extend an existing rich control. Assume for example, that you have a lookup data in your website and it always is fed via a custom data source, and is initiated with an enumerable type. In this case, you may extend a dropdown list that has a property as an enumerable value, and use it as
After rendering:
Using the Decorator pattern, for more dynamic functionality, which means you inherit from the abstract class
For a good documentation of the Decorator pattern, have a look at this article.
Extending the control using the Decorator pattern is out of scope (you will see a sample in the code), but you can create an extender for specific types of controls to add more functionality, or you may create an extender for all visual controls if you inherited and referenced a control of type public class LinkedEmailDecoratorExtender:WebControl {
WebControl _baseControl = null;
public LinkedEmailDecoratorExtender(WebControl baseControl) {
_baseControl = baseControl;
}
protected override void Render(System.Web.UI.HtmlTextWriter writer) {
writer.Write(" <div {0}="{1}">", "style",
"'border: medium dotted #FF00FF; padding: 5px; margin: 5px;" +
" width: auto; height: auto; font-family: Arial; font-weight: bold;'");
_baseControl.RenderControl(writer);
writer.Write("</div>");
}
Custom controls and Web PartsWeb Parts are amazing features that come in ASP.NET 2.0, and the beauty of ASP.NET 2.0 Web Pats is the compatibility with SharePoint. If you inherit your custom control from a Other featuresThe topic of custom controls is very wide, and I have explained only a very small part. But, we can consider non-visual controls as well, including data sources, AJAX components, and the AJAX Extender toolkit. But, it is not recommended to make a big jump to creating AJAX components, which is very tempting, before learning how to create and use custom controls. Step by step summary for creating your first custom controlWe may summarise building our first custom control (have a look at CustomEditBox.cs):
I hope that you enjoyed the article, and the code.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||