Basic Instincts

My Namespace Extensions with My Extensibility

Matthew DeVore

Code download available at:BasicInstincts2008_04.exe(163 KB)

Contents

Ways to Expand the My Namespace
Expanding My with a Singleton
Miscellaneous My Extension Scenarios
Coding the Extension
Packaging the Extension into a Template
My Extensions as Templates
The .customdata File
Try It!
Publish the Extension in a Visual Studio Installer File

The My namespace feature was introduced in Visual Basic® 2005 to provide shortcut methods and APIs for common coding tasks. Since then, users have been able to write My namespace extensions to make it easy to access their own code libraries. My Extensibility, new in Visual Basic 2008, makes extending the My namespace even easier.

With the new My Extensibility feature, My namespace extensions can be activated or deactivated through the Project Properties Designer, or when an associated reference is added or removed from a project. This capability makes extending the Visual Basic development environment simple through deploying APIs for common coding tasks.

In this installment, I will focus on how to integrate your extension with the My Extensibility feature. If you find yourself wanting more information on how to write the actual code extensions to the My namespace, please read "Simplify Common Tasks by Customizing the My Namespace" in the July 2005 issue of MSDN® Magazine (msdn.microsoft.com/msdnmag/issues/05/07/My).

Ways to Expand the My Namespace

There are a few code models you can use to expand My. Let's start with the easiest one, the one you already know how to use. In fact, adding anything to the My namespace is very much like adding something to any other namespace. Here is the simplest example:

Namespace My.HandyStuffForMy
    <Global.Microsoft.VisualBasic.HideModuleName()> _
    Friend Module HandyStuffForMyModule
        Sub Foo()
            ...
        End Sub

        Property Bar()
            ...
        End Property
    End Module
End Namespace

This will cause a sub called Foo and a property called Bar to appear in a namespace called HandyStuffForMy within the My namespace. There are a couple of things here that you should notice: the HideModuleName attribute and the Friend access modifier on the Module. The HideModuleName attribute will prevent the Module name from showing up in IntelliSense®. Because you don't need to type Module names in order to refer to their members, showing the Module would only clutter up IntelliSense (see Figure 1).

Figure 1 Sub Namespace in IntelliSense

Figure 1** Sub Namespace in IntelliSense **

If you would like to add these members directly to My without putting them in a sub namespace (see Figure 2), change Namespace My.HandyStuffForMy to just Namespace My, like the following:

Namespace My
    <Global.Microsoft.VisualBasic.HideModuleName()> _
    Friend Module HandyStuffForMyModule
        Sub Foo()
            ...
        End Sub

        Property Bar()
            ...
        End Property
    End Module
End Namespace

Figure 2 No Sub Namespace

Figure 2** No Sub Namespace **

Additionally, if you use a namespace that already exists in My, such as Resources, these members will be added to that namespace. This gives you a lot of flexibility for how your extensions are accessed. The second thing to be aware of is the access modifier on the module. The Friend access modifier will prevent other assemblies from accessing these members. If you use Public access, the elements may conflict with symbols declared in these other project's My namespace.

I'll set up the sample My Extension in a moment using the strategy I just outlined. However, not everything in My is a namespace. There are also Properties that return global instances of certain classes, also known as singletons.

Expanding My with a Singleton

My.Computer, My.Settings, and My.Application are singleton instances of classes called My.MyComputer, My.MySettings, and My.MyApplication, respectively. We can easily add members to these classes using the partial class feature. For instance, the following code adds a sub called Shutdown to the My.Computer object:

Namespace My
    Partial Class MyComputer
        Sub Shutdown()
            ...
        End Sub
    End Class
End Namespace

This code can be used for expanding My.Application and My.Settings. Partial classes let you add members to a class that is defined somewhere else as long as the source code for that class exists in the current project. This includes classes like MyComputer for Visual Basic projects, as the compiler injects the classes automatically before the code is compiled.

What if you want to allow your added elements to be accessed via a singleton, just like My.Computer, My.Settings, and My.Application, but you want to create your own? This involves adding a ReadOnly Property to the My namespace using the strategy I introduced at the beginning of this column and then having that Property return the global instance as supplied by the ThreadSafeObjectProvider(Of T) class. Comparing this to the previously mentioned new namespace strategy, this would cause a new instance of the class to be generated on a per-thread basis. Figure 3 shows how you would add a global object called My.CustomMyObject with members named Foo and Bar. Figure 4 shows the resulting IntelliSense experience in the IDE.

Figure 3 Adding My.CustomMyObject

