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. namespace XrmToolBox.WhoAmIPlugin
  5. {
  6. // Do not forget to update version number and author (company attribute) in AssemblyInfo.cs class
  7. // To generate Base64 string for Images below, you can use https://www.base64-image.de/
  8. [Export(typeof(IXrmToolBoxPlugin)),
  9. ExportMetadata("Name", "WhoAmI Plugin"),
  10. ExportMetadata("Description", "This is a description for my first plugin"),
  11. // Please specify the base64 content of a 32x32 pixels image
  12. ExportMetadata("SmallImageBase64", null),
  13. // Please specify the base64 content of a 80x80 pixels image
  14. ExportMetadata("BigImageBase64", null),
  15. ExportMetadata("BackgroundColor", "Lavender"),
  16. ExportMetadata("PrimaryFontColor", "Black"),
  17. ExportMetadata("SecondaryFontColor", "Gray")]
  18. public class MyPlugin : PluginBase
  19. {
  20. public override IXrmToolBoxPluginControl GetControl()
  21. {
  22. return new MyPluginControl();
  23. }
  24. }
  25. }
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. namespace XrmToolBox.WhoAmIPlugin
  7. {
  8. /// <summary>
  9. /// This class can help you to store settings for your plugin
  10. /// </summary>
  11. /// <remarks>
  12. /// This class must be XML serializable
  13. /// </remarks>
  14. public class Settings
  15. {
  16. public string LastUsedOrganizationWebappUrl { get; set; }
  17. }
  18. }
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. namespace XrmToolBox.WhoAmIPlugin
  15. {
  16. public partial class MyPluginControl : PluginControlBase
  17. {
  18. private Settings mySettings;
  19. public MyPluginControl()
  20. {
  21. InitializeComponent();
  22. }
  23. private void MyPluginControl_Load(object sender, EventArgs e)
  24. {
  25. ShowInfoNotification("This is a notification that can lead to XrmToolBox repository", new Uri("https://github.com/MscrmTools/XrmToolBox"));
  26. // Loads or creates the settings for the plugin
  27. if (!SettingsManager.Instance.TryLoad(GetType(), out mySettings))
  28. {
  29. mySettings = new Settings();
  30. LogWarning("Settings not found => a new settings file has been created!");
  31. }
  32. else
  33. {
  34. LogInfo("Settings found and loaded");
  35. }
  36. }
  37. private void tsbClose_Click(object sender, EventArgs e)
  38. {
  39. CloseTool();
  40. }
  41. private void tsbSample_Click(object sender, EventArgs e)
  42. {
  43. // The ExecuteMethod method handles connecting to an
  44. // organization if XrmToolBox is not yet connected
  45. ExecuteMethod(GetAccounts);
  46. }
  47. private void GetAccounts()
  48. {
  49. WorkAsync(new WorkAsyncInfo
  50. {
  51. Message = "Getting accounts",
  52. Work = (worker, args) =>
  53. {
  54. args.Result = Service.RetrieveMultiple(new QueryExpression("account")
  55. {
  56. TopCount = 50
  57. });
  58. },
  59. PostWorkCallBack = (args) =>
  60. {
  61. if (args.Error != null)
  62. {
  63. MessageBox.Show(args.Error.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
  64. }
  65. var result = args.Result as EntityCollection;
  66. if (result != null)
  67. {
  68. MessageBox.Show($"Found {result.Entities.Count} accounts");
  69. }
  70. }
  71. });
  72. }
  73. /// <summary>
  74. /// This event occurs when the plugin is closed
  75. /// </summary>
  76. /// <param name="sender"></param>
  77. /// <param name="e"></param>
  78. private void MyPluginControl_OnCloseTool(object sender, EventArgs e)
  79. {
  80. // Before leaving, save the settings
  81. SettingsManager.Instance.Save(GetType(), mySettings);
  82. }
  83. /// <summary>
  84. /// This event occurs when the connection has been updated in XrmToolBox
  85. /// </summary>
  86. public override void UpdateConnection(IOrganizationService newService, ConnectionDetail detail, string actionName, object parameter)
  87. {
  88. base.UpdateConnection(newService, detail, actionName, parameter);
  89. mySettings.LastUsedOrganizationWebappUrl = detail.WebApplicationUrl;
  90. LogInfo("Connection has changed to: {0}", detail.WebApplicationUrl);
  91. }
  92. }
  93. }

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. namespace XrmToolBox.Extensibility
  5. {
  6. public class WorkAsyncInfo
  7. {
  8. public WorkAsyncInfo();
  9. public WorkAsyncInfo(string message, Action<DoWorkEventArgs> work);
  10. public WorkAsyncInfo(string message, Action<BackgroundWorker, DoWorkEventArgs> work);
  11. public object AsyncArgument { get; set; }
  12. public Control Host { get; set; }
  13. public bool IsCancelable { get; set; }
  14. public string Message { get; set; }
  15. public int MessageHeight { get; set; }
  16. public int MessageWidth { get; set; }
  17. public Action<RunWorkerCompletedEventArgs> PostWorkCallBack { get; set; }
  18. public Action<ProgressChangedEventArgs> ProgressChanged { get; set; }
  19. public Action<BackgroundWorker, DoWorkEventArgs> Work { get; set; }
  20. }
  21. }

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

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.