Metadata-based Intellisense

Automatic metadata-driven schema generation for server control intellisense in Whidbey
Andrew Duthie recently blogged about intellisense generators for ASP.NET controls. He also has an MSDN article on how to author those .XSD files directly.

For Whidbey, we've improved the state of the art (so to speak). The idea is simple: You should never have to author the schema file manually. The tool automatically generates one on-the-fly when the page developer first uses your control, i.e. when the Register directive gets added to the page. This is the way it should always have been - but we had could not get this ready in time for v1.0. As you might expect, our approach is metadata driven, but the nice thing is that the functionality is based on the existing metadata attributes used for parsing and persistence, so there aren't a whole lot of new concepts to learn. As an aside, all of these attributes are described in my server controls book.

Which Properties are Emitted into the Schema?
  • By default all public read-write properties are emitted as valid attributes into the schema for a tag.
  • You can hide properties by marking them such that they cannot be declaratively specified in the markup using the DesignerSerializationVisibility attribute. This is useful if you do want your property visible in the property grid, or if you have a designer-only property. An example where we do this is in data-bound controls where the DataSource property is not persisted, but its associated data-binding is:
    [
    DesignerSerializationVisibility(DesignerSerializationVisibiliy.Hidden),
    Bindable(true)
    ]
    public virtual object DataSource { ... }
  • An exception to this rule is properties marked as Bindable(true) as shown in the example above. These show up even if they are marked as hidden so that the page developer can see them while typing in source view and bind them using the <%# %> data-binding syntax.
  • If your property is marked as DesignerSerializationVisibility(DesignerSerializationVisibility.Content), then the sub-properties of property type are also emitted into the schema. This is used for Style properties for example.
  • The schema generator also looks at Browsable, DefaultValue, Description, and Category attributes but these are primarily for property grid display in source view, rather than for intellisense.

Which Form are Properties Persisted In?
This is determined by the PersistenceMode attribute on the property. The default value of this metadata attribute implies persistence in the form of attributes on the main tag. If the property contains sub-properties, these get persisted in a flattened out fashion using the dash syntax (eg. Font-Name, Font-Bold etc.)

You could mark such a complex property such that it gets persisted as a nested tag (eg. The ItemStyle and other style properties on DataList):
[PersistenceMode(PersistenceMode.InnerProperty]
public virtual Style ItemStyle { ... }

If you have a single string property that you could persist it as inner content (similar to Text on TextBox):
[PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)]
public virtual string Text { ... }


How is the Content Model for a Tag Determined?
This is the one of the more complex decisions made by the schema generator.
  • First the schema generator needs to detect if your tag should contain other controls or if it should contain nested properties. This is done based on the ParseChildren attribute.
    The following sets your tag's content model to allow any other top-level server control tag, or HTML markup.
    ParseChildren(/* childrenAsProperties */ false)
    The following sets your Your tag's content model to allow the set of properties marked with PersistenceMode=InnerProperty.
    ParseChildren(/* childrenAsProperties */ true)
  • Second the schema generator detects if any of your inner tags are optional. For example, the ListItems inside a DropDownList do not need a surrounding <Items> tag. This is achieved by specifying the default property in the ParseChildren attribute. ParseChildren(/* childrenAsProperties*/ true, "Items")
  • If the tag represents a type that is not a Control, the contents are always assumed to be properties. Nested controls don't make much sense in this scenario.
  • If the tag represents a collection type, then the schema generator detects the type of elements in the collection by examining the Item property of the collection, and finds all available types that match or derive from the element type.
  • In Whidbey we added a mechanism for a control to indicate that it performs custom persistence of its content via the PersistChildren attribute. In this case, the schema generator filters out all the tags from within your content, and the validator (which drives off the schema as well), skips validation of the content. For example, the Xml control persists its xml document as inner content.
    [PersistChildren(/* persistChildren */ false, /* usesCustomPersistence */ true)]
    public class Xml : Control { ... }

And What are the Set of Top-level Tags?
The schema generator inspects the ToolboxItem attribute applied to controls. If a control has ToolboxItem(false) applied to it, it does not show up as a top-level control. TableRow and TableCell are examples of controls that are marked in this manner. The reason we overloaded this attribute is because its quite likely that a control that cannot be dropped from the toolbox onto the design surface directly cannot appear by itself either.

By default, Control has an associated ToolboxItem, and all derived controls simply pick it up, so you only need to use it when you want to hide something from the toolbox, or from the list of top-level tags.


How is the Tag Prefix (Eg. "asp" in <asp:Label>) Determined?
The tag prefix is generated based on the TagPrefix assembly level attribute, that maps namespaces in an assembly into a preferred (or hint) prefix. The generated schemas are key'd off of the tag prefix as well. For example, System.Web contains the following:
[assembly: TagPrefix("System.Web.UI.WebControls", "asp")]

Other Bells and Whistles
  • The schema generator also picks up on whether a property is of Color type, which causes the editor to automatically provide a quick mechanism to launch the color picker while typing in source view.
  • The schema generator automatically picks up enumeration types as well, to determine the list of valid values that are presented in intellisense dropdowns as the page developer types in source view.
  • For string properties that are URLs, you can mark them with the UrlProperty attribute. This allows the schema generator to tell a URL-property apart from any other string property. The generated schema records this, which allows the source editor to provide a quick mechanism to launch the URL builder. This is a new attribute introduced in Whidbey.
If you are authoring custom controls, be sure to check out the intellisense for your control in Visual Studio 2005, and make sure you've applied the appropriate metadata to your controls and its properties.

Undoubtedly, a question that's bound to come up is the relevance of the xaml syntax and how it could be applied to server-side markup. The syntax we have today was primarily designed to keep it simple for manual coding of pages. ASP.NET has various other constructs such as the <%= %> etc. as a result of its ASP legacy, and support for static (and interspersed) HTML markup is essential, which may or may not play well with an alternative syntax. Also, this has the implication of creating multiple formats for developers to understand, for tools to understand, support and preserve across edits, etc. etc. Suffice to say, more thought needs to be put into this... after Whidbey. If you have any thoughts to share in the meantime, be sure to drop a comment.

By the way, I should thank Todd Grunke on the Venus team, who works on various aspects of the HTML source editor, including the intellisense schema generator, for reviewing this material on short notice.
Posted on Tuesday, 7/20/2004 @ 3:52 PM | #ASP.NET


Comments

2 comments have been posted.

Victor Garcia Aprea

Posted on 7/21/2004 @ 12:50 AM
Great post!! Please keep this stuff coming... even if it means you've to travel by bus everyday :-)

Tom Carpe

Posted on 8/29/2005 @ 3:49 PM
Great! Lots of people inheriting DropDownList will be happy to have a solution to the "schema does not support the element" ListItem problem, especially one that does not involve writing a new XSD. This is especially important now since it seems like the error actually keeps the page from compiling; it used to only be a warning.

Thanks again.
The discussion on this post has been closed. Please use my contact form to provide comments.