Namespace My
  <Global.System.ComponentModel.EditorBrowsable _
  (Global.System.ComponentModel.EditorBrowsableState.Never)> _
  Partial Friend Class MyCustomMyObject
    Public Sub Foo()
            ...
    End Sub

    Public Property Bar()
            ...
        End Property
  End Class

  <Global.Microsoft.VisualBasic.HideModuleName()> _
  Friend Module CustomMyObjectModule
    Private instance As ThreadSafeObjectProvider(Of MyCustomMyObject)

    ReadOnly Property CustomMyObject() As MyCustomMyObject
      Get
        Return instance.GetInstance()
      End Get
    End Property
  End Module
End Namespace

Figure 4 My.CustomMyObject in IntelliSense

Figure 4** My.CustomMyObject in IntelliSense **

Miscellaneous My Extension Scenarios

Extending My.User is probably the most difficult scenario. It is defined outside of your project, in the Visual Basic runtime DLL, which means you cannot extend it with partial classes. In order to extend My.User, a project-level compilation constant must be defined, which means that extending My.User requires that the developer using the extension execute this manual step.

In any case, if you want code examples and guidance for extending My.User, please refer to the aforementioned MSDN Magazine article, which also contains information on specialized scenarios, such as expanding My.Application.Log, expanding My.Computer.Network, and customizing the way My.Settings stores application configuration information.

What about objects such as My.WebServices and My.Forms? The classes of these objects are nested types defined in Modules, which means they cannot be used with the Partial Class feature, and they are NotInheritable, which means they cannot be extended like My.User. There is no easy way to add members to them. In this case, you must find a better way for your extension to fit into the My namespace.

Coding the Extension

Now that I've explained extensions to the My namespace, let's see how you can easily implement and share these extensions in our projects. I just showed how My is a namespace like any other namespace, and you can add top-level properties to it by simply adding a module and specifying a namespace of My. Let's now create a set of validation functions that can check if a string matches the format of U.S. and Canadian postal codes, phone numbers, e-mail addresses, and a few other important formats.

Let's begin by starting Visual Studio® and creating a new console application. Name the project MyValidation. Next, add a new Module to the solution called MyValidation.vb and replace the code in the MyValidation.vb file with the code shown in Figure 5 (which has been excerpted here for space).

Figure 5 Validation Code Excerpt

Imports System.Text.RegularExpressions
Imports System
Imports System.Linq
Imports Microsoft.VisualBasic

Namespace My.Validation
  <Global.Microsoft.VisualBasic.HideModuleName()> _
  Public Module MyValidationMod
    Public Function IsEmpty(ByVal value As Object) As Boolean
      If (value Is Nothing) OrElse _
         (value Is System.DBNull.Value) OrElse _
         (value.ToString = String.Empty) OrElse _
         (TypeOf value Is Date AndAlso CDate(value) = Date.MinValue) _
      Then
        Return True
      Else
        Return False
      End If
    End Function

    Public Function IsAlphaNumeric(ByVal value As String, _
     Optional ByVal emptyOK As Boolean = False, Optional ByVal _
     whitespaceOK As Boolean = False) As Boolean
      If IsEmpty(value) Then Return emptyOK

      Dim expr As String
      If whitespaceOK Then
        expr = "^[a-zA-Z0-9\s]+$"
      Else
        expr = "^[a-zA-Z0-9]+$"
      End If

      Return Regex.IsMatch(value, expr)
    End Function

...

    Public Function IsCanadianProvince(ByVal st As String) As Boolean
      Dim allProv = "|AB|BC|MB|NB|NL|NT|NS|NU|ON|PE|QC|SK|YT"
      Return st.Length = 2 AndAlso allProv.IndexOf("|" & st) <> -1
    End Function

    Public Function IsUSAState(ByRef st As String) As Boolean
      Dim allStates = "|AL|AK|AZ|AR|CA|CO|CT|DE|FL|GA|HI|ID|IL|IN|IA|" & _
        "KS|KY|LA|ME|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|" & _
        "OK|OR|PA|RI|SC|SD|TN|TX|UT|VT|VA|WA|WV|WI|WY"
      Return st.Length = 2 AndAlso allStates.IndexOf("|" & st) <> -1
    End Function

    Public Function IsUSAPostalCode(ByVal zip As String) As Boolean
      If String.IsNullOrEmpty(zip) Then Return False

      Return Regex.IsMatch(zip, "^\d{5}(-\d{4})?$")
    End Function

    Public Function IsCanadianPostalCode(ByVal zip As String) As Boolean
      If String.IsNullOrEmpty(zip) Then Return False

      Return Regex.IsMatch(zip, "^[A-Z]\d[A-Z] \d[A-Z]\d$")
    End Function

    Public Function IsNorthAmericanPhone(ByRef phone As String) As Boolean
      If String.IsNullOrEmpty(phone) Then Return False

      Dim expr As String = "^(?:\([2-9]\d{2}\)\ ?|[2-9]\d{2}" & _
                           "(?:\-?|\ ?))[2-9]\d{2}[- ]?\d{4}$"

      Return Regex.IsMatch(phone, expr)
    End Function

    Public Function IsEmail(ByRef email As String) As Boolean
      If String.IsNullOrEmpty(email) Then Return False

      Dim localPartCharset = "[0-9a-zA-Z!#$%*/?|\^{}`~&'+\-=_]"
      Dim domainPartCharset = "[0-9a-zA-Z\-]"
      Dim expr = String.Format("^{0}+(\.{0}+)*@{1}+(\.{1}+)*", _ 
        localPartCharset, domainPartCharset)

      Return Regex.IsMatch(email, expr)
    End Function
  End Module
