Click here to Skip to main content
15,885,546 members
Articles / Web Development / ASP.NET
Article

NoSpamEmailHyperlink: 4. Design-Time Functionality

Rate me:
Please Sign up or sign in to vote.
4.86/5 (16 votes)
11 Oct 20038 min read 71.1K   41   3
Adding full design time functionality to your custom control in a few easy steps.

Transformation from ASP.NET properties to HTML

Introduction

This is the fourth in a series of six articles, following the design, development and practical use of a fully functional ASP.NET custom control.

The full list of articles is as follows:

These articles are not intended to be a comprehensive look at custom control development (there are 700+ page books that barely cover it), but they do cover a significant number of fundamentals, some of which are poorly documented elsewhere.

The intent is to do so in the context of a single fully reusable and customizable control (as opposed to many contrived examples) with some awareness that few people will want many parts of the overall article but many people will want few parts of it.

This article examines techniques to make the control work correctly from the Visual Studio .NET toolbox, to customize rendering in the designer and to further customize rendering of Databound controls. It is intended for those who are experienced in writing web controls but do not understand the functionality provided by the .NET Framework to make your controls appear professional in a WYSIWYG designer, such as Visual Studio .NET.

It assumes at least a basic knowledge of C#, WebControl-derived classes and .NET Framework attributes.

Downloads are available from the first article in the series.

Adding Designer Functionality

One of the least documented aspects of WebConrtol development is how to make a control "look" good in a WYSIWYG designer.

If we limit our code to what we have covered in articles 2 and 3, the control will not work properly from the toolbar, will appear as a small box if none of the properties are populated, will appear with an encrypted email address in the .Text property (because the designer does not support JavaScript) and may not appear at all when databound.

None of this is good news for the web page developer, so it is important to include a little designer functionality.

Toolbox Operations

A custom control will not operate properly from the toolbox unless you inform the designer what the control tag should look like.

C#
[
// ...
ToolboxData("<{0}:NoSpamEmailHyperlink runat=server></{0}:NoSpamEmailHyperlink>"),
// ...
]
public class NoSpamEmailHyperlink : System.Web.UI.WebControls.WebControl
{
// ...
}

The ToolboxDataAttribute uses string formatting to create the full tag wherever you drop it within the page, or parent control.

By default, the {0} marker is replaced with "cc1". This prefix can be customized using a TagPrefixAttribute.

C#
[assembly: TagPrefix("CP.WebControls", "cpspam")]

This attribute can be defined anywhere in the project, but as it is scoped to the assembly, it makes more sense to define it in AssemblyInfo.cs.

In addition to adding the relevant tag to the page, the designer will check that the assembly has been "registered" with the page, using a line such as:

ASP.NET
<%@ Register TagPrefix="cpspam" Namespace="CP.WebControls"
    Assembly="CP.WebControls.NoSpamEmailHyperlink" %>

If the same assembly is already registered using a different TagPrefix, the TagPrefixAttribute is ignored and the registered prefix will be used to build the tag.

If another assembly is already registered using the same TagPrefix then a number is appended to make it a unique registration, and is subsequently used to build the tag for further instances of controls in this assembly.

If neither the assembly or the TagPrefix are registered then a <%@ Register ... %> line is added by the designer.

Custom Toolbox Icon

There is also a custom icon included with the NoSpamEmailHyperlink. This is easy enough to do and adds an element of style to your control.

Just create a 16x16 .bmp bitmap with the same base filename as your control (in this case, NoSpamEmailHyperlink.bmp). Use bright green (RGB = 00FF00) for pixels you wish to be transparent and draw yourself an icon.

Visual Studio .NET users can select the file in the Solution Explorer, change the "Build Action" property to "Embedded Resource". Developers using other IDEs should consult their documentation to see how to do this.

Control appears different in browser than it did in the designer

Note that the bitmap will assume the default namespace of the project and that this should match the namespace of your control class, otherwise you must use the ToolboxIconAttribute to point the control to its icon.

Personal disclaimer: I readily admit that I am not good with design - it is an envelope and that's the best I can do in a 16x16 space. If anyone has a better icon, I will be happy to consider replacing it.

Custom ControlDesigner Class

The WebControl class is attached to a ControlDesigner class which handles the way the control appears at design time. It essentially uses the control's .Render() method to generate some HTML. If that is zero-length then it generates some standard HTML to make the control visible.

In many cases this is sufficient but there are two common purposes for a custom ControlDesigner class.

  • Overriding GetDesignHtml() to simulate some JavaScript that is not handled by the designer.
  • Overriding GetEmptyDesignHtml() to create a custom marker when the HTML returned by .Render() is empty.
C#
public class NoSpamEmailHyperlinkDesigner : ControlDesigner
{
// ...
}

We inform our control that it has a custom designer using the ControlDesignerAttribute.

C#
[
// ...
ControlDesigner(typeof(NoSpamEmailHyperlinkDesigner)),
// ...
]
public class NoSpamEmailHyperlink : System.Web.UI.WebControls.WebControl
{
// ...
}

Control appears different in browser than it did in the designer

For the NoSpamEmailHyperlink, we do not want the email address to appear encrypted in the designer when it is included in the text - after all, it will not appear that way at run-time (see above). Thus we need a NoSpamEmailHyperlinkDesigner class to simulate the JavaScript.

All implementations of a custom designer should override the .Initialize() method to throw an exception where the ControlDesigner is used with a type of control not derived from the one it is intended for.

C#
public override void Initialize(System.ComponentModel.IComponent component)
{
    // Throw an exception if the designer is attached
    // to a control for which it is not intended
    if (! (component is NoSpamEmailHyperlink))
    {
        throw new InvalidOperationException(
            this.GetType().FullName 
            + " only supports controls derived"
            + " from CP.WebControls.NoSpamEmailHyperlink"
        );
    }

    base.Initialize (component);
}

This allows any overloaded methods to make certain assumptions about the type of control it is dealing with, as demonstrated in the code below.

Rather than parsing the HTML returned by base.GetDesignTimeHtml() and decoding the email address wherever found, we can set the control's .EncodeInText property for the duration of the rendering.

It is a much more efficient solution than encoding and decoding every time the control is rendered and is completely invisible to anyone using the control. As long as we return the control to its initial state after rendering, the page designer will never see the change.

This is, in fact, a recommended design strategy for any custom implementation of .GetDesignTimeHtml():

  • Change the nature of the control.
  • Call the base implementation.
  • If anything fails, use the exception to generate an error marker.
  • Return the control to its initial state.
  • If there is no HTML to return then get an empty control marker.
C#
public override string GetDesignTimeHtml()
{
    // Initialize
    NoSpamEmailHyperlink nseh = (NoSpamEmailHyperlink)Component;
    bool storeEncodeInText = nseh.EncodeInText;
    string rtn = null;

    try
    {
        // Do not encode the Text property
        // because JavaScript will not be run in designer
        nseh.EncodeInText = false;
        rtn = base.GetDesignTimeHtml();
    }
    catch (Exception ex)
    {
        // If something fails, report as usual
        rtn = GetErrorDesignTimeHtml(ex);
    }
    finally
    {
        // Always reset the control's flag to initial state
        nseh.EncodeInText = storeEncodeInText;
    }

    // If we have nothing to display, go with
    // the default "Empty" Html
    if (rtn == null || rtn.Length == 0)
        rtn = GetEmptyDesignTimeHtml();

    return rtn;
}

The only problem is that the control's .Render() method will return open and close tags (<a></a>), even if there is nothing in the .Text or .Email properties. So the control will appear as a small empty box in the designer, which is not particularly easy to select.

This problem can be bypassed by overriding the control's .Render() method.

C#
protected override void Render(HtmlTextWriter output)
{
    // If we don't have anything to display don't even render
    // the start and end blocks.  This assures that the page shows
    // nothing but the VS.NET designer has a selectable label
    if (Email.Length == 0 && Text.Length == 0 && Controls.Count == 0)
        return;

    base.Render(output);
}

Now a non-databound NoSpamEmailHyperlink control will usually appear to the page designer exactly as it would to the end-user. If it will be invisible in a browser, it is still displayed to the page designer, but using a standard display string that will be recognizable to the page developer.

Custom DataBindingHandler Class

When a WebControl is databound, the bound properties are considered blank when rendering in design mode. One exception to this is an innertext property where the content is only partly databound. In this case, any text completely outside of the bound area is used, anything else is considered blank.

