In preparing my custom server control (webEdit and webMenu) for general distribution, I've taken the time to make the package more of a complete offering. That is, I would not generally be comfortable simply releasing my code without any explanation of how to use it. As a direct result of this inexplicable compulsiveness, I've enhanced the package to support design time intellisense. I am one of the (few/many?) developers that actually prefers to do all of my design work in the raw .aspx/.ascx environment, rarely if ever relying on the visual designers. In fact, one of the first things I do on a fresh VS.NET install is turn off the setting to default to design view for HTML files.
In this article, I'd like to venture into the world of creating our own custom Intellisense for your web server controls that is supported in the IDE. Note: from what I understand, this will not be necessary in the Whidbey timeframe (yipee!), but it will suffice for the time being.
I'll start at the beginning, assuming no knowledge of how to add support for Intellisense.
VS developers have been accustomed (spoiled?) by the prevalence of the code-completion support built into the IDE. Open any code document and begin typing and you'll quickly see the help appear (e.g. <asp:…). This assistance appears based on context and can display classes, properties, values, methods, events, and much more. The Intellisense that appears within the IDE for .cs or .vb files is driven off of reflection. You don't have to do any work to get it to appear other than change your code. The Intellisence within the HTML designer, on the otherhand, is driven by metadata. This metadata file is found in C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Packages\schemas\xml\asp.xsd in a default installation.
If we want to add our own Intellisense, we have two options 1) edit asp.xsd or 2) create our own. Needless to say, you SHOULD NOT edit asp.xsd. No, the programming police will not come knock down your door and drag you off (though perhaps they should), but if in a patch or update the file ever gets replaced you'll lose your changes.
Instead we should create our own file and plop it in the same directory. I start to cringe at this thought, because I don't want to have to maintain two files (one in my \dev folder and one in the VS folder). I am loathe to have my only file in the VS folder. If you're developing your software on Windows XP or higher on an NTFS partition (which you should be doing if you're not), here's a cool tip. If you only want to maintain a single source file on your system (e.g. the one in your \dev folder), and still have it referenced elsewhere without creating a copy (which is easy to forget to do):
fsutil hardlink create "C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Packages\schemas\xml\devstone.xsd" "C:\Dev\......\devstone.xsd"
The FSUTIL tool (File System Utility) allows us to create a hard link so that the file appears in another directory. ACLs are shared and changes made in one file are reflected in the other because, well, it's the same file.
So to facilitate my development, I created my devstone.xsd file in my \Dev folder and a batch file called 'Register Intellisense.bat' that performs the FSUTIL operation (I only need to run it once, but if I ever get my source to another computer, it'll be there for me to run and I'll be set).
Now to creating our devstone.xsd file:
Begin by creating a new .xsd file and place the following Xml elements therein, changing the namespace to something appropriate for your situation:
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema targetNamespace='urn:http://www.devstone.com/schemas'
elementFormDefault='qualified'
xmlns='urn:http://www.devstone.com/schemas'
xmlns:xsd='http://www.w3.org/2001/XMLSchema'
xmlns:vs='http://schemas.microsoft.com/Visual-Studio-Intellisense'
vs:friendlyname='Devstone Web Forms Controls'
vs:ishtmlschema='false'
vs:iscasesensitive='false'
vs:requireattributequotes='false'>
<xsd:annotation>
<xsd:documentation>
Devstone ASP.NET Web Forms Controls schema.
</xsd:documentation>
</xsd:annotation>
</xsd:schema>
In order to create your Intellisense then, you create <xsd:element /> entries that define your valid 'top-level' objects. Each <xsd:element /> is tagged with a type attribute that identifies an <xsd:complexType /> defined elsewhere in the document:
<xsd:element name="WebEdit" type="WebEditDef" />
<xsd:complexType name="WebEditDef" vs:noambientcontentmodel="true">
<xsd:attribute name="Runat" type="runat" />
<xsd:attribute name="Enabled" type="xsd:boolean" />
<xsd:attribute name="Height" type="ui4" />
<xsd:attribute name="ID" type="xsd:string" />
<xsd:attribute name="Text" type="xsd:string" />
<xsd:attribute name="Width" type="ui4" />
<xsd:choice>
<xsd:element name="Toolbar" form="unqualified" type="WebEditToolbarDef" />
</xsd:choice>
</xsd:complexType>
There are a few things to notice here. First of all, I've decorated the <xsd:complexType /> with a schema annotation vs:noambientcontentmodel. That's a special attribute that VS.NET reads and understands that children of this type when entered on the .aspx/.ascx should be limited to the set of valid children as defined in the <xsd:choice />. By default, you'd get a comprehensive set of the children of the page as well. In this case we want to see only the <toolbar> element.
Now to get this tied onto our WebForm, we add the @Register directive to the top of the page and then add a reference to the Xml namespace within an element that will (directly or indirectly) be parent to our WebControl. The <body> tag is a good candidate for .aspx, but since .ascx pages (a.k.a Web UserControls, HTML Controls) don't have a <body> tag, you'll need to find another element, such as a <div>, <td>, or something like that.
<%@ Register tagPrefix="devstone" Namespace="Devstone.Web.WebControls" Assembly="Devstone.Web.WebControls.WebSuite" %>
<body xmlns:devstone="urn:http://www.devstone.com/schemas">
Notice that the Namespace matches identically to the targetNamespace of our schema file. Also, the devstone namespace matches the tagPrefix within the @Register directive. This ties our control's Intellisense to that element. Now we would get autocompletion for our <devstone:webEdit /> control on the page.
There's a lot more to this than what I've posted here in this blog entry, but I've also included my .xsd file for download if you'd like to check it out.
Enjoy!