End Namespace

Be careful when creating a My Extension template that your code can work in any project configuration. You need to pay particular attention to the possible values of Option Explicit, Option Strict, Option Compare, and (new in Visual Basic 2008) Option Infer. You can either write code that will work in the least-flexible configuration (Option Explicit On, Option Strict On, Option Infer Off, and Option Compare unspecified), or specify each option explicitly at the top of each code file in your extension, which is the recommended approach.

Also, you need to be careful to account for different project imports to prevent conflict with symbols defined in the project. You do this by qualifying any symbol not defined in your template with the Global or the My keyword. For instance, instead of Text.Encoding, use Global.System.Text.Encoding. If you only use Text.Encoding, your template will not work in Windows® Forms Applications, which imports two namespaces that contain a namespace called Text. If you omit Global and only use System.Text.Encoding, then your template may not work in a project that defines a namespace called System.

It's always a good idea to thoroughly test your code before distributing it to others, and My extensions are no different. Let's use the automatically created module (Module1.vb) to write a test for the My Extension. It becomes a hassle to re-export the extension when you find a bug, so it is best to make the extension as functional as possible and make sure it's properly tested before you export it for the first time.

Open Module1.vb by double-clicking it in the Solution Explorer and place the test code, which has been excerpted in Figure 6, into the file, replacing the code that was there before. For the full code, see the code download on the MSDN Magazine Web site. Now you are ready to test the My Extension, so press F5 to execute the project and attach the debugger. You should see the following output:

Figure 6 Test Code Excerpt

Module Module1
  Private testNumber As Integer
  Private testGroup As String

  Sub StartTestGroup(ByVal name As String)
    testGroup = name
    testNumber = 0
  End Sub

  Sub Test(ByVal result As Boolean, ByVal expected As Boolean)
    testNumber += 1

    If result <> expected Then
      Console.WriteLine("Test #{0} of group {1} FAILED.",  _
        testNumber, testGroup)
    End If
  End Sub

  Sub Main()

    StartTestGroup("IsAlphaNumeric")
    Test(My.Validation.IsAlphaNumeric("123HH2", True, True), True)

    ...

    StartTestGroup("IsCanadianPostalCode")
    Test(My.Validation.IsCanadianPostalCode("H0H 0H0"), True)

    ...

    StartTestGroup("IsCanadianProvince")
    Test(My.Validation.IsCanadianProvince("ON"), True)

    ...

    StartTestGroup("IsEmail")
    Test(My.Validation.IsEmail("a@b.com"), True)

    ...

    Console.WriteLine _
      ("Tests are finished. No FAILED messages indicates success.")
    Console.WriteLine("Press any key to continue.")

    Console.ReadKey(True)
  End Sub
End Module

Tests are finished. No FAILED messages indicates success.
Press any key to continue.

Packaging the Extension into a Template

Now that the extension is written and properly tested, it's time to package it up into a My Extension template. With the project open, click the File menu, then select Export template.... If you don't see this option on the File menu, right-click on the toolbar and select Customize, then select the Commands tab, choose the File category, then drag the Export Template command onto the File menu. Once you export the template, the Export Template Wizard appears. Select Item template (see Figure 7). My Extensions are always item templates—never project templates.

Figure 7 Choose Template Type

Figure 7** Choose Template Type **(Click the image for a larger view)

You can select the project from which the template will be created; in this solution there is only one project, so just click Next. In the Items to Export listbox, select just the MyValidation.vb file and click Next. On the next step, you can select which references you would like to include with this item. You have to select all the references required for the My Extension to operate once it has been added to a project. For this example, select System.Core, then click Next again.

