Click here to Skip to main content
15,860,972 members
Articles / Programming Languages / C#

The Razor Framework :: Part 1 :: Plugins/Extensibility

Rate me:
Please Sign up or sign in to vote.
4.93/5 (127 votes)
11 Mar 2005CPOL36 min read 348.9K   1.4K   446   120
An extensible dependency based plugin framework for .NET Applications.

Contents

Introduction

This is the first article in a multipart series I plan on writing. I have so much to present, and so little space in one article I feel it warrants separating it into more. Below are some topics that will all have their own articles, but if you are interested in more, dig into the code or drop me comment and I’ll help explain as much as I can, or work with you personally to help you get things working. Now on with the intro.

What is an application framework? That may mean different things to different people, but let me attempt to define what it means to me. An application framework is a library or set of libraries upon which many applications can be built or built upon. It should help speed development time by providing a common architecture for accomplishing common programming tasks that occur every time an application gets developed. My goal when designing Razor was to tackle the many areas that I continually encounter in my day to day programming tasks and help to make them transparent or as painless as I could.

In my experiences there are a few areas that have to be addressed in almost every single application. These areas include the following topics.

  • Extensibility (Plugins)
  • Configuration (Options and how they are stored)
  • Logging (Log files and diagnostic information)
  • Instance Management (Single instance or multiple instance)
  • AutoUpdate (Intranet or Internet based updates)

Extensibility encompasses how the application provides an extensible object model to allow future improvements or bug fixes to be built on top of or along side existing functionality without creating development nightmares or support issues.

Configuration encompasses how the application manages configuration files and controls options that may be manipulated by the user.

Logging encompasses how the application logs information for diagnostic information that the developer might need for troubleshooting.

Instance management encompasses how the application handles multiple instances of itself. It may be necessary to force the application to be implemented as a single instance application, like Windows Media Player, or may need multiple instance functionality like Notepad.

AutoUpdate encompasses being able to update your program and provide your users with new features and bug fixes easily. These areas seem to rear their ugly heads with every new project and warrant a carefully designed approach before the applications lifetime grows and adds complexity by allowing multiple solutions to each of these problems.

I will attempt to show you how a design like Razor can help you tackle all of these chores, and make your daily programming work less of a nightmare and more of an outlined walkthrough. With a carefully designed application framework in hand you can be free of these decisions and get down to the real programming challenges of developing your programs and not redeveloping and redesigning the same things over and over again.

In this article I will be showing you how to setup a Razor based application, and create some simple plugins to run inside it. I will only cover plugins in this article, for sake of time and space. More articles will follow to help you cover the remaining topics and how they are handled in Razor. The sample applications do include all of the above, except AutoUpdate which will require a lengthy article because that is a semi-complicated and in-depth topic! So read on to find out about plugins for now! This is more than you’ll need to get your appetite wet and to get started. I promise, Razor will intrigue you, and leave you wishing you had seen it before! Trust me, Razor should help you understand how you can build your own plugin framework, even if you don’t use mine. I just want to give something back.

What is the Razor Framework?

I’ve been developing for about 5 years now, professionally, and I’ve worked on both small and large projects. These topics I referred to above, have all been dealt with on various levels no matter what project and team I worked on. And painfully I’ve watched as multiple teams and developers designed different systems each time. Each new system added new complexities and complications that I felt it was necessary to design an easy to use application framework to bring all of these areas together.

The Razor Framework is the culmination of about a years worth of work professionally, and I have been deploying several applications based on it, for about a year now. This collection of classes is designed to provide you the developer with a robust and thoroughly test plugin architecture. The architecture was designed from the ground up to provide the most extensibility I could manage, without making things impossible to understand or work with. After all it's supposed to help you, not make your head explode.

In addition to allowing you to develop your programs entirely of plugins, the Razor Framework provides a robust and rich suite of classes to make configuring your application's options a breeze. Using Xml and encryption the Framework provides automatic configuration files, and an easy to use object model to allow you to create a heirarchical view of your options. The object model was built upon existing .NET technologies and sports an interface similar to that of the options available in Visual Studio.NET. It's not strictly a look and feel either, the framework consists of objects that work similarly to a dataset providing you with edit modes and events that allow you to be notified when options are changed, both by code or by the user interface, easily allowing your application to reconfigure itself when your options change.

Additional work and design was implemented to allow you to create single instance or multiple instance applications. Providing you with a simple event model to receive the command line arguments from secondary instances of your application. The framework seamlessly integrates the instance management for you into every Razor based program.

In addition, no framework should be without an extensible AutoUpdate architecture. The object model allows you to extend the updating mechanisms to allow different means of providing updates to your application. It currently supports Http downloads from a web service, and via file shares.

I feel that if any of these areas intrest you, that reading through this series will either provide you with a design model upon which you can learn to roll your own framework, or spark some creative ideas for you to contribute to the project. Hopefully this article will generate enough interest for you to enjoy the design, as well as inspire you to help me turn this into a well known and widely used framework.

Getting Started

Let’s get started. First things first, let me explain the elements that you will need to create to get up and running on Razor.

  • The Filesystem Folder Structure
  • The Bootstrap Executable
  • The Hosting Engine Executable
  • SnapIns (Plugins are called SnapIns in Razor)

The Filesystem Folder Structure

When you deploy an application based on Razor, your filesystem should look like this.

  • ”Company Name”
    • “AppName”
    • AppName.exe – This is the bootstrap executable
      • “Version” ie “1.0.0”
      • AppName.exe – This is the hosting engine
      • PluginA.dll
      • PluginB.dll
      • PluginN.dll

Here’s some shots of my file system showing the contents of the bootstrap and hosting engine folders. This may not be clear just yet, as a bulleted list is far from descriptive. :P Patrick Murphy, a developer I had to pleasure to work with, stated recently that he felt understanding the folder structure was more complicated than writing plugins. I lol’lerred after hearing that. Pat’s a smart guy, so if he says I should include the screenshots, I’ll listen. :P

