ProGuide License Your Add in - kataya/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:          11/24/2020
ArcGIS Pro:    2.7
Visual Studio: 2017, 2019

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="true"/>
      
...

This creates an entry in the Pro backstage licensing tab, External Extensions list:

LIcensing_3_Extension.png

Notice that the extensionConfig hasExtendedInfo attribute is set to true. This tells Pro that the Add-in also has an IExtensionConfig implementation (see Step 7).

Step 2

Next, you'll create a corresponding condition in your module whose enabled/disabled state will be based on whether or not your add-in is correctly authorized. Pro uses conditions to control the enabled state and/or visibility of the UI. You'll use a custom condition to propagate the enabled/disabled state of your add-in to your custom 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 also added as part of an "and" clause. This ensures that your custom condition is only enabled if the add-in is enabled (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

If you haven't already added a button to your add-in, add one now. 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 the following method to your Module to evaluate a given product 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 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").

Step 6

Now you'll add a WPF window to your add-in to allow users to enter a product ID to enable the add-in.

Add a project folder called UI to your project. Right-click the project node in Visual Studio. On the context menu, click Add->New Folder, and name the folder UI.

Licensing_4_folder

Add a new WPF window to the UI folder. Right-click the UI folder and click Add->New Item. Choose Window (WPF). Name the window RegistrationWindow.xaml.

Licensing_5_Window

Double-click RegistrationWindow.xaml (or whatever you named the WPF window) to open the designer. Click the XAML tab. Copy and paste the following XAML to overwrite the default XAML for the window. You may need to change the class name and/or namespaces in the XAML depending on the namespaces and class names in your module.

<controls:ProWindow x:Class="Licensing.UI.RegistrationWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:controls="clr-namespace:ArcGIS.Desktop.Framework.Controls;assembly=ArcGIS.Desktop.Framework"
        xmlns:extensions="clr-namespace:ArcGIS.Desktop.Extensions;assembly=ArcGIS.Desktop.Extensions"
        mc:Ignorable="d"
        Title="Acme Add-in Authorization" Height="150" Width="400">
  <controls:ProWindow.Resources>
    <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
        <extensions:DesignOnlyResourceDictionary Source="pack://application:,,,/ArcGIS.Desktop.Framework;component\Themes\Default.xaml"/>
      </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
  </controls:ProWindow.Resources>
    <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_SimpleButton}"></Button> 
    </Grid>
</controls:ProWindow>

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):

    /// <summary>
    /// Interaction logic for RegistrationWindow.xaml
    /// </summary>
    public partial class RegistrationWindow : 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. Implementing IExtensionConfig on your Add-in is optional and is only required for add-ins desiring to programmatically control their Add-in-enabled state.

Add the IExtensionConfig interface to your Module class declaration as follows:

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

Use Visual Studio to generate the interface implementation stubs. 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 5).

Notice that empty strings are returned for the ProductName and Message properties. Return a non-empty string to override the corresponding productName and message attributes of the <extensionConfig .../> DAML element. 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.

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 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** ⚠️