In the last step, fill in the Template description textbox; for instance, "Extends the My namespace with a set of string validation methods." Finally, uncheck the checkbox "Automatically import the template into Visual Studio." This setting ensures that the template won't be imported right away. You're going to need to modify a few things in the template before it's ready to use as a My Extension, so just click Finish.

My Extensions as Templates

In a My Extension template, as in any Visual Studio template, all of the template parameters are available. These are special strings that can be inserted into the My Extension code automatically when it is added to a project. For instance, $clrversion$ will be replaced with the current version of the CLR. In the case of My Extension templates, $safeitemname$ will be replaced with the value of the <DefaultName> element in the .vstemplate file, minus the file extension. This will be equal to the name of the .vb file containing the template code.

Anything else that you can do with a regular template can also be done with a My Extension template. For instance, within the .vstemplate file, by setting the value of the <Hidden> element to false, you can include the template in the Add New Item Dialog when adding items to projects. You can also force the addition of assembly references with the <References> section, and you can add a family of code files as is done by the Windows Form template to include the form's design code as well as the user code.

For more information on configuring the template through the .vstemplate file, see the "VSTemplate Element" reference that is available in the MSDN Library at go.microsoft.com/fwlink/?LinkId=110905. For more information on template parameters, see the "Visual Studio Template Parameters" documentation at go.microsoft.com/fwlink/?LinkId=110907.

There are a few key differences between typical item templates and My Extensibility item templates. In order to enable the Item Template so it can be managed from the My Extensions page of the Visual Basic Project Designer, you must take special steps to configure it properly.

First you need to add a <CustomDataSignature> element to the MyTemplate.vstemplate file located in the exported template .zip file. This indicates that the Item Template is to be treated as a My Extension. You also need to prevent the items in the template from being opened automatically when the template is added to the project by adding a couple of attributes onto the <ProjectItem> element. This is because My Extension template code should be invisible to the consumer. In addition, you must add the <Hidden> element into the MyTemplate.vstemplate to indicate that the template should not be displayed in the list of installed templates when the user elects to add items to his project through the Add New Item dialog. Finally, you need to add a .customdata file to the package in order to provide information that does not apply to typical templates, namely a unique ID, a version number, and a trigger assembly.

To do this, you will have to unpack the .zip file, perform the alterations, and then repack the necessary files. So, first unzip the template's contents to an empty directory so you can edit them. Visual Studio will place the file in %MyDocs%\Visual Studio 2008\My Exported Templates, where %MyDocs% is your My Documents folder.

Open the MyTemplate.vstemplate file in a text or XML editor and make the edits that you see highlighted in red in Figure 8. Remember that XML, unlike Visual Basic, is case sensitive.

Figure 8 My Extensibility Item Template

<VSTemplate Version="2.0.0"
   xmlns=https://schemas.microsoft.com/developer/vstemplate/2005
   Type="Item">
  <TemplateData>
    <DefaultName>MyValidation.vb</DefaultName>
    <Name>MyValidation</Name>
    <Description>Extends the My namespace with a set of string 
       validation methods.</Description>
    <ProjectType>VisualBasic</ProjectType>
    <SortOrder>10</SortOrder>
    <Icon>TemplateIcon.ico</Icon>

    <!-- Configures the template to not be shown as an option 
      when the user selects to add an item template to his project -->
    <Hidden>true</Hidden>

    <!-- Indicates it is to be treated as a My Extension. -->
    <CustomDataSignature>Microsoft.VisualBasic.MyExtension
    </CustomDataSignature>

  </TemplateData>
  <TemplateContent>
    <References>
      <Reference> <Assembly>System.Core</Assembly> </Reference>
    </References>
    <ProjectItem SubType="Code" TargetFileName="$fileinputname$.vb"
      ReplaceParameters="true" OpenInEditor="false"
      OpenInWebBrowser="false" OpenInHelpBrowser="false">MyValidation.vb     
    </ProjectItem>
  </TemplateContent>
</VSTemplate>

Create a new file to be packed into the .zip file called MyValidation.customdata. Note that any file that ends with .customdata will work. In Notepad or any text editor, add the following markup:

<VBMyExtensionTemplate 
    ID="MyValidationMyNamespaceExtension" 
    Version="1.0.0.0" />

The AssemblyFullName attribute, which is omitted in this example, indicates which assembly will trigger the My Extension. In other words, you can specify here that whenever this assembly is added to the project, the user will be invited to also add the My Extension. Whenever this assembly is removed from the project, and the My Extension is currently active, the user will be invited to remove the My Extension. To specify a trigger assembly, such as System.Windows.Forms, you would insert this text into the .customdata file:

