Разработка
24
Фев
3

Хранение конфигурационных данных для плагина

Часто при разработке плагинов требуется использовать информацию, которая меняется в зависимости от среды развертывания (например, имя CRM сервера). Конечно можно ее захардкодить, но это не эффективно с точки зренея переносимости и вообще… 🙂 Поэтому более разумно вынести ее за пределы кода. Вот парочка способов:

Unsecure Configuration и Secure Configuration

Строковый тип

Регистрируя шаг в плагине, Вы можете определить значения для двух параметров: Unsecure Configuration и Secure Configuration. Эти параметры принимают строковые значения и передают их к plugin constructor. Оба эти параметра строковые и могут содержать любые данные, в любом виде.

Как с ними работать…

  • Создайте плагин с таким кодом:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.Crm.Sdk;
    using Microsoft.Crm.SdkTypeProxy;
    
    namespace ClassLibrary1
    {
        public class configTest : IPlugin
        {
            // Объявляем глобальные переменные
            string sUnsecure;
            string sSecure;
    
            // Создаем метод для полученя конфигурационных данных
            // Имя метода должно совпадать с тем, что объявлено в классе с контекстом IPlugin
            public configTest(string unsecure, string secure)
            {
                // Сохраняем конфигурационные данные в переменных 
                if (!String.IsNullOrEmpty(unsecure))
                {
                    sUnsecure = unsecure;
                }
                if (!String.IsNullOrEmpty(secure))
                {
                    sSecure = secure;
                }
    
            }
            
            // При срабатываении плагина...
            public void Execute(IPluginExecutionContext context)
            {
                // Выводим в сообщении Unsecure и Secure Configuration
                throw new Exception("Unsecure: " + sUnsecure + ", Secure: " + sSecure);
            }
        }
    }
    
  • Зарегистрируйте его на любое событие, при этом в шаге задайте Unsecure Configuration и/или Secure Configuration;
  • Вызовите событие в CRM – появится сообщение с Вашими конфигурационными данными.


XML

Для более удобного хранения и последующей обработки большого количества конфигурационных данных лучше всего использовать формат XML. Примерно такого вида:

<Settings>
	<setting name="name1">
		<value>value1</value>
	</setting>
	<setting name="name2">
		<value>value2</value>
	</setting>
	<setting name="name3">
		<value>value3</value>
	</setting>
</Settings>

При этом тип данных в Unsecure Configuration и Secure Configuration по прежнему останется строковым, но визуальное представление соответствует формату XML и данный фрагмент может быть легко преобразован к xml-типу с помощью стандартных C#-функций, что упрости последующий доступ к данным XML-дерева.

Рассмотрим пример…

  • Создайте плагин со следующим кодом:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.Crm.Sdk;
    using Microsoft.Crm.SdkTypeProxy;
    using System.Xml;
    
    namespace ClassLibrary1
    {
        public class configXMLTest : IPlugin
        {
            // Объявляем глобальные переменные
            string TaskPrefix;
            bool FirstRun;
            int RetryCount;
    
            public configXMLTest(string unsecure, string secure)
            {
                // Обявляем перменную с типом XML и загружаем в нее содержимое Unsecure Configuration
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(unsecure);
    
                // Вызываем функцию PluginConfiguration и соответствующие типу данных подфункции для полученя значения какого-либо узла в дереве XML
                TaskPrefix = PluginConfiguration.GetConfigDataString(doc, "TaskPrefix");
                FirstRun = PluginConfiguration.GetConfigDataBool(doc, "FirstRun");
                RetryCount = PluginConfiguration.GetConfigDataInt(doc, "RetryCount");
            }
            
            class PluginConfiguration
            {
                private static string GetValueNode(XmlDocument doc, string key)
                {
                    XmlNode node = doc.SelectSingleNode(String.Format("Settings/setting[@name='{0}']", key));
    
                    if (node != null)
                    {
                        return node.SelectSingleNode("value").InnerText;
                    }
                    return string.Empty;
                }
    
                // Подфункция для извлечения данных с типом GUID
                public static Guid GetConfigDataGuid(XmlDocument doc, string label)
                {
                    string tempString = GetValueNode(doc, label);
    
                    if (tempString != string.Empty)
                    {
                        return new Guid(tempString);
                    }
                    return Guid.Empty;
                }
    
                // Подфункция для извлечения данных с бинарным типом
                public static bool GetConfigDataBool(XmlDocument doc, string label)
                {
                    bool retVar;
    
                    if (bool.TryParse(GetValueNode(doc, label), out retVar))
                    {
                        return retVar;
                    }
                    else
                    {
                        return false;
                    }
                }
    
                // Подфункция для извлечения данных с типом integer
                public static int GetConfigDataInt(XmlDocument doc, string label)
                {
                    int retVar;
    
                    if (int.TryParse(GetValueNode(doc, label), out retVar))
                    {
                        return retVar;
                    }
                    else
                    {
                        return -1;
                    }
                }
    
                // Подфункция для извлечения строковых данных
                public static string GetConfigDataString(XmlDocument doc, string label)
                {
                    return GetValueNode(doc, label);
                }
            }
            
            // При срабатываении плагина...
            public void Execute(IPluginExecutionContext context)
            {
                // Выводим в сообщении значения переменных дерева XML
                throw new Exception("TaskPrefix: " + TaskPrefix + ", FirstRun: " + FirstRun + ", RetryCount: " + RetryCount);
            }
        }
    }
    

    Данный код при помощью подфункций функции PluginConfiguration извлекает следующие типы данных: GUID, Строки, Булевый и Iinteger. При этом в подфункции передаются два параметра: XML-объект и имя узла, значение которого необходимо получить.

  • Зарегистрируйте его на любое событие, при этом в шаге, в поле Unsecure Configuration поместите такой XML-фрагмент:
    <Settings>
    	<setting name="RetryCount">
    		<value>5</value>
    	</setting>
    	<setting name="TaskPrefix">
    		<value>Тынц-тынц</value>
    	</setting>
    	<setting name="FirstRun">
    		<value>false</value>
    	</setting>
    </Settings>
    
  • Вызовите зарегистрированное событие в CRM. Данные из XML-дерева подтянутся на экран 🙂