The bootstrap folder…

the boostrap folder

And the hosting engine folder…

the hosting engine folder

You should always install your applications in a folder with your company name, but this is rather a recommendation than a must. Inside this folder you will create a folder with the name of your application, and place the bootstrap executable inside this folder. All shortcuts to the app should point to this exe. When it is started it will scan the directories below it, and attempt to figure out which directory contains the latest version of your hosting engine. Once it finds the version it calculates as the latest version it will start the exe with the same name as itself, and pass it any command line arguments that were passed to the bootstrap exe. This way if you had shortcut containing command line arguments they will be passed on to the hosting engine executable transparently. When you send out updates and add a new version of your program, the shortcuts won’t have to change on the users system, and will continue to work as normal. Each new version of your app simply gets placed inside the bootstrap’s folder with a folder name that parses out to a .NET Version. Pretty simple, and it’s design was based after looking at the Microsoft Application Updater Block.

The Bootstrap Executable

The first project you will need to create is called the bootstrap executable. The bootstrap executable is a small executable residing in a root folder that will find and start the latest version of the hosting engine. This is necessary for implementing AutoUpdate and will be covered in more detail when I discuss the AutoUpdate features in Razor. I have provided a sample bootstrap executable in the source of the project. Here’s the full and complete code for a Razor bootstrap executable. It’s pretty simple. Start a new project, type will be a Windows Application. Add a reference to the Razor Framework (Razor.Framework.dll), and add the following code to the project. Of course you’ll want to delete out the stock form that gets added by VS.NET and add a class called Startup to the project. Just follow along. :P

C#
using System;
using Razor;

 
namespace BootStrapping
{
    /// <summary>
    /// This is the bootstrap for the Razor hosting engine.
    /// </summary>
    public class Startup
    {
      [STAThread]
      static void Main(string[] args) 
      {
          VersioningBootStrap bootStrap = new VersioningBootStrap();
          bootStrap.Run(args, 
                      System.Reflection.Assembly.GetExecutingAssembly());
      } 
   }
}

The Hosting Engine Executable

Once you have created the bootstrap folder and executable, the next step is to create the hosting engine executable. Think of this as the real application. Give it the same name as your bootstrap executable so the bootstrap can find the exe and can start it up when the program is launched. The hosting engine executable will contain a very simple startup class relying on code from Razor to create and run a hosting engine. The hosting engine will scan its folder for .DLLs and load any SnapIns it finds and get them running. This may sound complicated, but I assure you its all quite trivial. Again I have provided a sample hosting engine for you in the source code of this article. If you are following along, now is the time to add a new project to your solution, a new Windows Application project. Again add a reference to the Razor Framework, and delete out all the stock code. Add another class called Startup to the project, and insert code like the following.

There is one key point here that you do not want to miss. Remember I discussed configuration, well they will first come into play here when you create the hosting engine. Take a look at the code in the sample or just below here. Find the subPath variable, and change the value accordingly. This will define the path where the common and local user configuration files will be stored in on disk. Don’t worry I’ll cover this more later. You should just change this to be something like your companyName\appName so that it’s a little more personalized. Feel free to use mine, but for every app these should be unique so that each app can keep it’s configuration files separate.

A quick word on configuration files, because I’m sure you’re curiosity peaked when I mentioned them. Every Razor based application has two separate directories and sets of configuration files. One stored in the All users data path, and another in the Local users data path. There is a common configuration file for storing application global options like database connections, and a local user configuration file for storing things like window positions and options specific to each user. If these files aren’t there, don’t worry they will be created by the hosting engine when it runs. Again, I’ll cover this more later! For now concentrate on the hosting engine code!

C#
using System;
using System.Windows.Forms;
using Razor.SnapIns; 

namespace Razor
{        
    /// <summary>

    /// The Startup class contains a single static method called Main 
    /// which should be used as the applications main entry point.
    /// </summary>

    public class Startup
    {
       /// <summary>

       /// The main application entry point, thru which command 
       /// line arguments will be passed
       /// </summary>

       /// <param name="args">An array of strings representing any 

       /// command line arguments that were passed at startup
       /// </param>

       [STAThread()]
       public static void Main(string[] args)
       {      
           bool tracedExceptionThrown = false;
           try
           {        
              // define the data paths
              string subPath = @"CodeReflection\Razor";
          
              // safely use a hosting engine configured with an additional 
              // common data path and additional local user data path
              using(SnapInHostingEngine host
                                = new SnapInHostingEngine(subPath, subPath))
              {
                 try
                 {
                    // run the hosting engine using the command line and the 
                    // currently executing assembly (aka. the exe for the 
                    // process)
                    host.Run(args, 
                          System.Reflection.Assembly.GetExecutingAssembly());
                 }
                 catch(System.Exception systemException)
                 {
                    // flag the fact that we are going to trace this exception
                    // and rethrow it
                    tracedExceptionThrown = true;
             
                    // give the loggers a chance to catch it if they have 
                    // been successfully loaded before the host is disposed 
                    // of and the logging sub system detached from the Trace 
                    // and Debug output
                    System.Diagnostics.Trace.WriteLine(systemException);
            
                    // rethrow the exception so that it may be displayed for 
                    // the user
                    throw systemException;
                 }
              }      
           }
           catch(System.Exception systemException)
           {
              // if the exception hasn't already been traced
              if (!tracedExceptionThrown)
              {
                 tracedExceptionThrown = true;
                 // trace it now
                 System.Diagnostics.Trace.WriteLine(systemException);
              }
          
              // also, since it's more likely we'll not have a debugger 
              // attached, display the exception to the user
              string info = string.Format(
                  "The following exception was thrown by '{0}'.\n\n" + 
            "Please refer to the log files for further information.\n\n{1}", 
                  Application.ProductName, systemException.ToString());
 
              System.Windows.Forms.MessageBox.Show(null, info, 
                     "Application Exception");
              // exit the current thread to force safe application shutdown
              Application.ExitThread();
           }
           finally
           {
             // one final trace to let everyone know we have shutdown 
             // completely
             System.Diagnostics.Trace.WriteLine("'" + Application.ProductName
                 + "' has " +  (tracedExceptionThrown ? 
               "terminated because of an exception." : "exited gracefully."));
           }
       }
    }
}

