New information has been added to this article since publication.
Refer to the Editor's Update below.

Visual Studio .NET

Top Ten Cool Features of Visual Studio .NET Help You Go From Geek to Guru

Jon Flanders and Chris Sells

Code download available at:NETTopTen.exe(164 KB)

This article assumes you're familiar with Visual Studio .NET

Level of Difficulty123

SUMMARY

Visual Studio .NET has lots of cool features. Obviously, it supports languages that target the common language runtime, like C# and Visual Basic .NET, and it lets you write CLR-managed code. But did you know that there are features of the environment itself, independent of the language features, that earn Visual Studio .NET the cool tool stamp of approval?

In this article the authors count down their favorite environment features—the ones they think every developer will applaud. They include support for debugging stored procedures, project reference management, metadata in Class View, a richer immediate window, custom environment programming with macros, and more.

Contents

10. Processes Dialog
9. SQL Server Stored Procedure Debugging
8. Project References
7. Class View
6. Command Window
5. Designer-friendly Custom Controls
4. Macros
3. Custom Add-ins
2. Custom Tools
1. Custom Wizards
Visual Studio Community

When we were just lads, knee-high to a 16-bit window, we religiously read the predecessor of this magazine, Microsoft Systems Journal. We always scoured the magazine cover-to-cover, but would especially savor the words of Dave Edson, who more than once presented his gems as David Letterman-style Top Ten lists. In the spirit of those bygone days and to focus on some of our favorite parts of Visual Studio® .NET, the culmination and combination of more than a decade of work on Visual C++®, Visual Basic®, Visual InterDev®, and Visual J++®, here's our list of the top ten coolest features in Visual Studio .NET.

10. Processes Dialog

In order to debug your DLL projects (for example a COM or Microsoft® .NET component), you first need to launch an EXE that loads your component. If there's an EXE project in the same solution as your DLL project, you can right-click on that project in the Solution Explorer and choose Set as Start-Up Project, press F5 to debug, and you're all set. Or, if the EXE you'd like to use is outside of the solution but can be started manually, you can use the path to the EXE as the Start Application. Unfortunately, neither of these techniques works if you need to debug your DLL inside of an already running process, like the ASP or ASP.NET worker processes that are started by Internet Information Services (IIS).

If you want to debug your code inside of an existing process, you can set the breakpoints in your source file and select Debug | Processes. This brings up the Processes dialog (see Figure 1), which allows you to pick a process to which you would like Visual Studio .NET to attach its debugger. You can choose to see all processes on a machine including system processes and processes in other logon sessions.

Figure 1 Processes Dialog

Figure 1** Processes Dialog **

Once you select a process to debug and press the Attach button, you get the Attach to Process dialog, which gives you the option of debugging any combination of managed common language runtime (CLR) code, Microsoft T-SQL code, native (unmanaged) code, and script. After you have attached to the process, all you need to do is cause that process to trigger your breakpoints, perhaps by surfing to the appropriate URL if you are debugging an ASP.NET application in the aspnet_wp.exe process. Once your breakpoints have been hit, you can debug away.

9. SQL Server Stored Procedure Debugging

Not only does Visual Studio .NET make attaching to processes much easier, one option you have when attaching to those processes is debugging SQL Server™ stored procedures. To enable this feature, you'll need to make sure that the SQL Server debugging components have been installed on your machine and that you have the appropriate permissions to debug stored procedures. Once that's been taken care of, you can use the Server Explorer to select any SQL Server database on your network, bring up your stored procedure, set a breakpoint, and then execute the code that calls that stored procedure. Once your SQL breakpoint is hit, you've got single-stepping, stepping in, data tips, and the rest of the Visual Studio .NET debugging tricks that you've grown to know and love. Figure 2 shows a breakpoint hit in a stored procedure.

Figure 2 Stored Procedure Debugging

Figure 2** Stored Procedure Debugging **

8. Project References

