ProGuide License Your Add in - Esri/arcgis-pro-sdk GitHub Wiki

This guide demonstrates how 3rd parties can integrate their licensing schemes into an ArcGIS Pro add-in. The code used to illustrate this add-in can be found in the Licensing Sample.

Language:      C#
Subject:       Framework
Contributor:   ArcGIS Pro SDK Team <[email protected]>
Organization:  Esri, http://www.esri.com
Date:          10/06/2024
ArcGIS Pro:    3.4
Visual Studio: 2022

Background

At 10.x, add-ins implemented the IExtensionConfig interface to create a "configurable extension". A configurable extension in 10.x is listed on the ArcMap Extensions dialog box where users can toggle its enabled state on or off. Configurable extensions can execute their own proprietary licensing logic to determine their enabled state within their respective IExtensionConfig implementations.

In ArcGIS Pro, the configurable extension mechanism is likewise supported. Add-ins that implement the configurable extension pattern in Pro are shown on the licensing tab on the ArcGIS Pro application backstage as an External Extension.

Licensing_1_Backstage.png

When a user attempts to enable a configurable extension from backstage, the 3rd party developer can execute custom licensing code via IExtensionConfig (same as at 10x) to determine whether or not the enabling action is authorized.

Licensing_2_Enabled.png

This ProGuide steps you through the process of implementing a configurable extension to execute custom licensing logic and integrate your add-in onto the Pro backstage licensing tab.

Prerequisite

Create a new add-in. Add a button control to the add-in. You can follow the steps in the ProGuide Build Your First Add-in.

Step 1

Modify the Config.daml file. Add an <extensionConfig/> element to the Module daml definition. The attributes of extensionConfig are used to populate the entry for your custom add-in in the list of External Extensions.

Set the attributes of your extensionConfig element as follows:

<modules>
    <insertModule id="Licensing_Module" className="Module1" autoLoad="false" caption="Module1">
      <extensionConfig description="Acme IExtensionConfig Add-in Sample" 
                       productName="Acme Add-in" message="Acme Add-in message" 
                       hasExtendedInfo="false"/>
      
...

Note that the hasExtendedInfo attribute is false: hasExtendedInfo="false". Open Pro backstage, Licensing tab and you should see an entry for your "licensed" addin similar to this. The Enabled checkbox will be disabled and fixed to ExtensionState.Enabled.

IExtensionConfig

In the following steps, we will add logic to our addin to determine the module enabled/disabled state based on whether it is correctly authorized and on the application state. The state will be based off our module implementation of the ArcGIS.Desktop.Framework.Contracts.IExtensionConfig interface and supporting logic.

Step 2

In this step, you'll create a corresponding condition in your module. Pro uses conditions to control the enabled state and/or visibility of the UI. In this case, our condition will be based on whether or not our add-in is correctly authorized and if a 2D map is enabled. We will the custom condition to propagate our addin enabled/disabled state to our addin UI. Conditions are described in detail in the Pro SDK ProConcepts for Framework and ProGuide Code Your Own States and Conditions.

<AddInInfo ...>
   ....
 </AddInInfo>

  <conditions>
    <insertCondition id="acme_check_licensing">
      <and>
        <state id="acme_module_licensed"/>
        <state id="esri_mapping_mapViewingMode2DState"/>
      </and>
    </insertCondition>
  </conditions>
  
  <modules>
    <insertModule ...>

Note that the COTS esri_mapping_mapViewingMode2DState state is added as part of an "and" clause. This ensures that the custom condition is only enabled if the add-in is activated (via "acme_module_licensed" state activation) and when a 2D map is active. A complete list of the Pro states and conditions for every public extension is available here.

Step 3

Add a button to your add-in. Call the button SelectFeatures. Change the button caption to Select Features. Add a condition attribute to the button DAML declaration. Set its value to acme_check_licensing (which is the ID of the condition defined in Step 2).

 <controls>
    <!-- add your controls here -->
    <button id="Licensing_SelectFeatures" caption="Select Features" 
      className="SelectFeatures" loadOnClick="true" 
      smallImage="Images\GenericButtonBlue16.png" largeImage="Images\GenericButtonBlue32.png"
      condition="acme_check_licensing">
      <tooltip heading="Select Features">Select all the features in the visible extent<disabledText /></tooltip>
    </button>
 </controls>

Step 4

Paste this code into the OnClick handler of your button. It will select all the features of all visible layers in the current 2D map extent.

   protected override void OnClick() {
       QueuedTask.Run(() => MapView.Active.SelectFeatures(MapView.Active.Extent));
   }

Step 5

Your add-in will require that a valid product ID be entered to authorize (enable) your add-in. For this example, you'll define a valid product ID as any number divisible by 2. In practice, you'll implement your own proprietary licensing logic as needed. Once a valid ID has been entered, it will be cached for the duration of the ArcGIS Pro session.

First, modify the Module class file by adding static member variables that will cache the product ID and add-in extension state as follows:

internal class Module1 : Module {
       
