Creating workspace extensions


Summary
A workspace extension extends the functionality of a geodatabase in a manner that applies to the whole database rather than individual datasets. For this reason, workspace extensions are used less often than class extensions as a way of extending geodatabase behavior.
This topic shows how to implement a workspace extension using Visual Studio and the .NET Framework. It explains the advantages and disadvantages associated with the different methods of deployment for a workspace extension and how to deploy an extension.
The required interfaces implemented by workspace extensions are discussed, as are event handlers, which can be used with workspace extensions. Code examples show a simple extension that implements the required interfaces and optional interface. The extension shown has minimal functionality, but provides a starting point for application specific functionality to be added.


Common uses for workspace extensions

Use workspace extensions to customize geodatabase behavior that applies to the entire geodatabase, rather than individual datasets. The following are examples of functionality that can be implemented with workspace extensions:
  • Hiding datasets from workspace users. This is a common requirement for application specific data dictionary tables that users should not have access to; use IWorkspaceExtension and IWorkspaceExtension2 for this.
  • Defining events that should occur when a user opens or disposes of a workspace; use IWorkspaceExtensionControl to define this behavior.
  • Handling dataset creation, deletion, and modification events. This is done by implementing the IWorkspaceEvents optional interface.
  • Handling workspace edit events, such as the start and stop of edit sessions and operations. This is done by implementing the IWorkspaceEditEvents optional interface.
  • Handling versioning events, such as reconciliation or the detection of conflicts. This is done by implementing the IVersionEvents and IVersionEvents2 optional interfaces.
  • Handling replication events, such as the beginning of replication and the completion of replication. This is done by implementing the IWorkspaceReplicaEvents optional interface.
  • User-defined interfaces can be implemented by a workspace extension. This is a useful place to implement behavior or store data that affects the geodatabase as a whole.
If the geodatabase is only being accessed by ArcGIS for Desktop clients, investigate implementing an editor extension as an alternative to the geodatabase extension, as it might be more appropriate for some of these tasks (that is, listening to edit events).

Geodatabase registration vs. component category registration

A workspace extension can be registered in two different ways, which results in significantly different behavior. Whether geodatabase registration or component category registration is appropriate depends on the purpose of the extension, and should be considered before the extension's design and implementation. Design an extension for geodatabase or component category registration (not both).
 
Registering a workspace extension with a geodatabase adds the name and globally unique identifier (GUID) of the extension to the GDB_Extensions geodatabase system table. When a client connects to the geodatabase, the extension will be initialized. If the client does not have the extension, it will be unable to use the geodatabase. The advantage of this method guarantees any client connecting to the geodatabase behaves in a similar manner. For example, if the workspace extension has logging functionality, the logs include every client that has used or is using the geodatabase. Only a single workspace extension can be registered with a geodatabase and only a geodatabase administrator can register an extension with a geodatabase. A significant caveat of using this type of extension is if it unexpectedly fails, the data can be unavailable from any client.
 
Using component category registration with a workspace extension adds the extension to the client's Esri Geodatabase Workspace Extensions component category. Rather than being associated with a specific geodatabase, the extension will be initialized and used whenever the client connects to any geodatabase, be it personal, file, or ArcSDE. The decision to use component category registration with a workspace extension should be carefully considered before implementation, as in most cases it is not required. One advantage that this type of registration offers is that a geodatabase can have multiple workspace extensions operating simultaneously.
The following table shows the advantages and disadvantages of geodatabase and component category registration:
 
Geodatabase registration
Component category registration
Advantages
  • Behavior is present for every client.
  • Multiple workspace extensions can operate simultaneously.
  • In the event of extension failure, data can be reached from another client.
  • Extensions can be registered without administrative privileges.
Disadvantages
  • Geodatabase and extensions have, at most, a 1:1 relationship.
  • In the event of extension failure, the data can be unreachable from all clients.
  • Administrative privileges are required to register an extension with the geodatabase.
  • Clients are not required to have the extension and might not implement the desired behavior.