<VBMyExtensionTemplate 
    ID="MyValidationMyNamespaceExtension" 
    Version="1.0.0.0"
    AssemblyFullName="System.Windows.Forms" />

Now you are ready to copy the files back into the template .zip file. You may be tempted to edit the template's icon, but the icon of My Extension templates are not shown to the user, so I recommend leaving it as is. Copy the four files back into the .zip file using Windows Explorer. Then copy the .zip file to %MyDocs%\Visual Studio 2008\Templates\ItemTemplates\, where %MyDocs% is your My Documents folder.

The .customdata File

The handling of My Extensions within a project is a largely hidden and automated process. For Visual Studio to handle these files smoothly with little input from the user, the Project file, which has a .vbproj extension, has some new attributes. You can see them if you open them in a text editor the .vbproj file of the project to which you added the MyValidation extension. You will see something like this:

<ItemGroup>
  ...
  <Compile Include="My Project\MyExtensions\MyValidation.vb">
    <VBMyExtensionTemplateID>
      MyValidationMyNamespaceExtension
    </VBMyExtensionTemplateID>
    <VBMyExtensionTemplateVersion>
      1.0.0.0
    </VBMyExtensionTemplateVersion>
  </Compile>
  ...
</ItemGroup>

But if you take a look at the entry for a typical code file, such as Module1.vb, you will only see this:

<ItemGroup>
  <Compile Include="Module1.vb" />
</ItemGroup>

As you can see, there are a few differences. First, the MyValidation.vb file is stored in the My Project folder, which means it is not visible unless the user presses the Show All Items button in the Solution Explorer pane. Additionally, the MyValidation.vb file has two extra pieces of metadata, which correspond to the values you added in the MyValidation.customdata file.

By storing this information here, Visual Studio can allow the user to add and remove the extension through the My Extensions Project Designer tab rather than adding or removing the file directly. Additionally, if the reference to the trigger assembly is removed, Visual Studio can warn the user that the My Extension may stop working and prompt you to remove the template. This information also enables Visual Studio to prevent two copies of the Extension from existing in the same project.

Try It!

The My Extension is now ready for integrated use in the My Extensibility feature. To use it, first start a new instance of Visual Studio and create a new project. Then, open the Project Designer by right-clicking on the Project and selecting Properties or double-clicking on My Project in the Solution Explorer. On the My Extensions tab, click Add Extension and select the template you just created, and then click OK. The extension is now loaded into the project. If you type My. in your code (notice the dot), you will now see Validation as one of the namespaces available through IntelliSense.

Publish the Extension in a Visual Studio Installer File

In order to make it much easier to distribute your extension to other developers, you can create an installer file. A Visual Studio Installer file is a .zip file renamed to have an extension of .vsi. In the file are the Visual Studio add-on and a .vscontent file that describes it. Let's expand on the sample and create a .vsi file for the My Extension. First, create a text file named MyValidation.vscontent with the text in Figure 9 in it to describe the content.

Figure 9 MyValidation Installer

<VSContent xmlns="https://schemas.microsoft.com/developer/vscontent/2005">
    <Content>
        <FileName>MyValidation.zip</FileName>
        <DisplayName>My.Validation Extension</DisplayName>
        <Description>Extends the My namespace with a set of string
           validation methods.</Description>
        <FileContentType>VSTemplate</FileContentType>
        <ContentVersion>1.0</ContentVersion>
        <Attributes>
            <Attribute name="TemplateType" value="Item"/>
        </Attributes>
    </Content>
</VSContent>

When you create a different My Extension, the only elements likely to change are the FileName, DisplayName, Description, and ContentVersion values. For more information on creating a Visual Studio Installer file, as well as in-depth explanations of these elements, see the MSDN Library topic at go.microsoft.com/fwlink/?LinkId=110908.

Put this MyValidation.vscontent and the MyValidation.zip file into another .zip file called MyValidationInstall.zip. Then rename MyValidationInstall.zip to MyValidationInstall.vsi. Now all you have to do is double-click on it in order to install the template into Visual Studio automatically. Note that you will be prompted to overwrite the template if you already have one with the same name. If you've been following along that's exactly what will happen, so overwriting the file should be fine. Now you can distribute this .vsi file in order to easily install your My Extension on other developers' machines.

Send your questions and comments to instinct@microsoft.com.

Matthew DeVore has one-and-a-half years of experience on the Visual Basic Language QA team, working on the latest evolution of the language he first learned to program in, QBasic. In Visual Basic 2008, Matt tested Anonymous Types, My Extensibility, and, to a lesser extent, Lambda Expressions.