Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#

CodeStash - a journey into the dark side of Visual Studio, or how I lost my hair

,
Rate me:
Please Sign up or sign in to vote.
4.93/5 (76 votes)
20 Mar 2012CPOL47 min read 220.1K   115   98
A look into the CodeStash Visual Studio Extension.

CodeStash source

Codestash Codeplex site : http://codestash.codeplex.com/

CodeStash Article Listings

Part 1 : CodeStash web site overview / High Level Architecture / How To Install CodeStash
Part 2 : CodeStash web site Low Level Architecture
Part 3 : (This article) CodeStash addin

I beg your indulgence

This article was originally intended to be launched alongside Sacha's two articles. Unfortunately, due to circumstances beyond his control, Sacha is unable to post his articles tonight. Saying that, I did promise that there would be a posting of CodeStash, so I am uploading this part for those who are interested in how I developed the extension. Once Sacha has uploaded his articles, I will come back and update the section above to link to those articles.

I promised that there would be an announcement, and I will not disappoint. When Sacha and I developed CodeStash, which consists of a website and a Visual Studio extension, there was always the question hanging around about how we were going to host the website. We decided that we would initially offer CodeStash as a self-hosting site in order to gauge how useful people found the idea, and to get feedback on the type of features they would like to see.

Obviously, this is not an ideal situation on something which is intended to be a central repository for code snippets. Well, who should step into the fray to help us and to offer to host CodeStash for us but our very own Chris Maunder, who has kindly offered to host the site for us. To this end, we will be working with the Code Project team to turn CodeStash into a Code Project packaged snippet manager. And we need your help. We need you, the Code Project community, to try out CodeStash. We need your requests and your feedback. We need your enthusiasm and your boundless creativity.

A brief history

Around about a year ago, Code Project wunderkind Sacha Barber mentioned that he had a project he wanted to kick off, and he would need somebody who knew how to write Visual Studio addins to help with it. Well this was too good an opportunity to pass up; anybody who has read any of his articles knows what a superstar he is, and it's a great chance to work with one of the superstars of the development world.

Anyway, time went on, but no project appeared - but this was simply down to the massive number of articles that Sacha had on the go over this period. Finally, around about last September/October, Sacha sent me a spec for this project called CodeStash and asked if I was still interested - this was not something I was going to turn down. So, towards the back end of November, we actually started working on CodeStash.

This project has been a labour of love. Working with Sacha is an absolute joy, he's been so patient waiting for me to deliver my side, even when I completely changed my mind about what MVVM framework I was going to use internally, ripped out the core and effectively started again (obviously the fact that I decided to use Cinch might have helped sway the matter a bit).

Well, here we are, Sacha's vision has come to fruition and it's good. My humble contribution just builds onto the fantastic underpinnings that Sacha put in place, which have made this a complete joy to work on and a huge amount of fun. So, from the bottom of my heart, I thank you Sacha. You are, without a doubt, the man.

For those who have been patiently been waiting for the next in my Windows Phone series, I apologise most humbly. I haven't forgotten you, and I haven't lost interest in the series. CodeStash has been pretty much my all consuming passion for the last few months, and I hope you feel that this makes the delay worth the wait.

So, what is CodeStash all about? As this started as the brainchild of Sacha, I think it's only fair that I use his words to describe CodeStash.

Well CodeStash, it is a productivity tool for a single developer or team of developers (where they could be part of the same team).

The tool itself can be thought of as a web based centralized snippet repository for the single developer (or team of developers), where the develop(s) may manage useful code snippets that they wish to use to carry out their day to day tasks.

The web site will provide the following functionality

• OpenId authorization which will work in conjunction with standard ASP .NET forms authentication
• Tag cloud for most common snippet types, to allow quick search lookup for these types of snippet
• The ability to search for existing code snippets (ones that have been stored), by keyword tag, group, language, code content
• The ability to create/delete/edit existing code snippets
• The ability to group snippets into certain groups such that when searching for a single code snippet all related snippets will be shown. For example if I search for INPC I would get a C# snippet for declaring a INPC based model/ViewModel but would possibly get a XAML snippet that would also come back as part of the search

The web site is largely concerned with CRUD (Create/Retrieve/Update/Delete) of code snippets, which in its self is not that tricky, or even revolutionary (although none of the existing solutions out there deal with the concept of grouping at all).

There is however another angle to this project, in that it is intended to have seamless integration with Visual Studio (2010 and above), in that it will come with a managed VS addin, that will integrate with VS by way of certain content menus and property pages, that will allow the VS user to upload code to CodeStash, and also allow the VS user to search CodeStash by the use of a set of standard ASP .NET MVC controller actions that will expose/accept RESTful data to the Bespoke UI that will be launched from inside of the VS.

When a user searches for a code snippets that has previously been stored within CodeStash they will have the option to insert the matched CodeStash snippet into the VS editing pane (providing the CodeStash snippet type matches the current VS editor file type).

Existing Solutions

Like I say the web site itself is not such a novel thing, there are a number of existing solutions out there that do a similar job to the web site aspect of CodeStash, after all it really just comes down to CRUD operations, but what great idea doesn't really boil down to that in the end, even Facebook is just CRUD with some marketing veener.

Thing was once you looked at all these existing solutions they all seemed to have certain areas where the functionality was lacking, for example some of the existing solutions suffered in these areas:

  1. Searching was very limited, if offered at all, which made it very hard to find a snippet once you uploaded it.
  2. No concept of grouping, which is pretty bad I feel. These days code is made up of many elements, you may have a HTML file a CSS file a JavaScript file, all of which could form a logic snippet. There may of course be a single file, but where there are multiple related files the ability to bring these back within the same search is extremely important and aids productivity.
  3. No Visual Studio integration

For completeness here is a list of some of the existing solutions out there that we examined before embarking on the mission to create CodeStash

That is obviously not an exhaustive list, but they were the best of the bunch we found while doing our research into what was out there. You know there was no point inventing a wheel if someone had already come out with a Ferrari.

So in a nutshell that's what CodeStash is all about. Now if any of you have bothered to click the link you will notice that is just takes you to a codeplex web site, with not much there apart from source code. Before we go any further with this article, it's important to realise that the addin is just one part of this project, so I would highly recommend that you go and read Sacha's article, and learn how to set the website up - you're going to need to do that before the addin will work.

The "We couldn't have done this without" section

First and foremost, I have to thank Sacha. Working with him has been a complete blast and I think that this project is huge.

I would also like to thank Chris and the team at Code Project for giving us hope for the future. Honestly, their involvement has been a huge boost that kept our flagging spirits up as we were writing the articles.

What we will cover

In this article, we are going to cover some of the thought processes behind writing a reasonably complex multi-layered Visual Studio extension. We will find out how the commands were put together and how to interact with a well defined REST interface from inside an addin. We will see how we can use MEF to ease the pain of working with shared code, how to interact with internal Visual Studio services and to provide our own property pages.

