How To Create Plugins For XrmToolBox

This article will help you to create your own plugins for XrmToolBox.

XrmToolBox has plenty of useful plugins, and awesome people like you from community keep adding new plugins to solve Dynamics 365 developer’s day to day hurdles and make them more productive. Recently I was working on an XrmToolBox plugin (have a look at GitHub Dynamics 365 Bulk Solution Exporter). Let me share my learning experience with you. 

XrmToolBox is basically a class library, you can create your plugin in 2 ways. 
  1. Start by taking a class library project and installing XrmToolBoxPackage using NuGet. You need to create one Custom Windows Form Control in this approach. I created Dynamics 365 Bulk Solution Exporter this way because the template was not available back then.

  2. Tanguy Touzard (Creator of XrmToolBox) has simplified the process by creating XrmToolBox Plugin Project Template for Visual Studio, which configures most of the things automatically. This is the preferred way of creating plugins now.

Start Project with XrmToolBox Plugin Project Template

You won’t be getting this project template by default in Visual Studio, to install goto new project dialog, navigate to online section in the left pane and search for XrmToolBox in the right pane; the template will appear in the result. Install it. Visual Studio needs to be restarted in order to install the template.

Dynamics CRM

After installing, create a new Project using this template. Framework version should be selected as 4.6.2.

Dynamics CRM
 
Components of Project

You will get 2 main files in the newly created project where you need to work on.

MyPlugin.cs

This file contains metadata like the name of the plugin, icons, and color etc., which you can change according to the purpose of your plugin. I am changing the name of the plugin to “WhoAmI Plugin”.

  1. using System.ComponentModel.Composition;  
  2. using XrmToolBox.Extensibility;  
  3. using XrmToolBox.Extensibility.Interfaces;  
  4.   
  5. namespace XrmToolBox.WhoAmIPlugin  
  6. {  
  7.     // Do not forget to update version number and author (company attribute) in AssemblyInfo.cs class  
  8.     // To generate Base64 string for Images below, you can use https://www.base64-image.de/  
  9.     [Export(typeof(IXrmToolBoxPlugin)),  
  10.         ExportMetadata("Name""WhoAmI Plugin"),  
  11.         ExportMetadata("Description""This is a description for my first plugin"),  
  12.         // Please specify the base64 content of a 32x32 pixels image  
  13.         ExportMetadata("SmallImageBase64"null),  
  14.         // Please specify the base64 content of a 80x80 pixels image  
  15.         ExportMetadata("BigImageBase64"null),  
  16.         ExportMetadata("BackgroundColor""Lavender"),  
  17.         ExportMetadata("PrimaryFontColor""Black"),  
  18.         ExportMetadata("SecondaryFontColor""Gray")]  
  19.     public class MyPlugin : PluginBase  
  20.     {  
  21.         public override IXrmToolBoxPluginControl GetControl()  
  22.         {  
  23.             return new MyPluginControl();  
  24.         }  
  25.     }  
  26. }  
Settings.cs

This file help you to save/update any configuration value permanently, which will be available when you next time open the tool.

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Threading.Tasks;  
  6.   
  7. namespace XrmToolBox.WhoAmIPlugin  
  8. {  
  9.     /// <summary>  
  10.     /// This class can help you to store settings for your plugin  
  11.     /// </summary>  
  12.     /// <remarks>  
  13.     /// This class must be XML serializable  
  14.     /// </remarks>  
  15.     public class Settings  
  16.     {  
  17.         public string LastUsedOrganizationWebappUrl { getset; }  
  18.     }  
  19. }   
MyPluginControl.cs