Viewing a geodatabase's extensions

Use the IWorkspaceExtensionManager interface to obtain a list of a workspace's extensions. The following code example shows how to do this:
[C#]
public void ListWorkspaceExtensions(IWorkspace workspace)
{
   // Cast the workspace to the IWorkspaceExtensionManager and loop through 
    // its extensions.
   IWorkspaceExtensionManager extensionManager =  (IWorkspaceExtensionManager)workspace;
   for (int i = 0; i < extensionManager.ExtensionCount; i++)
   {
       IWorkspaceExtension extension = extensionManager.get_Extension(i);
       UID extensionUID = extension.GUID;
       Console.WriteLine("Name: {0} - UID: {1}", extension.Name, extensionUID.Value);
   }
}
[VB.NET]
Public Sub ListWorkspaceExtensions(ByVal workspace As IWorkspace)
  ' Cast the workspace to the IWorkspaceExtensionManager and loop through 
  ' its extensions.
  Dim extensionManager As IWorkspaceExtensionManager = CType(workspace, IWorkspaceExtensionManager)
  Dim i As Integer
  For i = 0 To extensionManager.ExtensionCount - 1
    Dim extension As IWorkspaceExtension = extensionManager.Extension(i)
    Dim extensionUID As UID = extension.GUID
    Console.WriteLine("Name: {0} - UID: {1}", extension.Name, extensionUID.Value)
  Next i
End Sub
Even with a new geodatabase, the preceding code example shows several workspace extensions, because it includes geodatabase registered and component category registered extensions (which includes the address locator extension, the representation class extension, and so on) in the list.

Geodatabase registration and unregistration

The easiest way to register an extension with a geodatabase is to create a small, focused application for the task. Registering a workspace extension with a geodatabase is straightforward and only has to be done once. Any client will thereafter be required to have the extension to work with the geodatabase.
To register an extension this way, connect to the geodatabase in the usual way (for more information, see Connecting to geodatabases and databases) and cast the provided IWorkspace reference to the IWorkspaceExtensionManager interface. This interface provides several properties and methods for extension registration and access. The following code example shows how to use this interface to register an extension:
[C#]
// For example, extensionName = "ConnectLog.ConnectLogWorkspaceExtension".
// extensionUID = "{d10a65c7-9257-4902-bd23-e666537bce01}".
public void RegisterWorkspaceExtension(IWorkspace workspace, String extensionName, String extensionUID)
{
   // Create a unique identifier (UID) object for the extension.
   UID uid = new UIDClass();
   uid.Value = extensionUID;

   // Cast the workspace to the IWorkspaceExtensionManager interface.
   IWorkspaceExtensionManager extensionManager = (IWorkspaceExtensionManager)workspace;

   // Try to register the extension.
   try
   {
       extensionManager.RegisterExtension(extensionName, uid);
   }
   catch (COMException comExc)
   {
       // TODO: Handle the exception in a way appropriate to your application.
   }
}
[VB.NET]
' For example, extensionName = "ConnectLog.ConnectLogWorkspaceExtension".
' extensionUID = "{d10a65c7-9257-4902-bd23-e666537bce01}".
Public Sub RegisterWorkspaceExtension(ByVal workspace As IWorkspace, ByVal extensionName As String, ByVal extensionUID As String)
  ' Create a unique identifier (UID) object for the extension.
  Dim uid As UID = New UIDClass()
  uid.Value = extensionUID

  ' Cast the workspace to the IWorkspaceExtensionManager interface.
  Dim extensionManager As IWorkspaceExtensionManager = CType(workspace, IWorkspaceExtensionManager)

  ' Try to register the extension.
  Try
    extensionManager.RegisterExtension(extensionName, uid)
  Catch comExc As COMException
    ' TODO: Handle the exception in a way appropriate to your application.
  End Try
End Sub
Attempting to register multiple extensions with a geodatabase, results in a Component Object Model (COM) exception.
If an extension is to be unregistered—because it is not needed or it fails and makes the geodatabase unusable—use a similar method. The following code example shows how to do this:
[C#]
// For example, extensionUID = "{d10a65c7-9257-4902-bd23-e666537bce01}".
public void UnregisterWorkspaceExtension(IWorkspace workspace, String extensionUID)
{
    // Create a UID object for the extension.
    UID uid = new UIDClass();
    uid.Value = extensionUID;

    // Cast the workspace to the IWorkspaceExtensionManager and unregister the extension.
    IWorkspaceExtensionManager extensionManager =  (IWorkspaceExtensionManager)workspace;   extensionManager.UnRegisterExtension(uid);
}
[VB.NET]
' For example, extensionUID = "{d10a65c7-9257-4902-bd23-e666537bce01}".
Public Sub UnregisterWorkspaceExtension(ByVal workspace As IWorkspace, ByVal extensionUID As String)
  ' Create a UID object for the extension.
  Dim uid As UID = New UIDClass()
  UID.Value = extensionUID

  ' Cast the workspace to the IWorkspaceExtensionManager and unregister the extension.
  Dim extensionManager As IWorkspaceExtensionManager = CType(workspace, IWorkspaceExtensionManager)
  ExtensionManager.UnRegisterExtension(uid)
End Sub

Component category registration and unregistration

Component category registration can be implemented by including COM registration and unregistration methods that call static methods from classes in the ArcGIS .NET Application Development Framework (ADF). For more information, see ESRI.ArcGIS.ADF.CATIDs Namespace.
The following code example shows these methods:
[C#]
[ComRegisterFunction()]
[ComVisible(false)]
static void RegisterFunction(Type registerType)
{
    string regKey = string.Format("HKEY_CLASSES_ROOT\\CLSID\\{{{0}}}", registerType.GUID);
    GeodatabaseWorkspaceExtensions.Register(regKey);
}

[ComUnregisterFunction()]
[ComVisible(false)]
static void UnregisterFunction(Type registerType)
{
    string regKey = string.Format("HKEY_CLASSES_ROOT\\CLSID\\{{{0}}}", registerType.GUID);
    GeodatabaseWorkspaceExtensions.Unregister(regKey);
}
[VB.NET]
<ComRegisterFunction(), ComVisible(False)> _
Shared Sub RegisterFunction(ByVal registerType As Type)
  Dim regKey As String = String.Format("HKEY_CLASSES_ROOT\CLSID\{{{0}}}", registerType.GUID)
  GeodatabaseWorkspaceExtensions.Register(regKey)
End Sub

<ComUnregisterFunction(), ComVisible(False)> _
Shared Sub UnregisterFunction(ByVal registerType As Type)
  Dim regKey As String = String.Format("HKEY_CLASSES_ROOT\CLSID\{{{0}}}", registerType.GUID)
  GeodatabaseWorkspaceExtensions.Unregister(regKey)
End Sub
With these methods added, the workspace extension can be registered like other custom COM components. For more information, see How to register COM components.

Adding the required attributes

The following attributes are required when creating a workspace extension:
The following code example shows the preceding attributes:
[C#]
[Guid("d10a65c7-9257-4902-bd23-e666537bce01")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("ConnectLog.ConnectLogWorkspaceExtension")]
[ComVisible(true)]
public class ConnectLogWorkspaceExtension : IWorkspaceExtension2, IWorkspaceExtensionControl
{
    // Class members here...
}
[VB.NET]
<Guid("d10a65c7-9257-4902-bd23-e666537bce01"), _
  ClassInterface(ClassInterfaceType.None), _
  ProgId("ConnectLog.ConnectLogWorkspaceExtension"), _
  ComVisible(True)> _
Public Class ConnectLogWorkspaceExtension
  Implements IWorkspaceExtension2, IWorkspaceExtensionControl

  ' Class members here...

End Class
The GUID is a randomly generated unique value. The class interface attribute defines whether an interface is generated for the workspace extension class; pass the ClassInterfaceType.None value to the attribute (see the preceding code example). The ProgID attribute's value is a qualified name of the workspace extension.

Implementing required and optional interfaces

At a minimum, workspace extensions need to implement two interfaces—either IWorkspaceExtension or IWorkspaceExtension2, and IWorkspaceExtensionControl. IWorkspaceExtension and IWorkspaceExtension2 (and children) interfaces have properties for the extension's name and GUID, as well as properties for hiding data dictionary and private tables from users. The IWorkspaceExtensionControl interface provides methods for extension initialization and shutting down.
Implement IWorkspaceExtension2 rather than IWorkspaceExtension. The IWorkspaceExtension2 interface has a Workspace property that allows objects referencing the extension to easily find the workspace. Although this is not required for all extensions, it can be a convenience for those that do.
In addition to these interfaces, the following optional interfaces can also be implemented:
  • IWorkspaceEvents
  • IWorkspaceEvents2 
  • IWorkspaceEditEvents
  • IWorkspaceEditEvents2
  • IVersionEvents
  • IVersionEvents2
  • IWorkspaceReplicaEvents
  • IWorkspaceReplicaSyncEvents
Finally, new interfaces can be created and implemented by an extension as a way of caching geodatabase wide behavior and data with new methods and properties. Although Esri clients (such as ArcCatalog) will not have access to these members, custom clients that know about the new interface or interfaces will.
It is important to remember that a workspace extension can be called by any geodatabase client, including ArcMap, ArcCatalog, and custom clients. Because of this, an extension should never be responsible for output, as both graphical user interface (GUI) output (that is, in the form of message boxes) or console output is inappropriate for some applications.

IWorkspaceExtensionControl

IWorkspaceExtensionControl is the first interface implemented when creating a workspace extension (has two methods Init and Shutdown). Init is called when the workspace extension is initialized, and has an IWorkspaceHelper parameter. From a workspace helper, the source workspace can be accessed. In most workspace extension implementations, it will be necessary to store a reference to the workspace helper in a member variable. The following code example shows how to do this:
[C#]
private IWorkspaceHelper workspaceHelper = null;

public void Init(IWorkspaceHelper workspaceHelper)
{
    // Set the private workspace helper field to the parameter.
    this.workspaceHelper = workspaceHelper;
}
[VB.NET]
Private workspaceHelper As IWorkspaceHelper = Nothing

Public Sub Init(ByVal workspaceHelper As IWorkspaceHelper) Implements IWorkspaceExtensionControl.Init
  ' Set the private workspace helper field to the parameter.
  Me.workspaceHelper = workspaceHelper
End Sub
Do not store a reference to the workspace in a member variable—rather than the helper—as it creates a circular reference and can cause problems (that is, the Shutdown method might not be called). Rather than storing the workspace in a member variable, derive it from the workspace helper when needed.
It is important to note that a workspace extension is not necessarily initialized when a connection to the workspace is made. Specific actions, such as requesting a list of datasets from the workspace, starting an edit session, or opening a dataset triggers the initialization. With a client such as ArcCatalog, this is not an issue, since ArcCatalog always requests a list of datasets following a connection. If an extension's functionality is dependant on it always being initialized, and custom clients are being used, test this carefully to ensure the clients are triggering the initialization.
The Shutdown method is also required, but with garbage collection being native to the .NET Framework, it is much less important than it is when creating a workspace extension outside the .NET Framework. The following is a simple code example for Shutdown:
[C#]
public void Shutdown()
{
    workspaceHelper = null;
}
[VB.NET]
Public Sub Shutdown() Implements IWorkspaceExtensionControl.Shutdown
  workspaceHelper = Nothing
End Sub
As with the Init method, the timing of when (and if at all) the Shutdown method is called must be considered. The Shutdown method is called by a workspace when it is being disposed of. If this method contains crucial functionality, any workspaces using the extension should be explicitly released using the Marshal.ReleaseComObject method, rather than leaving the garbage collection to the .NET runtime environment; this ensures Shutdown is called.

IWorkspaceExtension and IWorkspaceExtension2

A workspace extension is required to implement IWorkspaceExtension or IWorkspaceExtension2. There are six properties these interfaces specify, the details of which can be found by clicking the interfaces' links. The first two properties to implement are Name and GUID. The following code example shows this:
[C#]
public String Name
{
    get
    {
        return "Connect Log Workspace Extension";
    }
}

public UID GUID
{
    get
    {
        UID uid = new UIDClass();
        uid.Value = "{d10a65c7-9257-4902-bd23-e666537bce01}";
        return uid;
    }
}
[VB.NET]
Public ReadOnly Property Name() As String Implements IWorkspaceExtension.Name, IWorkspaceExtension2.Name
 Get
   Return "Connect Log Workspace Extension"
 End Get
End Property

Public ReadOnly Property GUID() As UID Implements IWorkspaceExtension.GUID, IWorkspaceExtension2.GUID
 Get
   Dim uid As UID = New UIDClass()
   uid.Value = "{d10a65c7-9257-4902-bd23-e666537bce01}"
   Return uid
 End Get
End Property
The DataDictionaryTableNames and PrivateDatasetNames properties are similar in purpose but implemented different. See the following:
  • DataDictionaryTableNames returns an enumerator of table names that are used by the workspace extension to store application specific data. If none exists, null can be returned. 
  • PrivateDatasetNames returns an enumerator of feature datasets, feature classes, relationship classes, and other datasets that are used internally by the workspace and should not be exposed to the user.
The return type of these properties is IEnumBSTR. ArcObjects libraries do not provide a cocreatable class that implements this interface, leaving it the developer's responsibility to do so. The interface defines two methods, Next and Reset. A simple implementation could be little more than a wrapper around a generic list of strings and an enumerator. The following code example uses a cocreatable class, EnumBSTR, with a constructor that accepts an array of strings as a parameter. The following code example also shows how to define a table named "COUNTRY" as a data dictionary table:
[C#]
public IEnumBSTR DataDictionaryTableNames
{
    get
    {
        String[] hiddenTables = { "COUNTRY" };
        IEnumBSTR enumBSTR = new EnumBSTR(hiddenTables);
        return enumBSTR;
    }
}
[VB.NET]
Public ReadOnly Property DataDictionaryTableNames() As IEnumBSTR Implements IWorkspaceExtension.DataDictionaryTableNames, IWorkspaceExtension2.DataDictionaryTableNames
  Get
    Dim hiddenTables() As String = New String() {"COUNTRY"}
    Dim enumBSTR As IEnumBSTR = New EnumBSTR(hiddenTables)
    Return enumBSTR
  End Get
End Property
If a geodatabase does not contain the table or tables specified as data dictionary tables, no exception is thrown. It is extremely important, however, not to request dataset names from the workspace to dynamically construct a list of data dictionary tables, as doing so adversely affects the performance of the geodatabase and its clients. If a dynamic list of data dictionary tables is required, a table containing such a list (marked as a data dictionary table) can be used, and a custom interface can be defined on the workspace extension for table access and modification.
The IWorkspaceExtension2 interface has two additional properties, OwnsDatasetType and Workspace. OwnsDatasetType accepts an esriDatasetType value as a parameter and returns a Boolean indicating whether the extension "owns" the dataset. An example of a dataset type owned by a workspace extension is the Feature Class Representation extension, which owns representation classes. For most workspace extensions, returning a false value from this property is appropriate. The Workspace property should return a reference to the extension's workspace. If a private field has been created for the workspace helper (for more information, see IWorkspaceExtensionControl in this topic), this can be implemented easily, as the following code example shows:
[C#]
public IWorkspace Workspace
{
    get
    {
        return workspaceHelper.Workspace;
    }
}
[VB.NET]
Public ReadOnly Property Workspace() As IWorkspace Implements IWorkspaceExtension2.Workspace
  Get
    Return workspaceHelper.Workspace
  End Get
End Property

Optional interfaces

IWorkspaceEvents and IWorkspaceEvents2 are optional interfaces that can be implemented by workspace extensions for notification of dataset creation, deletion, renaming and modification. IWorkspaceEditEvents and IWorkspaceEditEvents2 are used to handle notification related to edit sessions. IVersionEvents and IVersionEvents2 can be implemented if a workspace extension should be notified of versioning events, such as reconciliation and conflict detection. IWorkspaceReplicaEvents and IWorkspaceReplicaSyncEvents make it possible to perform custom workflows during replication creation and synchronization, respectively.
A workspace extension is not required to implement any of these, but the following explains how to implement the IWorkspaceEvents interface for the purpose of logging.
The first step in the process is creating the methods called by the workspace. The following code example shows a simple example of the interface's three methods, with a private field for storing a log file name:
[C#]
private const String logFile = @"C:\Temp\IWorkspaceEventsLog.txt";

public void OnCreateDataset(IDataset dataset)
{
    String createMessage = "Dataset created: {0} At: {1}\n";
    String output = String.Format(createMessage, dataset.Name, DateTime.Now);
    File.AppendAllText(logFile, output);
}

public void OnDeleteDataset(IDataset dataset)
{
    String deleteMessage = "Dataset deleted: {0} At: {1}\n";
    String output = String.Format(deleteMessage, dataset.Name, DateTime.Now);
    File.AppendAllText(logFile, output);
}

public void OnRenameDataset(IDataset dataset, String oldName, String newName)
{
    String renameMessage = "Dataset renamed from: {0} to: {1} At: {2}\n";
    String output = String.Format(renameMessage, oldName, newName, DateTime.Now);
    File.AppendAllText(logFile, output);
}
[VB.NET]
Private Const logFile As String = "C:\Temp\IWorkspaceEventsLog.txt"

Public Sub OnCreateDataset(ByVal dataset As IDataset) Implements IWorkspaceEvents.OnCreateDataset
  Dim createMessage As String = "Dataset created: {0} At: {1}{2}"
  Dim output As String = String.Format(createMessage, dataset.Name, DateTime.Now, vbCrLf)
  File.AppendAllText(logFile, output)
End Sub

Public Sub OnDeleteDataset(ByVal dataset As IDataset) Implements IWorkspaceEvents.OnDeleteDataset
  Dim deleteMessage As String = "Dataset deleted: {0} At: {1}{2}"
  Dim output As String = String.Format(deleteMessage, dataset.Name, DateTime.Now, vbCrLf)
  File.AppendAllText(logFile, output)
End Sub

Public Sub OnRenameDataset(ByVal dataset As IDataset, ByVal oldName As String, ByVal newName As String) Implements IWorkspaceEvents.OnRenameDataset
  Dim renameMessage As String = "Dataset renamed from: {0} to: {1} At: {2}{3}"
  Dim output As String = String.Format(renameMessage, oldName, newName, DateTime.Now, vbCrLf)
  File.AppendAllText(logFile, output)
End Sub
With the methods created, add IWorkspaceEvents to the list of interfaces implemented by the extension.
Event handling with a workspace extension is slightly different from other event handlers, such as custom event listeners. Rather than explicitly "wiring" event handling methods to the source of the events, workspace extensions automatically receive events by implementing their optional interfaces, such as IVersionEvents. Custom objects can also define event handlers for this interface. For more information, see Listening to versioned events.


See Also:

How to register COM components
Registering classes in COM component categories
Listening to versioned events
Using replica creation events to extend the replica creation process
Connecting to geodatabases and databases




To use the code in this topic, reference the following assemblies in your Visual Studio project. In the code files, you will need using (C#) or Imports (VB .NET) directives for the corresponding namespaces (given in parenthesis below if different from the assembly name):
Development licensing Deployment licensing
ArcGIS for Desktop Basic ArcGIS for Desktop Basic
ArcGIS for Desktop Standard ArcGIS for Desktop Standard
ArcGIS for Desktop Advanced ArcGIS for Desktop Advanced