After you have create the hosting engine executable, it’s time to get busy creating the plugins, or SnapIns as they are called in Razor.

What is a SnapIn?

In a nutshell, a SnapIn is what I call plugins in Razor. A SnapIn is a class that is exported from a managed assembly and loaded at runtime by the SnapInHostingEngine class. The hosting engine will notify the SnapIn that it should take a particular action through interface methods which are manifested in the SnapIn base classes using Events. You will respond to these events and provide your own functionality.

Plugins, now that’s a concept that seems so complicated at first glance, most folks just shy away from it. But if they are so complicated, why does virtually every program you run yourself on a daily basis seem to be capable of using them? That’s because they really aren’t all that complicated. They are only as complicated as the framework upon which they were built. Razor SnapIns are easy to design, easy to deply, and easy to debug.

It seems that over the years I’ve seen some of the best and worst designs for plugins. Everything from being forced to define a plugin in external files like Ini files or Xml files to COM like registry entries which are even more complicated because most folks should never even think about getting into the Registry for any reason! Using a file to define what plugins are available to the application is a bad idea in my opinion because if you are trying to add plugins or remove plugins you have to edit some stupid file. This just ends up being a bigger nightmare the minute you have a user on the phone that doesn’t even know his/her mouse has two buttons. Imagine the headaches you’ll end up with trying to instruct them in editing some proprietary plugin format in some file just to get a new plugin installed or uninstalled. That sucks. Registry entries, well they are old, and twice as bad because if you mess up, you could seriously screw up other aspects of your system. There’s a lot of important stuff in the Windows Registry besides whatever strange keys and values you’ve made, no sense in adding that to the mix.

When I set about to design a plugin system for Razor, I decided that it must not force the user or developer to edit files to get a plugin running in the application. That’s just one more step that can go wrong, and it’s really pretty dumb when you think about it. Why can’t the program be smart enough to find the plugins on it’s own? Well, Razor is. Here’s how. When you create a SnapIn for any Razor based application, you will simply add an attribute to the assembly that exports the SnapIn. When the hosting engine starts up, it’ll scan all the DLLs and find the SnapIns automatically. If you want to add a SnapIn, simply build the assembly and drop it into the hosting engine’s folder. If you want to remove a SnapIn, stop the application and delete or move the assembly containing the SnapIn from the hosting engine’s folder. That’s about as easy as you can get.

When the hosting engine starts up, it scans its folder for all of the DLLs. Using reflection it will read a pre-determined assembly level attribute from each assembly that contains the Type of the SnapIn that the assembly is exporting. You can export as many SnapIns from a single assembly as you want, there’s really no limit other than those you design into the system. If the attribute wasn’t in place, then the hosting engine would have to enumerate every single Type in the assembly to try and figure out if it is a SnapIn or not. And even then, you may need further control over whether the Type is really supposed to be a plugin at that moment in time or not! What if you put a SnapIn Type, or class, into a dll and then another SnapIn needs to reference that dll. By the .NET copy local design all of the referenced DLLs will be placed in the same directory. Now you could be facing a problem of controlling which SnapIns are loaded.

I usually place the attribute into the AssemblyInfo.cs file along with the other assembly level attributes. This just makes it easy to find out which if any SnapIns a particular assembly is exporting. For all you C guys, think of this as the exports definition file (*.def) that contained all of the exported functions. Instead of exporting functions, you are exporting SnapIns, or plugins. Let’s take a look at the attribute I’ve been talking about now. Here is the contents of the entire AssemblyInfo.cs file from one of the included SnapIns in the sample project I have provided.

C#
using System.Reflection;
using System.Runtime.CompilerServices;
using Razor.Attributes;
[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("CodeReflection")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("Copyright © CodeReflection 2004")]
[assembly: AssemblyTrademark("Trademark ™ CodeReflection 2004")]
[assembly: AssemblyCulture("")]        
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("")]
[assembly: AssemblyKeyName("")]
[assembly: SnapInExportedFromAssembly(typeof(
                Razor.SnapIns.ApplicationWindow.ApplicationWindowSnapIn))]

The last line contains the SnapInExportedFromAssembly attribute that specifies the fully qualified name of the SnapIn that you want to export from the assembly. The hosting engine will use this attribute to load the Type using Reflection and create an instance of it at runtime. You may add as many export attributes as you wish, but it’s generally a good idea to limit your assemblies to one SnapIn per assembly. This will help simplify debugging and deployment. Notice that I have also added a using statement to include the Razor.Attributes namespace to allow the compiler to find the attribute.

Creating a SnapIn

Ok, so now the fun starts. Creating a SnapIn for your new hosting engine. In every Razor based application, every bit of functionality you create will be created as a SnapIn. Everything is a plugin. The exe’s are completely generic save the few lines of code to change up configuration paths. If you create a SnapIn, it’ll work in any Razor based app. Assuming of course it’s dependencies and references are intact.

Dependencies? What the heck is that? Well, for anyone that writes code, a dependency is just another dll or file that your app has to have to run. SnapIns are no different and I’ve gone to great lengths to make this work to your benefit. If everything is a plugin, and plugins are loaded at runtime, how do you control when your plugin loads in regards to another plugin. And what do you do if you are referencing a separate plugin to add functionality to it? If your dependencies aren’t there or aren't loaded when your plugin starts, it could be a serious problem.