This is a Windows Form Control which is composed of 3 files.

  1. MyPluginControl.cs[Design]
    This file contains the UI where we can pull the controls from Toolbox and make the Plugin UI what user will interact with.

  2. MyPluginControl.designer.cs
    This files contains autogenerated code, which is generated while we are placing and configuring controls in UI using drag & drop. we don’t need to directly interact with this file.

  3. MyPluginControl.cs
    This is where we need bind our logic to different events generated from UI, like ButtonClick, OnLoad etc. This file is shown below, is contains some sample code which retrieves count of Account records and displays to user.
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel;  
  4. using System.Drawing;  
  5. using System.Data;  
  6. using System.Linq;  
  7. using System.Text;  
  8. using System.Threading.Tasks;  
  9. using System.Windows.Forms;  
  10. using XrmToolBox.Extensibility;  
  11. using Microsoft.Xrm.Sdk.Query;  
  12. using Microsoft.Xrm.Sdk;  
  13. using McTools.Xrm.Connection;  
  14.   
  15. namespace XrmToolBox.WhoAmIPlugin  
  16. {  
  17.     public partial class MyPluginControl : PluginControlBase  
  18.     {  
  19.         private Settings mySettings;  
  20.   
  21.         public MyPluginControl()  
  22.         {  
  23.             InitializeComponent();  
  24.         }  
  25.   
  26.         private void MyPluginControl_Load(object sender, EventArgs e)  
  27.         {  
  28.             ShowInfoNotification("This is a notification that can lead to XrmToolBox repository"new Uri("https://github.com/MscrmTools/XrmToolBox"));  
  29.   
  30.             // Loads or creates the settings for the plugin  
  31.             if (!SettingsManager.Instance.TryLoad(GetType(), out mySettings))  
  32.             {  
  33.                 mySettings = new Settings();  
  34.   
  35.                 LogWarning("Settings not found => a new settings file has been created!");  
  36.             }  
  37.             else  
  38.             {  
  39.                 LogInfo("Settings found and loaded");  
  40.             }  
  41.         }  
  42.   
  43.         private void tsbClose_Click(object sender, EventArgs e)  
  44.         {  
  45.             CloseTool();  
  46.         }  
  47.   
  48.         private void tsbSample_Click(object sender, EventArgs e)  
  49.         {  
  50.             // The ExecuteMethod method handles connecting to an  
  51.             // organization if XrmToolBox is not yet connected  
  52.             ExecuteMethod(GetAccounts);  
  53.         }  
  54.   
  55.         private void GetAccounts()  
  56.         {  
  57.             WorkAsync(new WorkAsyncInfo  
  58.             {  
  59.                 Message = "Getting accounts",  
  60.                 Work = (worker, args) =>  
  61.                 {  
  62.                     args.Result = Service.RetrieveMultiple(new QueryExpression("account")  
  63.                     {  
  64.                         TopCount = 50  
  65.                     });  
  66.                 },  
  67.                 PostWorkCallBack = (args) =>  
  68.                 {  
  69.                     if (args.Error != null)  
  70.                     {  
  71.                         MessageBox.Show(args.Error.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);  
  72.                     }  
  73.                     var result = args.Result as EntityCollection;  
  74.                     if (result != null)  
  75.                     {  
  76.                         MessageBox.Show($"Found {result.Entities.Count} accounts");  
  77.                     }  
  78.                 }  
  79.             });  
  80.         }  
  81.   
  82.         /// <summary>  
  83.         /// This event occurs when the plugin is closed  
  84.         /// </summary>  
  85.         /// <param name="sender"></param>  
  86.         /// <param name="e"></param>  
  87.         private void MyPluginControl_OnCloseTool(object sender, EventArgs e)  
  88.         {  
  89.             // Before leaving, save the settings  
  90.             SettingsManager.Instance.Save(GetType(), mySettings);  
  91.         }  
  92.   
  93.         /// <summary>  
  94.         /// This event occurs when the connection has been updated in XrmToolBox  
  95.         /// </summary>  
  96.         public override void UpdateConnection(IOrganizationService newService, ConnectionDetail detail, string actionName, object parameter)  
  97.         {  
  98.             base.UpdateConnection(newService, detail, actionName, parameter);  
  99.   
  100.             mySettings.LastUsedOrganizationWebappUrl = detail.WebApplicationUrl;  
  101.             LogInfo("Connection has changed to: {0}", detail.WebApplicationUrl);  
  102.         }  
  103.     }  
  104. }  

This file has few other examples too like ShowInfoNotification(), LogWarning() & UpdateConnection() etc. for a complete list of available methods you can check PluginContolBase class from which this is inherited to.

Understanding the Framework

Here in this sample we will me making WhoAmIRequest and will be showing the response to the user. Before that, you should have a look at GetAccounts() that how it is written. We need to understand 2 main methods while getting started one is WorkAsync(WorkAsyncInfo info) and other is ExecuteMethod(Action action).

In XrmToolBox plugins all requests to the server should be made asynchronously but here is a twist, we won’t be using async & await, instead WorkAsync(WorkAsyncInfo info) is provided in XrmToolBox.Extensibility namespace, Let’s look into WorkAsyncInfo class of framework which is a main class to execute code of any plugin.

  1. using System;  
  2. using System.ComponentModel;  
  3. using System.Windows.Forms;  
  4.   
  5. namespace XrmToolBox.Extensibility  
  6. {  
  7.     public class WorkAsyncInfo  
  8.     {  
  9.         public WorkAsyncInfo();  
  10.         public WorkAsyncInfo(string message, Action<DoWorkEventArgs> work);  
  11.         public WorkAsyncInfo(string message, Action<BackgroundWorker, DoWorkEventArgs> work);  
  12.   
  13.         public object AsyncArgument { getset; }  
  14.         public Control Host { getset; }  
  15.         public bool IsCancelable { getset; }  
  16.         public string Message { getset; }  
  17.         public int MessageHeight { getset; }  
  18.         public int MessageWidth { getset; }  
  19.         public Action<RunWorkerCompletedEventArgs> PostWorkCallBack { getset; }  
  20.         public Action<ProgressChangedEventArgs> ProgressChanged { getset; }  
  21.         public Action<BackgroundWorker, DoWorkEventArgs> Work { getset; }  
  22.     }  
  23. }  

