Close

Information Risk Management: 

Microsoft Office 365, Microsoft Teams, Microsoft SharePoint Server, Microsoft SharePoint Online, Microsoft Exchange Online, Azure DevOps & App Services, Azure Machine Learning

Unregistering Custom Record Declaration Handlers

John Holliday

September 14, 2011

Table of Content

As you know, using the SharePoint 2010 server object model, you can install your own custom record declaration handlers.  Didn't know that?  Oh?  You mean you haven't already setup your site to keep track of every time a record is declared or undeclared?  Well, you're in for a treat!  But you'll have a wait a bit, 'cause I'm busy building demos for my upcoming sessions at the Great SharePoint Conference of 2011.  After that, I'll be posting a series of articles that will go into custom record declaration handlers in greater detail.

In the meantime, I wanted to alert you to a bug in the SharePoint OM that I found along the way that might be stumping some of the more adventurous among you.

THE PROBLEM

In a nutshell - there is no way to remove a custom record declaration handler for a site collection via the prescribed methods available in the SharePoint object model.

For instance, if you create a site-scoped feature that implements a custom record declaration handler and then registers it in your FeatureActivated event receiver by calling RegisterCustomCodeForRecordDeclaration(), you cannot then unregister your handler when the feature is deactivated by calling a corresponding Unregister method.  Why?  Because that method doesn't exist.  The way I believe it was supposed to work was that you'd call the same method again from your FeatureDeactivating override, but passing empty strings for the assemblyName and className parameters.

The problem is that this method has a bug that prevents it from properly unregistering your code.  The end result is that your custom declaration handler method still continues to be called even after your feature has been deactivated, potentially causing ALL future attempts to declare or undeclare records in the site collection to fail.

As an example, consider the following code:

using Microsoft.Office.RecordsManagement.RecordsRepository;
using Microsoft.SharePoint;

namespace SharePointArchitects.RecordsManagement
{
    public class MyDeclarationHandler : IRecordDeclarationHandler
    {
        public static void Register(SPSite site)
        {
            Records.RegisterCustomCodeForRecordDeclaration(site,
                typeof(MyDeclarationHandler).Assembly.FullName,
                typeof(MyDeclarationHandler).FullName);
        }

        // Called whenever a record is declared.
        public RecordOperationResult OnDeclare(SPListItem item)
        {
            RecordOperationResult result 
                = RecordOperationResult.ContinueRecordProcessing;
            // ... custom code ...
            return result;
        }
    }
}

 

Here we have a typical implementation for a custom record declaration handler that exposes a static method to perform the registration of the class into a specified site collection by calling the RegisterCustomCodeForRecordDeclaration method.  As you can see, it passes the SPSite object along with the assembly and class name.

Since there is no “UnregisterCustomCodeForRecordDeclaration” method provided by the SDK, you might assume (wrongly) that in order to remove the registration of your class, you would simply call the same registration method again with empty strings or null values for the assembly name and class name.  Seems reasonable, right?  Not so fast.

Here is what happens:

  • Passing null for the ‘site’ parameter throws an ArgumentNullException because custom record declaration handlers are always installed at the site collection level.   Therefore, it makes sense that the SPSite parameter would be required.
  • Passing empty strings for either the assembly or class name causes ALL subsequent attempts to declare or undeclare records anywhere within the site collection to fail.

You heard me.

Users will now open the “compliance details” dialog and see the familiar “Declare as record” link.  They will click it and be prompted to confirm the declaration, and then nothing will happen.  They will do this repeatedly until they get tired, and then someone will call you.  They won’t be happy.

So what to do?  Digging a little deeper into the code reveals a minor bug in the RegisterCustomCodeForDeclaration() method.

THE BUG

An internal method is called during the record declaration process to retrieve the assembly and class name of any record declaration handler that may (or may not) be associated with the site collection.  It correctly checks whether the root web property bag contains an entry for the property "ecm_SiteCustomRecordsClass".  It then checks whether that value is null or empty.  If not, then it attempts to instantiate the declaration handler class specified.  If it’s empty, then it goes along its merry way declaring the document as a record.
However, there is a bug in the "RegisterCustomCodeForRecordDeclaration" method.  It ALWAYS assigns the "ecm_SiteCustomRecordsClass" property to a non-null value, even if the assemblyName and className parameters retrieved from the property bag are empty.  This is because it inserts a '|' delimiter between the assemblyName and className property values.  Thus, if you pass empty strings for the assembly and class name parameters, the property is set to the value "|", resulting in an exception when the GetClassForCustomRecordsCode method tries to instantiate the class.  This in turn causes all subsequent attempts to declare or undeclare records in the site to fail.

THE WORK-AROUND

Until there is a fix provided by Microsoft, the only work-around is to manually remove the “ecm_SiteCustomRecordsClass” property from the root web of the site collection using the hard-coded property name as shown below.

[ Note that this code may break if the property name ever changes. ]

public static void Unregister(SPSite site)
{
    // NOTE: There is no 'unregister' API call.

    // The only way to unregister the handler is to clear the
    // property "ecm_SiteCustomRecordsClass" in the root web
    // of the site.
    const string key = "ecm_SiteCustomRecordsClass";
    if (site.RootWeb.Properties.ContainsKey(key))
    {
        site.RootWeb.Properties[key] = "";
        site.RootWeb.Properties.Update();
    }
}

With these routines in place, you can easily register and unregister your custom record declaration handler from the Feature receiver, and all is well in SharePoint land.

using System;
using System.Runtime.InteropServices;
using Microsoft.SharePoint;

namespace SharePointArchitects.RecordsManagement
{
    [Guid("2a4480d1-a985-45ea-a297-47c5461d79c3")]
    public class RecordTrackerEventReceiver : SPFeatureReceiver
    {
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPSite site = properties.Feature.Parent as SPSite;
            MyDeclarationHandler.Register(site);
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPSite site = properties.Feature.Parent as SPSite;
            MyDeclarationHandler.Unregister(site);
        }
    }
}
Share on facebook
Share on twitter
Share on linkedin
Share on email
Share on print