AJ's blog

November 11, 2006

Reach out at design time

Filed under: .NET, .NET Framework, ASP.NET, C#, Design Time, Software Development — ajdotnet @ 6:05 pm

Some developers are not even aware that their code actually runs at design time. Others try to ignore that fact as long as possible. One reason for this is probably this: design time is different. Not just “not runtime”, but really different. The weird kind of different. Like Babylon 5 hyperspace where physical laws don’t apply. Objects get instantiated with the type of their base class, some events occure while others don’t, types may not be available or change their version, data that depends on configuration will be unconfigured, context information available at runtime will not be present, and so on.

There are usually two reasons to have to look into design time: One is some issue at design time, e.g. a control that no longer renders itself properly. The other is the desire to provide better design time support.

There are several “levels of design time involvement” of your code:

  1. Regular code that does not run at design time. We can safely ignore this code from now on. It’s still worth mentioning since it is sometimes quite important to know whether the code will be called or not (e.g. in the case of certain events).
  2. Regular code that runs at design time and may need to protect itself for unexpected context data. This is usually adressed with defensive programming style (check every object, don’t expect certain contexts, e.g. a request, etc.) or with explicitely checking for design time (i.e. testing Component.Site.DesignMode)
  3. Regular code that is (perhaps primarily) intended to be used at design time (yet still may play a role at runtime). TypeConverters are a perfect example since they are used at design time to check inputs within the properties pane and also to provide the values shown in the drop down list of the property (e.g. for properties of an enumeration type).
  4. Code that uses the design time services offered by the environment (i.e. the .NET Framework based design time support). This code can run only in design time and plays no role at runtime. The design time services are available with calls to GetService (offered by various objects within the design time model).
  5. Code that also uses the Visual Studio automation model. While the design time services offer only limited support, the automation model (based on COM and also available to VBA) open up the whole Visual Studio as playground, including access to the solution structure.

Let’s look into these levels with some concrete examples:

1. Regular code not running at designtime

Done. :-)

2. Regular code running at designtime

Have look at the following label class, intended to show the current request URL:

[ToolboxData("<{0}:UrlLabel runat=server />")]
public class UrlLabel : Label
{
    public string CurrentUrl
    {
        get { return this.Page.Request.Url.AbsoluteUri; }
    }
    
    public override string Text
    {
        get { return CurrentUrl; }
    }
}

This innocent looking code will work properly at runtime, yet at design time it looks like this:

The reason is that the accessed objects in the property are not what they are expected to be. The page may be there but in case you don’t know, the actual page class is the base class of the class used at runtime. And of course the request object is null, since there is no request. Here’s a version of the property that adresses these issues:

public string CurrentUrl
{
    get
    {
        if ((this.Site != null) && this.Site.DesignMode)
            return http://www.designtime-is-fun.com&#8221;;
        return this.Page.Request.Url.AbsoluteUri;
    }
}

This solved the problem at design time:

At runtime, both versions look the same:

Better?

3. Regular code that supports design time

There’s plenty of areas to support the design time experience. The most commonly used things are attributes on classes and properties and TypeConverters. TypeConverters are used at design time to check the values typed in the properties pane and also to provide the content of a drop down list provided there. Of course they stilll have value at runtime, e.g. to interpret configuration data.

public class NamesTypeConverter : TypeConverter
{
    public override bool GetStandardValuesSupported
        (ITypeDescriptorContext context)
    {
        return true;
    }
    public override bool GetStandardValuesExclusive
        (ITypeDescriptorContext context)
    {
        return false;
    }
    public override StandardValuesCollection GetStandardValues
        (ITypeDescriptorContext context)
    {
        StandardValuesCollection values =
            new StandardValuesCollection(new string[] { “Alex”, “John”, “Steve”, “Zoe” });
        return values;
    }
}

[ToolboxData("<{0}:NameLabel runat=server />")]
public class NameLabel : Label
{
    string _owner= “<no owner>”;
    
    [TypeConverter(typeof(NamesTypeConverter))]
    public string Owner
    {
        get { return _owner; }
        set { _owner = value; }
    }
    
    public override string Text
    {
        get { return Owner; }
    }
}

This will offer the following drop down list at design time:

Gee, a string property and still a drop down list with several predefined choices!

4. Code using design time services

Sooner or later one needs information from the design time environment or would like to react on some events (e.g. the addition af a new control at design time). Unfortunately the area of design time services is also the area where the documentation begins to get sparse. Some areas are well documented, others are not. Especially the background story definitely lacks some information.

This TypeConverter offers a list of IDs of textboxes and is used by a label that simply shows the content of that textbox.

public class TextBoxIDsTypeConverter : TypeConverter
{
    public override bool GetStandardValuesSupported
        (ITypeDescriptorContext context)
    {
        return true;
    }
    public override bool GetStandardValuesExclusive
        (ITypeDescriptorContext context)
    {
        return true;
    }
    public override StandardValuesCollection GetStandardValues
        (ITypeDescriptorContext context)
    {
        if (context == null)
            return null;
        
        IDesignerHost host =
            (IDesignerHost)context.GetService(typeof(IDesignerHost));
        Control parent = host.RootComponent as Control;
        List<string> list= new List<string>();
        foreach (Control control in parent.Controls)
        {
            if (control is TextBox)
                list.Add(control.ID);
        }
        list.Sort();
        StandardValuesCollection values =
            new StandardValuesCollection(list);
        return values;
    }
}

