A Raiser’s Edge C# Plugin

A few days ago I put a question on both Blackbus and the official Blackbaud forums asking if anybody had written a plug-in in C#. I have written a number of customisations in C# (VB.NET is my most common programming language – although I do enjoy using C#) but I had never written a plug-in before. It has always been a web service of application of some kind. I managed to work out how to do it and thought that I would share my solution here.In VB.NET you simply create a COM Class item in a class library project. This automatically sets all the assembly settings so that the assembly is visible to COM. Once you have done that you have to implement the IBBPlugin interface fleshing out the name and description of your plug-in as well as some other methods that are called on loading and terminating etc. You build your assembly and either register it using regasm (see this post about using installing a plug-in) or create a setup project (see this setup project post). It is relatively straight forward (well it is now that I have done it several hundred times)

In C# there is no COM Class item to add. Instead you need to implement your own. This needs to consist of both an interface that COM will look at and the implementation. There are a number of attributes whose function I am none too clear about so it would be foolish for me to try to explain what it is they do exactly. Needless to say that for the class, for the product and for any events there needs to be a unique guid that COM reads in order to register the component properly.

Here is the code that I used:

//First the implementation. The guids are used in the interface too
[ComClass("10DCEEEE-5445-411C-A7C4-86A41A0D108A", "71F9B2B9-585F-4DC9-95E2-3BBEC0691F6F", "5F444D9C-DBCC-45C8-B1B6-C10815B5265E"), Guid("10DCEEEE-5445-411C-A7C4-86A41A0D108A"), ClassInterface(ClassInterfaceType.None)]
public class MyPlugin : _MyPlugin, IBBPlugIn
{
// Fields
private IBBSessionContext _session;

// Methods
public void OnClosedown()
{ this._session = null; }

public void OnInit(ref IBBShellHost oREHost)
{ this._session = oREHost.SessionContext; }

public void OnLoad(ref object oDoc)
{ this.StartPlugin(); }

public void OnQueryUnload(ref bool bCancel, bool bShellIsUnloading)
{
}

public string PluginDescription()
{ return "Runs my plug-in."; }

public string PluginName()
{ return "My Plug-in"; }

public void StartPlugin()
{ new MyForm().ShowDialog(); }

// Properties
public string DocumentName
{
get { return ""; }
}

public REShellDocumentTypes DocumentType
{
get { return REShellDocumentTypes.redocActiveXDocument; }
}

public string HeaderCaption
{
get { return "My Plug-in"; }
}

public string HeaderImage
{
get { return ""; }
}

}

//Interface
[Guid("71F9B2B9-585F-4DC9-95E2-3BBEC0691F6F"), ComVisible(true)]
public interface _MyPlugin
{
[DispId(1)]
string DocumentName { [DispId(1)] get; }
[DispId(2)]
REShellDocumentTypes DocumentType { [DispId(2)] get; }
[DispId(3)]
string HeaderCaption { [DispId(3)] get; }
[DispId(4)]
string HeaderImage { [DispId(4)] get; }
[DispId(5)]
void OnClosedown();
[DispId(6)]
void OnInit(ref IBBShellHost oREHost);
[DispId(7)]
void OnLoad(ref object oDoc);
[DispId(8)]
void OnQueryUnload(ref bool bCancel, bool bShellIsUnloading);
[DispId(9)]
string PluginDescription();
[DispId(10)]
string PluginName();
[DispId(11)]
void StartPlugin();
}