Solutions are often made up of multiple projects, like a Windows® Forms EXE project with one or two dependent component DLL projects. When that happens, not only will you be using the EXE project as the startup project, you'll want to make sure that the DLL projects are built first and referenced appropriately. To build the DLL project first, you can use the Project Build Order dialog, available by right-clicking on the solution in the Solution Explorer. However, as your solution grows, and components depend on other components, you don't want to be messing with the build order directly; you really want to establish dependencies. This can be done via the Project Dependencies dialog (see Figure 3), also available in the context menu of your solution in the Solution Explorer.

Figure 3 Project Dependencies

Figure 3** Project Dependencies **

Rearranging these dependencies will make sure that things are built in the appropriate order. However, this is still too much work. If one project depends on another assembly, shouldn't Visual Studio .NET be smart enough to notice this, figure out the dependencies, and make sure everything builds appropriately? Yes it should be, and yes it is. The key is Project References.

To add an assembly reference to your project, right-click on the referencing project in the Solution Explorer and choose Add Reference. This will bring up the Project References dialog. You should use the third tab on this dialog box when working with component references between projects. If you create or add your existing component projects to your solution, you can then share a project reference between your projects, rather than a file reference (which is what the other two tabs give you). When you use a project reference, Visual Studio .NET will calculate the dependencies and build order and make sure that the referenced assemblies are always up to date and copied to the appropriate directories so that everything runs properly.

7. Class View

Previous versions of Visual Studio, notably Visual C++, had a Class View window, but never like the one in Visual Studio .NET. Of course, like its predecessor, the Class View allows you to see the metadata for the types in your project, providing for more complete information than Visual C++ 6.0 ever did. You can even add methods, properties, fields, and so on, to your types, depending on whether it's a C++ class, a C# class, or a COM interface. You can do this by right-clicking on a class in the Class View window, selecting Add, and choosing the member to add to your type.

But that's not all; you can also use the Class View to generate skeleton implementations of methods to override or entire interfaces (unfortunately this does not work under Visual Basic .NET projects). To override a method in a base class, right-click on the method in the Class View under Bases and Interfaces and choose Add | Override. To implement an entire interface, make sure that your class lists the interface as one it implements, right-click on the interface under the Bases and Interfaces node of your class, right-click on the interface and choose Add | Implement Interface. Starting with the following C# code

class Foo : IDisposable { }

and then using Add | Implement Interface will yield the following skeleton implementation:

class Foo : IDisposable { #region Implementation of IDisposable public void Dispose() { } #endregion }

6. Command Window

The Command window in Visual Studio .NET has the same capabilities as the Immediate windows you were used to in earlier IDEs—in other words, the ability to set and get values from a stopped program (see Figure 4).

Figure 4 Command Window

Figure 4** Command Window **

However, there's more to Command than just value checking. Almost any command that you can execute with a menu, keyboard shortcut, or toolbar button can also be executed in the Command window. For example, to open a Solution in the Command window, type File.NewFile followed by the name of the file to create. This starts a new editor window with all of the syntax highlighting in place, but without the hassle of the New File dialog.

While you were executing the File.NewFile command, you might have noticed that as soon as you typed the period, you got a list of all of the file-related commands via IntelliSense®. This is because IntelliSense extends to the file system as well. For example, if you typed File.OpenSolution followed by "c:\", you'd be presented with a list of the files and directories in the root of your hard drive. For more documentation on your favorite commands, you can see the usage by passing /? as an argument, which will show the online help page for that command.

Furthermore, if you grow tired of typing File.NewFile, you can assign an alias to it like so:

>alias new File.NewFile

Now you can create a new file in the Command window with just the alias:

>new foo.cs

If you like aliases, there are a number of predefined aliases listed in the Visual Studio .NET online help.

Finally, if for some reason you'd like to avoid the Command window altogether but you'd still like its benefits, you can type commands directly into the mini Find window in the toolbar by preceding all commands with a leading ">". You even get IntelliSense. What will they think of next?

5. Designer-friendly Custom Controls

So far we've shown cool features in Visual Studio .NET from the user's point of view. These last five cool features are oriented towards the developer interested in extending the capabilities of Visual Studio .NET. Let's start with integrating custom controls more tightly into the Visual Studio .NET designers.

The Framework Class Library (FCL) classes are built with the idea that components need to be easier to work with at design time. Towards that end, Microsoft added features to the FCL that allow Visual Studio .NET (or any compatible IDE) to work with a class or control at design time in a special way. For example, if you create a WebForm or Windows Form and add a button control, when you double-click on the button a function gets generated automatically for the "default" event for that control and the event is hooked up to the newly generated function. The secret to making this, and other similar integration tasks, work lies inside the System.ComponentModel namespace. This namespace contains the classes that can be used to decorate a class with attributes which, in turn, tell Visual Studio .NET how to deal with that class at design time. For event generation specifically, the attribute in question is System.ComponentModel.DefaultEventAttribute. This attribute gets applied at a class level, like so:

[DefaultEvent("ClickMe")] class MyControl : Control { public event EventHandler ClickMe; }

Of course, finding the default event is one thing, but generating the code is another. Visual Studio .NET itself doesn't actually generate this code; that task is reserved for another object. This object is created by the Visual Studio .NET editor to act as the designated designer object for the object being designed. Another attribute class in the System.ComponentModel namespace, DesignerAttribute, can be applied to a class. Once the editor detects this attribute, it creates an instance of this designer class and uses that object as the controller for dealing with the object being designed:

[Designer("System.Windows.Forms.Design.ControlDesigner, System.Design.DLL", typeof(IRootDesigner))] class MyControl : Control { public event EventHandler ClickMe; }

When the MyControl class is dropped onto a form in the designer, the designer class is notified and then given the chance to persist whatever code it wants to the source file to which it is being added.

This model has multiple extensibility points built into it. This will allow you to not only take advantage of the designers that come with the .NET Framework, but also to create your own custom designers or decorate your own custom controls with the designer attributes. You can read more about creating and using designer classes in the documentation for System.ComponentModel.

4. Macros

The next step to extending Visual Studio .NET is to program it directly. The easiest way to do that is using macros. Macros are files written in Visual Basic .NET that are saved and loaded by the IDE and executed on command. They interact with the IDE through its automation object model.

The best way to get started using macros, and the macro IDE embedded inside the Visual Studio .NET IDE, is to record one. Start recording by choosing Tools | Macros | Record Temporary Macro, and then manipulate Visual Studio .NET to perform the operations you'd like recorded. When you press the Stop button on the macro tool bar (which will appear when you are recording a macro), you can examine your new macro using the Macros IDE (Tools | Macros | Macros IDE). Once you've got the cursor inside of a macro, debugging is as simple as setting a breakpoint on your source line (F9) and pressing F5 to run the macro.

Recording macros can be a powerful way to automate simple repetitive tasks. Also, since almost all of the functionality in Visual Studio .NET is exposed via its automation object model, hand-crafted macros can also be used to automate tasks that don't lend themselves to recording.

3. Custom Add-ins

Although macros are useful, once a macro runs it has no further effect. If you need some custom manipulation of the IDE that has a longer lifetime than a single operation, you'll want to create an add-in. An add-in is a COM class that implements the IDTExtensibility2 interface. There are several things you can do with an add-in that you can't do with a macro: create custom property pages for the Options dialog, use tool windows, add menu items, and add information to the Visual Studio .NET About box.

For example, one thing that I find missing in Visual Studio .NET is an integrated shell. The Command window is great, but it's not a full shell. I want a full shell integrated into Visual Studio .NET, as shown in Figure 5.

Figure 5 Full Shell Integration

To create an add-in, Visual Studio .NET provides the Add-in project template. In addition to the basics (like the name and description of the add-in), this wizard asks all kinds of questions like what language would you like to use to implement the add-in, what IDE to plug into (Visual Studio .NET is just one of several possibilities), whether to add the add-in to the Tools menu, allow access to the add-in on the command line, start the add-in immediately when Visual Studio .NET starts up, and even whether you'd like a default About box.

Choosing one of the managed languages causes the wizard to build a project that exposes a .NET class as a COM object that implements the IDTExtensibility2 interface. Creating a command-line shell in the IDE begins with creating a tool window for holding the control that interacts with cmd.exe. It's best to place this code in the OnConnection method, which Visual Studio .NET will call when the add-in is first loaded (see Figure 6).

Figure 6 Create Window

public void OnConnection( object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom) { applicationObject = (_DTE)application; addInInstance = (AddIn)addInInst; object objTemp = null; String guidstr = "{858C3FCD-8B39-4540-A592-F31C1520B174}"; // Create the Command line tool window windowToolWindow = applicationObject.Windows.CreateToolWindow( addInInstance, "CmdAddIn.CommandWindow", "Command Line window", guidstr, ref objTemp); windowToolWindow.Visible = true; // Add a new command to the Tool menu object[] o = null; Command cmd = applicationObject.Commands.AddNamedCommand( addInInstance,"ShowToolWindow", "", "", false, 1000, ref o,0); cmd.AddControl(applicationObject.CommandBars["Tools"],1); }

The call to CreateToolWindow creates the new tool window (the Command line window as shown in Figure 5) and also gives the IDE the ProgID that it should use to create an ActiveX® control to host in the new tool window (which, in our case, is a .NET Windows Form control acting as an ActiveX control). The ActiveX control then becomes the client area for the new tool window.

To make this window appear, we'll need a new command in the Tools menu, which is what the call to AddNamedCommand does in OnConnection. When the user selects the new command from the Tools menu, Visual Studio .NET calls the Exec method of the IDTCommandTarget interface to handle it:

public void Exec( string CmdName, EnvDTE.vsCommandExecOption ExecuteOption, ref object VariantIn, ref object VariantOut, ref bool handled) { if (CmdName == "CmdAddIn.Connect.ShowToolWindow" ) { windowToolWindow.Visible = true; handled = true; } }

When you create an add-in project with Visual Studio .NET, it creates a setup project that will add all the appropriate registry entries. Once your new add-in has been built, you can load it via Tools | Add-In Manager. At this point, you should be able to choose the new menu item from the Tools menu and start enjoying command-line shell goodness inside Visual Studio .NET (although the sample is admittedly far short of the ideal in this area).

2. Custom Tools

One popular use for macros and add-ins is to generate code. For example, as nice as the built-in collection classes in .NET are, they only hold objects, not specific types. This means that it's easy to put things into a collection that don't belong, and you'd never know it until you hit a runtime error, which is too late. One classic way around this problem is to wrap the collection in a type-safe wrapper, as shown in Figure 7.

Figure 7 Creating a Type-safe Wrapper

class IntList { public int Add(int value) { return _list.Add(value); } ••• private ArrayList _list = new ArrayList(); // Test static void Main() { IntList list = new IntList(); list.Add(1); // OK list.Add("two"); // ERR (good) } }

The problem, unfortunately, is that to really wrap a collection completely requires a lot of work for each combination of types, which is why code generation add-ins and macros are often used. However, even add-ins and macros require manual activation. What we'd really like is a way to describe the type of collection we're after (for example, an ArrayList of Int32 or a HashTable of Point), and just have the code generated as needed, maybe even as part of the build process. For that, Visual Studio .NET provides custom tools for all of your C# and Visual Basic .NET projects.

Visual Studio .NET provides two built-in custom tools, the MSDataSetGenerator that generates a type-safe DataSet from an XSD file, and the MSDiscoCodeGenerator that generates a Web Service proxy class from a MAP file. If you've added a DataSet file or a Web Service proxy to a project, you can see from the properties that each of them has a Custom Tool setting, as shown in Figure 8.

Figure 8 Custom Tool Properties

Figure 8** Custom Tool Properties **

A custom tool is a component that is run every time the input file changes and is saved. The custom tool is handed the name of the input file as well as bytes in the file via the Generate method on the IVsSingleFileGenerator COM interface. This method is responsible for producing bytes that will be dumped into C# or Visual Basic .NET, depending on what kind of a project it is. A generated file associated with a custom tool can be seen by pressing the Show All Files button in the Solution Explorer. The generated file provides the IntelliSense during development time and the code at compile time.

[Editor's Update - 12/6/2004: The BaseCodeGeneratorWithSite class was public in the Microsoft.VSDesigner.dll assembly that came with Visual Studio .NET 2002. However, this class was made internal in Visual Studio .NET 2003, and as a result you can't reference this class from your own assemblies. The class needs to be decorated with the appropriate COM attributes, but even so, a minimal custom tool is very simple:

[Guid("A0B5E5E9-3DF8-48bc-A6BA-E0DFD35C6237")] public class MyGenerator : BaseCodeGeneratorWithSite { public override byte[] GenerateCode(string file, string contents) { string code = "<<generated code>>"; return System.Text.Encoding.ASCII.GetBytes(code); } }

Once you've built your custom tool, the COM class needs to be exposed from your .NET component and registered via regasm. Also, custom keys letting Visual Studio .NET know about a new custom tool are needed in the Registry. You can take a look at the Registry entries required for the collection generator custom tool sample that are shown in Figure 9.

Figure 9 Custom Tool Registry Settings

Once your custom tool has been registered, using it is just a matter of setting it in the file properties of the input file, just like Visual Studio .NET does for its custom tools. For a more complete custom tool sample, check out the SBCollectionGenerator in the code download (see the link at the top of this article). It generates complete type-safe vector and hashtable collections from input files like this:

<typeSafeCollections> <typeSafeCollection> <templateKind>Vector</templateKind> <itemType>int</itemType> <collectionName>MyIntegerCollection</ collectionName> <collectionNamespace>MyCollections</collectionNamespace> </typeSafeCollection> </typeSafeCollections>

1. Custom Wizards

As nifty as the custom collection generation tool is, the input files are pretty primitive XML files, which are easy to edit once the initial options have been laid down, but a pain to start from scratch—not to mention the agony of setting the custom tool properties every time you want to add collections to a new project. It would be nice to have an automated way to add XML input files to a project so they can be used to set up the custom tool properties. To do that, you'll need to build a custom wizard, just like the DataSet project item wizard that comes with Visual Studio .NET.

One way to do this is to build a custom COM object that implements an interface named IDTWizard. After adding some configuration information to the Visual Studio .NET configuration files and implementing all of the user interface and the code generation back end, you can have your very own project template wizard that plugs into Visual Studio .NET. But why would you go to all the trouble of writing a bunch of custom code when you can use the generic wizard engine that runs all of the project wizards provided by Microsoft?

When you create a new project using one of the built-in project templates, Visual Studio .NET creates an instance of a particular COM object (the ProgID of this class is VsWizard.VsWizardEngine). The wizard object displays an HTML-based UI which can take input and can use these input values to expand a set of template files, which can then be added to the project. The basic wizard model is the same whether you're creating a new project or adding a new item to an existing project.

To build a custom tool item wizard, the first thing you need is a .vsz file. This file tells Visual Studio .NET the ProgID of the COM object to create for this wizard, the type of wizard, and whether the wizard has a UI. Also you can specify a directory where the wizard files live. Here is the .vsz file for the custom tool item wizard:

VSWIZARD 7.0 Wizard=VsWizard.VsWizardEngine Param="PROJECT_TYPE = CSPROJ" Param="WIZARD_NAME = SBCollectionItem" Param="ABSOLUTE_PATH = C:\Documents and Settings\All Users.WINDOWS\Documents\SBCollectionItem" Param="FALLBACK_LCID = 1033" Param="WIZARD_UI = TRUE"

An optional item in a .vsz file (but one that does appear in this .vsz file) is the ABSOLUTE_PATH element. A wizard's files can either live in the well-known folder for each language (C:\Program Files\Microsoft Visual Studio .NET\VC#\VC#Wizards for C# wizards) or in a separate folder. Having this wizard in a separate folder allows you to reuse this wizard for other languages. The .vsz file needs to be in the appropriate folder (appropriate both for language and for type of wizard). On our machine, the C# item wizards are in the C:\Program Files\Microsoft Visual Studio .NET\VC#\CSharpProjectItems folder.

Once the .vsz file is in place, you'll also need to edit a .vsdir file, which will tell Visual Studio .NET where in the New Item dialog to show the new wizard. Adding the new wizard to the local project items for C# projects means editing the C:\Program Files\Microsoft Visual Studio .NET\ VC#\CSharpProjectItems\LocalProjectItems\LocalProjectItems.vsdir file and adding the following line:

..\SBCollectionItem.vsz|{FAE04EC1-301F-11d3-BF4B- 00C04F79EFBC}|SBCollectionItem|10|A Type-safe collection metafile|{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}|4524|0|Collection.xml

We've got several fields here, all separated by old-fashioned pipes. The first field is the relative path of the .vsz file we created earlier. The third field is a short description for the Add New Item dialog. A long description (also shown in the dialog) can be provided in the fifth field. The fourth field is the sort order, where smaller means closer to the top. Also notice the last field. It's the template for the file to be generated and added to the project. The other fields are GUIDs that we copied from the other entry in this file to use their icon for display.

The contents of the Template\1033 (US English) directory is the manifest for the project item in a file called templates.inf and the template file itself. The template file and the templates.inf file use wizard-provided symbols to expand statements like [!output SAFE_CLASS_NAME] into strings like MyClass. The template file for the collection generator item looks like this:

<typeSafeCollections> <typeSafeCollection> <templateKind>[!output TEMPLATE_KIND]</templateKind> <itemType>[!output TYPE]</itemType> <collectionName>[!output COLLECTION_NAME]</collectionName> <collectionNamespace>[!output COLLECTION_NAMESPACE] </collectionNamespace> </typeSafeCollection> </typeSafeCollections>

To populate the symbols, we'll need a UI that we will put into the HTML\1033 directory. The HTML file gets loaded by the wizard when it executes, as shown in Figure 10.

Figure 10 Custom Project Item Wizard UI

Figure 10** Custom Project Item Wizard UI **

In addition to showing the UI, the HTML is responsible for taking the values the user enters and putting them into the name/value dictionary that the wizard uses to expand the templates. This is most easily accomplished using the OnWizFinish helper function provided by the standard wizard environment and providing a mapping between the HTML control managing the value for each template argument using the custom SYMBOL tag, as shown here:

<INPUT id="COLLECTION_NAME" type="text" name="Text2"> <SYMBOL VALUE="IntHash" TYPE="text" NAME="COLLECTION_NAME"></SYMBOL>

Once the user presses Finish, the default.js file in the Script\1033 directory of your wizard is what uses the templates.inf file to process each template file. Adding the generated file to the project after it's been generated can be accomplished in the OnFinish function in default.js using the AddFilesToCSharpProject helper function, as shown in the sample. Once all files have been generated, the wizard will give the script one last chance to set any project options by calling it back via the SetFileProperties function. The collection generation project item uses this opportunity to set the custom tool property of the newly generated file:

function SetFileProperties(oFileItem, strFileName) { oFileItem.Properties.Item("CustomTool").Value = "SBCollectionGenerator"; }

Visual Studio Community

As a final note it must be pointed out that as cool as Visual Studio .NET is, it's the community of users and extenders that really makes Visual Studio .NET shine. It only takes a quick check of the Visual Studio .NET Web site to realize just how much community involvement has strengthened Visual Studio .NET. Of course, Visual Studio .NET enables the community by providing so many features and extensibility hooks. This article has really just scratched the surface of what's available in Visual Studio .NET, and who knows where the community will continue to take it into the future.

For background information see:
Visual Studio .NET Home Page
DevelopMentor Mailing List
Visual Studio .NET Fun Facts

Jon Flanders teaches Essential ASP.NET at DevelopMentor. He is the author of ASP Internals (Addison-Wesley, 2000) and coauthor with Chris Sells of Mastering Visual Studio .NET (O'Reilly, October 2002). Reach Jon at JFland@develop.com.

Chris Sells is an independent consultant, specializing in distributed applications in .NET and COM and an instructor for DevelopMentor. More information about Chris and his various projects is available at https://www.sellsbrothers.com.