[ToolboxData("<{0}:TextboxLabel runat=server />")]
public class TextboxLabel : Label
{
    string _textbox= “textbox_id”;
    
    [TypeConverter(typeof(TextBoxIDsTypeConverter))]
    public string Textbox
    {
        get { return _textbox; }
        set { _textbox = value; }
    }
    
    public string TextboxText
    {
        get
        {
            if ((this.Site != null) && this.Site.DesignMode)
                return “<textbox text>”;
            TextBox t = (TextBox)this.Page.FindControl(_textbox);
            if (t==null)
                return “<textbox “+_textbox+” not found>”;
            return t.Text;
        }
    }
    
    public override string Text
    {
        get { return TextboxText; }
    }
}

This will offer a list of textboxes on the same page:

It even works at runtime, like the following snapshot taken after a postback shows:

We are beginning to build up relationships.

5. Code using the Visual Studio automation model

The design time services are somewhat limited in what they offer. For example there is no service that tells one what file he is working on. Within the design time model this information is not even available. But it is available outside, within the Visual Studio automation model. It’s hardly documented (unless you peek at the Visual Studio SDK documentation) but all you have to do is to ask for the DTE interface which recides in the EnvDTE assembly.

public class TextfileTypeConverter : TypeConverter
{
    public override bool GetStandardValuesSupported
        (ITypeDescriptorContext context)
    {
        return true;
    }
    public override bool GetStandardValuesExclusive
        (ITypeDescriptorContext context)
    {
        return false;
    }
    public override StandardValuesCollection GetStandardValues
        (ITypeDescriptorContext context)
    {
        if (context == null)
            return null;
        
        DTE dte = (DTE)context.GetService(typeof(DTE));
        Project p = ((object[])dte.ActiveSolutionProjects)[0] as Project;
        string filename = Path.Combine(p.FileName, “prefixes.txt”);
        string[] lines = File.ReadAllLines(filename);
        
        StandardValuesCollection values =
            new StandardValuesCollection(lines);
        return values;
    }
}

[ToolboxData("<{0}:PrefixedLabel runat=server />")]
public class PrefixedLabel : Label
{
    string _prefix= “PREFIX:”;
    
    [TypeConverter(typeof(TextfileTypeConverter))]
    public string Prefix
    {
        get { return _prefix; }
        set { _prefix = value; }
    }
    
    public override string Text
    {
        get { return _prefix + base.Text; }
        set
        {
            if (value.StartsWith(_prefix))
                value = value.Substring(_prefix.Length);
            base.Text = value;
        }
    }
}

Given a textfile named prefixes.txt in the web project with the respective content it will look like this:

Great, we cannot only provide a list of choices, we can even edit this list by means of a simple text file.

So, now you are free to do what you want. Your code is in control and you can reach wherever you want. However you still need the Visual Studio to call your code and unless you knock it down it will eventually have to return (or be brought back). The next level that does not suffer from those limitations will be any kind of Visual Studio AddIn. But that’s another story.

That’s all for now folks,
AJ.NET

About these ads

8 Comments »

  1. Now that’s what I call a tutorial! Examples, simple code, screenshots… perfect. Thanks.

    Comment by Peter — January 19, 2007 @ 2:39 am

  2. Great article, but it really proofs an important point. Don’t use the design view – ever. It may take some time to learn, but it does eliminate many bad surprises and it doesn’t take longer to build websites that way.

    Comment by Mads Kristensen — February 6, 2007 @ 12:59 am

  3. Well, I came to realize that good design time support really helps people – be it the ones not as experienced as you are or those as lazy as I am – and in the end it saves money. I wrote about the motivation in http://ajdotnet.wordpress.com/2006/08/28/do-you-value-design-time-support/ .
    And by the way: design time support is not limited to the design view. Intellisense, wizards, or snippets are also part of the design time infrastructure.

    Comment by ajdotnet — February 6, 2007 @ 8:17 am

  4. For components to distribute it’s cool, but for internal use it really doesnt matter. Great example though. Thumbs up!

    Comment by Mads Kristensen — June 15, 2007 @ 1:36 am

  5. Awesome. Just what i needed. Thanks so much.

    Comment by Sudarshan — November 29, 2007 @ 10:52 pm

  6. Hi! Is there any way to to add a property to a custom ASP.NET page class MyBasePage : System.Web.UI.Page in such a way, that it will be visible/modifyable in Visual Studio at design time? (Details here http://pro-thoughts.blogspot.com/2008/04/design-time-support-for-custom.html)

    Comment by Vladimir Kelman — April 21, 2008 @ 9:44 pm

  7. @Vladimir: At design time the page class is _not_ used, rather its base class is instantiated. You could try to work around this issue by providing another derivate, however I never tried this and I am not sure it will work properly (I rather doubt it).
    I would provide a (non-visual) control that does the work. This will give you full control over designtime and serialization and if necessary it will be available on user controls as well. ASP.NET AJAX does something similar with the ScriptManager.

    HIH,
    AJ.NET

    Comment by ajdotnet — April 22, 2008 @ 7:07 am

  8. Thanks for this great help. It really explores the design time properties programing in detail.

    Comment by Anoop Jayaram — March 11, 2009 @ 9:51 am


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Shocking Blue Green Theme. Create a free website or blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 244 other followers

%d bloggers like this: