How to Build a Custom WebControl using CompositeControl

Yesterday, I posted about a new Email WebControl I just published with promise of more follow up posts about it. You can probably find some of the same pointers outlined in this post elsewhere, and likely written better and more authoritatively than my attempt at it. However, I'll add to blogosphere my own version of "how I wrote a custom WebControl library".


First of all, you’ll quickly note that the code below is nothing magical, in fact it could very likely be refactored into something far more elegant and extensible. If you have suggestions, criticisms, or comments, please let me know — I welcome them all.

Since what I was wanting to accomplish didn’t involve any great complex HTML, JavaScript, or CSS wizardry, I decided to just compose a new control as a composite of existing form elements (e.g. Labels, TextBoxes, a Button, and a Validator).

I cheated and used Reflector to see how Microsoft did the same thing with the new Login controls. I then stripped it down to the bare necessities — the elements themselves and some basic event handling. You’ll find no fancy templates or containers in this example.

CompositeControl
First thing to note is that I subclass from (the new in 2.0) [CompositeControl](http://msdn2.microsoft.com/en- us/library/system.web.ui.webcontrols.compositecontrol.aspx). [Scott Guthrie highlights some good points about the CompositeControl](http://weblogs.asp.net /scottgu/archive/2006/01/29/436854.aspx) that I wish I would have seen earlier (it would have saved me some “figure it out time”). CompositeControl gives me a lot of base class functionality (much of which I haven’t spent the time to learn yet — this should demonstrate 1) how easy it is to create your own control library, and 2) the power and elegance of proper API and OO design in the Framework.

namespace AltmanSoftware.WebControls { [DefaultProperty(“From”)] [ToolboxData(”<{0}:Email runat=server></{0}:Email>”)] public class Email : CompositeControl { /* … */ } }

Properties
The properties on a WebControl maintain state outside of the object themselves. Remember that the Web tier is basically a stateless platform. Things like ViewState, Cookies, Databases, XML, etc., have been leverages and/or developed to give the Web tier stateful options.

Unlike normal Properties on a class that would store the data in a private variable, it really doesn’t make much since to do so here because the object’s lifetime won’t carry over from request to request without some form of persistence.

So, rather than serializing the entire object and deserializing, we just store and retrieve values from the ViewState. These Properties can be set at design time in the markup tag, or in the PropertyGrid when the composite control is selected.

[Bindable(true)] [Category(“Appearance”)] [DefaultValue("")] [Localizable(true)] [Description(“The value of the From field. It must be a valid email address.”)] public string From { get { String s = (String)ViewState[“From”]; return ((s == null) ? String.Empty : s); } set { ViewState[“From”] = value; } }

CreateChildControls()
This is where all the magic happens and the composite is built. I prefer the flexibility in laying out my web documents with DIV elements instead of TABLE structures. I believe this gives more control and flexibility by being able to do most, if not all design work in CSS. That stated, you can see that I wrap with several layers of nesting the controls with DIV containers.

protected override void CreateChildControls() { Controls.Clear();

        HtmlGenericControl divContainer = new HtmlGenericControl("div");
        divContainer.Attributes.Add("class", CssClassEmailContainer);

        HtmlGenericControl divFromContainer = new HtmlGenericControl("div");
        divFromContainer.Attributes.Add("class", CssClassEmailFromContainer);

        HtmlGenericControl divFromLabel = new HtmlGenericControl("div");
        divFromLabel.Attributes.Add("class", CssClassEmailFromLabelContainer);

        Label fromLabel = new Label();
        fromLabel.Text = FromLabelText;
        fromLabel.CssClass = CssClassEmailFromLabel;
        divFromLabel.Controls.Add(fromLabel);

        HtmlGenericControl divFromTextBox = new HtmlGenericControl("div");
        divFromTextBox.Attributes.Add("class", CssClassEmailFromTextBoxContainer);

        TextBox from = new TextBox();
        from.CssClass = CssClassEmailFromTextBox;
        from.Text = From;
        from.ID = "txtFrom";
        from.TextChanged += new EventHandler(from_TextChanged);
        divFromTextBox.Controls.Add(from);

        RegularExpressionValidator emailValid = new RegularExpressionValidator();
        emailValid.ValidationExpression = EmailValidationExpression;
        emailValid.Text = EmailInvalidText;
        emailValid.ControlToValidate = from.ID;
        emailValid.CssClass = CssClassEmailFromTextBoxValidator;
        divFromTextBox.Controls.Add(emailValid);

        divFromContainer.Controls.Add(divFromLabel);
        divFromContainer.Controls.Add(divFromTextBox);
        divContainer.Controls.Add(divFromContainer);

        HtmlGenericControl divSubjectContainer = new HtmlGenericControl("div");
        divSubjectContainer.Attributes.Add("class", CssClassEmailSubjectContainer);

        HtmlGenericControl divSubjectLabel = new HtmlGenericControl("div");
        divSubjectLabel.Attributes.Add("class", CssClassEmailSubjectLabelContainer);

        Label subjectLabel = new Label();
        subjectLabel.Text = SubjectLabelText;
        subjectLabel.CssClass = CssClassEmailSubjectLabel;
        divSubjectLabel.Controls.Add(subjectLabel);

        HtmlGenericControl divSubjectTextBox = new HtmlGenericControl("div");
        divSubjectTextBox.Attributes.Add("class", CssClassEmailSubjectTextBoxContainer);

        TextBox subject = new TextBox();
        subject.CssClass = CssClassEmailSubjectTextBox;
        subject.Text = Subject;
        subject.TextChanged += new EventHandler(subject_TextChanged);
        divSubjectTextBox.Controls.Add(subject);

        divSubjectContainer.Controls.Add(divSubjectLabel);
        divSubjectContainer.Controls.Add(divSubjectTextBox);
        divContainer.Controls.Add(divSubjectContainer);

        HtmlGenericControl divMessageContainer = new HtmlGenericControl("div");
        divMessageContainer.Attributes.Add("class", CssClassEmailMessageContainer);

        HtmlGenericControl divMessageLabel = new HtmlGenericControl("div");
        divMessageLabel.Attributes.Add("class", CssClassEmailMessageLabelContainer);

        Label messageLabel = new Label();
        messageLabel.Text = MessageLabelText;
        messageLabel.CssClass = CssClassEmailMessageLabel;
        divMessageLabel.Controls.Add(messageLabel);

        HtmlGenericControl divMessageTextBox = new HtmlGenericControl("div");
        divMessageTextBox.Attributes.Add("class", CssClassEmailMessageTextBoxContainer);

        TextBox message = new TextBox();
        message.CssClass = CssClassEmailMessageTextBox;
        message.Text = Message;
        message.TextMode = TextBoxMode.MultiLine;
        message.TextChanged += new EventHandler(message_TextChanged);
        divMessageTextBox.Controls.Add(message);

        divMessageContainer.Controls.Add(divMessageLabel);
        divMessageContainer.Controls.Add(divMessageTextBox);
        divContainer.Controls.Add(divMessageContainer);

        HtmlGenericControl divButton = new HtmlGenericControl("div");
        divButton.Attributes.Add("class", CssClassEmailButtonContainer);

        Button submit = new Button();
        submit.Text = ButtonText;
        submit.CssClass = CssClassEmailSubmitButton;
        submit.Click += new EventHandler(submit_Click);
        divButton.Controls.Add(submit);

        divContainer.Controls.Add(divButton);

        HtmlGenericControl divResultLabel = new HtmlGenericControl("div");
        divResultLabel.Attributes.Add("class", CssClassEmailResultLabelContainer);

        Label result = new Label();
        result.Text = ResultLabelText;
        result.CssClass = CssClassEmailResultLabel;
        divResultLabel.Controls.Add(result);

        divContainer.Controls.Add(divResultLabel);

        Controls.Add(divContainer);
    }

submit_Click()
Last, but certainly not least, is the EventHandler for the ButtonClick event that is raised when the user clicks the control’s button. This builds an SMTP object, constructs the message, and sends it. This further saves the user the trouble of writing EventHandler code to deal with this fairly common pattern of email transmission in code.

void submit_Click(object sender, EventArgs e) { System.Net.Mail.SmtpClient sc = new System.Net.Mail.SmtpClient(); sc.Host = SMTPServer; sc.Port = SMTPPort; sc.DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.Network;

        string smtpUser = SMTPUsername;
        string smtpPassword = SMTPPassword;

        if (smtpUser != null &amp;&amp; smtpUser != string.Empty)
        {
            sc.Credentials = new System.Net.NetworkCredential(smtpUser, smtpPassword);
        }

        sc.EnableSsl = SMTPRequiresSSL;

        try
        {
            sc.Send(From, SendToEmailAddress, Subject, Message);
            ResultLabelText = MessageWasSentText; 
        }
        catch (Exception ex)
        {
            ResultLabelText = MessageWasNotSentText;

            if (ExceptionInfoOnError)
            {
                ResultLabelText += 
                   "&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;" + 
                   ex.Message + 
                   Environment.NewLine + 
                   ex.StackTrace + 
                   "&lt;/pre&gt;";
            }
        }

        CreateChildControls();
    }

I hope that this was someway useful to you. I still plan on putting together another “walk-through” on how to consume this control and make use of it in your ASP.NET applications in the next several days or so.

Stay tuned, or better yet, hit the RSS link on the menu bar to get a subscription so you don’t miss anything!