This article does not teach writing XAML code, or how to use MVVM. We will not be covering how to use Cinch (I would heartily recommend reading Sacha's series on the Cinch framework to get this background). It also does not aim to cover MEF development in great depth, as all these concepts are tangential to the aim of understanding what the CodeStash addin is designed to achieve. CodeStash also makes use of Daniel Grunwald's excellent AvalonEdit control to display snippets, the sheer depth of this control means that I have to recommend reading the original article he wrote to support it; right now I'd just like to say a big thanks to Dan for writing this control and making it freely available.

Prerequisites

In order to write Visual Studio 2010 extensions you're going to need Visual Studio 2010 Professional (or above), Visual Studio 2010 Service Pack 1 and the Visual Studio 2010 SP1 SDK.

A brief note in advance

In order to make this article a little less dry, and a little bit chattier, I have taken deliberate liberties with the way sentences are formatted, along with moving backwards and forwards in terms of pronouns. I apologise to the English purists out there, but I wanted to take what could have been a very dry and technical article and make it less formal, and hopefully easier to read. This is why I slip backwards and forwards with such things as the use of Visual Studio and VS to represent Visual Studio. In order to bridge the gap between developers who are used to developing addins in previous versions of Visual Studio, I use the terms addin and extensions interchangeably, but they are both referring to an extension that can be installed via the extension manager.

The addin is, effectively three parts. The first part is the menus used to show our CodeStash functionality. The second part are the views that comprise saving and inserting snippets, and the third part is the settings that the user needs to actually use CodeStash. In this article, we are going to take a look at how these all come together, picking up some tips and tricks for doing some cool Visual Studio addin stuff, and how to interact with the CodeStash website. This is not going to be a line by line breakdown of every part of the extension - frankly, there's so much there that this article would end up being one vast code dump with your will to live draining away. The article is, also, not going to touch on every cool little trick we have put into the code base; hopefully, once you see the code in action, you'll want to explore the code for yourself. This article should give you enough information to continue the voyage of Visual Studio Package discovery all by yourself.

The extension in action

The CodeStash menu in action 

When text is selected in the editor window, the Save snippet option is enabled to let our intrepid users save their snippets.

Saving a snippet

Saving a snippet is as easy as filling in a simple dialog and clicking Save.

Searching for snippets 

This dialog shows snippet retrieval in action.

Displaying snippets to check what they contain.

It's easy to see what code is present for a snippet in the snippet view.

CodeStash in the About Visual Studio dialog. 

Here we can see that CodeStash is listed in the About Visual Studio dialog.

Running CodeStash

A brief note on debugging addins. Starting the project up in Debug mode can take a lot of time when you debug a Visual Studio extension. What I like to do is to start the project using "Start without Debugging", and then attach the debugger to the Visual Studio that's running the extension. Try it yourself, you'll find that it saves you a load of time.

The config file has the following settings.

  1. EncryptionEnabled. This must be set to the same value as is set in the CodeStash website web.config file.
  2. RestAddress. This is the location that the RESTful services are located. By default, this is set to http://localhost:8300/Rest/. Change the localhost:8300 to the address of the website you deploy the application to.
  3. CodeSnippetAddress. This is the location of the code snippet address RESTful services. By default, this is set to http://localhost:8300/CodeSnippet/.. Change the localhost:8300 to the address of the website you deploy the application to.

The CodeStash website must be running before this extension can communicate with it, so I'd recommend starting that before you attempt to run the addin. When you run the extension from within the editor, an experimental instance of Visual Studio will open up - you may need to adjust where it's fired from in the Debug tab (in the CodeStash.Addin properties window), depending on whether you are using Visual Studio on 32 bit or 64 bit version of Windows.

When you run CodeStash, your first real interaction with it is probably going to be via the context menu commands, so let's start by seeing how they actually work.

Wiring up the commands

One of the biggest issues we faced when creating the addin was that it has to work in multiple different editor windows, with more than one menu item grouped into a sub-menu. With the best will in the world to Microsoft, the documentation surrounding how to actually do this is scattered in lots of different locations and is downright arcane in places.

In order to get the plumbing in place to hook the commands up in the way we wanted, we had to battle with the innermost workings of something known as the Visual Studio Command Table (VSCT). When you create a Visual Studio addin, you'll see a file that ends with the extension .vsct; this is the file that supports this feature. When you open it up, you'll see that it's an XML file, and it simply describes the layout and appearance of the command items in a VSPackage. Well, I say simply, but getting your head around it is anything other than straightforward. Here, I'm going to lay out what the file looks like, and then I'll deconstruct it so that the other parts of the command structure make more sense (hopefully).

XML
<?xml version="1.0" encoding="utf-8"?>
<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <!--This header contains the command ids for the menus provided by the shell. -->
  <Extern href="vsshlids.h"/>

  <Commands package="guidCodeStash_AddinPkg">
    <Groups>

      <Group guid="CodeStashGrouping" id="CodeStashGroupedMenus" priority="0x0600">
        <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CODEWIN"/>
      </Group>

      <Group guid="CodeStashGrouping" id="SubMenuGroup" priority="0x0602">
        <Parent guid="CodeStashGrouping" id="SubMenu" />
      </Group>

    </Groups>

    <Menus>
      <!-- The Code Stash submenu. -->
      <Menu guid="CodeStashGrouping" id="SubMenu" priority="0x0200" type="Menu">
        <Parent guid="CodeStashGrouping" id="CodeStashGroupedMenus" />
        <Strings>
          <ButtonText>Code Stash</ButtonText>
          <CommandName>CodeStash</CommandName>
        </Strings>
      </Menu>
    </Menus>
    <Buttons>
      <!-- Code editor menus -->
      <Button guid="CodeStashGrouping" id="SaveSnippetId" priority="0x0100" type="Button">
        <Parent guid="CodeStashGrouping" id="SubMenuGroup" />
        <CommandFlag>DefaultDisabled</CommandFlag>
          <Strings>
            <CommandName>SaveSnippetId</CommandName>
            <ButtonText>Save snippet</ButtonText>
        </Strings>
      </Button>

      <Button guid = "CodeStashGrouping" id="InsertSnippetId" priority="0x101" type="Button">
        <Parent guid="CodeStashGrouping" id="SubMenuGroup"/>
        <CommandFlag>DynamicVisibility</CommandFlag>
        <Strings>
          <CommandName>InsertSnippetId</CommandName>
          <ButtonText>Insert snippet</ButtonText>
          <ToolTipText>Insert the snippet from CodeStash into the editor window.</ToolTipText>
        </Strings>
      </Button>
      <!-- End code editor menus -->
    </Buttons>

  </Commands>

  <CommandPlacements>

    <CommandPlacement guid="CodeStashGrouping" id="CodeStashGroupedMenus" priority="0x0600">
      <Parent guid="HtmlEditorWindows" id="IDMX_HTM_SOURCE_BASIC"/>
    </CommandPlacement>
    <CommandPlacement guid="CodeStashGrouping" id="CodeStashGroupedMenus" priority="0x0600">
      <Parent guid="HtmlEditorWindows" id="IDMX_HTM_SOURCE_HTML"/>
    </CommandPlacement>
    <CommandPlacement guid="CodeStashGrouping" id="CodeStashGroupedMenus" priority="0x0600">
      <Parent guid="HtmlEditorWindows" id="IDMX_HTM_SOURCE_SCRIPT"/>
    </CommandPlacement>
    <!-- Similar CommandPlacements removed for brevity -->
    <CommandPlacement guid="CodeStashGrouping" id="CodeStashGroupedMenus" priority="0x0600">
      <Parent guid="CssEditorWindows" id="IDMX_HTM_SOURCE_CSS"/>
    </CommandPlacement>
    <CommandPlacement guid="CodeStashGrouping" id="CodeStashGroupedMenus" priority="0x0600">
      <Parent guid="XamlEditorWindows" id="IDMX_XAML_SOURCE_BASIC"/>
    </CommandPlacement>
  </CommandPlacements>

  <Symbols>
    <!-- This is the package guid. -->
    <GuidSymbol name="guidCodeStash_AddinPkg" value="{857c13ce-c509-4244-9216-59b112462c5f}" />

    <!-- This is the guid used to group the menu commands together -->
    <GuidSymbol name="CodeStashGrouping" value="{4f6378f6-4249-474b-bd22-d5ecf4996156}">
      <IDSymbol name="SubMenu" value="0x1001"/>
      <IDSymbol name="SubMenuGroup" value="0x1000"/>
      <IDSymbol name="InsertSnippetId" value="0x0101"/>
      <IDSymbol name="CodeStashGroupedMenus" value="0x1020" />
      <IDSymbol name="SaveSnippetId" value="0x0100" />
    </GuidSymbol>

    <!-- These are the IDs for the various HTML style editors that VS uses. -->
    <GuidSymbol name="HtmlEditorWindows" value="{d7e8c5e1-bdb8-11d0-9c88-0000f8040a53}">
      <IDSymbol name="IDMX_HTM_SOURCE_BASIC" value="0x32" />
      <IDSymbol name="IDMX_HTM_SOURCE_HTML" value="0x33" />
      <IDSymbol name="IDMX_HTM_SOURCE_SCRIPT" value="0x34" />
      <!-- IDSymbol definitions removed for brevity -->
      <IDSymbol name="IDMX_HTM_SOURCE_ASMX_CODE_VB" value="0x39" />
    </GuidSymbol>

    <GuidSymbol name="CssEditorWindows" value="{A764E896-518D-11D2-9A89-00C04F79EFC3}">
      <IDSymbol name="IDMX_HTM_SOURCE_CSS" value="0x0102"/>
    </GuidSymbol>

    <GuidSymbol name="XamlEditorWindows" value="{4C87B692-1202-46AA-B64C-EF01FAEC53DA}">
      <IDSymbol name="IDMX_XAML_SOURCE_BASIC" value="0x0103"/>
    </GuidSymbol>
  </Symbols>
</CommandTable>

I appreciate that there's a lot going on there, and that it looks quite daunting but, hopefully, by the time you reach the end of this section it will make a lot more sense.

Near the top of this file, the addin links to an external resource. It does this in the line:

XML
<Extern href="vsshlids.h"/>

As we can see, this line looks like it's importing a C/C++ header file. Well, surprise surprise that, in effect, is exactly what it's doing. In the same way that we use header files in C/C++ to provide definitions, you do the same for Visual Studio extensions. This betrays the fact that you used to have to write extensions in C or C++, so it's a convenient way to get at the standard Visual Studio shell ids (that's what vsshlids stands for). You don't have to use this file - we could do this entirely in code, as we'll see later on, but as it's there and it's convenient, we'll use it.