8 thoughts on “A Raiser’s Edge C# Plugin

  1. Thanks for this. So tell me, what C# project type did you use? Also, the section new MyForm().ShowDialog(); does this refer to a Windows form?

    Thanks

  2. This was a class library project. Yes the form bit refers to a windows form. It is possible to show an html page with your component on it but I think it is simpler and a better user experience if you just start a new windows form from the plugins page.

    David

  3. Hi David

    I’m getting ‘microsoft.Build.Tasks.Deployment.ManifestUtilities.ComClass is not an attribute class’ when I compile. did you get this?

    Thanks

  4. No, I did not get that. These are the references that I used:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Runtime.InteropServices;
    using Blackbaud.PIA.RE7.BBREAPI;
    using Microsoft.VisualBasic;

    Not sure if that helps

    David

  5. You should also generate new GUIDs for each plug-in you develop otherwise they will conflict with each other. Not that that is the problem in this case I imagine.

    David

  6. I am doing exactly the same but somehow the plugin doesn’t load.

    here is my code

    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    using Blackbaud.PIA.RE7.BBInterfaces;
    using Microsoft.VisualBasic;

    namespace Plugins.BlackBaud
    {
    [ComClass(“A632CE0C-F1BA-4E9D-8A87-3A79ACA60677”, “DBA7FFAF-6F89-412A-A538-5F95BB3ECD56”, “5F444D9C-DBCC-45C8-B1B6-C10815B5265E”), Guid(“10DCEEEE-5445-411C-A7C4-86A41A0D108A”), ClassInterface(ClassInterfaceType.None)]
    public class MyPlugin : IMyPlugin, IBBPlugIn
    {
    #region Constants

    private const string HEADER_CAPTION = “CCCLogic .NET 2.0 Demo Plugin”;
    private const string HTML_DOCUMENT_NAME = “WebAstraPluginPage.html”;
    private const string PLUGIN_DESCRIPTION = “Demonstrates a simple way to create an RE7 plugin using .NET 2.0 (Visual Studio 2008)”;
    private const string PLUGIN_NAME = “CCCCLogic .NET 2.0 Demo Plugin”;

    #endregion

    #region IBBPlugIn Members
    public void OnClosedown()
    {
    }

    public void OnInit(ref IBBShellHost oReHost)
    {
    EventLog.WriteEntry(“Plugin”, “>> OnInit”);

    if (Utilities.ReSessionContext == null)
    {
    Utilities.ReSessionContext = oReHost.SessionContext;
    }

    EventLog.WriteEntry(“Plugin”, “<< OnInit");
    }

    public void onLoad(ref object oDoc)
    {

    }

    public void OnQueryUnload(ref bool bCancel, bool bShellIsUnloading) { }

    public string PluginDescription()
    {
    return PLUGIN_DESCRIPTION;
    }

    public string PluginName()
    {
    return PLUGIN_NAME;
    }

    public void StartPlugin()
    {
    MessageBox.Show("StartPlugin");
    }

    [DispId(0x68030003)]
    public string DocumentName
    {
    get
    {
    ExtractPluginResources();
    return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Plugins\" + HTML_DOCUMENT_NAME);
    }
    }

    [DispId(0x68030002)]
    public REShellDocumentTypes DocumentType
    {
    get { return REShellDocumentTypes.redocHTMLPage; }
    }

    [DispId(0x68030001)]
    public string HeaderCaption
    {
    get { return HEADER_CAPTION; }
    }

    [DispId(0x68030000)]
    public string HeaderImage
    {
    get { return null; }
    }
    #endregion

    #region Private Methods

    private void ExtractPluginResources()
    {
    try
    {
    ExtractTextResource("REDotNetSamplePluginPage.html");
    }
    catch (Exception ex)
    {
    Trace.WriteLine(ex);
    }
    }

    private void ExtractTextResource(string sResource)
    {
    string sTarget = GetType().Namespace + "." + sResource;
    string sOutputFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Plugins\" + sResource);

    Assembly executingAssembly = Assembly.GetExecutingAssembly();
    Stream stream = executingAssembly.GetManifestResourceStream(sTarget);

    if (stream != null)
    {
    StreamReader oStrReader = new StreamReader(stream);
    StreamWriter oStrWriter = new StreamWriter(sOutputFile);
    oStrWriter.Write(oStrReader.ReadToEnd());
    oStrReader.Close();
    oStrWriter.Close();
    }
    }

    #endregion
    }
    }

    using System.Runtime.InteropServices;
    using Blackbaud.PIA.RE7.BBInterfaces;

    namespace Plugins.BlackBaud
    {
    [Guid("DBA7FFAF-6F89-412A-A538-5F95BB3ECD56"), ComVisible(true)]
    public interface IMyPlugin
    {
    [DispId(1)]
    string DocumentName
    {
    [DispId(1)]
    get;
    }

    [DispId(2)]
    REShellDocumentTypes DocumentType
    {
    [DispId(2)]
    get;
    }

    [DispId(3)]
    string HeaderCaption
    {
    [DispId(3)]
    get;
    }

    [DispId(4)]
    string HeaderImage
    {
    [DispId(4)]
    get;
    }

    [DispId(5)]
    void OnClosedown();

    [DispId(6)]
    void OnInit(ref IBBShellHost oReHost);

    [DispId(7)]
    void onLoad(ref object oDoc);

    [DispId(8)]
    void OnQueryUnload(ref bool bCancel, bool bShellIsUnloading);

    [DispId(9)]
    string PluginDescription();

    [DispId(10)]
    string PluginName();

    [DispId(11)]
    void StartPlugin();
    }
    }

Comments are closed.