Хранение данных в объекте CRM

У описанных выше способов есть один небольшой недостаток – они требуют наличия Plug-in Registration Tool’а для изменения настроек. Гораздо проще было бы делать эти настройки непосредственно в CRM. Посмотрим, как это можно сделать:

  • Первым делом создайте новый объект с именем pluginsetting, в котором будут храниться настройки для плагина. Для основного атрибута задайте имя схемы pluginname;
  • Добавьте новый атрибут с именем схемы setting и типом ntext. Выставите для него Бизнес-требование. Вынесите его на форму и опубликуйте объект;
  • Затем создайте новую запись этого объекта, в качестве имени задайте testplugin, а в качестве настроек:
    <Settings>
    	<setting name="RetryCount">
    		<value>15</value>
    	</setting>
    	<setting name="TaskPrefix">
    		<value>Тынц-тынц 2</value>
    	</setting>
    	<setting name="FirstRun">
    		<value>false</value>
    	</setting>
    </Settings>
    
  • Затем создайте новый плагин в VS с таким кодом:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.Crm.Sdk;
    using Microsoft.Crm.SdkTypeProxy;
    using Microsoft.Crm.Sdk.Query;
    using System.Xml;
    
    namespace ClassLibrary1
    {
        public class configXMLEntityTest : IPlugin
        {
            class PluginConfiguration
            {
                // Задаем имя объекта в котором хранятся кнофинурационные данные и имена его атрибутов
                private const string PLUGIN_ENTITY = "new_pluginsetting";
                private const string PLUGIN_NAME_ATTRIBUTE = "new_pluginname";
                private const string PLUGIN_SETTING_ATTRIBUTE = "new_settings";
    
                // Объявляем перменную с типом XML
                private static XmlDocument _settingsDoc = new XmlDocument();
    
                // Функция возвращает значение настроечного поля из требуемой записи
    			public static void RetrieveSettings(ICrmService crmService, string settingsName)
                {
                    QueryExpression query = new QueryExpression();
    
                    query.EntityName = PLUGIN_ENTITY;
    
                    query.ColumnSet = new ColumnSet(new string[] { PLUGIN_SETTING_ATTRIBUTE });
    
                    query.Criteria = new FilterExpression();
                    query.Criteria.FilterOperator = LogicalOperator.And;
    
                    ConditionExpression condition1 = new ConditionExpression();
                    condition1.AttributeName = "statecode";
                    condition1.Operator = ConditionOperator.Equal;
                    condition1.Values = new object[] { 0 };
    
                    ConditionExpression condition2 = new ConditionExpression();
                    condition2.AttributeName = PLUGIN_NAME_ATTRIBUTE;
                    condition2.Operator = ConditionOperator.Equal;
                    condition2.Values = new object[] { settingsName };
    
                    query.Criteria.Conditions.AddRange(new ConditionExpression[] { condition1, condition2 });
    
                    OrderExpression order1 = new OrderExpression();
                    order1.AttributeName = PLUGIN_NAME_ATTRIBUTE;
                    order1.OrderType = OrderType.Ascending;
    
                    query.Orders.Add(order1);
    
                    RetrieveMultipleRequest request = new RetrieveMultipleRequest();
                    request.ReturnDynamicEntities = true;
                    request.Query = query;
    
                    RetrieveMultipleResponse retrieved = (RetrieveMultipleResponse)crmService.Execute(request);
    
                    DynamicEntity entity = (DynamicEntity)retrieved.BusinessEntityCollection.BusinessEntities[0];
    
                    _settingsDoc.LoadXml(GetStringProperty(entity, PLUGIN_SETTING_ATTRIBUTE));
                }
    
                // Подфункция для извлечения данных с типом GUID
    			public static Guid GetConfigDataGuid(string label)
                {
                    string tempString = GetValueNode(label);
    
                    if (tempString != string.Empty)
                    {
                        return new Guid(tempString);
                    }
                    return Guid.Empty;
                }
    
                // Подфункция для извлечения данных с бинарным типом
    			public static bool GetConfigDataBool(string label)
                {
                    bool retVar;
    
                    if (bool.TryParse(GetValueNode(label), out retVar))
                    {
                        return retVar;
                    }
                    else
                    {
                        return false;
                    }
                }
    
                // Подфункция для извлечения данных с типом integer
    			public static int GetConfigDataInt(string label)
                {
                    int retVar;
    
                    if (int.TryParse(GetValueNode(label), out retVar))
                    {
                        return retVar;
                    }
                    else
                    {
                        return -1;
                    }
                }
    
                // Подфункция для извлечения строковых данных
    			public static string GetConfigDataString(string label)
                {
                    return GetValueNode(label);
                }
    
                private static string GetValueNode(string key)
                {
                    XmlNode node = _settingsDoc.SelectSingleNode(String.Format("Settings/setting[@name='{0}']", key));
    
                    if (node != null)
                    {
                        return node.SelectSingleNode("value").InnerText;
                    }
    
                    return string.Empty;
                }
    
                public static string GetStringProperty(DynamicEntity parent, string prop)
                {
                    string retVar = string.Empty;
    
                    if (AttributeExists(parent, prop))
                    {
                        retVar = parent.Properties[prop].ToString();
                    }
    
                    return retVar;
                }
    
                public static bool AttributeExists(DynamicEntity entity, string attr)
                {
                    return entity.Properties.Contains(attr);
                }
            }
            
            // При срабатываении плагина...
            public void Execute(IPluginExecutionContext context)
            {
                // Конфигурируем CrmService с помощью контекста плагина
    			ICrmService crmService = context.CreateCrmService(true);
               
                /*
    			Получаем данные из настроечной записи. Вызов подфункции RetrieveSettings должен обязательно предшествовать получению данных.
    			Первым параметром идет CRM сервис, а вторым значение основного атрибута требуемой записи.
    			Таким образом одна настроечная запись соответствует одному плагину (хотя можно назначить и больше 🙂 ).
    			*/
    			PluginConfiguration.RetrieveSettings(crmService, "testplugin");
    
                // Вызываем функцию PluginConfiguration и соответствующие типу данных подфункции для полученя значения какого-либо узла в дереве XML
                string TaskPrefix = PluginConfiguration.GetConfigDataString("TaskPrefix");
                bool FirstRun = PluginConfiguration.GetConfigDataBool("FirstRun");
                int RetryCount = PluginConfiguration.GetConfigDataInt("RetryCount");
    
                // Выводим в сообщении значения переменных дерева XML
                throw new Exception("TaskPrefix: " + TaskPrefix + ", FirstRun: " + FirstRun + ", RetryCount: " + RetryCount);
            }
        }
    }
    

    Данный код подключается к CRM и вытаскивает из нее одну настроечную запись (точнее значение одного ее поля). Какую именно запись, он определяет по названию объекта и значению основного атрибута. Поэтому обязательно смените названия объекта и атрибутов на Ваши (если они у Вас отличаются). А также поменяейте имя вызваемой записи (т.е. каждая запись CRM будет соответствовать своему плагину);

  • Зарегистрируйте плагин и вызовите событие в CRM.




Учтите только, что данный способ несколько ухудшает производительность плагина, так как требует запроса к БД для того, чтобы вернуть данные конфигурации.

Комментарии (3)
  • Александр 24.02.2010

    Добрый день!
    Подскажите, по какой причине, использую Unsecure Configuration и Secure Configuration, могут не приходить данные в конструктор плагина. Плагин зарегистрирован для account на Update, все остальные параметры, как в примере выше. В Execute пытаюсь вывести то что пришло из Unsecure и Secure, но throw выводит пустые значения.((

  • Александр 24.02.2010

    Нашлось решение:
    отключение шага(Disable) и повторное включение помогло. Сборка была уже зарегистрирована с пустыми параметрами, по видимому значение бралось из кэша.:)

  • slivka_83 24.02.2010

    Спасибо 🙂 будем значть 🙂

*

code