A Visual Studio extension to really clean your projects

A while back one of the participants at one of my C# courses asked me if there was a way to really clean your projects: that is, delete the bin and obj folders. Because I think we all know that the "clean project" option of Visual Studio does none of that :)

I told him that by default, there isn't, but there should be some extensions up in the marketplace that can be used. And I looked at some of the solutions, but all of them were a bit off for my taste. Some of them have to be set up (I mean, all I want is to delete some folders), some are hidden somewhere in the already-overcrowded VS menubar, some shows all sorts of fancy pop-ups and other UI elements. That's why (and because it seemed like a fun project) I decided to implement my own. I never thought it would actually be used by someone (because as I have said, there are already a couple other ones out there), but last week I actually got a Github issue :) so I thought it was time to do a post about it.

If you want to just go ahead and download the extensions, you can find it in the marketplace If you are interested in the process of creating the extension, read on.

Setting up the requirements

So some of the most important requirements were:

  • Should be plain and simple: nothing but deleting these folders. Maybe options to delete for the whole solution (bin,obj for every project, with packages) or just for a project (bin and obj folders).
  • Not another menu option hidden somewhere in the menu bar or one of its submenus. That's crowded as it is :) I added options to delete to the context menus of the project and the solution.
  • Keybinding for the solution option: Ctrl+Shift+Delete.
  • Should communicate via the "General" pane of the output window. No dialogs, messageboxes or other UI elements.

The Github issue that was raised was about deleting the bin and obj folders for every project, but keeping the packages; I added that as well.

Implementing the solution

The solution itself is pretty simple as well. I created a Visual Studio extension and added a couple of custom commands, then refactored the solution with a single basecommand so that other commands can derive from that:

public abstract class CleanCommandBase
{
  protected readonly IProjectFolderSerivce projectFolderService;
  protected readonly IErrorHandlerService errorHandlerService;
  private readonly IVsOutputPaneService vsOutputPaneService;
  protected CleanCommandBase(IProjectFolderSerivce projectFolderService,
                                   IErrorHandlerService errorHandlerService,
                                   IVsOutputPaneService vsOutputPaneService,
                                   OleMenuCommandService commandService, Guid guidPackageCmdSet, int commandId)
  {
    if (commandService == null)
      throw new ArgumentNullException(nameof(commandService));
    this.projectFolderService = projectFolderService ?? throw new ArgumentNullException(nameof(projectFolderService));
    this.errorHandlerService = errorHandlerService ?? throw new ArgumentNullException(nameof(errorHandlerService));
    this.vsOutputPaneService = vsOutputPaneService ?? throw new ArgumentNullException(nameof(vsOutputPaneService));
    var menuCommandID = new CommandID(guidPackageCmdSet, commandId);
    var menuItem = new MenuCommand(this.Execute, menuCommandID);
    commandService.AddCommand(menuItem);
  }

  protected abstract void Execute(object sender, EventArgs e);
  protected void SafeDelete(string folder)
  {
    if (!Directory.Exists(folder))
      return;

    try
    {
        Directory.Delete(folder, true);
        vsOutputPaneService.WriteMessage($"Folder {folder} deleted");
    }
      catch (Exception ex)
    {
        errorHandlerService.WriteErrorMessage($"Could not delete folder {folder}", ex);
    }
  }
}

So that's the command. It has a bunch of dependencies. The OleMenuCommandService, the Guid guidPackageCmdSet and the int commandId are used by the Visual Studio extension API to pick up the command. The other dependencies are the actual services that the commands use:

  • The IProjectFolderService is responsible for discovering the bin, obj and pacakges folders. Source code for the implementing class.
  • The IVsOutputPaneService allows me to write to the Output pane of Visual Studio. Source code for the implementing class.
  • The IErrorHandlerService is simply used to write error messages to the Output pane. Basically just wraps the IVsOutputPaneService.

I use the SafeDelete() methods to delete folders with extra-careful error handling. The descendant command classes call the IProjectFolderService for the folders to be deleted (depending on which type of command they are) and then call back to this method to delete the folders.

When the commands are done, they must be registered in the vsct file of the extension. This takes some getting used to, but if you know the syntax, you can get around it pretty easy. This is the file where the keybindings can be specified as well. You can check out the file here. And of course, everything must be wired together.

Go ahead and check out the Github repository for the full source code. If you have any ideas, feel free to suggest, but remember to K.I.S.S. :)