I’ve seen a lot of different approaches, and I’ve tried most of them. Everything from having a property that defines a load level, like Low, Medium, or High, to defining and hard-coding a loading order in some external config file. Those ways all have limlitations and are just a pain in the butt. I just want to write a plugin, and know that when it gets loaded and started, that everything I need to run is going to be loaded and started so that I don’t have to worry about it. That’s what Razor does. Razor determines at runtime which SnapIns are dependencies of other SnapIns and then loads them in order of the least dependent to the most dependent. This loading algorithm allows you to specify which other SnapIns if any you need to run, and then makes sure that they are loaded and running before your SnapIn gets the nod. Think back to Windows Services, there’s a tab that shows which services a service requires to run, and if one of them isn’t available that service won’t start. Same concept applies. If one of the dependencies stops, your SnapIn in turn will stop. This all happens automatically, after you define your dependencies.

How do you define your dependencies? Using attributes of course! Metadata is a wonderful thing, and it’s used generously in Razor. Every SnapIn that you create will have one or more attributes added to the SnapIn class. These attributes will contain information about you, your company, the SnapIn itself, and its dependencies. Take a look at this code snippet from one of the sample SnapIns included with this article.

using System;
using System.Diagnostics;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using Razor;
using Razor.Attributes;
using Razor.Configuration;
using Razor.Features;
using Razor.SnapIns;
using Razor.SnapIns.WindowPositioningEngine;
 
namespace Razor.SnapIns.ApplicationWindow
{
    /// <summary>

    /// Provides a generic window plugin that other plugins can modify as 
    ///  their main window
    /// </summary>

