Part Deux: Storing Configuration Data for Microsoft Dynamics CRM Plug-ins


CRM MVP Mitch Milam returns as a guest blogger with part two of this post. You can read more from Mitch at his blog.

In part one of this series we discussed the built-in mechanism provided by CRM to store plug-in application settings. Based on several comments on the article, I’ve decided to enhance the process by moving the storage of our settings from the plug-in configuration step to a custom CRM entity.

Creating a Custom Entity for Settings Storage

The first step in this process is to create a custom CRM entity which will store our settings. The New Entity form looks like this:

clip_image002

The primary attribute for the Plug-in Setting entity will be configured as follows:

clip_image004

We then add a single attribute that will actually hold our settings information:

clip_image006

Modify the main form to allow entry of the Plug-in Name and Settings information then publish the new entity and we’re ready for business. Here is how we populate a plug-in settings record:

clip_image008

Modified Plug-In Configuration Class

The PluginConfiguration class created in the previous article has been modified slightly to retrieve our settings from the CRM database instead of using the normal plug-in configuration methods.

Note: The constants at the beginning of the class will need to be modified to match the entity and attributes you created on your system.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4: using System.Xml;
   5: using Microsoft.Crm.Sdk;
   6: using Microsoft.Crm.Sdk.Query;
   7: using Microsoft.Crm.SdkTypeProxy;
   8:  
   9: namespace CRMAccelerators
  10: {
  11:     class PluginConfiguration
  12:     {
  13:         private const string PLUGIN_ENTITY = "m3_pluginsetting";
  14:         private const string PLUGIN_NAME_ATTRIBUTE = "m3_pluginname";
  15:         private const string PLUGIN_SETTING_ATTRIBUTE = "m3_settings";
  16:  
  17:         private static XmlDocument _settingsDoc = new XmlDocument();
  18:  
  19:         public static void RetrieveSettings(ICrmService crmService, string settingsName)
  20:         {
  21:             QueryExpression query = new QueryExpression();
  22:  
  23:             query.EntityName = PLUGIN_ENTITY;
  24:  
  25:             query.ColumnSet = new ColumnSet(new string[] { PLUGIN_SETTING_ATTRIBUTE });
  26:  
  27:             query.Criteria = new FilterExpression();
  28:             query.Criteria.FilterOperator = LogicalOperator.And;
  29:  
  30:             ConditionExpression condition1 = new ConditionExpression();
  31:             condition1.AttributeName = "statecode";
  32:             condition1.Operator = ConditionOperator.Equal;
  33:             condition1.Values = new object[] { 0 };
  34:  
  35:             ConditionExpression condition2 = new ConditionExpression();
  36:             condition2.AttributeName = PLUGIN_NAME_ATTRIBUTE;
  37:             condition2.Operator = ConditionOperator.Equal;
  38:             condition2.Values = new object[] { settingsName };
  39:  
  40:             query.Criteria.Conditions.AddRange(new ConditionExpression[] { condition1, condition2 });
  41:  
  42:             OrderExpression order1 = new OrderExpression();
  43:             order1.AttributeName = PLUGIN_NAME_ATTRIBUTE;
  44:             order1.OrderType = OrderType.Ascending;
  45:  
  46:             query.Orders.Add(order1);
  47:  
  48:             RetrieveMultipleRequest request = new RetrieveMultipleRequest();
  49:             request.ReturnDynamicEntities = true;
  50:             request.Query = query;
  51:  
  52:             RetrieveMultipleResponse retrieved = (RetrieveMultipleResponse)crmService.Execute(request);
  53:  
  54:             DynamicEntity entity = (DynamicEntity)retrieved.BusinessEntityCollection.BusinessEntities[0];
  55:  
  56:             _settingsDoc.LoadXml(GetStringProperty(entity, PLUGIN_SETTING_ATTRIBUTE));
  57:         }
  58:  
  59:         public static Guid GetConfigDataGuid(string label)
  60:         {
  61:             string tempString = GetValueNode(label);
  62:  
  63:             if (tempString != string.Empty)
  64:             {
  65:                 return new Guid(tempString);
  66:             }
  67:             return Guid.Empty;
  68:         }
  69:  
  70:         public static bool GetConfigDataBool(string label)
  71:         {
  72:             bool retVar;
  73:  
  74:             if (bool.TryParse(GetValueNode(label), out retVar))
  75:             {
  76:                 return retVar;
  77:             }
  78:             else
  79:             {
  80:                 return false;
  81:             }
  82:         }
  83:  
  84:         public static int GetConfigDataInt(string label)
  85:         {
  86:             int retVar;
  87:  
  88:             if (int.TryParse(GetValueNode(label), out retVar))
  89:             {
  90:                 return retVar;
  91:             }
  92:             else
  93:             {
  94:                 return -1;
  95:             }
  96:         }
  97:  
  98:         public static string GetConfigDataString(string label)
  99:         {
 100:             return GetValueNode(label);
 101:         }
 102:  
 103:         private static string GetValueNode(string key)
 104:         {
 105:             XmlNode node = _settingsDoc.SelectSingleNode(String.Format("Settings/setting[@name='{0}']", key));
 106:  
 107:             if (node != null)
 108:             {
 109:                 return node.SelectSingleNode("value").InnerText;
 110:             }
 111:  
 112:             return string.Empty;
 113:         }
 114:  
 115:         public static string GetStringProperty(DynamicEntity parent, string prop)
 116:         {
 117:             string retVar = string.Empty;
 118:  
 119:             if (AttributeExists(parent, prop))
 120:             {
 121:                 retVar = parent.Properties[prop].ToString();
 122:             }
 123:  
 124:             return retVar;
 125:         }
 126:  
 127:         public static bool AttributeExists(DynamicEntity entity, string attr)
 128:         {
 129:             return entity.Properties.Contains(attr);
 130:         }
 131:     }
 132: }

Putting PluginConfiguration to Work

The only change made to the usage of the PluginConfiguration class I the requirement that we call RetrieveSettings before attempting to access any properties found within the settings attribute:

PluginConfiguration.RetrieveSettings(CrmService, "CoolPlugin");

string TaskPrefix = PluginConfiguration.GetConfigDataString("TaskPrefix");

Note: Since we need a connection to CRM to retrieve settings from the database, you will need to locate the code that retrieves settings after the code that has initiated a connection to the CRM web service.

Conclusion

By moving the settings from the Plug-in step configuration to the CRM database we’ve reduced the maintenance level required to store our settings in a central location. There will be a slight performance hit since we have to retrieve a database record, but I don’t think it will be huge.

I’ll place the source code for the modified pluginconfiguration class on my blog so you may download it and add it to your next plug-in project.

Cheers,

Mitch Milam

Comments (3)

  1. The second in a series of two articles regarding the storage of plug-in configuration data has been posted

  2. Chris says:

    Hi Mitch,

    I agree to you, this is a very flexible way to store a configuration for your plugins.

    It also works fine for offline plugins.

    The Problem is, that the user can decide if he synchronizes the configuration entitity into the offline database.

    So if the plugin is executed offline there seems to be no way to rely on this configuration.

    Is there a way to entforce the presence of the entity on the offline client ?

    I have tried to set <IsReplicationUserFiltered>0</IsReplicationUserFiltered> in the customizations.xml but this has no effect.

    Greets, Chris