If you look in the project, you won't actually see this file anywhere; it's stored in a predefined location that the compiler knows to retrieve the include files from (path to program files directory\Microsoft Visual Studio 2010 SDK\VisualStudioIntegration\Common\Inc).

What we are interested in, in this file, are the following:

XML
 // Guid for Shell's group and menu ids
DEFINE_ Guid (guidSHLMainMenu,
0xd309f791,0xd309f791, 0x903f, 0x11d0, 0x9e, 0xfc, 0x00, 0xa0, 0xc9, 0x11, 0x00, 0x4f);
C++
#define IDM_VS_CTXT_CODEWIN 0x040D

These two parts represent the  Guid and command ids that are needed to hook up to the context menu in the code editor window. This is an important part of getting to know how to add commands to windows in Visual Studio - each window is represented by a  Guid, and each command is represented by an Id. Every command that you add to an addin will be represented by a  Guid and an Id. As I said before, you could get away without importing this command file - all you have to do is drop the Extern element, and add the following to the Symbols section at the bottom:

XML
<GuidSymbol name="guidSHLMainMenu" value="{D309f791-903f-11d0-9efc-00a0c911004f}">
  <IDSymbol name="IDM_VS_CTXT_CODEWIN" value="0x040D" />
</GuidSymbol>

I'm going to jump around the vsct file a bit now because we need to take a look at the Buttons and see how they come into play. This will help to make the other sections of this file more apparent.