You can look into constructors and properties yourself, let me talk more about callbacks available, which are Work, PostWorkCallback & ProgressChanged.

  1. Work
    Here we do our main processing, it has 2 arguments BackgroundWorker & DoWorkEventArgs.

  2. PostWorkCalllBack
    Once Work is completed, this is triggered to show output to a user. This gets the results from RunWorkerCompletedEventArgs parameter, which is returned from of Workcallback.

  3. ProgressChanged
    If our process is long-running, Unlike Message = “Getting accounts”; in GetAccounts(), we must show the progress to the user. We can pass progress as an integer in ProgressChangedEventArgs parameter.

ExecuteMethod(Action action) helps to get rid of connection hurdles for a plugin developer, all methods which require CRM connection should be called from ExecuteMethod which accepts Action as a parameter. If CRM is not connected then it will show a popup to connect before executing method.

Implementing & Consuming WhoAmI()

Open MyPluginControl.cs[Design] and place a Button control in panel. Change name property to btn_WhoAmI. Optionally, you can change other properties and decorate.

Dynamics CRM

Add one list box also with name lst_UserData below the button to show current user’s data.

Dynamics CRM

Double click on this button to create an event and open codebehind file(MyPluginControl.cs) and write the below code.

  1. private void btn_WhoAmI_Click(object sender, EventArgs e)  
  2. {  
  3.     // calling WhoAmI() from ExecuteMethod so connection will be smooth  
  4.     ExecuteMethod(WhoAmI);  
  5. }  
  6.   
  7. private void WhoAmI()  
  8. {  
  9.     WorkAsync(new WorkAsyncInfo  
  10.     {  
  11.         // Showing message until background work is completed  
  12.         Message = "Retrieving WhoAmI Information",  
  13.   
  14.         // Main task which will be executed asynchronously  
  15.         Work = (worker, args) =>  
  16.         {  
  17.             // making WhoAmIRequest  
  18.             var whoAmIResponse = (WhoAmIResponse)Service.Execute(new WhoAmIRequest());  
  19.   
  20.             // retrieving details of current user  
  21.             var user = Service.Retrieve("systemuser", whoAmIResponse.UserId, new Microsoft.Xrm.Sdk.Query.ColumnSet(true));  
  22.   
  23.             // placing results to args, which will be sent to PostWorkCallBack to display to user  
  24.             var userData = new List<string>();  
  25.             foreach (var data in user.Attributes)  
  26.                 userData.Add($"{data.Key} : {data.Value}");  
  27.             args.Result = userData;  
  28.         },  
  29.   
  30.         // Work is completed, results can be shown to user  
  31.         PostWorkCallBack = (args) =>  
  32.         {  
  33.             if (args.Error != null)  
  34.                 MessageBox.Show(args.Error.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);  
  35.             else  
  36.                 // Binding result data to ListBox Control  
  37.                 lst_UserData.DataSource = args.Result;  
  38.         }  
  39.     });  
  40. }   

Congratulations! You are done with your first XrmToolBox plugin now. Let’s test it now.

Test Your Plugin

Build your code and grab the DLL from bin/debug folder, and place it in %AppData%\MscrmTools\XrmToolBox\Plugins folder. (You may refer to my previous article Installing XrmToolBox Plugins in No Internet OnPremises Environments).

Open XrmToolBox and search for your plugin, click to open it, when it asks to connect, click No, so you can verify ExcuteMethod functionality.

Dynamics CRM

Here is your brand new plugin, all created by yourself. Click on Who Am I Button, it will ask to connect an orgnization first, because we have used ExecuteMethod() here.

Dynamics CRM

Connect to an organization, after connecting to CRM, it will show the retriving message which is set in our Message property is WhoAmI(). Finally, it will show all informaion about current user in ListBox.

Dynamics CRM
 
Get complete source code from GitHub here. 

This DLL can be shared with anyone and they can use it. But to make it available to everyone you need to publsh it, which I will discuss in next article.