        private static string _authorizationID = "";
        private static ExtensionState _extensionState = ExtensionState.Disabled;

Add a static property to the Module class to expose the Authorization ID to the rest of the addin.

internal static string AuthorizationID {
   get
   {
      return _authorizationID;
   }
   set
   {
       _authorizationID = value;
   }
 }

Add the following method to your Module to evaluate a given product/authorization ID.

  /// <summary>
 /// Execute your authorization check
 /// </summary>
 /// <param name="id"></param>
 /// <returns></returns>
 internal static bool CheckLicensing(string id) {
     int val = 0;
     if (string.IsNullOrEmpty(id) || !Int32.TryParse(id, out val)) {
        val = -1; //bogus
     }
     //Any number divisible by 2
     if (val % 2 == 0) {
        FrameworkApplication.State.Activate("acme_module_licensed");
        _extensionState = ExtensionState.Enabled;
     }
     else {
        FrameworkApplication.State.Deactivate("acme_module_licensed");
        _extensionState = ExtensionState.Disabled;
     }
     return val % 2 == 0;
 }

Notice how, for any product/authorization ID evenly divisible by 2, the Module is enabled (_extensionState = ExtensionState.Enabled). Also notice how the custom state ("acme_module_licensed") that you defined for the "acme_check_licensing" condition in Step 2 is activated to propagate the enabled state to your custom Add-in's UI. FrameworkApplication.State.Activate("acme_module_licensed").

Call CheckLicensing(...) from your Module class constructor to initialize the extension state (based on whatever value you do or dont initialize _authorizationID to):

internal Module1() {
  //TODO - read authorization id from....
  //file, url, etc. as required

  //preset _authorizationID to a number "string" divisible by 2 to have 
  //the Add-in initially enabled
  CheckLicensing(_authorizationID);
}

Step 6

Now you'll add a ProWindow to your add-in to allow users to enter a product/authorization ID to enable the add-in. Name the window RegistrationWindow.xaml.

Licensing_5_Window

When you generate the Pro Window, the template will also add a button into your Config.daml to show it (i.e. to show the Pro window). Delete this button and any button reference from your Config.daml. You can also delete the code behind file generated for the button as well (but it is not required).

 <!-- delete any button reference for the button generated to show the Pro Window -->
 <group id="...." caption="Acme Addin">
    <button refID="Licensing_SelectFeatures" size="large" />
    <!-- delete this reference -->
    <button refID="Licensing_RegistrationWindow" size="large" />
 </group>
</groups>
<controls>
  <!-- add your controls here -->
  ...
  <!-- delete this -->
  <button id="Licensing_RegistrationWindow" caption="RegistrationWindow" className="... >
  </button>
</controls>

Double-click RegistrationWindow.xaml (or whatever you named the Pro window) to open the designer. Click the XAML tab. Copy and paste the following XAML for the <Grid>...</Grid> element.

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>

    <TextBlock Text="Please enter your Product ID:" Grid.Row="0" Grid.Column="0" 
               Margin="10,10,0,0" Style="{DynamicResource H5TextBlock}"></TextBlock>
        <TextBox x:Name="ProductId" HorizontalAlignment="Stretch" Margin="10,10,10,0" Grid.Row="0" Grid.Column="1"
                 Text="{Binding Path=AuthorizationID, Mode=TwoWay}"></TextBox>
        <TextBlock Text="(To enable the Add-in provide any number divisible by 2)" Grid.Row="1" Grid.Column="0" 
                   Grid.ColumnSpan="2" HorizontalAlignment="Center" Margin="0,10,0,0" 
                   Style="{DynamicResource H5TextBlock}"></TextBlock>
        <Button Content="Authorize" Width="100" Height="25" HorizontalAlignment="Center" VerticalAlignment="Bottom"
                Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Margin="0,0,0,20"
                Command="{Binding Path=AuthorizeCommand}" 
                Style="{DynamicResource Esri_Button}"></Button> 
    </Grid>

Change the default Pro window title, height, and width on the <controls:ProWindow ...> element to be Title="Acme Add-in Authorization" Height="150" Width="400":

<!--Before -->
<controls:ProWindow x:Class="...
 ...
 Title="RegistrationWindow" Height="300" Width="300"
        WindowStartupLocation="CenterOwner">

<!-- After -->
<controls:ProWindow x:Class="...
 ...
 Title="Acme Add-in Authorization" Height="150" Width="400"
        WindowStartupLocation="CenterOwner">

Double-click the RegistrationWindow.xaml.cs code behind file. Copy and paste the following code (changing the class name if your code behind file has a different name):

...
using ArcGIS.Desktop.Framework;
using System.ComponentModel;
using System.Runtime.CompilerServices;
...

    /// <summary>
    /// Interaction logic for RegistrationWindow.xaml
    /// </summary>
    public partial class RegistrationWindow : ArcGIS.Desktop.Framework.Controls.ProWindow, INotifyPropertyChanged {

        private ICommand _authorizeCommand = null;
        public RegistrationWindow() {
            InitializeComponent();
            (this.Content as FrameworkElement).DataContext = this;
        }