CodeStash will add two menu options to the context menus in the editor windows (I'm going to be coming back to this one in a bit as this isn't as easy as you'd hope). In order to add these menus, we have to create Button elements. Let's take another look at this section:

XML
<Buttons>
  <!-- Code editor menus -->
  <Button guid="CodeStashGrouping" id="SaveSnippetId" priority="0x0100" type="Button">
    <Parent guid="CodeStashGrouping" id="SubMenuGroup" />
    <CommandFlag>DefaultDisabled</CommandFlag>
      <Strings>
        <CommandName>SaveSnippetId</CommandName>
        <ButtonText>Save snippet</ButtonText>
    </Strings>
  </Button>

  <Button guid = "CodeStashGrouping" id="InsertSnippetId" priority="0x101" type="Button">
    <Parent guid="CodeStashGrouping" id="SubMenuGroup"/>
    <CommandFlag>DynamicVisibility</CommandFlag>
    <Strings>
      <CommandName>InsertSnippetId</CommandName>
      <ButtonText>Insert snippet</ButtonText>
      <ToolTipText>Insert the snippet from CodeStash into the editor window.</ToolTipText>
    </Strings>
  </Button>
  <!-- End code editor menus -->
</Buttons>

Each item that is going to appear in our CodeStash menu is represented as a Button element. Again, you can see that we have identified the button uniquely with a  Guid and an Id, but where do they come from? The answer lies in the

Symbols
section in the vsct file. In this section, we have added several GuidSymbol elements. The GuidSymbol is created with a symbolic name and a  Guid as the value field. The symbolic name is what we use elsewhere when we want to refer to the  Guid; effectively you can think of it as an alias, so when you see CodeStashGrouping in any other element, what it actually means is use the  Guid {4f6378f6-4249-474b-bd22-d5ecf4996156}. (When we look at the way we actually hook the buttons up to code, we'll see how this Guid becomes relevant).

The id refers to an IDSymbol that is contained inside the GuidSymbol group. So, in the case of our buttons, we look at the IDSymbol that exists inside the CodeStashGrouping element. Again, this is an alias, so when you see id="...", what is really being used is the value referred to by that id. Note that when you use the  Guid from a GuidSymbol the id that you use must be listed inside that particular GuidSymbol element. The following diagram should make this a little bit clearer.

CodeStash grouping overlaid on Button element. 

Each Button has a Parent. We're going to come back to this one a bit later, as this helps us to determine the hierarchy of how the buttons are laid out.

The Strings section is the part we're actually going to set up the text that appears in the menu (ButtonText), and any optional Tooltip text (ToolTipText) that we want to appear.

At this point, I think we need to take a quick break from the vsct file. We need to see how this file actually fits into the bigger picture, after all there's no real code in there - all we have is a description of what commands we have available to play with.

Core command files

When you create a Visual Studio addin, the addin template creates a lot of files. There are three files that we are going to concentrate on right now.

Core files of interest.

"Gosh Pete, they even have a file called Guids in there. They must be obsessed with them" I hear you say. Well, yes, I suppose you're right. The Guid.cs file represents the same Guids that we've had to create in the Symbols section in the vsct file. See, I told you we'd come back to this. It looks like this:

C#
using System;

namespace CodeStash.Addin
{
  static class GuidList
  {
    public const string guidCodeStash_AddinPkgString = "857c13ce-c509-4244-9216-59b112462c5f";
    public const string CodeStashGroupingString = "4f6378f6-4249-474b-bd22-d5ecf4996156";

    public static readonly Guid CodeStashGrouping = new Guid(CodeStashGroupingString);

    public const string PropertyPageGuid = "D6B8D576-8A4F-402A-8D20-B1FD98322EEC";
}

As this is such a handy place to store Guids, we have sneaked an extra one in here that has nothing to do with the commands. The Guid at the bottom maps to a property page that we have added into the settings dialog in Visual Studio. The ability to add your own property pages is just one of the many cool things that you can do with a Visual Studio addin.

Something that may surprise you is how few Guids we actually have in this file. After all, we added a lot more GuidSymbol elements in the vsct file. The reason for this is that not every GuidSymbol entry relates to our CodeStash commands - in fact, we only need two of the Guids. The other Guids relate to internal Visual Studio elements which we will use later on when we are wiring our commands up into the different editor windows.

At this stage, you've leaped ahead of me and realised that the PkgCmdID.cs file actually maps to the command ids laid out in the IDSymbol elements.

C#
namespace CodeStash.Addin
{
  static class PkgCmdIDList
  {
    public const uint SaveSnippetId = 0x100;
    public const uint InsertSnippetId = 0x101;
  };
}

Not much to worry about there. Again, it doesn't matter that we have far fewer ids in this list than we have IDSymbol elements, by now you'll have figured out that these other ids have something to do with the ways that the commands are placed inside Visual Studio.

One thing you will have noticed about these two files is that they merely define constants. This would indicate that other code is going to use them, and in the case of this package, the file we are interested in is CodeStash.AddinPackage.cs. Rather than listing the whole file out, I'm going to concentrate o how we went about creating the CodeStash menus, and actually getting them to appear on the screen.

If you take a look at this class, you'll see that it inherits from Package. You can think of this as the entry point into the addin, so it stands to reason that this is the file we are going to use to actually hook code into the command table. Fortunately, there is an initialisation routine that we can use to do exactly this:

C#
protected override void Initialize()
{
  base.Initialize();

  GetDTE();

  // Add our command handlers for menu (commands must exist in the .vsct file)
  OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
  if (null != mcs)
  {
    // Create the command for the menu item.
    CommandID menuCommandID = new CommandID(GuidList.CodeStashGrouping, PkgCmdIDList.SaveSnippetId);
    CommandID searchSnippetCommandID = new CommandID(GuidList.CodeStashGrouping, PkgCmdIDList.InsertSnippetId);

    saveSnippetMenu = new OleMenuCommand(AddSnippetMenuItemCallback, menuCommandID);
    saveSnippetMenu.BeforeQueryStatus += OnQuerySaveSnippet;
    mcs.AddCommand(saveSnippetMenu);

    MenuCommand searchMenuItem = new MenuCommand(SearchSnippetsMenuItemCallback, searchSnippetCommandID);
    mcs.AddCommand(searchMenuItem);
  }
}

We will come back the the call to GetDTE in a little while. Before then, we will discuss the actual menu creation code.

The Package class provides the ability to get access to any of the services that Visual Studio makes available through a call to GetService. Simply pass in the type of service that is needed, and it's available for us to use. So, in the case of adding menus, we need to get an instance of IMenuCommandService (GetService returns an object, so we have to cast it to an appropriate type).

Now we can see that we create two CommandID instances, and at this point the parts that we've discussed before should start to become apparent. Each CommandID is instantiated with the Guid from Guids.cs and the id from PkgCmdIDList.cs which, as we've already discussed, match the elements defined in the vsct file. See, I told you that this would all start to come together.

Anyway, creating CommandID instances isn't enough on its own. We actually need to do something with them, and what we do is use these to create the actual MenuCommand and OleMenuCommand instances (each of which have a parameter that accepts a callback routine which we use to actually perform the operation that we want from the menu command).

Now, you may be wondering why we have two different types of menu here. The reason is actually pretty simple; if we don't need to have Visual Studio automatically enable and disable the menu for in response to things happening, then you should use a MenuCommand. If, however, we do need this facility then we have to use OleMenuCommand (we use the OleMenuCommand.BeforeQueryStatus event to allow us to hook our enable/disable logic up). Once we have created our menu commands, it's a simple matter to add the commands to the menu command service using AddCommand.

If we ran the code based on what I've just described, we would get two menu items, but they would be in the main editor window context menu, but that's not what we want. What we really want to do is have a CodeStash submenu off the context menu, it's a great way to make your commands stand out in a myriad of other commands. Now, the documentation on how to do this is an absolute nightmare to find and get your head around - I'm sorry Microsoft, but if you want people to be able to understand how this all hangs together, you have to get in the habit of providing decent samples with decent naming conventions (this is why we don't use guid..... for our menus in CodeStash).

So, how do we actually go about creating the submenu? Well, it's back to the vsct file I'm afraid. Don't worry, there's not much more to cover there. By now you have enough information for me to be able to introduce some more "advanced" concepts without causing headaches. I'll break this down a little bit at a time, starting with the way we hook the menu to the editor window context menu.

XML
<Group guid="CodeStashGrouping" id="CodeStashGroupedMenus" priority="0x0600">
  <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CODEWIN"/>
</Group>

Right, there we've created a group that we are going to make a child of the editor context window. It doesn't have a physical representation, it merely acts as a way to hook the menu in.

XML
<Menu guid="CodeStashGrouping" id="SubMenu" priority="0x0200" type="Menu">
  <Parent guid="CodeStashGrouping" id="CodeStashGroupedMenus" />
  <Strings>
    <ButtonText>CodeStash</ButtonText>
  </Strings>
</Menu>

As you can see, the Menu element is linked to the Group we listed above via the Parent element. OK, so we have a Menu in the command table, but how do we actually get our menus hooked into it? It should come as no surprise that we need to add another Group that has this Menu as the Parent.

XML
<Group guid="CodeStashGrouping" id="SubMenuGroup" priority="0x0602">
  <Parent guid="CodeStashGrouping" id="SubMenu" /> 
</Group>

Finally, our Buttons are parented to this Group. When you need to add submenus, this mechanism of linking Groups and Menus via a child/parent relationship is the way to do it.

OK, so if we run the extension now it will show our CodeStash submenu and our buttons under it, right? Well, that really depends which editor you open up inside Visual Studio. There isn't one standard editor and they all have different context menus, so our menu will only display in the standard text editor window, such as the C# editor window. Well, this isn't much use for an addin that is meant to work with just about any language you can host in Visual Studio. There must be a way that we can add our menus to the other editor windows. Well, fortunately for us, there is.

If you look at the

Symbols
section, you can see that we've created other GuidSymbol elements. Each editor window has a Guid and command id associated with it, so we have added these into this section. With this information, we can place our menu against other editor windows through another vsct feature called a CommandPlacement.

XML
<CommandPlacement guid="CodeStashGrouping" id="CodeStashGroupedMenus" priority="0x0600">
  <Parent guid="HtmlEditorWindows" id="IDMX_HTM_SOURCE_BASIC"/>
</CommandPlacement>

As you can see here, we have effectively said "place the Menu that has the CodeStashGrouping Guid and CodeStashGroupedMenus in the context menu for the window that has the Guid identified by HtmlEditorWindows and the id is IDMC_HTM_SOURCE_BASIC". I know that's a little wordy, but that is what is happening internally. Do this with all the different editor windows and ids that we can find, and our menus should appear.

Again, Microsoft, could you please make your documentation on this clearer? Finding this information should be a lot more straightforward, and should not involve having to sacrifice a cucumber on a cold December night, wearing nothing but a loin cloth and a scarf made of pocket lint.

The Visual Studio experience

While it's handy to have the ability to create Visual Studio addins, if there was no way to actually interact with Visual Studio itself, addins would be of little value. Fortunately for us, we can get access to Visual Studio functionality through the Package class, using GetService to retrieve the services that provide this functionality. If you recall, in the previous section, I said that CodeStash has a method called GetDTE; this is the point at which we choose the functionality we are interested in. In our case, we want to get access to the Design Time Environment (DTE) which lets us get to the documents in the editor, and the status bar for providing status information.

C#
private void GetDTE()
{
  EnvDTE.DTE provider = (EnvDTE.DTE)GetService(typeof(EnvDTE.DTE));
  MEFPartsResolver.Instance.Resolve<IDocumentService>().SetDTE(provider);

  IVsStatusbar statusBar = (IVsStatusbar)GetService(typeof(SVsStatusbar));
  MEFPartsResolver.Instance.Resolve<IStatusBarService>().SetStatusBar(statusBar);
}

The addin makes a fair use of interfaces, so we use MEF to resolve to the physical implementation of these interfaces. We will cover more on this shortly.

One of the nicer features provided to addin developers is the ability to add your own settings pages to the standard Visual Studio settings dialog. With this ability, it's straightforward to provide a seamless designer experience, making it look like the addin belongs inside VS. We use this in CodeStash to store the login information for the current user.

Adding the page consists of three parts, creating the UI for the property page, adding a DialogPage class that shows the page and registering it with the package. I won't cover the creation of the UI as that's simply a user control that gets displayed. The interesting parts are the DialogPage and the registration part. The DialogPage is implemented in CodeStash.Addin.CodeStashSettingsPropertyPage (look in the PropertyPage folder in CodeStash.Addin).

C#
[Guid(GuidList.PropertyPageGuid)]
public class CodeStashSettingsPropertyPage : DialogPage
{
  [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  protected override System.Windows.Forms.IWin32Window Window
  {
    get
    {
      CodeStashSettingsView settings = new CodeStashSettingsView();
      settings.Location = new System.Drawing.Point(0, 0);
      return settings;
    }
  }
}

It probably comes as no surprise to you now to see that the property page has a Guid associated with it. Yup, Visual Studio uses that Guid as an id when it has to look this page up, and when we register the page. This Guid is one that we create, and I have chosen to place it in GuidList.cs.

"Ah hah Pete. That's all very straightforward, but how does Visual Studio know about our settings page?" I hear you ask (or that could just be the voices in my head). Well, registering settings with an addin is as simple as decorating the package class with a simple attribute.

C#
[ProvideOptionPageAttribute(typeof(CodeStashSettingsPropertyPage), "CodeStash", "Settings", 101, 1000, true)]

If you run CodeStash, open up the VS settings dialog using Tools > Options... You will see that the CodeStash settings are in there as CodeStash > Settings (the names the settings pages appear as, and the key they appear under are specified in the attribute).

CodeStash settings page. 

Obviously, if the user hasn't entered their credentials and they try to run CodeStash, they aren't going to get very far with saving or retrieving snippets, so we need a way to guide them to enter their credentials. We can't rely on them reading documentation, and we don't want them to only find out they can't get to CodeStash by opening only after they've opened up one of the snippets windows. This means that we need to guide them to the settings page so that they can enter the details. Fortunately, getting access to our settings page programatically couldn't be easier. All we need to do is get a reference to the DTE, and open up the Options window, passing in the Guid of our property page.

C#
private void ShowCodeStashSettings()
{
  EnvDTE.DTE dte = (EnvDTE.DTE)GetGlobalService(typeof(SDTE));
  dte.ExecuteCommand("Tools.Options", GuidList.PropertyPageGuid);
}}

That's it, if you know the Guid of a settings page, you can access it using that simple command (although it's not well documented that you can do this, again a lot of digging around was needed to find this).

So, where exactly are we storing this login information? Well, surprise surprise, we are using isolated storage to store this data. There are two classes that are of interest to us here. The first class, LoginDetails, is the "model" containing the login information. The second,

CredentialManager
, is responsible for saving the settings to isolated storage, and loading them back in again from isolated storage. For security purposes, the code uses DESCryptoProvider to encode and decode the file. (To find these files, look in the Login directory in CodeStash.Addin.Core.dll).

Saving the file involves serializing the LoginDetails then converting this to a byte array, which is then saved to disk.

C#
public static void Save(LoginDetails login)
{
  login.Validate();

  XmlSerializer serializer = new XmlSerializer(typeof(LoginDetails));
  using (StringWriter sw = new StringWriter())  
  {
    serializer.Serialize(sw, login);
    SaveFile(ASCIIEncoding.ASCII.GetBytes(sw.ToString()));
  }
}

private static void SaveFile(byte[] fileData)
{
  using (IsolatedStorageFile store = GetStore())
  {
    using (IsolatedStorageFileStream ifs = GetStream(store, FileMode.OpenOrCreate))  
    {
      using (DESCryptoServiceProvider des = GetCryptoProvider())
      {
        using (CryptoStream crypt = new CryptoStream(ifs, des.CreateEncryptor(), CryptoStreamMode.Write))
        {
          crypt.Write(fileData, 0, fileData.Length);
          crypt.Close();
        }
      }
    }
  }
}

Loading the file is simply a matter of reading the file back in, decrypting the file stream, and deserializing the data back to an instance of LoginDetails. If the details haven't previously been saved, a new instance of LoginDetails is created.

C#
public static LoginDetails Load()
{
  XmlSerializer serializer = new XmlSerializer(typeof(LoginDetails));
  string fileContents = ReadFile();
  if (!string.IsNullOrWhiteSpace(fileContents))
  {
    using (StringReader sr = new StringReader(fileContents))
    {
      return (LoginDetails)serializer.Deserialize(sr);
    }
  }
  // If we get here, we haven't previously created the file.
  return new LoginDetails();
}

private static string ReadFile()
{
  using (IsolatedStorageFile store = GetStore())
  {
    if (!store.FileExists(FileName))
    {
      return string.Empty;
    }

    using (IsolatedStorageFileStream ifs = GetStream(store, FileMode.Open))
    {
      using (DESCryptoServiceProvider des = GetCryptoProvider())
      {
        using (CryptoStream crypt = new CryptoStream(ifs, des.CreateDecryptor(), CryptoStreamMode.Read))
        {
          using (StreamReader reader = new StreamReader(crypt))
          {
            string retVal = reader.ReadToEnd();
            crypt.Close();

            return retVal;
          }
        }
      }
    }
  }
}

CodeStash - bringing two worlds together

This would seem to be a fairly opportune point to talk about credential management, and how the addin works with the actual CodeStash services. If you have read Sacha's article on the CodeStash website (and if you haven't, why not - go read it, I'll wait for you to come back) you'll know that CodeStash uses REST to do pretty much all of the heavy lifting with regards to the CodeStash functionality. Well, the addin makes extensive use of these same REST services - which means that it needs to use exactly the same credentials as you would use on the website, and it abides by the same credential encryption rules as specified in the website.

OK, that's pretty wordy. What exactly does it mean? Well, now that you've read Sacha's article, you know that there's a setting that says whether or not the credential information is encrypted. This setting needs to be the same in both places, and our route into it is managed by the EncryptionHelper class in the CodeStash.Common assembly reading the EncryptionEnabled key in the configuration file. This setting determines whether or not the user credentials are encrypted, and should be set in the configuration file in the addin as well as web.config.

If you take a look in the constructor for the EncryptionHelper class, you will see that it looks like this:

C#
/// <summary>
/// Static constructor reads the EncryptionEnabled from the App.Config or Web.Config
/// </summary>
static EncryptionHelper()
{
  encryptionEnabled = CodeStash.Common.Helpers.ConfigurationSettings.EncryptionEnabled;
}

Seems innocent enough, doesn't it? Well, that one line requires some gnarly work going on in the background to ensure that the Addin and the website can both use config files to retrieve the value from.

As you're aware, you can associate a config file with a .NET executable, and as long as it's copied into a location such as the output path, you can access the values in it. I'm not going to rehash stuff that you know inside out here, other than to say that the key is that the config file is associated with the executable, not a DLL. Now, this could present us with a bit of a problem because we can't assign a config file to Visual Studio and start working with it. But, as you can see, we are just using a single property to get the configuration value that we need. There must be something else going on in there, mustn't there?

Well, yes there is. In order to associate the config file with the DLL, we name it CodeStash.Addin.Dll.Config, mimicking the naming convention of exe config files. We then do a little bit of trickery to get the value out of the config file in the file CodeStash.Common.Helpers.ConfigurationSettings.

C#
private static Configuration configuration;

private static string RetrieveSetting(string setting)
{
  string value = ConfigurationManager.AppSettings[setting];
  if (string.IsNullOrWhiteSpace(value))
  {
    GetConfiguration();
    KeyValueConfigurationElement ret = configuration.AppSettings.Settings[setting];
    if (ret != null)
    {
      value = ret.Value;
    }
  }
  return value;
}

private static void GetConfiguration()
{
  if (configuration != null) return;
  string location = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "CodeStash.Addin.Core.dll");
  configuration = ConfigurationManager.OpenExeConfiguration(location);
}

OK, so what our configuration settings properties do is call RetrieveSetting. If the setting they are after has a value, it means we are pulling the value from the web.config file. If it's empty, we need to delve a bit deeper, so the extension calls GetConfiguration, which uses the standard .NET ConfigurationManager.OpenExeConfiguration to open our addin config file. Once we have opened the configuration file, it's a simple matter to retrieve the setting and return it. In order to avoid having to get the config file every time, we assign it as we are opening it.

MEF, Sacha and the Big Lebowski

I've already said that one of the big holdups with delivering the addin was down to changing my mind about the MVVM framework that underpinned it. Initially, I hadn't intended to use a third party framework - and I wrote a lot of functionality to support this. There was always that nagging doubt at the back of my mind, though, that it wasn't right doing this so, after some soul searching, navel gazing and toe wrestling, I decided to use an existing framework. I looked at using my own Goldlight framework but decided, in the end, that this would be a good time to use Sacha's Cinch framework.

One of the big things about Cinch is that it works with MEF, and works nicely with a project I had some small part in developing, MEFedMVVM. Visual Studio makes heavy use of MEF, so it seemed that hooking it together would be a simple matter and progress would shoot ahead. Hah! I say. Hah! No matter what I tried, I couldn't get MEFedMVVM to play nicely inside Visual Studio - Sacha offered to look at it for me, and I gladly accepted his offer. Yup, you guessed it, Sacha ran into exactly the same problems that I did, and we came to the conclusion that our MEF loader was conflicting with the fact that Visual Studio is heavily MEF based, so it has its own MEF load infrastructure. Well, never say never is Sacha's motto (it is now that I've assigned it to him).

Eventually, Sacha decided to take matters into his own hands and he wrote a new MEF export provider for CodeStash, which would work alongside Cinch. You can find out how he implemented this by looking at the files in the MEF folder in CodeStash.Addin. From the CodeStash point of view, the really interesting point can be seen in the file MEFPartsResolver. In here, the MEFfed up services that we want to use inside CodeStash are resolved and hooked up. This class is then called by other parts of the code base, whenever they need access to one of the services.

Time for a REST

Well, it's all very well doing all this plumbing work with the addin, but we have to actually be able to communicate with the website, and Sacha has provided some very handy RESTful services for the addin to hook into. Fortunately for us, actually hooking into these services is incredibly easy. At the heart of this, the addin uses a single class, CodeStashRestBase, which provides the core implementation that we will use. Let's take a look at one call that the addin makes, and see how it actually calls the REST code.

C#
 protected byte[] Search(string searchText, SearchType searchType, CodeSnippetVisibility visibility, string[] tags, int pageSize, int pageNumber)
{
  JSONSearchInput input = new JSONSearchInput(
    openId, // If not specified, will be an empty string but the password must be set.
    emailAddress, // Must always be present.
    password, // If not specified, will be an empty string, but the OpenID must be set.
    searchType,
    searchText,
    pageNumber,
    pageSize,
    visibility,
    tags
  );
  return CallService(input, CodeStash.Common.Helpers.ConfigurationSettings.RestAddress, "Search");
}

private byte[] CallService<T>(T input, string address, string methodToCall)
{
  values = new NameValueCollection();
  Utilities.AddValue(values, "input", input);

  WebClient client = new WebClient();
  return client.UploadValues(string.Format("{0}{1}", address, methodToCall), values);
}

When we attempt a search from the addin, we build up a json type (the json types are defined in the CodeStash.Common assembly) which we pass into a very handy little method which actually calls the service, getting the address of the service from the config file. All of the CodeStash json types take the open id, email address and password as the first three parameters.

When the code does the search, we get a byte array back which we need to convert back into a type we can use in the addin, after all, a byte array isn't that easy to read. To get the type, we use a handy little conversion routine, so in the search case, our code is converted out like this:

C#
return Utilities.GetValue<JSONPagesSearchResultCodeSnippet>(base.Search(searchText, searchType, visibility, tags, PageSize, pageNumber));

And this method is as simple as:

C#
internal static T GetValue<T>(Byte[] results) where T : class
{
  using (MemoryStream ms = new MemoryStream(results))
  {
    jss = new DataContractJsonSerializer(typeof(T));
    return (T)jss.ReadObject(ms);
  }
}

As this plainly demonstrats, all we are doing is creating a MemoryStream encapsulating the stream that has come back from the REST service. We then use this to reconstruct a proper json type from the stream. The code is fairly straightforward, and should be pretty familiar to any reasonably experienced .NET developer.

Note: The REST services are designed to be accessed via MEF, so when we look at the ViewModel code you'll see it being accessed via an interface. All interaction with the REST services are implemented in the interface.

C#
public interface IRestService
{
  int PageSize { get; set; }

  JSONGroupingResult RetrieveGroups();

  JSONLanguagesResult RetrieveLanguages();

  JSONCodeSnippetAddSingleResult AddCodeSnippet(
    string actualCode,
    string categoryName,
    int languageId,
    string language,
    string tags,
    string description,
    string title,
    int? groupId,
    string groupName,
    CodeSnippetVisibility visibility);

  JSONPagesSearchResultCodeSnippet Search(
    string searchText, 
    SearchType searchType, 
    CodeSnippetVisibility visibility, 
    string[] tags, 
    int pageNumber
  );
}

As you can see from this, there's not a lot to the interface. The addin doesn't actually have to do that much to talk to the website, so the interface is pretty simple.

"Enough with the waffle Pete, show me the screens. If you don't, I will rend thee in the gobberwarts with my blurglecruncheon". OK, I appreciate that we've had to cover a lot of background functionality here, and it can seem a little bit disjointed. How does this relate to showing some screens, interacting with the REST services, and actually doing stuff with the windows in Visual Studio? Sit back, relax and enjoy the ride. We're about to start pulling this together.

You might be wondering where all the WPF stuff in this is. After all, Sacha and I are big fans of WPF, and I've already said that we are using a MVVM framework here, yet I haven't talked about it at all. Let's take a look at the functionality we use for actually saving the snippets into the CodeStash database.

Saving snippets

When the user selects some text in an editor window in Visual Studio, the Save snippet menu becomes enabled via the

BeforeQueryStatus
event. Clicking Save snippet creates a model window using the command:

C#
new AddSnippetView().ShowModal();

If you open up the view windows though, you'll see that we haven't created a straightforward WPF dialog. As we want our application to behave consistently with Visual Studio, we use a different base for our window based on

DialogWindow
.

XML
<ui:DialogWindow x:Name="Window"
  x:Class="CodeStash.Addin.Views.AddSnippetView"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:enum="clr-namespace:CodeStash.Common.Enums;assembly=CodeStash.Common"
  xmlns:extension="clr-namespace:CodeStash.Addin.Extensions"
  xmlns:local="clr-namespace:CodeStash.Addin"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:ui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.10.0"
  xmlns:vsfx="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.10.0"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:core="clr-namespace:CodeStash.Addin.Core;assembly=CodeStash.Addin.Core"
  xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
  SizeToContent="Height"
  Width="500"
  Background="{DynamicResource {x:Static vsfx:VsBrushes.ToolWindowBackgroundKey}}"
  Foreground="{DynamicResource {x:Static vsfx:VsBrushes.ToolWindowTextKey}}"
  ShowInTaskbar="False"
  Title="Save Snippet"
  WindowStartupLocation="CenterScreen"
  mc:Ignorable="d">
...
</ui:DialogWindow>

Please don't be put off if you're not comfortable with XAML, I'll give a quick run through of what this code means, and then I'll cover the

Background
and Foreground lines. If you're an expert with XAML, please feel free to take a comfort break at this point while we skim through this code.

Effectively, this code tells the compiler to create a class that inherits from DialogWindow. The class it creates is called

AddSnippetView
and then some XML namespaces (xmlns)are added, in much the same way we would add a using statement to a standard .cs file. The lines starting with SizeToContent and Width set up how big the dialog is going to be - WPF provides the ability to size the window to the size of the child elements or you can explicitly set the size. I'm going to skip the next two lines because I want to come back to them for the benefit of the XAML experts who might have chosen to skip over this paragraph.

The dialog is then set up so that it doesn't appear in the windows taskbar, "Save Snippet" appears in the title bar, and the window starts up in the center of the screen. Phew, that's a lot taken care of, and it's not as scary as it looks at first.

XAML experts, you can come back into the room now.

Now, for the lines that I emboldened above. What, exactly, do they do? Well, they set the background and foreground colours to predefined Visual Studio colours. By using DynamicResource, we let our dialog respond to changes to the underlying VS themes, so if the user changes their VS themes, our dialog will use the updated themes.

I'm not going to cover the rest of the XAML now - it's fairly straightforward, and it isn't really doing anything clever. It's purely there to present the UI. The interesting parts are all in the ViewModel that goes behind the view, and the code we have in the extension to hook into the RESTful services. Let's start with the constructor.

C#
[ImportingConstructor]
public AddSnippetViewModel(
  IViewAwareStatus viewAwareStatusService,
  IMessageBoxService messageBoxService,
  IUIVisualizerService uiVisualizer,
  IDocumentService documentService,
  IRestService restService,
  IStatusBarService statusBarService)
  {


  // Default to the user choosing a group.
  chooseGroup = true;
  SnippetVisibility = CodeSnippetVisibility.JustMe;

  this.viewAwareStatusService = viewAwareStatusService;
  this.messageBoxService = messageBoxService;
  this.uiVisualizer = uiVisualizer;
  this.documentService = documentService;
  this.restService = restService;
  this.statusBarService = statusBarService;

  RetrieveLanguages();
  RetrieveGroups();

  SaveSnippetCommand = new SimpleCommand<object, object>(x => isValid, ExecuteSaveSnippet);

  Validator = new AddSnippetViewModelValidator(this);

  statusBarService.SetText(Messages.Ready);
}

The ImportingConstructor attribute is used by MEF to allow us to hook into the services that match the parameters in the constructor argument list. The first three parameters are provided by Cinch, while the last three are specific to our extension. Note: You won't find any code in the code base that calls this ViewModel with this parameter list; this is handled for us by MEF, so we don't have to worry about it.

The internals of the constructor are straightforward enough, with the code specifiying some default values and hooking up members to the argument list. The interesting line in here is the line

statusBarService.SetText(Messages.Ready);
which clearly demonstrates that, even though we have passed in an interface to this constructor and we haven't added any code to actually hook up to the status bar service implementation, the MEF resolution means that we are working with the resolved instance here.

In the constructor, we call two methods which use the REST service; one to retrieve the list of languages, the other to retrieve the groups to display in the group selection drop down. Let's see what the code for retrieving the languages looks like.

C#
private void RetrieveLanguages()
{
  statusBarService.SetText(Messages.GetLanguage);
  JSONLanguagesResult languages = restService.RetrieveLanguages();
  Languages = languages.Languages;
  statusBarService.Clear();
}

In this method, the status bar service writes that CodeStash is retrieving the languages list to the Visual Studio status bar (it's the ability to give little bits of feedback like this to the user that VS provides that helps to give your extensions that little bit of professional polish). The REST service is used to retrieve the languages list, and the languages returned are stored in an ObservableCollection. Finally, the method clears the status bar text.

Quick Pete Detour

We have mentioned the status bar service a couple of times here, and we haven't actually seen much code that interacts with Visual Studio itself. Now seems to be a really opportune moment to show just how easy it is to hook into these Visual Studio services.

There are two parts to working with the status bar service in CodeStash. The first part revolves around getting a hook into the status bar service itself. This is handled in CodeStash_AddinPackag by the following code:

C#
IVsStatusbar statusBar = (IVsStatusbar)GetService(typeof(SVsStatusbar));
MEFPartsResolver.Instance.Resolve<IStatusBarService>().SetStatusBar(statusBar);

There we simply get a reference to the Visual Studio status bar service, and then set this reference inside our concrete status bar service. This leads to the second part, which is the actual implementation of the status bar service.

C#
[Export(typeof(IStatusBarService))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class StatusBarService : IStatusBarService
{
  private IVsStatusbar statusBar;

  public void SetStatusBar<T>(T statusBar)
  {
    this.statusBar = statusBar as IVsStatusbar;
  }

  public void SetText(string text)
  {
    if (!IsFrozen)
    {
      statusBar.SetText(text);
    }
  }

  public void Clear()
  {
    if (!IsFrozen)
    {
      statusBar.Clear();
    }
  }

  private bool IsFrozen
  {
    get
    {
      int frozen;
      statusBar.IsFrozen(out frozen);
      return frozen != 0;
    }
  }
}

The attributes are used by MEF to identify this class as an implementation of a MEFfable "contract", and to state that the same instance of this service will be used whenever it's encountered in the addin. Most of the functionality is pretty self explanatory, possibly the only area that needs discussion being the property IsFrozen. This property is used to determine whether or not we can interact with the status bar, or if some other service has frozen it for their usage. If you are using the VS status bar in your own extensions, I would urge you to make sure that you are doing the same - it's important that extensions cooperate with each other, and don't cause any unexpected side effects in other peoples extensions.

Back to the show

As we are using Cinch as the underlying framework, the code makes heavy use of the features and concepts provided there. One of the more interesting features from our perspective is the provision of validation - it's common to see this implemented internally in the ViewModel. Rather than merging the code together like this, CodeStash moves validation out to separate classes which can be consumed from the View. This means that the validation code is simple to test, and exists in isolation from the ViewModel.

When the user has successfully filled in all the information she needs to save the snippet and they have clicked Save, the application passes this information over to the REST service. One little wrinkle is that we must HTML encode any text that we pass across so that it can be passed over. When the text is encoded, characters that would normally choke the REST service are converted so that the service won't fall over because, for instance, we've tried to use a < character (in this case, the character would be encoded to

&lt;
). What this means is that we have to decode the text when we receive the code back from the service so that what we insert is the same text as we originally selected, and not the HTML encoded version; in order to display the text in the search results dialog, we use a converter to decode the encoded data.

The code to save the snippet looks like this:

C#
private void ExecuteSaveSnippet(object parameter)
{
  try
  {
    statusBarService.SetText(Messages.Save);

    int? groupId = null;
    string groupName = Encode(NewGroup);
    if (SelectedGroup != null)
    {
      groupId = SelectedGroup.GroupId;
      groupName = Encode(SelectedGroup.Description);
    }

    restService.AddCodeSnippet(
      Encode(this.documentService.SelectedText), 
      Encode(Category), 
      SelectedLanguage.LanguageId, 
      SelectedLanguage.Language, 
      Encode(Tag), 
      Encode(Description), 
      Encode(Title), 
      groupId, 
      groupName, 
      SnippetVisibility);
    ((DialogWindow)this.viewAwareStatusService.View).Close();
    statusBarService.SetText(Messages.Ready);
    messageBoxService.ShowInformation(Messages.SnippetSaved);
  }
  catch (Exception ex)
  {
    messageBoxService.ShowError(Messages.SnippetFailed);
  }
}

private string Encode(string text)
{
  return WebUtility.HtmlEncode(text);
}

The actual snippet text is retrieved from the document service. This simple little service just hooks into the Visual Studio DTE and picks up the selected text like so:

C#
public string SelectedText
{
  get 
  {
    TextSelection selection = GetSelectedText();
    if (selection == null) return string.Empty;
    return selection.Text; 
  }
}

private TextSelection GetSelectedText()
{
  if (provider == null || provider.ActiveDocument == null) return null;
  return (TextSelection)provider.ActiveDocument.Selection;
}

Right, that's covered some of the interesting parts of saving the snippet to the database and that's all well and good, but what about retrieving the snippets? Well, this is where things get slightly more complicated. We're going to start by looking at how the search actually takes place, and then move onto what happens when we want to display the results and insert a snippet into the code editor. Here's the code to actually do the search:

C#
private void DoSearch(int pageNumber, bool fetchingDueToPaging)
{
  AsyncState = AsyncType.Busy;
  WaitText = "Searching for snippets";
  ApplicationHelper.DoEvents();

  CancellationToken cancellationToken = cancellationTokenSource.Token;
  Task<bool> cancellationDelayTask = TaskHelper.CreateDelayTask(searchTimeOutMilliSeconds);
    cancellationDelayTask.ContinueWith(dt =>
    {
      cancellationTokenSource.Cancel();
    }, TaskContinuationOptions.OnlyOnRanToCompletion);

  try
  {
    Task<JSONPagesSearchResultCodeSnippet> searchTask = Task.Factory.StartNew<JSONPagesSearchResultCodeSnippet>(() =>
    {
      string searchText = GetSearchText();
      string[] tagsArray = !String.IsNullOrEmpty(this.Tags) && this.SelectedSearchType == SearchType.ByTag
        ? this.Tags.Split(new string[] { ";", ",", ":" }, StringSplitOptions.RemoveEmptyEntries)
        : new string[] { };

      if ((!string.IsNullOrEmpty(searchText) && this.SelectedSearchType != SearchType.ByTag) ||
        (tagsArray.Any() && this.SelectedSearchType == SearchType.ByTag))
      {
        JSONPagesSearchResultCodeSnippet results = restService.Search(
          searchText,
          this.SelectedSearchType,
          this.SelectedVisibility,
          tagsArray,
          pageNumber);
        return results;
      }
      else
      {
        return null;
      }
    }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);

    searchTask.ContinueWith(ant =>
    {
      if (!fetchingDueToPaging)
      {
        Mediator.Instance.NotifyColleagues<JSONPagesSearchResultCodeSnippet>("DisplaySearchResults", ant.Result);
      }
      else
      {
        Mediator.Instance.NotifyColleagues<JSONPagesSearchResultCodeSnippet>("DisplayPagedSearchResults", ant.Result);
      }
      AsyncState = AsyncType.Content;
      ApplicationHelper.DoEvents();
    }, cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());

    searchTask.ContinueWith(ant =>
    {
      AsyncState = AsyncType.Error;
      ErrorMessage = "Search failed to run to completion";
    }, cancellationToken, TaskContinuationOptions.NotOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
  }
  catch (AggregateException aggEx)
  {
    ErrorMessage = "Search failed to run correctly";
    AsyncState = AsyncType.Error;
    ApplicationHelper.DoEvents();
  }
}

Phew. There's a lot going on in there, and it looks quite scary doesn't it? The first thing to be aware of is that the search results are paged, so this method will only retrieve the number of items needed to be displayed on a single page, and the heavy lifting for the actual search is perfomed via the REST service. By this stage, we're comfortable with this concept, so we can take that out of the complexity stakes. The next thing to think about is that the search is performed asynchronously with the Task Parallel Library (TPL). For an excellent series on using TPL, I would heartily recommend reading Sacha's series starting with this article.

So, why have we stopped by this method if it's all so straightforward? Well, I want to cover a really cool little trick that Sacha has put in here. We don't want the search to run indefinitely waiting for the server to time out, and we don't want to introduce blocking in the user interface, so Sacha has implemented this really nifty cancellable task that doesn't block the UI. The code in this method that it affects is this:

C#
Task<bool> cancellationDelayTask = TaskHelper.CreateDelayTask(searchTimeOutMilliSeconds);
    cancellationDelayTask.ContinueWith(dt =>
    {
      cancellationTokenSource.Cancel();
    }, TaskContinuationOptions.OnlyOnRanToCompletion);

The code that sits behind this is neat and simple in its elegance:

C#
public static Task<bool> CreateDelayTask(int milliSecondsTimeout)
{
  TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
  new Timer(self => 
  {
    ((IDisposable)self).Dispose();
    tcs.TrySetResult(true);
  }).Change(milliSecondsTimeout, -1);
  return tcs.Task;
}

Anyway, once you've done your search, you obviously want to display your results and let the user select and display an individual snippet. Well, the UI uses a data grid and simple binding to actually display the data, but do you remember that I mentioned that there was a wrinkle here? Earlier on I said that the text elements had to be decoded as they were stored using HTML encoding. There are two ways that we could achieve this effect. The first way would be to loop through all the returned data and decode the text before we display it; doable but inefficient. The second way to achieve this, and funnily enough the way I chose, is to use a converter to perform the decode for us on the fly. This is a simple piece of code, but I hope it helps to demonstrate that XAML applications often just need simple little solutions to what seem to be complex problems:

C#
public class DecodeConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    if (value == null) return string.Empty;

    return WebUtility.HtmlDecode(value.ToString());
  }

  public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    throw new NotImplementedException();
  }
}

Inserting the snippet is a nice and simple operation (well simplish), so let's cover that now. The document service provides the following method to actually insert text into the editor:

C#
public void InsertText(string textToInsert)
{
  if (provider == null || provider.ActiveDocument == null) return;

  if (provider.UndoContext.IsOpen)
    provider.UndoContext.Close();

  provider.UndoContext.Open(Messages.UndoContext);

  try
  {
    TextSelection sel = (TextSelection)provider.ActiveDocument.Selection;
    EditPoint startPoint = sel.TopPoint.CreateEditPoint();
    EditPoint endPoint = sel.BottomPoint.CreateEditPoint();

    if (sel.Text.Length == 0)
    {
      endPoint.Insert(textToInsert);
    }
    else
    {
      endPoint.ReplaceText(startPoint, textToInsert, (int)EnvDTE.vsEPReplaceTextOptions.vsEPReplaceTextAutoformat);
    }

    Autoformat(startPoint, endPoint);
  }
  finally
  {
    provider.UndoContext.Close();
  }
}

This method starts off by ensuring that we have an open document in the editor to insert in to, and it then closes any open undo context before opening a new one specific to CodeStash (don't worry about what an UndoContext is right now, we'll be covering this one shortly). Next, we create a couple of EditPoint elements. These represent points in the code editor that are the current position of the editor. Next we determine whether or not we have any text selected; if we do, we know that we are going to have to replace it with the snippet, otherwise we will be inserting the snippet. Once the snippet is inserted, the document is automatically formatted so that the snippets are neat, and finally the undo context is closed.

So, what is the undo context? Well, it represents an atomic action that can be undone or redone by Visual Studio. This allows us to group many internal operations together into a single item that Visual Studio can undo or redo, just like this:

The UndoContext in action, displaying CodeStash context information at the top of the undo stack. 

The undo context in action.

So there we go, snippets retrieved from the service and added into Visual Studio - all managed in a handy "undoable" operation. As I said before, I haven't covered the snippet viewer; this is based on Daniel Grunwald's excellent AvalonEdit control, and you really need to read the article that goes with the control to get an understanding of how it works.

I have a question Pete. Why is the addin split into two projects?

I'm glad that you asked this. When we look at the code, we see that we could easily have moved all of the code into one project, and it seems strange that the interfaces work in the way that they do. The reason for this is down to a decision I took early on in the development to put the infrastructure in place to support future functionality. While this version of CodeStash is code complete, it is a long way away from finished - in fact, I doubt it will ever be finished. Sacha and I always envisaged that the first release would be to gauge the reaction to CodeStash, and to see if it was something that would interest people. Development does not stop here though. We want people to feed back ideas about what they would like to see in place, and we have some great plans for enhancing CodeStash in some really cool and exciting ways. If we had pushed ahead with all the features that we wanted, these articles would have been delivered a year later, so we would rather get a version 1 that has all the basic functionality in place, and that provides the underpinnings to push forward with new functionality.

So, to answer the question, the addin is split into two projects to provide the base that we need for the next versions of CodeStash, such as client side caching of groups, snippet autocompletion and other great features.

What have we really learned by now?

Well, hopefully you've come away from this article thinking that CodeStash is a cool extension and one that you will want to use on a daily basis. You've also learned that Pete O'Hanlon plays fast and loose with the rules of the English language when it suits him. You should have an understanding of how to add commands to a Visual Studio package, and how to associate the same command with multiple windows. You know how to utilise Visual Studio services, and how to hook into external REST services without having to import service contracts. Finally, you know a quick way to associate config files with DLLs.

A point to ponder. This article spent so long covering how the command tables work inside Visual Studio because the documentation for it is so disjointed, and lacking in some parts that I wanted to take this opportunity to put what I learned down so that you don't have to go through the pain points that I did. I hope you find it worth your while.

Please, download the code and read it. Let us know what features you would like to see in future iterations of CodeStash. The only way it will grow is with feedback.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Written By
CEO
United Kingdom United Kingdom
A developer for over 30 years, I've been lucky enough to write articles and applications for Code Project as well as the Intel Ultimate Coder - Going Perceptual challenge. I live in the North East of England with 2 wonderful daughters and a wonderful wife.

I am not the Stig, but I do wish I had Lotus Tuned Suspension.

Comments and Discussions

 
Questionproject is dead? Pin
kiquenet.com21-Jan-17 3:55
professionalkiquenet.com21-Jan-17 3:55 
QuestionHow did I miss this??? Pin
Jammer25-Feb-14 3:29
Jammer25-Feb-14 3:29 
AnswerRe: How did I miss this??? Pin
Pete O'Hanlon25-Feb-14 4:04
subeditorPete O'Hanlon25-Feb-14 4:04 
GeneralRe: How did I miss this??? Pin
Jammer25-Feb-14 4:20
Jammer25-Feb-14 4:20 
AnswerRe: How did I miss this??? Pin
Sacha Barber25-Feb-14 5:32
Sacha Barber25-Feb-14 5:32 
GeneralRe: How did I miss this??? Pin
Pete O'Hanlon25-Feb-14 6:00
subeditorPete O'Hanlon25-Feb-14 6:00 
GeneralRe: How did I miss this??? Pin
Sacha Barber25-Feb-14 9:32
Sacha Barber25-Feb-14 9:32 
GeneralRe: How did I miss this??? Pin
Jammer25-Feb-14 22:10
Jammer25-Feb-14 22:10 
GeneralRe: How did I miss this??? Pin
Pete O'Hanlon25-Feb-14 22:31
subeditorPete O'Hanlon25-Feb-14 22:31 
GeneralRe: How did I miss this??? Pin
Jammer26-Feb-14 1:56
Jammer26-Feb-14 1:56 
GeneralRe: How did I miss this??? Pin
Jammer25-Feb-14 22:09
Jammer25-Feb-14 22:09 
GeneralRe: How did I miss this??? Pin
Sacha Barber26-Feb-14 0:45
Sacha Barber26-Feb-14 0:45 
GeneralRe: How did I miss this??? Pin
Jammer26-Feb-14 1:53
Jammer26-Feb-14 1:53 
GeneralMy vote of 5 Pin
Ahmed Ibrahim Assaf15-Jan-13 21:23
professionalAhmed Ibrahim Assaf15-Jan-13 21:23 
GeneralRe: My vote of 5 Pin
Pete O'Hanlon15-Jan-13 22:15
subeditorPete O'Hanlon15-Jan-13 22:15 
GeneralMy 5 Pin
David C# Hobbyist.15-Jul-12 8:45
professionalDavid C# Hobbyist.15-Jul-12 8:45 
GeneralRe: My 5 Pin
Pete O'Hanlon15-Jul-12 9:25
subeditorPete O'Hanlon15-Jul-12 9:25 
GeneralRe: My 5 Pin
David C# Hobbyist.15-Jul-12 11:48
professionalDavid C# Hobbyist.15-Jul-12 11:48 
GeneralRe: My 5 Pin
Pete O'Hanlon16-Jul-12 0:09
subeditorPete O'Hanlon16-Jul-12 0:09 
GeneralRe: My 5 Pin
David C# Hobbyist.16-Jul-12 0:22
professionalDavid C# Hobbyist.16-Jul-12 0:22 
GeneralMy vote of 5 Pin
Farhan Ghumra13-Jun-12 0:04
professionalFarhan Ghumra13-Jun-12 0:04 
GeneralRe: My vote of 5 Pin
Pete O'Hanlon22-Jun-12 4:59
subeditorPete O'Hanlon22-Jun-12 4:59 
GeneralMy vote of 5 Pin
Lynn Langit23-Apr-12 8:36
Lynn Langit23-Apr-12 8:36 
GeneralRe: My vote of 5 Pin
Pete O'Hanlon23-Apr-12 11:22
subeditorPete O'Hanlon23-Apr-12 11:22 
GeneralMy vote of 5 Pin
Halil ibrahim Kalkan16-Apr-12 20:53
Halil ibrahim Kalkan16-Apr-12 20:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.