    [SnapInTitle("Application Window")]    
    [SnapInDescription("Provides a generic window to be used as the main 
window for an application.")]
    [SnapInCompany("CodeReflection")]
    [SnapInDevelopers("Mark Belles")]
    [SnapInVersion("1.0.0.0")]    
    [SnapInImage(typeof(ApplicationWindowSnapIn))]
    [SnapInDependency(typeof(
                    WindowPositioningEngine.WindowPositioningEngineSnapIn))]
    public class ApplicationWindowSnapIn : SnapInWindow, 
                                           IWindowPositioningEngineFeaturable 
    {

Taking a look at the code snippet above, you’ll see some new attributes being applied to a class. The class contains the code specific to your SnapIn, and has been omitted here for lack of room. These are pretty simple, and shouldn’t require any great intellect to figure out what’s going on. You’ll see that there are attributes for setting information about the SnapIn such as title, description, company, developer, version (SnapIns are versioned), a toolbox like image, and dependencies.

You should note the last attribute in the code above. It is the SnapInDependency attribute that I’ve been discussing. Notice that it takes the Type of another SnapIn class. The hosting engine will ensure that every SnapIn you specify as a dependency is loaded and started before your SnapIn gets created and started. It’s that easy. If you know there is one or more SnapIns that you rely on, and cannot function properly without, then simply mark those SnapIns as dependencies.

The framework provides some means of viewing all of the SnapIns installed in the current hosting engine, as well as viewing the SnapIn metadata, SnapIn dependencies, and provides some common troubleshooting hints when you have a SnapIn that isn’t working like it should. Here’s a quick glance at a few of those dialogs that are provided for every Razor based application.

The SnapIns window for viewing the SnapIns that are loaded

the snapins window

Their general properties...

their general properties

Viewing their dependency links, what they depend on, and who depends on them...

Sample screenshot

And some troubleshooting flags that the engine will determine after it loads the SnapIn...

Sample screenshot

Once you're added the metadata to your SnapIn class, and added an export attribute to the assembly to allow the hosting engine to find your SnapIn, you’re ready to get started writing the actual SnapIn code. So let’s get started on that, okay? You are probably wondering how your SnapIn will interact with the hosting engine. How will it start, how does it stop? There are 3 classes of interest here. They are SnapIn, SnapInControl, and SnapInWindow. Each of these classes provides events and virtual methods to allow you override and implement your own custom functionality. By providing events and virtual methods, you will be responding to the same events in every SnapIn, and hopefully use the virtual methods as a starting point to keep a consistent design throughout your suite of SnapIns.

At the core of the hosting engine lies the IsnapIn interface. Here’s a look at the interface, which is how the hosting engine communicates with every single plugin loaded into the system at runtime.

C#
using System;
namespace Razor.SnapIns
{      
    /// <summary>

    /// Defines the methods and events of a SnapIn class used by the 
    /// SnapInHostingEngine class
    /// </summary>

    public interface ISnapIn 
    {      
       event EventHandler InstallCommonOptions;
       event EventHandler InstallLocalUserOptions;
       event EventHandler UpgradeCommonOptions;
       event EventHandler UpgradeLocalUserOptions;
       event EventHandler ReadCommonOptions;
       event EventHandler ReadLocalUserOptions;
       event EventHandler WriteCommonOptions;
       event EventHandler WriteLocalUserOptions;      
       event EventHandler UninstallCommonOptions;
       event EventHandler UninstallLocalUserOptions;
       event EventHandler Start;
       event EventHandler Stop;  
 
       void OnInstallCommonOptions(object sender, System.EventArgs e);      
       void OnInstallLocalUserOptions(object sender, System.EventArgs e);
       void OnUpgradeCommonOptions(object sender, System.EventArgs e);
       void OnUpgradeLocalUserOptions(object sender, System.EventArgs e);
       void OnReadCommonOptions(object sender, System.EventArgs e);
       void OnReadLocalUserOptions(object sender, System.EventArgs e);
       void OnWriteCommonOptions(object sender, System.EventArgs e);
       void OnWriteLocalUserOptions(object sender, System.EventArgs e);
       void OnUninstallCommonOptions(object sender, System.EventArgs e);    
       void OnUninstallLocalUserOptions(object sender, System.EventArgs e);
       void OnStart(object sender, System.EventArgs e);
       void OnStop(object sender, System.EventArgs e);            
    }
}

Once you are ready to write a SnapIn for a Razor based application. Simply decide which base class you will inherit. I’ve provided the 3 main classes that should fulfill every situation you will need. And trust me, I’ve handled a ton of different ones, all of which have fallen into my predefined usage categories. Choose from one of the following base classes.

  • SnapIn
  • SnapInControl
  • SnapInWindow

If you need to create a plugin that has designer support, then inherit from either the SnapInControl class or the SnapInWindow class, otherwise choose the SnapIn class. Once you have inherited from the appropriate class, jump to your constructor and add some event handlers to the base classes’ events. These events are triggered when the hosting engine calls the methods of the base classes IsnapIn interface. Take the following code as an example. It wires up to the Start and Stop events. These are triggered when your SnapIn starts, and when it Stops. Once per session, unless you are triggering them manually using the SnapIns dialog.

C#
/// <summary>

/// Initializes a new instance of the ApplicationWindowSnapIn class
/// </summary>

public ApplicationWindowSnapIn() : base()
{
    _theInstance = this;
        this.InitializeComponent();
        
    base.Start += new EventHandler(OnSnapInStart);
        base.Stop += new EventHandler(OnSnapInStop);
        this.FileMenuItem.Popup += new EventHandler(OnFileMenuItemPopup);
        this.ToolsMenuItem.Popup += new EventHandler(OnToolsMenuItemPopup);
        this.HelpMenuItem.Popup += new EventHandler(OnHelpMenuItemPopup);
}

Here’s the event handlers for these events. Notice that I like to pass these events on to the overridden methods in my SnapIn class, just so that there’s no confusion. I know that for a given event every SnapIn is going to follow the same rules and I can know to look for the same set of methods in every SnapIn. Trust me when you have developed more than a hundred plugins, you’ll appreciate having some standards to rely upon.

C#
#region My SnapIn Events
       
/// <summary>

/// Occurs when the snapin starts
/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void OnSnapInStart(object sender, EventArgs e)
{
    this.StartMyServices();
}

/// <summary>

/// Occurs when the snapin stops
/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void OnSnapInStop(object sender, EventArgs e)
{
    this.StopMyServices();
}
    
#endregion

Notice that I also called the methods using the this notation. It is good practice to specify whether the method implementation is found in your class or the base class by specifying the this or base qualifiers in front of your method names. Every SnapIn has virtual methods that you can override and put your specific code into, just to give you a predefined mechanism for consolidation’s sake. These virtual methods are not called automatically by the hosting engine, but rather are there to help you keep things constant. Override the methods and then call them from the event handlers. Here’s what some overrides might look like, this snippet is taken from one of the sample SnapIns.

C#
#region My Overrides
    
/// <summary>

/// Provides startup services
/// </summary>

protected override void StartMyServices()
{
    base.StartMyServices ();
 
    try
    {
       // assimilate the proprties of the starting executable
      this.MorphAttributesToReflectStartingExecutable();
          
      // use the window positioning engine to manage our state
      WindowPositioningEngine.WindowPositioningEngineSnapIn.Instance.Manage(
          this, WindowPositioningEngineKey);
          
      // wire up to previous instance events
      SnapInHostingEngine.GetExecutingInstance().InstanceManager.
CommandLineReceivedFromAnotherInstance += 
       new ApplicationInstanceManagerEventHandler(
                                   OnCommandLineReceivedFromAnotherInstance);
 
      // add ourself as a top level window to the hosting engine's 
      // application context
      SnapInHostingEngine.Instance.ApplicationContext.AddTopLevelWindow(this);
 
      // show ourself
      this.Show();
    }
    catch(Exception ex)
    {
       Trace.WriteLine(ex);
    }
  }
}

/// <summary>

/// Provides shutdown services
/// </summary>

protected override void StopMyServices()
{
      base.StopMyServices ();

        // wire up to previous instance events
        SnapInHostingEngine.GetExecutingInstance().InstanceManager.
CommandLineReceivedFromAnotherInstance -= 
                new ApplicationInstanceManagerEventHandler(
                                   OnCommandLineReceivedFromAnotherInstance);
 
        // close ourself
        this.Close();
}

#endregion

One detail I left out, but is critical, is that when you make your interface, you’ll generally have one or more windows that you want to act like a main window. In other words, you’ll want the program to stop when you close those windows. Check out the StartMyServices method above, and you’ll notice that the window adds itself to the hosting engine’s application context as a top level window. The hosting engine runs the application context, and exits when all of the top level windows added to it are closed. If you never add your window to the hosting engine’s application context, it won’t know when it’s time to close, unless you close it manually. Which is past the scope of this article.

Take a look at the sample SnapIns. I’ve provide you with two, one to create a main window, that when closed will exit the Application Context and close the hosting engine, and another SnapIn that the main window SnapIn depends upon for storing it’s window positions. I’ve mentioned configuration in the paragraphs above, and trust me I’ve not forgotten about it. In the next article I’ll come back and explain more about that. Trust me when I say you will love it.

So now you’ve seen how you get your code running in Razor as a SnapIn. Let’s talk a bit about design. If everything is a plugin, that means there is nothing there by default. No windows, nothing. I’ve created a SnapIn upon which every application starts, and depends. It’s a simple SnapIn that displays a window, and adds common functionality. Things like displaying a menu with items for accessing the Options, SnapIns, and Features dialogs. All of which are provided by the framework, you just need a place to access them.

What is the hosting engine?

Throughout this article you heard me repeatedly refer to this little guy, and I haven’t explained it in any great detail. This is the main class responsible for everything that happens behind the scenes. The SnapInHostingEngine class is responsible for loading all of the SnapIns from the file system, as well as every other detail like starting and stopping the SnapIns. Keeping the configuration files and log files going, pretty much this class rules all in a Razor world. Without it, you got a whole lotta nothing.

The class is responsible for keeping track of which plugins are installed and uninstalled, which ones are started and stopped, and therefore knows pretty much everything there is to know about the SnapIns in your program. It also has many other handy properties, too numerous to discuss at once, but I’ll throw a few at you. If you want to access the configuration files, they are properties of the hosting engine class. If you want to find another SnapIn there are methods and properties to help you look it up by its Type. If you want to programmatically start, stop, install, or uninstall another SnapIn, this guy has the methods. It wakes you up in the morning, and tucks you in bed at night… if you are a SnapIn.

At all times you may access the running hosting engine by one of two ways.

C#
SnapInHostingEngine.GetExecutingInstance();

// Or

SnapInHostingEngine.Instance

When you want to use the hosting engine, just grab the current executing instance using either the method or the property listed above. Feel free to browse it’s properties. There’s a lot there, and I’ll cover them more as we need to.

AppDomains and Dynamic Loading

One item of interest, is the manner in which the SnapInHostingEngine loads the SnapIns. At runtime, it will scan the Application's folder, for all assemblies (*.dll). Once the hosting engine has located the assemblies, it will load each of them using their full path. Once the assembly is loaded into memory, the hosting engine will attempt to read the SnapInExportedFromAssembly attribute from the assembly to determine if any SnapIns are being exported from the assembly.

In the event that a SnapIn is not being exported from the assembly, the hosting engine is now faced with a problem. How to unload the assembly from memory. Unfortunately the assembly cannot be unloaded from memory, in the same manner it was loaded. However, if the AppDomain that loaded the assembly is unloaded, then all of the assemblies loaded by the AppDomain will be unloaded.

Based upon an article by Eric Gunnerson, found here, I created the SnapInProvider class which is responsible for loading a separate AppDomain into which all of the assemblies are loaded during the SnapIn loading phase. Once the Type information has been extracted from the assemblies, the provider unloads the AppDomain and thereby unloads all of the unused assemblies.

If you are curious of what the code looks like, check out the snippet below taken from the SnapInProvider class. On a side note, if I were to rewrite this, I'd use a strongly typed collection instead of an ArrayList.

C#
public static ArrayList FindSnapIns(Search search, 
                                    IProgressViewer progressViewer)
{
    try
     {
        // create a new appdomain
        AppDomain domain = AppDomain.CreateDomain(Guid.NewGuid().ToString());
 
        // create an instance of the provider inside the new app domain     

        SnapInProvider provider = 
                   (SnapInProvider)domain.CreateInstanceFromAndUnwrap(
                  System.Reflection.Assembly.GetExecutingAssembly().Location, 
                  typeof(SnapInProvider).FullName);
     
        // use it to search for the snapin types we need to load
        ArrayList array = provider.SearchForTypes(search, typeof(ISnapIn), 
                                                  progressViewer);
  
        // unload the appdomain and any assemblies that we won't create a 
        // snapin from
        AppDomain.Unload(domain);
  
        return array;
     }
     catch(System.Exception systemException)
     {
        System.Diagnostics.Trace.WriteLine(systemException);
     }
     return new ArrayList();
}

Accessing other SnapIns

What if you want to grab a hold of another running SnapIn? Since everything is a SnapIn, virtually everything that is running besides the hosting engine itself is going to be loaded at runtime. There are two ways to do accomplish this, and believe me this is a very common question.

The first way is the uber complicated and elite way, the second is the easy way, which I prefer because it saves me time. :P The first is to grab the hosting engine and ask it for a SnapInDescriptor for the SnapIn of the particular Type that you are after. I prefer not to have to do this, so I opt for the second method. Which is to simply make a static variable in your SnapIn of itself, and then set it in the SnapIn’s constructor. Since you will only ever have one instance of each SnapIn created and running, you just have to refer to that static method or property to get access to the SnapIn’s instance that is loaded and running.

The code snippet above demonstrates how I used the second method to gain access to the hosting engine itself, and the application window SnapIn. Just some basic stuff really.

The first method would require you to call upon the following method found in the SnapInHostingEngine.

C#
public SnapInDescriptor FindDescriptorByType(Type type)
{
    foreach(SnapInDescriptor descriptor in _descriptors)
       if (Type.Equals(descriptor.Type, type))
       return descriptor;
       return null;
}

The SnapInDescriptor class is a wrapper class that contains dependency linkage information about each SnapIn. As well as the SnapIn's meta data, retrieved from the attributes that may be applied to the SnapIn's class. And finally a pointer to the ISnapIn interface implemented by each SnapIn.

Using the descriptor class you can programmatically determine what other SnapIns a particular SnapIn depends upon, as well as cast the ISnapIn pointer to whatever Type you know the SnapIn is. For example, if you were looking for SnapInA, you would use the FindDescriptorByType method passing it typeof(SnapInA). Once the descriptor is returned, you simply cast the descriptor. SnapIn an instance of the SnapInA class. Your code might look something like this.

C#
private SnapInA FindMyDependencies()
{
    SnapInDescriptor descriptor = 
        SnapInHostingEngine.Instance.FindDescriptorByType(typeof(SnapInA));

 
     if (descriptor != null)
     return (SnapInA)descriptor.SnapIn;
     return null;
}

Once you've got your object back, you'd continue to use it just like it was a reference to a class instance as you would any other object instance. You would be free to call the methods and properties that the class provides. The fact that it is a SnapIn does not affect how you use it. The fact that it is a SnapIn just means that it loads dynamically so you will need to make it a dependency to ensure that you load after it does. If you do not, then you could end up trying to use a class that isn't ready to be used. Think carefully on your designs in this area. Make sure to get your dependencies right in larger projects!

Log Files

What about log files you say? Yep, that’s covered too. The SnapInHostingEngine class installs a custom TraceListener that writes Trace output to a log file. I put some reflection and diagnostic information into the log file traces to include things like the class that made the call, the source code filename, the line number where the call originated. The hosting engine will rotate through a series of 10 log files. Pushing the most recent towards the bottom. The most current file is always “Log.txt”, extending down from “Log2.txt” to “Log9.txt”. To access the log files simply browse to the current user’s documents and settings data path, and into the folders you had the hosting engine create. Remember the sub path that I explained would tell the engine where to create certain folders? Well, this is where it comes in.

As an example, if you created a hosting engine with a sub path of “CodeProject\MyApp”, you’d want to head for a folder named “C:\Documents and Settings\<YourCurrentUsername>\Local Settings\Application Data\CodeProject\MyApp”. Inside that folder resides the log file folder where all of the log files are stored, and some other configuration files. The configuration files are encrypted, it’s not all that secure, but I’m not going to focus on it, I’ve simply hard coded some things as far as salt values and keys to make those encrypt and decrypt automatically for you. If you want to have them stored without encryption, simply close down the hosting engine, and then edit the hosting engine’s config file. There’s a setting, I believe it’s “NoEncrypt”, set that to false, and re-run the hosting engine. You’ll want to delete both the common and local user config files, so keep that in mind if you start screwing with things too much before you know how it all works.

There are two paths where configuration files and log files are stored. As I said earlier, it’s necessary to separate items that are specific to individual windows users, and items that are common to all users. Here’s the paths you need to learn.

  • “..\Documents and Settings\All Users\Application Data\<YourCompany>\<YourApp>” for the common files
  • “..\Documents and Settings\<CurrentUsername>\Local Settings\Application Data\<YourCompany>\<YourApp>” for the local user files

If you find all of this too complicated, just run my sample, and using the menu items under “Tools->Explore” open the folders from within the program. That’ll open those folders up for you so you don’t have to browse for them! Use the tools provided, lol, they are there to help make your life easier.

Troubleshooting and Features

As with any program, it’s only a matter of time before you or your users mess something up. Not if, simply when. :P With that in mind I decided that I would include a handy means displaying key features and things that might need special attention. Things like configuration files becoming corrupt, or toolbars being saved in a funky state, or windows that are off the desktop at startup. For this purpose I create a FeatureEngine and some simple interfaces to allow you to respond and display some custom features when the time calls for it.

If you hold down the “Control” key, either one left or right as the program loads, it’ll show the Features and Troubleshooting window automatically. By default you get some features for resetting the Main Window, usefull when and if it ever decides to hide itself off-screen. Not that it ever happens, because the code is sooo good, :P but there more for demo purposes. Also you can reset the configuration files, which will force all the plugins to reinstall their options and hopefully fix whatever problem you’re faced with. And finally you have a re-install feature for every SnapIn. Handy if you want to test out the Installation and Uninstallation events that a SnapIn can use. I’ll be covering those more when I discuss the configuration files so don’t sweat it too much now. Here’s what the feature window looks like.

Sample screenshot

I won’t get into how to work with the features just yet, from code I mean. That’s another bowl of soup to deal with once we hit configuration. But know that we have some other interesting things yet to show you!

Configuration Files and Options

Ok, and just a quick peek at the options and configuration topic. I am going to devote an entire article to the configuration topic. There’s a lot of juicy goodness to deal with in a short amount of time. By now you are probably going, shut up already I want to go run this.

Like I said earlier every application is going to have options that you want to store, and then allow them to be changed in some manner or another. I personally like how Visual Studio’s Options look and feel, so I recreated them. Here’s what the Options look like, this is how every Razor based app by default can change and view options.

Sample screenshot

The configuration files are loaded by the hosting engine and are accessible as instance properties from the SnapInHostingEngine class. There are two different ones, by default, but there could be more if you wanted to add them. Check out the WindowPositioningEngineSnapIn if you want a glimpse at reading and writing options. Or wait and read my configuration article when I finish it.

The options are stored using hierarchical categories, whose categories will be merged together when displayed. If you had two options, in separate configuration files, and they both happened to fall into the same path, say “General”, then you would see both options displayed under the “General” category. The users really don’t need to know where or how the options are stored, just how to get to them.

This was written way back, before anyone else ever posted any article on using the PropertyGrid, and I’m supremely confident you’ll like it. And yes there are other articles on how to make options using the property grid, so I admit I’m not the only one to be struck with this idea. I just think this way is worth checking out if you are interested in that type of thing, I really have put a ton of work into it. Hence the need for another article. Just a flame prevention statement incase someone goes, “oh I wrote something like that already, why is yours so special? :P Gotta egg you boys on you know? A little creative competition is what fuels the fires right?

Points of Interest

With each new program, you may find it necessary to hide functionality from the user, or create an entirely new UI, so all you really have to do is swap out your interface SnapIn for a new one, and you’re off to the races. One thing to note, is that you need to copy each SnapIn’s assembly into your hosting engine’s folder before it will be loaded by the hosting executable. By default there aren’t any plugins, and no interface, so if you tried to run the hosting engine without any plugins, you wouldn’t have anything. It’d just run and then die, or depending on some hidden options stay running with a bare message loop. Every app is different, and I can make no assumptions on what each and every future app will need for an interface, so there’s simply not any by default. You have to create one. I’ve given one to you, and in future articles I’ll come back and add to this one with a game of Tetris, written as a SnapIn. I’ve just gotta add scoring and a few last minute tweaks, oh and then there’s the small task of writing the article!

So dig in, run the hosting executable in the sample project, and check out some of the dialogs that handle viewing and controlling SnapIns and Options. And I’ll get busy working on the configuration article. That’s the next logical step I think, storing options and letting your users change them. In case you are wondering, I’ve developed a nifty means of doing just that too.

One last note, the Razor Framework class library is filled with numerous classes. So numerous that I cannot even begin to do it justice here in one article. Everything from command line parsing, icon extraction, threading, networking, and custom controls. There are some classes that I have based my work upon, from the CodeProject. I’ve given due notice to those guys that I based my code on. However, there may be classes that resemble other authors works. One that comes to mind is the CommandLineParsing engine, I know for a fact I based that class upon an article I saw here on the code project several years ago, but I’ve been unable to find the original article to give credit. Please don’t hate me for this, and if you know of or are that guy, I’m sure you’ll notice design similarities if you are and look at the code, please give me a shout so I can credit you! I spent the better part of an afternoon trying to find articles that I found ideas on which I based my code. The Code Project has been my home for several years, and I’ve gleamed so much knowledge from this place, I thank you all very much for every article that has inspired this effort. I do not believe that I would be where I am today if it were not from learning many of the great articles here on the code project, and scouring the source code of so many excellent developers. To you all thanks, and you have my respect more than you will ever know!

Thanks for reading, and if you enjoy the article, please give me some feedback and help me improve on this design! I’ll be posting more later, so enjoy this for now!

Revision History

  • February 2005 - Initial Release
    • 3/11/2005 - Fixed demo and source file downloads

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 States United States
Senior Application Developer specializing in Windows desktop and network development.

Professional Experience
- B.S. of Computer Science (Graduated 2001 - PSU)
- Senior Application Developer (8+ yrs)
- Microsoft Certified Professional

Primary Interests
- C#, C++, HTML, Javascript
- XML, ASP.NET, Web Services, SOAP, UDDI
- Socket programming and anything network related
- Reflection, Serialization, and Plugin Frameworks
- Owner-drawn controls and GDI+ goodness

Comments and Discussions

 
QuestionIs there a new version for download? and are you still using razor? Pin
mhnyborg6-Jan-08 11:03
mhnyborg6-Jan-08 11:03 
QuestionPlugins for Carbon? Pin
Rakesh B Singh14-Sep-07 19:39
Rakesh B Singh14-Sep-07 19:39 
GeneralCarbon Framework Pin
AndrewBex14-Feb-07 0:39
AndrewBex14-Feb-07 0:39 
GeneralRe: Carbon Framework Pin
CPfx3000se26-Feb-07 3:57
CPfx3000se26-Feb-07 3:57 
QuestionUsing Razor as basis for proprietary product Pin
mickeymicks12-Dec-06 22:39
mickeymicks12-Dec-06 22:39 
AnswerRe: Using Razor as basis for proprietary product Pin
Mark Belles13-Dec-06 3:51
Mark Belles13-Dec-06 3:51 
GeneralUnable to debug Pin
afinnell12-Nov-06 7:16
afinnell12-Nov-06 7:16 
GeneralLicensing Pin
EverettMuniz5-May-06 6:38
EverettMuniz5-May-06 6:38 
GeneralRe: Licensing Pin
Mark Belles5-Aug-06 14:29
Mark Belles5-Aug-06 14:29 
GeneralIDM.Net Configuration Pin
VlastaH29-Mar-06 11:44
VlastaH29-Mar-06 11:44 
Questionwhere is the ICSharpCode namespace ? Pin
zhuomiao.ma@bankofamerica.com2-Mar-06 11:10
zhuomiao.ma@bankofamerica.com2-Mar-06 11:10 
AnswerRe: where is the ICSharpCode namespace ? Pin
M i s t e r L i s t e r5-Apr-06 19:16
M i s t e r L i s t e r5-Apr-06 19:16 
QuestionAny more Snap In examples Pin
Baz27-Feb-06 3:04
Baz27-Feb-06 3:04 
AnswerRe: Any more Snap In examples Pin
Mark Belles27-Feb-06 4:41
Mark Belles27-Feb-06 4:41 
GeneralRe: Any more Snap In examples Pin
Baz27-Feb-06 21:26
Baz27-Feb-06 21:26 
GeneralDependent Assembly missing Pin
RogerRabbit7817-Oct-05 1:37
RogerRabbit7817-Oct-05 1:37 
General&quot;cross-referenced&quot; snapins Pin
cadessi30-Sep-05 0:04
cadessi30-Sep-05 0:04 
QuestionVB.Net version? Pin
camainc15-Sep-05 8:56
camainc15-Sep-05 8:56 
AnswerRe: VB.Net version? Pin
Matt Morrison6-Jun-06 12:57
Matt Morrison6-Jun-06 12:57 
GeneralRe: VB.Net version? Pin
Adil_Bangush28-Sep-07 0:23
Adil_Bangush28-Sep-07 0:23 
QuestionHow to add control localization to the framework Pin
Patrick Blackman4-Sep-05 8:56
professionalPatrick Blackman4-Sep-05 8:56 
GeneralMultiple instance Pin
udrescu_bogdan29-Aug-05 5:23
udrescu_bogdan29-Aug-05 5:23 
GeneralRe: Multiple instance Pin
Patrick Blackman4-Sep-05 9:03
professionalPatrick Blackman4-Sep-05 9:03 
GeneralCreating SnapIn controls Pin
Patrick Blackman28-Aug-05 19:40
professionalPatrick Blackman28-Aug-05 19:40 
GeneralPort to ASP.Net Pin
Adriano Ribeiro3-Jul-05 16:49
Adriano Ribeiro3-Jul-05 16:49 

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.