        /// <summary>
        /// The current Authorization ID being used to authorize the add-in
        /// </summary>
        public string AuthorizationID {
            get{
                return Module1.AuthorizationID;
            }
            set {
                Module1.AuthorizationID = value;
                OnPropertyChanged();
            }
        }

        /// <summary>
        /// Execute the Authorization logic
        /// </summary>
        public ICommand AuthorizeCommand {
            get {
                return _authorizeCommand ?? (_authorizeCommand = new RelayCommand(() => {
                    if (Module1.CheckLicensing(Module1.AuthorizationID)) {
                        MessageBox.Show("Add-in authorized. Thank you", "Success!",
                            MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                    else {
                        MessageBox.Show("Invalid Product ID", "Authorization Failed",
                            MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                    this.Close();
                }));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        protected virtual void OnPropertyChanged([CallerMemberName] string propName = "") {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }

Step 7

Implement the IExtensionConfig interface on your module class. This allows us to programmatically control our Add-in-enabled state.

First, change the hasExtendedInfo attribute value in the Config.daml to true. This tells the Framework that our module will be providing an implementation of IExtensionConfig.

<insertModule id="... 
   <extensionConfig description="Acme IExtensionConfig Add-in Sample" 
     productName="Acme Add-in" message="Acme Add-in message" 
     hasExtendedInfo="true" />

Next, add the IExtensionConfig interface to your Module class declaration as follows:

...
using ArcGIS.Desktop.Framework.Contracts;
...

internal class Module1 : Module, IExtensionConfig {
    ...
    private static string _authorizationID = "";
    private static ExtensionState _extensionState = ExtensionState.Disabled;
    ...
}

Use Visual Studio to generate the interface implementation stubs.

IExtensionConfig

Replace the Visual Studio generated stubs for the IExtensionConfig interface with the following code:

        /// <summary>
        /// Implement to override the extensionConfig in the DAML
        /// </summary>
        public string Message {
            get { return "";}
            set{  }
        }

        /// <summary>
        /// Implement to override the extensionConfig in the DAML
        /// </summary>
        public string ProductName {
            get { return ""; }
            set { }
        }

        /// <summary>
        /// Handle enable/disable request from the UI
        /// </summary>
        public ExtensionState State
        {
            get {
                return _extensionState;
            }
            set {
                if (value == ExtensionState.Unavailable) {
                    return; //Leave the state Unavailable
                }
                else if (value == ExtensionState.Disabled) {
                    _extensionState = value;
                }
                else {
                    //check if we allow Enabling of our Add-in
                    if (!CheckLicensing(_authorizationID)) {
                        var regWindow = new RegistrationWindow();
                        regWindow.ShowDialog();
                    }
                }
            }
        }

In the IExtensionConfig public ExtensionState State property setter, if the user attempts to enable the extension, you open your RegistrationWindow dialog box if the user still needs to enter a valid product ID.

Within the IExtensionConfig State property, Add-in extension state can be set to one of three values:

  • Enabled: Developers should enable underlying add-in functionality.
  • Disabled: Developers should disable add-in functionality.
  • Unavailable: The extension is disabled and cannot be enabled via backstage. Developers should disable add-in functionality for the entire Pro session (that is, a restart should be required to clear an Unavailable state).

The add-in developer determines how the corresponding state is propagated within the add-in. The recommended method is to use a condition (see Step 2). Also notice the FrameworkApplication.State.Activate("acme_module_licensed") ... Deactivate("acme_module_licensed") statements in the implementation of the CheckLicensing(...) method from step 5.

Notice too that empty strings are being returned for the ProductName and Message properties. This means their value will default to whatever was defined in the corresponding productName and message attributes on the <extensionConfig ... element in the Config.daml. Return a non-empty string to override the DAML values. The ProductName and Message properties are refreshed by backstage every time the enabled state of the add-in (via IExtensionState.State) is queried (for example, in response to the Enabled check box being clicked). This means an add-in can dynamically update its message or product name text in its External Extension list entry.

Licensing_6_Message

Step 8

Compile your add-in. Fix any compilation errors.

Step 9

Debug your add-in. Run the debugger and start ArcGIS Pro. Open a project that contains a 2D map or add one to your project. Add at least one feature data set.

Click the backstage licensing tab. You should see an entry for your add-in on the licensing tab in the External Extensions list. The Enabled check box should be unchecked and show as Disabled (unless you changed your default _authorizationID value to be a number divisible by 2)

Check the Enabled check box. The RegistrationWindow dialog box will appear. Enter any ID and click Activate. If you enter a number evenly divisible by 2, your add-in will be enabled. If you enter anything else, the add-in will remain disabled.

Licensing_7_Popup

Once you enter a valid product/authorization ID, it will be cached for the duration of the session. If you disable the add-in (toggle off the Enabled check box) and re-enable it, the pop-up does not need to be shown.

Notice that when your add-in is enabled via backstage, the Select Features button (on the Add In tab) is enabled (via the activated "acme_module_licensed" condition) if you have a 2D map as the active map in your project.

Licensing_8_Button2

Click the Select Features button to select all the features in the active view.

⚠️ **GitHub.com Fallback** ⚠️