14.02.2009, 14:05 | #1 |
Участник
|
mscrm4ever: CRM 4.0 Concatenating Fields Plug-In
Источник: http://mscrm4ever.blogspot.com/2009/...s-plug-in.html
============== A few weeks back I carried out a boot-camp training course to a team of dot net developers. I have to say it’s quite amusing to see the frustration on experienced developers faces when they realize the amount of energy one needs to invest in order to implement a requirement as simple as the one covered in this post. No doubt dynamics requires getting use to, but the benefits of using such a vast platform makes it all worthwhile. Many CRM implementations require you to construct a field’s value by concatenating other fields. There are hands full of reasons for implementing this type of functionality and Most have to do with ways data is displayed or what I call data usability. There are three common approaches which one can utilize. If you find yourself in that crossroad consider the pros and cons of each approach before you start developing. The first solution is attaching a JavaScript event handler to the CRM form OnSave event. This allows you to set the target field before the form is saved and data is actually sent to the server. There is no arguing about the easiness of this approach since it only requires a few lines to get this done. Here is an example which you can follow: function OnCrmPageLoad() { crmForm.attachEvent(“onsave”,OnCrmPageSave); } function OnCrmPageSave() { var attribute1Value = (crmForm.new_attribute1.DataValue)? crmForm.new_attribute1.DataValue:””; var attribute2Value = (crmForm.new_attribute2.DataValue)? crmForm.new_attribute2.DataValue:””; crmForm.new_targetattribute.DataValue = attribute1Value + “ ” + attribute2Value; } This method has obvious drawbacks which must be considered. One which is especially important is when the application needs to utilize clients other then Internet explorer such as excel when importing data or a web service when constructing data from code. Of course this is more of a general statement since writing client side code is never about a client but about all available ones. This is something many dynamics developers tend to oversee. The second approach is creating a workflow using the workflow designer. Although this provides a remedy to the dilemma presented above, the method is not bullet proof and has its own drawbacks. The first one has to do with the way the workflows operate. Since the workflow process is asynchronous the user won’t see the result concatenation until he reopens the record. A second disadvantage worth mentioning has to do with designer limitations. Consider a scenario where you need to concatenate the values of a lookup field and a text field. The workflow designer does not allow you to concatenate dynamic values which are not of the same type. This might convince you choose the third and final approach which is using a plug-in Although this might take longer to implement, planning and coding it once will no doubt save you plenty of time down the road. And since the following plug-in offers a generic solution you’ll get there even faster. The plug-in also offers a remedy to the workflow drawbacks mentioned above. Basically, in order to provide full coverage, the plug-in needs to run on both pre create and pre update events. The grand idea of using a pre event is to construct the target field before the platform does its main operation. The Pre create event is always the simple one since CRM transfers the entire field-set to the plug-in pipeline which only requires you to read the properties from the target entity, construct a concatenated target field (property) and amend the results back to the target entity property collection. The Pre update event requires you to also add a pre entity image. This is a prerequisite step since the platform update operations only carry fields that are changed and not the entire field-set. In order to make the process friendlier I added a configuration xml which enables you to configure multiple target fields for each entity and set the desired format for each target field. Bellow is a sample configuration xml. You must add it to the pre-create and pre-update unsecure configuration boxes. You might wonder why this is required for both events. The reason is that the plug-in needs to address both the creation of a new record and the update of existing records when it loads / cached and re-cached by CRM. This sample uses fields from the contact entity as a showcase. This is also the case for the plug-in solution xml file which is displayed at the bottom of the post. The Target node describes the field whose value is constructed from the inner Field nodes. The Target ID attribute must specify and existing attribute name. The plug-in code makes use of the String.Format method which receives a format string such as “{0} – {1}” and a list of optional parameters. Each Field Node must specify an existing entity attribute and correct Type. The Type attribute is obviously used to differentiate between CRM types. The Generic concatenation Plug-in is build on top of 3 classes. A Handler class which implements the IPlugIn Interface. A TargetField class which represent a single Target. And the ConcatPlugin proxy class which implements the concatenation process. I’ve also added remarks and regions to make the code more readable. using System; using System.Xml; using System.Collections.Generic; using System.Text; using Microsoft.Crm.Sdk; using Microsoft.Crm.SdkTypeProxy; namespace GenericConcatPlugIn { /// /// Plug-In Handler /// public class Handler : IPlugin { #region IPlugin Members /// /// Holds all fields that require concatenation /// private Dictionary Targets; /// /// plug-in constructor /// /// /// public Handler(String config, String secureConfig) { XmlDocument TargetsDocument = TryLoadTargetsXml(config); #region Build Targets Settings XmlNodeList TargetsNodeList = TargetsDocument.SelectNodes("//Target"); this.Targets = new Dictionary(TargetsNodeList.Count); foreach (XmlElement ndField in TargetsNodeList) { TargetField Target = new TargetField(ndField); if (this.Targets.ContainsKey(Target.ID)) { this.Targets[Target.ID] = Target; } else { this.Targets.Add(Target.ID, Target); } } #endregion } /// /// Attempt to load entity configuration xml /// /// /// private XmlDocument TryLoadTargetsXml(String configXml) { #region Validate Plugin Configuration if (configXml == null || configXml.Length == 0) { throw new InvalidPluginExecutionException("Initialize: Configuration Xml cannot be null"); } #endregion #region Return Configuratoin Document try { XmlDocument document = new XmlDocument(); document.LoadXml(configXml); return document; } catch (Exception ex) { throw new InvalidPluginExecutionException("Initialize: Error loading Configuration Xml Document", ex); } #endregion } /// /// Plug-in Execute implementation /// /// public void Execute(IPluginExecutionContext context) { #region Execute Concatenation Plugin ConcatPlugin contactPlugIn = new ConcatPlugin(); contactPlugIn.Context = context; contactPlugIn.Execute(Targets); #endregion } #endregion } /// /// A Field whose value is constructed (concatenated) from other fields /// public class TargetField { #region Target Field Members public String ID = String.Empty; public String Format = String.Empty; public Dictionary Related; #endregion public TargetField(XmlElement ndTarget) { #region Set Field Members this.Related = new Dictionary(); this.ID = ndTarget.Attributes["ID"].Value; this.Format = ndTarget.Attributes["Format"].Value; #endregion #region Set Field Related (Concatenated) Fields XmlNodeList RelatedFieldList = ndTarget.SelectNodes("Field"); foreach (XmlElement related in RelatedFieldList) { String relatedId = related.Attributes["ID"].Value; String relatedType = related.Attributes["Type"].Value; if (!Related.ContainsKey(relatedId)) { Related.Add(relatedId, relatedType); } } #endregion #region Sample Target Xml Configuration /* */ #endregion } } /// /// Plug-in proxy /// public class ConcatPlugin { /// /// Plug-in Original Context /// public IPluginExecutionContext Context; /// /// Plug-in Main method /// /// internal void Execute(Dictionary Targets) { #region Validate Target Entity if (!this.Context.InputParameters.Contains(ParameterName.Target)) { return; } #endregion #region Concatenate Target Fields foreach (KeyValuePair Target in Targets) { CurrentProperties.Add(ConcatProperty(Target.Value)); } #endregion } /// /// Reference to Target Dynamic Entity being created or updated /// private DynamicEntity Target { get { return this.Context.InputParameters[ParameterName.Target] as DynamicEntity; } } /// /// Reference to the Target Property Collection /// private PropertyCollection CurrentProperties { get { return this.Target.Properties; } } /// /// Reference to the Pre Image Property Collection /// private PropertyCollection PreEntityProperties { get { return ((DynamicEntity)this.Context.PreEntityImages[ParameterName.Target]).Properties; } } /// /// Concatenates the target fields and returns the a StringProperty /// /// /// private StringProperty ConcatProperty(TargetField Target) { List TargetValues = new List(); StringProperty TargetProperty = new StringProperty(); TargetProperty.Name = Target.ID; #region Retrieve Each Concatenated Field Value foreach (KeyValuePair related in Target.Related) { String fieldId = related.Key; String fieldType = related.Value; #region Get Field Value or Name by Type PropertyCollection Properties; if (!CurrentProperties.Contains(fieldId)) { Properties = PreEntityProperties; } else { Properties = CurrentProperties; } switch (fieldType) { case "String": TargetValues.Add(Properties[fieldId] + ""); break; case "CrmNumber": TargetValues.Add(((CrmNumber)Properties[fieldId]).Value + ""); break; case "CrmFloat": TargetValues.Add(((CrmFloat)Properties[fieldId]).Value + ""); break; case "CrmDecimal": TargetValues.Add(((CrmDecimal)Properties[fieldId]).Value + ""); break; case "CrmMoney": TargetValues.Add(((CrmMoney)Properties[fieldId]).Value + ""); break; case "Lookup": TargetValues.Add(((Lookup)Properties[fieldId]).name + ""); break; case "Owner": TargetValues.Add(((Owner)Properties[fieldId]).name + ""); break; } #endregion } #endregion TargetProperty.Value = String.Format(Target.Format, TargetValues.ToArray()); return TargetProperty; } } } Following are the plug-in solutionxml. After you construct the plug-in project put the file inside the project debug folder where the plug-in dll resides and import the solution using the registration tool. The solution xml contains step information for the contact pre create and update events as described above.
__________________
Расскажите о новых и интересных блогах по Microsoft Dynamics, напишите личное сообщение администратору. |
|
|
|