For example, the following control definition can be included in a DataGrid:

HTML
<cpspam:NoSpamEmailHyperlink id="nseh" runat="server"
    Email='<%# DataBinder.Eval(Container.DataItem, "Email") %>'
    ScrambleSeed='<%# DataBinder.Eval(Container.DataItem, "ID") %>'>
    <%# DataBinder.Eval(Container.DataItem, "Name") %>
    (<%# DataBinder.Eval(Container.DataItem, "Email") %>)
</cpspam:NoSpamEmailHyperlink>

In the Visual Studio .NET designer, the .Email and .ScrambleSeed properties are treated as null, while the .Text inner property is considered to be ")".

The control does not appear to be a hyperlink in the designer, nor does it contain any logical representation of the text

In the case of a control that's otherwise visible, such as a TextBox or a DropDownList, this does not matter at all. We do not need to see the text to know that the control is there.

But in a text-only control, such as Label or NoSpamEmailHyperlink, we need to put some text into the properties or the control will be invisible in the designer.

This is achieved by implementing a DataBindingHandler class, overriding the .DataBindControl() method and informing our control of its new design-time responsibilities via the DataBindingHandlerAttribute.

C#
[
// ...
DataBindingHandler(typeof(NoSpamEmailHyperlinkDataBindingHandler)),
// ...
]
public class NoSpamEmailHyperlink : System.Web.UI.WebControls.WebControl
{
// ...
}

A Label control implements the TextDataBindingHandler, which simply populates the .Text property of the label with the string "Databound".

The control then appears within the designer as a piece of text: "Databound"

For the NoSpamEmailHyperlink, we want the control to appear as a hyperlink in the designer. Thus we must populate both the .Text and .Email properties in our DataBindingHandler class.

C#
public class NoSpamEmailHyperlinkDataBindingHandler : DataBindingHandler
{
    public override void DataBindControl(
        System.ComponentModel.Design.IDesignerHost designerHost,
        System.Web.UI.Control control
        )
    {
        NoSpamEmailHyperlink nseh = control as NoSpamEmailHyperlink;

        if (nseh != null)
        {
            // Set the text and email properties, so that the
            // control looks like a hyperlink in a datalist/grid

            nseh.Text = "Databound";
            nseh.Email = "Databound@Databound.com";
        }
    }
}

It makes absolutely no practical difference what is used to populate the .Email property. The contents will never be seen. The only rule is that it cannot be zero length because if it is then the href attribute of our tag is not populated and the control appears as plain text in the designer.

Conclusion

Most of the techniques detailed in this article are extremely easy to implement and add a certain element of quality to your control that cannot be quantified in terms of sales value.

A ToolboxDataAttribute and Icon should be implemented for any custom control. A custom ControlDesigner class is only required wherever JavaScript is used to change the appearance after rendering, or wherever the default "empty" design-time HTML is not sufficient. A custom DataBindingHandler is necessary wherever a control may appear invisible or otherwise incorrect when bound to data which is not available at design-time.

Having now examined the full code required for the NoSpamEmailHyperlink, all that remains is to consider a few examples and to look at how the NoSpamEmailHyperlink can be customized to further confuse anyone trying to get past the control functionality and harvest our hidden addresses.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United Kingdom United Kingdom
Paul lives in the heart of En a backwater village in the middle of England. Since writing his first Hello World on an Oric 1 in 1980, Paul has become a programming addict, got married and lost most of his hair (these events may or may not be related in any number of ways).

Since writing the above, Paul got divorced and moved to London. His hair never grew back.

Paul's ambition in life is to be the scary old guy whose house kids dare not approach except at halloween.

Comments and Discussions

 
GeneralIFRAME tag in GetDestingTimeHTML Method Pin
tech_raghav20-Mar-08 19:22
tech_raghav20-Mar-08 19:22 
GeneralPlease revise it all Pin
Anonymous12-Feb-05 23:05
Anonymous12-Feb-05 23:05 
GeneralRe: Please revise it all Pin
Paul Riley13-Feb-05 5:53
Paul Riley13-Feb-05 5:53 
Can you be more specific as to which parts you had trouble with?

I don't have a lot of time on my hands lately, but I'm always open to suggestions.

Paul

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.