Monday, August 16, 2010

Vs Add-in solution explorer context menus with a little MEF

My first MEF success. I wrote a visual studio add-in context menu for solution explorer where you can right click a project (or solution file for all projects) so that it:



  • reads the project file checking several problem areas 
    • hint paths
    • copy local
    • pre/post build events
    • target framework version
  • then makes a backup copy
  • cleans those local customizations
  • invokes msbuild to make sure it still builds
  • brings up the Source control commit dialog
  • restores your local customizations that we don't want in the source.
I used MEF so that the UI was not hard coded into the add-in. It  was amazingly simple. While I was developing a solution someone was nice enough to post on stackoverflow telling me you can't hydrate static properties. Which I did.
The composition code is longer than it needed to be, because I wanted a local default to run in case there was no extension available. I had long wondered how you could do preference/priority ordering of components. This isn't exactly the mental model I had for how it would work, but it works.

I compose my MEF parts in the static constructor for the class because I need to set static properties before anything else in the class happens:

static Connect( )
  {
   var batch = new CompositionBatch( );
   CompositionContainer container;
   var reflectionCatalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly( ));
 
   var extensionPath = System.IO.Path.Combine(Environment.CurrentDirectory, "extensions");
   if (System.IO.Directory.Exists(extensionPath))
   {
    var directoryCatalog = new DirectoryCatalog(extensionPath);
    var defaultCatalogEp = new CatalogExportProvider(reflectionCatalog);
    container=new CompositionContainer(directoryCatalog, defaultCatalogEp);
    defaultCatalogEp.SourceProvider=container;
   }
   else
    container=new CompositionContainer(reflectionCatalog);
 
   container.Compose(batch);
 
   Display=container.GetExportedValue<Action<IEnumerable<ProjectLogicChecks>>>( );
  }

[Import]
public static Action<IEnumerable<ProjectLogicChecks>> Display { getset; }
/// 
  /// Local default in case an MEF extension is not found
  /// 
  /// "_checkResults">
  [Export(typeof(Action<IEnumerable<ProjectLogicChecks>>))]
  private static void DisplayResults(IEnumerable<ProjectLogicChecks> _checkResults)
  {
   string result = string.Empty;
   foreach (var projectLogicCheck in _checkResults)
   {
    result+=projectLogicCheck.AssemblyName;
    if (projectLogicCheck.HasProblems)
     result+="*";
    result+=".";
   }
   MessageBox.Show(result);
  }
Note: the static part is not related to MEF, it's static for unrelated reasons.

MEF lives in System.ComponentModel.Composition, but this project also made use of System.ComponentModel.Composition.Hosting
My MEF Export (in a completely separate project) uses WPF to create a nice dialog to show the results of the add-in checks:
public class DisplayHooks
 {
 [Export(typeof(Action<IEnumerable<ProjectLogicChecks>>))]
  public static void Display(IEnumerable<ProjectLogicChecks> projectStatusList)
 {
  var projectLogicChecksDisplay = new ProjectLogicChecksDisplay(projectStatusList);
  
  projectLogicChecksDisplay.ShowDialog();
  
 }
 }
Note: Both the add-in and the display project have a reference to the 3rd project that contains ProjectLogicChecks
That's ALL the MEF code there was, 2 attributes, and some composition logic in the one class. In an app where you use MEF for more than just the one property you would likely put it in Global.Asax (or Application.cs, etc...) but for this the local few lines of code was all I wanted to Proof of Concept the Add-in.

No comments:

Post a Comment