Rise to distinction

Time & tide wait for none

Category Archives: MS Dynamics CRM plugin execution pipeline internals

Integrate LOB data details in CRM without loading large data volumes (aka.. virtual entity 8.x onwards)

Synopsis

Every integration solution has its own set of requirements making it stand different from others. However more or less in almost all CRM integrations scenarios exist where data is migrated into CRM entities only to be available readonly irrespective of user’s constellations and privileges. One such usual requirement is details of sales from ERP to be made available in CRM. Sales details are typically large volume of records with further reference links within sales, for example end-user sales through distributors and retailers.

Scenario:

Users wish to see sales details, within CRM, where periodic sales aggregates are maintained on distributors (accounts). As part of data integration (periodic batch), the aggregates are kept in-sync, which are good enough for reporting needs within CRM. However, at times more curious audience wish to see details for sales within pre-defined periods e.g, weekly, fort-nightly, monthly, quarterly etc.

Model Diagram

Model diagram

Solutions:

Two typical soltuions for this problem are:

Data integration

  • Integrate sales details in custom entity referenced by specific distributor & periodic sales.

Custom UI

  • Custom pages / contorls with lists to show data from ERP, in CRM forms

No matter which of above is taken, this data is readonly for all CRM users, including system administrors.

An alternate solution with CRM out-of-box views

  • Power of Plugins & Business logic extensibility: I recently designed a simpler solution without any custom UI but a couple of plugins to address this scenario. This is a typical “lazy approach as a solution designer” 🙂 :

Model and UI

  1. Design a custom entity representing sales period for account, so it is N-1 to account. This entity has the attributes where data is integrated for aggregated sales by period for an account
  2. Design a custom entity representing sales details within a period, so it is N-1 to sales period. This entity has all those attributes which hold values in usual data integration solution. However in this solution data is not integrated into this entity.
  3. Adjust existing or create new security roles in which users can only read these entities (deep prefered)
  4. Add a subgrid of sales details in sales period form, preferred to create a custom view with needed column. Make sure to show only related records of sales period in subgrid.

Plugins

1. RetrieveMultiple: Write a retrieve multiple plugin & register on Post Operation stage (40) of RetrieveMultiple messsage on sales details entity. The theme of this plugin is

  • Intercept view query execution, take query from InputParameters
  • Fetch records from extenral system based on account and sales period
  • Fill EntityCollection in OutputParameters with data from external system to return to view

Sample code as below (not complete code):

            
if (executionContext.MessageName == "RetrieveMultiple")
{
    Guid periodId = Guid.Empty;
    if (executionContext.InputParameters.ContainsKey("Query"))
    {
        object inputParam = executionContext.InputParameters["Query"];
        QueryExpression query = inputParam as QueryExpression;
        if (query != null)
        {
            if (query.Criteria != null && query.Criteria.Conditions.Count > 0)
            {
                ConditionExpression condition = query.Criteria.Conditions.Where(item => item.AttributeName == "cst_periodid").FirstOrDefault();
                if (condition != null && condition.Operator == ConditionOperator.Equal && condition.Values != null && condition.Values.Count == 1)
                {
                    object value = condition.Values[0];
                    if (value != null)
                    {
                        periodId = new Guid(value.ToString());
                    }
                }
            }
        }
        //need to make sure that subgrid is being loaded for the parent record, this will thus skip if the query is not triggered for a specific account
        if (periodId != Guid.Empty)
        {
            if (executionContext.OutputParameters.ContainsKey("BusinessEntityCollection"))
            {
                object outputParam = executionContext.OutputParameters["BusinessEntityCollection"];
                EntityCollection entityCollection = outputParam as EntityCollection;
                if (entityCollection != null && entityCollection.EntityName == cst_sales.EntityLogicalName)
                {
                    //Here based on period, fetch its parent account and fetch sales details records from external system based on account-salesperiod combination
                    List list = new List();
                    for (int i = 0; i < 33; i++) 
                    { 
                        cst_sales mock = new cst_sales 
                        { 
                            cst_salesid = Guid.NewGuid(), //this id needs to be translated from record key of external system, the reverse transalation of this id will be performed when user clicks a record in view to load in CRM form, so in Retrieve message the external system can be queried based on those keys
                            cst_name = "Mock " + i, 
                            cst_DateTime = DateTime.Now, 
                            cst_WholeNumber = i + 1000, 
                            cst_OptionSet = new OptionSetValue((int)cst_salescst_OptionSet.Item2) 
                        }; 
                        if (periodId != Guid.Empty) 
                        { 
                            mock["cst_periodid"] = new EntityReference(cst_period.EntityLogicalName, periodtId) { Name = "Mock Period" }; 
                        } 
                        list.Add(mock); 
                    } 
                    if (query.PageInfo == null) 
                    { 
                        list.ForEach(item => entityCollection.Entities.Add(item));
                    }
                    else
                    {
                        int pageNumber = query.PageInfo.PageNumber;
                        int recordsPerPage = query.PageInfo.Count;
                        list.Skip((pageNumber - 1) * recordsPerPage).Take(recordsPerPage).ToList().ForEach(item => entityCollection.Entities.Add(item));
                        if (entityCollection.Entities.Count < recordsPerPage) 
                        { 
                            entityCollection.MoreRecords = false; 
                        } 
                        else if (list.Skip(pageNumber * recordsPerPage).Take(1).Count() > 0)
                        {
                            entityCollection.MoreRecords = true;
                        }
                    }
                }
            }
        }
    }
}

2. Retrieve: Write a retrieve plugin & register on both Pre & Post Operation stages (20, 40) of Retrieve messsage on sales details entity. The theme of this plugin is

  • In Pre-Operation:
    • Intercept record id, (of mock record as above), and put it into a shared vaiable
    • Read or Create (if not found) a “DUMMY” record of sales detail and set its guid in Target InputParameter.
      • This is needed because the Mock record (injected in view) does not exist in CRM which aborts the platform stage (30) of retrieve, thus not proceeding to PostOperation stage (40). Once system fetches dummy record in platform stage, the PostOperation stage step is also executed.
  • In Post-Operation
    • Look for shared variable as set in PreOperation stage as that is the guid of mock. Reverse translate this guid to keys of external system to fetch sales data.
    • Fill BusinessEntity in OutputParameters with data from external system to return to CRM form

Sample code as below (not complete code):

            
if (executionContext.MessageName == "Retrieve")
{
    //pre-operation of retrieve is needed to fetch a dummy record when user clicks a record in the view
                //during this stage a dummy record's guid (which exists in crm db) is set into target, 
                //so platform operation (i.e, stage 30) does not fail, if platform operation fails the post-operation of
                //retrieve is not executed
    if (executionContext.Stage == 20)
    {
        if (executionContext.InputParameters.ContainsKey("Target"))
        {
            object inputParam = executionContext.InputParameters["Target"];
            EntityReference target = inputParam as EntityReference;
            Guid mockTargetId = target.Id;
            if (target != null)
            {
                IOrganizationService systemService = customPluginContext.OrganizationServiceFactory.CreateOrganizationService(null);
                using (DataContext dataContext = new DataContext(systemService))
                {
                    Guid dummyId = Guid.Empty;
                    var dummy = dataContext.cst_sales.Where(item => item.cst_name == "DUMMY").Select(item => new cst_testentity { cst_testentityId = item.cst_testentityId }).FirstOrDefault();
                    if (dummy == null)
                    {
                        dummyId = Guid.NewGuid();
                        dummy = new cst_sales 
                        { 
                            cst_name = "DUMMY", 
                            cst_salesId = dummyId };
                            dataContext.AddObject(dummy);
                            dataContext.SaveChanges();
                        }
                    else
                    {
                        dummyId = dummy.Id;
                    }
                    target.Id = dummyId;
                    //target.Id = new Guid("43BAE32A-1247-E511-80EC-00155D008406");
                }
            }
            executionContext.SharedVariables.Add("MockTarget", mockTargetId);
        }
    }
    else if (executionContext.Stage == 40)
    {
        if (executionContext.SharedVariables.ContainsKey("MockTarget"))
        {
            object sharedVariable = executionContext.SharedVariables["MockTarget"];
            if (sharedVariable != null)
            {
                Guid mockObjectId = Guid.Empty;
                if (Guid.TryParse(sharedVariable.ToString(), out mockObjectId))
                {
                    //mockObjectId is the guid that we built for each record in the view
                    //this id needs to be built in a way that the value of external record could be extracted 
                    //and then extracted value could then be used to lookup record values from external source
                    if (executionContext.OutputParameters.ContainsKey("BusinessEntity"))
                    {
                        object outputParam = executionContext.OutputParameters["BusinessEntity"];
                        Entity entity = outputParam as Entity;
                        if (entity != null)
                        {
                            cst_sales sales = entity.ToEntity();
                            sales.cst_name = "Mock";
                            sales.cst_DateTime = DateTime.Now;
                            sales.cst_FPNumber = 133.55;
                            sales.cst_WholeNumber = 400;
                            sales.cst_DecimalNumber = new decimal(93.247);
                            sales.cst_MultiText = "This is multi-text mock";
                            sales.cst_OptionSet = new OptionSetValue((int)cst_testentitycst_OptionSet.Item2);
                            sales.OwnerId = new EntityReference(SystemUser.EntityLogicalName, executionContext.InitiatingUserId);
                        }
                    }
                }
            }
        }
    }
}

Sequence of invokation

Sequence of call flow for loading LOB details

Sequence of call flow for loading LOB details

Conclusions

  • Dynamics CRM is purely model driven solution, however once an entity is defined in model, RetrieveMultiple & Retrieve messages can be hooked to show data that does not exist in CRM database.
  • Due to total serverside supported approach, this solution leverages CRMs out-of-box views to show data in UI
  • Further building on this approach it should be possible to show charts & dashboards, based data that is only available in connected LOB systems, CRM only needs to hold referencing keys of that data.
  • It usualy pays to “lazy load” the data :). Only query when it is really needed.
  • Avoid over-killing your data integration scheme

Improvements

  • Looking for a way to avoid translation to guid and reverse translation to source keys
  • Avoid reading dummy record in PreOperation, and cache it somehow
  • Implement paging in such a way that external system only returns the data for page instead of paging in plugin

Hope you find it of some use for your implementation scenarios!!

Identify usage of custom activities in workflows and dialogs

Synopsis

Some time back, I needed to find usage of custom workflow acitivies across all workflows and dialogs. Easiest out-of-box way was to click through each type on each plugin assembly (in customizations) and look for dependencies. Thats tedious and in-effective

Solution

Usage and dependency of custom workflow activities in workflows and dialogs of MS Dynamics CRM

To make my job easier, I came up with simple fetch XML as below to get desired results. One can easily build this in a report:

<fetch mapping="logical" count="5000" version="1.0">
  <entity name="workflow">
     <attribute name="name"/>
     <attribute name="category"/>
     <link-entity name="dependency" from="dependentcomponentobjectid" to="workflowid" alias="dep">
          <link-entity name="plugintype" from="plugintypeid" to="requiredcomponentobjectid" alias="pt">
               <attribute name="assemblyname"/>
               <attribute name="description"/>
               <attribute name="friendlyname"/>
               <attribute name="name"/>
               <attribute name="typename"/>
               <attribute name="workflowactivitygroupname"/>
               <attribute name="isworkflowactivity"/>
          </link-entity>
     </link-entity>
  </entity>
</fetch>

 

 

Hope this helps!!!

List plugins steps registrations in a CRM report

Synopsis

Got too many plugins & steps registered ? Getting ever too confusing to find out in one single view ? Plugin registration tool not serving the purpose ?

Solution

Custom plugins and steps registrations report

Custom plugins and steps registrations report

Build a s simplest possible report, all supported, using fetch XML as below:

<fetch mapping="logical" count="5000" version="1.0">
  <entity name="sdkmessageprocessingstep">
     <attribute name="sdkmessageprocessingstepid"/>
     <attribute name="name"/>
     <attribute name="description"/>
     <attribute name="eventhandler"/>
     <attribute name="impersonatinguserid"/>
     <attribute name="supporteddeployment"/>
     <attribute name="mode"/>
     <attribute name="rank"/>
     <attribute name="stage"/>
     <attribute name="statuscode"/>
     <attribute name="statecode"/>
     <attribute name="sdkmessagefilterid"/>
     <attribute name="filteringattributes"/>
     <attribute name="configuration"/>
     <attribute name="createdon"/>
     <attribute name="modifiedon"/>
     <link-entity name="systemuser" from="systemuserid" to="impersonatinguserid" alias="impersonateduser" link-type="outer">
        <attribute name="domainname"/>
     </link-entity>
     <link-entity name="sdkmessagefilter" from="sdkmessagefilterid" to="sdkmessagefilterid" alias="messagefilter" link-type="outer">
        <attribute name="secondaryobjecttypecode"/>
        <attribute name="primaryobjecttypecode"/>
        <attribute name="availability"/>
     </link-entity>
     <link-entity name="sdkmessage" from="sdkmessageid" to="sdkmessageid" alias="message" link-type="outer">
        <attribute name="name"/>
     </link-entity>
     <link-entity name="sdkmessageprocessingstepsecureconfig" from="sdkmessageprocessingstepsecureconfigid" to="sdkmessageprocessingstepsecureconfigid" alias="secureconfig" link-type="outer">
        <attribute name="secureconfig"/>
     </link-entity>
     <link-entity name="plugintype" from="plugintypeid" to="plugintypeid" alias="plugintype">
        <attribute name="assemblyname"/>
        <filter>
           <condition attribute="isworkflowactivity" operator="eq" value="0"/>
           <condition attribute="customizationlevel" operator="eq" value="1"/>
        </filter>
     </link-entity>
  </entity>
</fetch>

Some formatting will be required in report columns as filtering attributes and steps configurations could be quite long.

Hope this helps!!!

Tracing Plugins & Custom Workflow Activities in D365 – identify execution chains and much more

Deep into D365 plugins & async execution pipeline, when developers get puzzled with questions “why update of a single record field, triggers much more than needed business logic”, this framework for writing plugins and custom work flow activities can come of use.

What can this solution achieve

This solution is in place in production system for over 6 months at the time I first published this (i.e since September 2014).  I shall begin with first signifying its benefits:

  1. It provides detailed logging of plugin or custom workflow activity execution. Log is saved as a custom entity record with additional tracing details as attached notes.
  2. I implemented this long before Microsoft released Plugin Trace Log in 2015, and it provides much more details than trace logging. However it requires plugin & custom workflow activity code implement a base class as provided in framework for download at the end.
  3. When it is needed to get inner details of plugin execution context, register an empty logic plugin and step on the required message, entity and stage. Complete plugin context shall be serialized as a note attachment on plugin execution.
  4. In case of production system, where remote debugger cannot be attached due to security restrictions, this facility comes in very handy to locate errors in plugins.
  5. CRM Developers usually throw deliberate exception from plugin code to see the trace. This requires changing code. With this facility one just needs to tracing service and look at the execution log records for the concerned plugin
  6. The solution is intended for getting deeper insight of  MS Dynamics CRM execution pipeline, plugins and custom workflow activities. So it is best understood by individuals having handsome knowledge of these features in CRM.
  7. This goes without saying that this solution helped us identify and fix some scenarios that greatly improved performance so we could identify unexpected plugin chains as also posted here:

Plugins chain and usage of SYSTEM context

Unexpected triggering of update plugins on Appointment Completion / Cancellation

Avoid update plugin steps triggering during Assign request

 Added with solutions ver. 1.0.0.1

If you have followed this post before, I have added some additional trace logging features to this facility.

1. Is WF Activity — if the custom logic is a workflow activity this flag will be set in trace log

2. Link to workflow/dialog — similarly lookup will be set to the workflow / dialog which invoked workflow activity

3. A url in trace log message (text area & note TraceMessages.log) to the async job or dialog session of the workflow of dialog instance, which invoked workflow activity. This cannot be built as an entity relation.

Synopsis

In one of my recent projects, I needed a facility to see details of plugins’ executions and moreover being able to see these details in the exact sequence in which each plugin-step triggered in execution pipeline. The closest I could get, with out of the box tools was to attach profiler on individual plugin steps in registration tool, but that could not suffice my requirements.

My concerning system is a fairly large and customized MS Dynamics CRM 2011 implementation (1700+ Sales, Marketing and Service users, 1.5 million accounts & contacts from 30+ business units), with following salient features:

  1. 120+ custom entities encompassing the famous 3 sub-verticals: Sales automation, Marketing and Customer Service.
  2. Team ownership and sharing of core entities across business units hierarchy.
  3. Point to point integration with  corporate ERP system.
  4. Plugins implemented for outbound data from CRM.
  5. Middleware solution for inbound data from ERP consuming CRM webservices.
  6. More than 150+ plugins and steps registered on various entities.
  7. Most of update message steps registered  with Filtering set to “All attributes”.
  8. Some business logic also implemented as workflows and dialogs.

System’s behavior

Best explained as: “Unpredictable”, “Indeterministic”

  1. For some use-cases a plugin would execute in SYSTEM’s context while at times, same plugin would execute in another user’s context for same use-cases. This threw unprecedented security errors, becoming increasingly difficult to reproduce.
  2. In some instances “Ping-Pong” effects (i.e, cyclic data updates between CRM and ERP) were identified in accounts audit history.
  3. Some data updates triggered workflows, which due to asynchronous nature, executed silently but further triggered plugins.

Solution

An ideal solution would be to re-design integration interfaces and control the plugins chains execution smartly. This is obviously not practical, however even to fix existing implementation, a realtime overview of execution pipeline is what i needed. Something similar to OOTB “System Jobs” view, with data from execution context (of plugin or custom activity).

Requirements

I came up with following high-level requirements for this solution

  1. It should be possible to view details of plugins and custom workflow activities executions in a usual CRM view.
  2. From the view it should be possible to identify 1 or more executions which triggered within same request.
  3. It should be possible to see trace messages from each execution.
  4. It should be possible to see which execution failed with errors and error details. At the moment errors thrown from plugins (executed due to inbound information from ERP) can only be seen in the source system (middleware).
  5. It should be possible to enable/disable logging feature within CRM’s usual interface. Plugin profiler tool requires special permissions and requires more technical knowledge in order to do profiling and does not allow to profile custom workflow activities.
  6. It should be possible to see full execution context passed to plugin / custom workflow activity by the pipeline.

CRM Views

Something like this is then available in CRM views

Extended Execution Log View

Extended Execution Log View

While the following information is available on execution log’s form

Extended Execution Log Form 1

Extended Execution Log Form 1

While following notes are also available in the form

Extended Execution Log Form 2

Extended Execution Log Form 2

Design

Step 1

I started with designing two custom Organization Owned entities “Configuration Key” & “Extended Execution Log”.

Configuration Key — this is a simple entity with key, value & description. A simple key named “ExtendedLoggingEnabled” holding value “1, yes, y or true” (Case Insensitive) will enable the logging

Extended Execution Log — This entity holds the log of executions

Entity is enabled for “Notes”

  1. Correlation Id — String; Guid value, key to group custom logic executions triggered due to one request to CRM
  2. Sequence — Whole number; millisecond of the day when custom logic executed
  3. Message — String; all messages written to tracingservice during code (TracingService.Trace(“message”);
  4. Initiating User — Lookup; Initiating user from execution context
  5. Name — String; Name of custom logic code component, class file name etc
  6. Message Name — String; MessageName from execution context which triggered the plugin
  7. Primary entity — String; name of primary entity from execution context
  8. Secondary entity — String; name of secondary entity from execution context
  9. Operation CreatedOn — DateTime; OperationCreatedOn from execution context to plugin
  10. Operation StartedOn — DateTime; Exact time when code logic actually started (typically before execute method is invoked)
  11. Operation CompletedOn — DateTime; Exact time when code logic completed (typically after execute method)
  12. Error in execution — Yes / No; if logic execution failed, value is set to Yes
  13. Error message — String; Exception / Error details
  14. Is WF Activity — Yes / No; Set if this is a custom wf activity (added with solutions ver. 1.0.0.1)
  15. Workflow/Process — Lookup; Workflow/Dialog triggering custom activity, if it is wf activity (added with solutions ver. 1.0.0.1)

Step 2

Time to write some utility library code :=). The facilities (API) from library are used in base classes for plugins and custom activities. Basically we need to achieve following:

  • An abstract base class for plugins (that implements IPlugin) and allows derived plugins to override OnExecute
  • An abstract base class for custom workflow activity (that extends CodeActivity) and allows derived activities to override OnExecute
  • Base classes inject custom interfaces into OnExecute methods which expose required details for implementing plugins / custom activities
  • The injected custom interfaces are known to library’s IExecutionRecorder which exposes methods to log the execution in the custom entity. Base classes make use of IExecutionRecorder
  • Provide following extended execution context interfaces which are injected by base classes into implementing classes
    • ICustomExecutionContext
    • ICustomPluginContext
    • ICustomWorkflowContext
  • Extend ITracingService interface as IRecordableTracing so base classes inject its implementation into implementing derived plugins / custom activities
  • Implement following serializable extensions of IExecutionContext. These implementations are only used internally by ExecutionRecorder in order to serialize execution context
    • TracingExecutionContext
    • TracingPluginContext
    • TracingWFContext
  • Being an interface driven approach, i made use of these facilities in existing base plugins class.

Solution Package

And here are the details of attached package:

1.   There are three managed solutions, each for 2011, 2013 & 2015.

(Note: Remember to import using Deployment Admin user).

Managed Solutions for 2011, 2013, 2015

Managed Solutions for 2011, 2013, 2015

2.   Deployed assemblies — view from plugins registration tool

  1. CRM.Toolkit.Plugins.Merged — demo plugins assembly created using MS CRM developer toolkit in VS 2012. Plugins are registered on entity + message + stage combination as also programmed in plugin class.
  2. CRM.Toolkit.Workflow.Merged — custom wf activities assembly created using MS CRM developer toolkit in VS 2012. Contains a demo custom wf activity that fetches user by domain name. It is used in a workflow process that is part of managed solution.
  3. CustomPluginsLib.Merged — a C# class library with two demo plugins classes. These plugins can be registered on any message on any entity i.e, not restricted by plugin code. One plugin throws deliberate exception.
Plugins and WF Activities Assemblies

Plugins and WF Activities Assemblies

3.   Visual Studion 2012 solution folder structure

  1. CRM.Models — C# class library to hold CRM early bound proxy classes and helper code
  2. CrmPackage — MS CRM developer toolkit deployment folder
  3. CustomPluginsLib — C# class library for custom plugins i.e, not created by toolkit’s utility.
  4. Library — solution folder where all required dlls / executables (for build etc) are placed. Also contains 3 framework dlls.
  5. Toolkit.Plugins — MS CRM developer toolkit plugins project. In order to see how execution recorder is used, look at how i changed LocalPluginContext & Plugin (base class) generated by toolkit. There is no change required in derived plugin classes
  6. Toolkit.Workflows — MS CRM developer toolkit custom wf activities project

Note: for 3, 5, 6 above, solution’s postbuild event makes sure to ILMerge required dlls to create merged plugin / wf activities dll for deployment. Framework only uses late bound classes, so implementing assemblies can generate and provide own namespace to early bound classes, this keeps the final dll from any types ambiguity during runtime.

VS 2012 Solution Projects and Folders

VS 2012 Solution Projects and Folders

4.   Contents of library folder (as in 3 above):

Following dlls comprise the framework (rest are SDK dlls etc)

  1. CRM.Common.Utils.dll — Contains interfaces, and concrete implementations of execution recorder, rerordable tracing, and a classes to serialize execution context into XML
    Following is the class diagram from utility library:
    CRM.Common.Utils Class Diagram
  2. CRM.Common.Plugins.dll — Contains plugins base class (extended as an example in CustomPluginsLib) and a concrete implementation of ICustomPluginContext (injected into derived classes)

    Classes in base plugins library

    Classes in base plugins library

  3. CRM.Common.WFs.dll — Contains wf activities base class (extended as an exmaple in Toolkit.Workflows) and a concrete implementation of ICustomWorkflowContext (injected into derived classes)

    Classes in base wf activities library

    Classes in base wf activities library

Note: I have kept base classes for plugins and wf activities in separate assemblies because I like clear separation of framework assembly names based on responsibilities. I also like to keep separate assemblies for derived plugins and wf activities. These two type of components should also be kept apart in different assemblies for ease of maintenance and deployment.

Library folder contents

Library folder contents

Download the zip package for this implementation here:

How to see it working

1.   After importing solutions, refresh and go to Settings area to create a Configuration Key entity with Key=ExtendedLoggingEnabled and Value=yes, y, 1, or true (case insensitive) in order to start tracing.

2. Define another Configuration Key as Key=OrgBaseUrl and Value=http://host:port/orgname. With this key defined, a full url to async job or dialog session will be available in trace message e.g, http://win-hov9h4ng751:5555/ahad/sfa/workflowsession/edit.aspx?id=737aef28-48cc-e411-90a0-080027199d20. (added with solutions ver. 1.0.0.1)

2.   Create a record of Test Entity from Settings area.

3.   Tracing will appear in Extended Execution Logs view in Settings area. Open trace and look into details and notes.

4.   Using plugin registration tool, register some steps on supported messages of any other entity on AnyMessageOnEntityPlugin class in CustomPluginsLib.Merged assembly.

Hope this helps you debug with much ease!!

Finally, zip package as described above, can be downloaded here:

Download Extended Logging.zip

Updates
November 23, 2015

Adjusted context serialization, which is attached as an XML (log file) note to the Logging record. In one of the online CRM 2015, the context Context serialization using .Net’s out-of-box was throwing security exception. This was because plugins could only be registered in sandbox. With custom serialization, I have made sure that all needed properties of whole context object graph are serialized. The custom serialization is done only if out-of-box serialization throws exception. Latest CRM solution version is 1.0.0.2. Also VS 2012 Solution has CRM.Common.Utils.dll which holds serialization logic

Conclusions

1.   Plugins are a powerful feature in CRM, however with power also comes responsibility. Know what your plugin is doing, and part of that knowledge also means to be aware of what events actually trigger plugins and what is passed in context

2.   Make use of SYSTEM user carefully, as that might give varying output if subsequent plugins make use of properties and relations of initiating user.

3.   Workflows execution could also trigger plugins that might not be required. So make necessary checks in plugins for such situations. Business users can create workflows, but likely not write plugins ;-).

4.   Throw meaningful exceptions from plugin code. This goes without saying that code should be written with some basic etiquttes in place to take care of errors like, Null Reference, Key Not Found, Duplicate Key and more 🙂

Generate smaller CRM plugins dll using LibZ than ILMerge

Synopsis:

In this post i am sharing my experience on why and how to use assemblies embedding capability of LibZ as compared to merging of ILMerge. Where most of us are very well aware of ILMerge, LibZ is likely not that commonly used for as an alternate. However my case to commend LibZ is not just based on assembly size.

Scenario: With a heavily custom developed plugins solution any horizontal change implemented at the framework level risks breaking complete solution. ILMerge is a commonly used tool to get single assembly for deployment, out of merging dependents (including CRM early bound proxy assembly). However, just recently i needed to merge another framework into our existing plugins dll.

Problem: The additional framwork also uses early bound proxy classes of OOTB crm entities for some common plugins. It did not give any problems during compile time and things seemed fine as ILMerge did not complain either. However at runtime plugins broke throwing early bound proxy types error during deserialization, as below:

The Web Service plug-in failed in OrganizationId: 
SdkMessageProcessingStepId: 5afc1b7f-09e8-e311-b0c2-005056b0000b; 
EntityName: appointment; Stage: 40; MessageName: SetStateDynamicEntity; 
AssemblyName: Core.Plugins.Entity_Appointment.BusinessPlugin, Core.Plugins.Merged, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7e7c44ffd3e5c71d; 
ClassName: Core.Plugins.Entity_Appointment.BusinessPlugin; 
Exception: Unhandled Exception: System.ArgumentException: A proxy type with the name account has been defined by multiple types. 
Current type: AdditionalFramework.Entities.Account, Core.Plugins.Merged, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7e7c44ffd3e5c71d, 
Existing type: Core.Models.Entities.Account, Core.Plugins.Merged, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7e7c44ffd3e5c71d
Parameter name: account
 at Microsoft.Xrm.Sdk.AssemblyBasedKnownProxyTypesProvider.AddTypeMapping(Assembly assembly, Type type, String proxyName)
 at Microsoft.Xrm.Sdk.KnownProxyTypesProvider.LoadKnownTypes(Assembly assembly)
 at Microsoft.Xrm.Sdk.KnownProxyTypesProvider.RegisterAssembly(Assembly assembly)
 at Microsoft.Xrm.Sdk.AssemblyBasedKnownProxyTypesProvider.GetTypeForName(String name, Assembly proxyTypesAssembly) 
at Microsoft.Xrm.Sdk.ProxySerializationSurrogate.System.Runtime.Serialization.IDataContractSurrogate.GetDeserializedObject(Object obj, Type targetType)
at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserializeWithSurrogate(XmlReaderDelegator xmlReader, Type declaredType
, DataContract surrogateDataContract, String name, String ns)
at System.Runtime.Serialization.DataContractSerializer.InternalReadObject(XmlReaderDelegator xmlReader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.XmlObjectSerializer.ReadObject(XmlDictionaryReader reader)
at Microsoft.Crm.Extensibility.InprocessServiceProxy.ConvertToEarlyBound[T](Object o)
at Microsoft.Crm.Extensibility.InprocessServiceProxy.RetrieveCore(String entityName, Guid id, ColumnSet columnSet)
at Microsoft.Xrm.Sdk.Client.OrganizationServiceProxy.Retrieve(String entityName, Guid id, ColumnSet columnSet)
at Core.Plugins.BusinessLogic.AppointmentEntity.BusinessLogic(EntityReference appointmentReference, Appointment preAppointment)
at Core.Plugins.Entity_Appointment.BusinessPlugin.Execute(LocalPluginContext localContext)
at Core.Plugins.Common.Plugin.Execute(IServiceProvider serviceProvider)
at Microsoft.Crm.Extensibility.V5PluginProxyStep.ExecuteInternal(PipelineExecutionContext context)
at Microsoft.Crm.Extensibility.VersionedPluginProxyStepBase.Execute(PipelineExecutionContext context)

In this case existing proxy assembly is Core.Model.Entities.dll which is merged into Core.Plugins.Merged.dll asembly. While the additional framework assembly is AdditionalFramework.Entities.dll, also being merged into Core.Plugins.Merged.dll assembly. Error is specifically thrown from the first call to casting Entity into a specific early bound proxy type.

Solution: I tried forcing proxy assembly in OrganizationServiceFactory instance using a class type from proxy assembly (as described here: http://www.datazx.cn/Forums/en-US/bc7e93d4-1b36-4e21-9449-f51b67a2e52c/action?threadDisplayName=crm-2011-online-early-binding-plugin&forum=crmdevelopment), but of no use.

The reason this could not work is the way ILMerge works. Due to merge, container assembly of all types resolved to Core.Plugins.Merged.dll, even though types were defined in separate assemblies being merged. That brought to the situation where merged assembly contained two types for each of OOTB proxy entities. Though one should ideally have single proxies assembly but in this scenario that meant significant changes in existing code or the framework.

I still needed a solution with one dll with proxies available from both underlying assemblies. ILMerge’s internalize could not suffice here, with having its own risks attached. So, any alternates to ILMerge ? Luckily LibZ rescued here (https://libz.codeplex.com many thanks to the project team), “embedding” instead of “merging“. This worked quite well though i have to enfornce proxy assembly on OrganizationServiceFactory, as described in the link above. There is some housekeeping to do when using DataContext from additionalframework proxies, code must enforce correct proxy in OrganizationServiceFactory.


IOrganizationServiceFactory organizationServiceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
TracingService.Trace("setting proxy behavior ...");
organizationServiceFactory.GetType().GetProperty("ProxyTypesAssembly").SetValue(organizationServiceFactory, typeof(DataContext).Assembly, null);
TracingService.Trace("... proxy behavior set");

Fact of the matter is, size of the resulting assembly reduced from 6.33 MB to 1.57 MB, which took away problems like timeouts during plugins deployment and out-of-memory errors. Secondly, when using crm toolkit, the register file does not need to be manually modified for newly added plugin, as the output assembly name of plugins project remains same. Though this is not that significant, still a value add!!!


REM -- adding a little bit of post build event script
REM -- just needed changing some lines from ILMerge to LibZ here
set LIBZ=$(SolutionDir)packages\LibZ.Bootstrap.1.1.0.2\tools\libz.exe
%LIBZ% inject-dll -a "$(TargetDir)$(TargetName).dll" -i "$(TargetDir)Core.Models.dll" -i "$(TargetDir)Core.Repositories.dll" -i "$(TargetDir)AdditionalFramework.Entities.dll"
REM -- copy plugins dll to toolkit packaging project 
copy "$(TargetDir)$(TargetName).dll" "$(SolutionDir)Core.Plugins\CrmPackage\$(OutDir)"

Last but not the least, CRM custom development frameworks should use late bound types instead of early bound proxy types. Does this scenario make a good case to commend that ?

Unexpected triggering of update plugins on Appointment Completion / Cancellation

Synopsis:

In one of my previous posts here: (‘Avoid update plugin steps triggering during Assign request’), i identified a scenario on unexpected continuation of plugins. Continuing on the same course on critical diagnosis of plugins pipeline, I have come across yet another similar situation.

Scenario: Appointment is closed or cancelled by a user. This triggers “SetStateDynamicEntity” message in pipeline on appointment instance. Custom business logic is implemented for plugin(s) on this message. In addition there are plugin(s) registered on appointment-update message. Not a recommended and yet overlooked deployment consideration in plugins design: not deciding correct filtering attributes — so registration is set to “All attributes”.

Problem: Update message plugins on appointment entity are also triggered when user closes the appointment.

Solution: I have implemented, a framework feature in our plugins solution, so after each execution of any plugin, diagnostic details of execution are logged in a custom entity:

  • Full plugin trace (every trace message done using tracingService.Trace(“”)), so developer does not need to throw deliberate exception to see the trace
  • Context received by plugin
  • Error details (if execution failed)
  • Plugin class name
  • Pipeline message and stage
  • Primary & Secondary entity
  • Correlation Id, so one can identify which plugins triggered during one request
  • Source of request (Async, WebService, Application) i.e, CallerOrigin
  • Time taken by each plugin execution
  • Calling user

With these diagnostic details, i have managed to make a number of improvements in a number of existing plugins.

In this specific scenario, i found following context received in update message step (after appointment is closed). For simplicity i have removed the namespaces URIs and qualifiers.

<BusinessUnitId>e603c74c-2a2a-e411-a702-005056b0000b</BusinessUnitId>
<CorrelationId>616b7cc4-236e-4d7b-ae00-f3eb4612335d</CorrelationId>
<Depth>1</Depth>
<InitiatingUserId>66b6698c-41be-e311-9d0e-005056b0000b</InitiatingUserId>
<InputParameters>
   <KeyValuePairOfstringanyType>
      <key>Target</key>
      <value type="Entity">
         <Attributes>
            <KeyValuePairOfstringanyType>
               <key>activityid</key>
               <value>60110d55-bdcc-e311-b40b-005056b0000b</value>
            </KeyValuePairOfstringanyType>
            <KeyValuePairOfstringanyType>
               <key>statecode</key>
               <value type="OptionSetValue">
                  <Value>1</Value>
               </value>
            </KeyValuePairOfstringanyType>
            <KeyValuePairOfstringanyType>
               <key>statuscode</key>
               <value type="OptionSetValue">
                  <Value>3</Value>
               </value>
            </KeyValuePairOfstringanyType>
            <KeyValuePairOfstringanyType>
               <key>modifiedon</key>
               <value>2014-09-28T22:27:12Z</value>
            </KeyValuePairOfstringanyType>
            <KeyValuePairOfstringanyType>
               <key>modifiedby</key>
               <value type="EntityReference">
                  <Id>66b6698c-41be-e311-9d0e-005056b0000b</Id>
                  <LogicalName>systemuser</LogicalName>
                  <Name nil="true"/>
               </value>
            </KeyValuePairOfstringanyType>
            <KeyValuePairOfstringanyType>
               <key>modifiedonbehalfby</key>
               <value nil="true"/>
            </KeyValuePairOfstringanyType>
            <KeyValuePairOfstringanyType>
               <key>timezoneruleversionnumber</key>
               <value type="int">0</value>
            </KeyValuePairOfstringanyType>
            <KeyValuePairOfstringanyType>
               <key>actualdurationminutes</key>
               <value type="int">30</value>
            </KeyValuePairOfstringanyType>
            <KeyValuePairOfstringanyType>
               <key>actualend</key>
               <value type="dateTime">2014-09-28T23:33:07Z</value>
            </KeyValuePairOfstringanyType>
         </Attributes>
         <EntityState nil="true"/>
         <FormattedValues/>
         <Id>60110d55-bdcc-e311-b40b-005056b0000b</Id>
         <LogicalName>appointment</LogicalName>
         <RelatedEntities/>
      </value>
   </KeyValuePairOfstringanyType>
</InputParameters>
<IsInTransaction>true</IsInTransaction>
<IsOfflinePlayback>false</IsOfflinePlayback>
<IsolationMode>1</IsolationMode>
<MessageName>Update</MessageName>
<Mode>0</Mode>

Besides above, the update message context also received information of parent context i.e, from the “SetStateDynamicEntity” message on appointment.

<Parent>
   <BusinessUnitId>e603c74c-2a2a-e411-a702-005056b0000b</BusinessUnitId>
   <CorrelationId>616b7cc4-236e-4d7b-ae00-f3eb4612335d</CorrelationId>
   <Depth>1</Depth>
   <InitiatingUserId>66b6698c-41be-e311-9d0e-005056b0000b</InitiatingUserId>
   <InputParameters>
      <KeyValuePairOfstringanyType>
         <key>EntityMoniker</key>
         <value type="EntityReference">
            <Id>60110d55-bdcc-e311-b40b-005056b0000b</Id>
            <LogicalName>appointment</LogicalName>
            <Name nil="true"/>
         </value>
      </KeyValuePairOfstringanyType>
      <KeyValuePairOfstringanyType>
            <key>State</key>

            <value type="OptionSetValue">
               <Value>1</Value>
            </value>
      </KeyValuePairOfstringanyType>
      <KeyValuePairOfstringanyType>
         <key>Status</key>
         <value type="OptionSetValue">
            <Value>-1</Value>
         </value>
      </KeyValuePairOfstringanyType>
   </InputParameters>
   <IsExecutingOffline>false</IsExecutingOffline>
   <IsInTransaction>true</IsInTransaction>
   <IsOfflinePlayback>false</IsOfflinePlayback>
   <IsolationMode>1</IsolationMode>
   <MessageName>SetStateDynamicEntity</MessageName>
   <Mode>0</Mode>
</Parent>

The context above showed update steps triggering on “SetStateDynamicEntity” message for appointment entity, which was triggering update message business logic, not needed in this case. Removing following filtering attributes from update step did the trick:

  • activityid
  • statecode
  • statuscode
  • modifiedon
  • modifiedby
  • modifiedonbehalfby
  • timezoneruleversionnumber
  • actualdurationminutes
  • actualend

Though the last two attributes could still be needed in filtering attributes, as per designed business logic.

Conclusion: Always carefully decide filtering attributes as per the business logic implemented in plugin. This greatly helps reducing overall system complexity.

Avoid update plugin steps triggering during Assign request

Synopsis:

I recently got a chance to work on an MS CRM 2011 implementation with significantly complex security roles and team based ownerhsip of business entities. The solution had been developed over a period of 2-3 years without much technical documentation, (Classic Problem). With too many plugins & steps registrations and webservice request taking much longer to execute, there was enough reason to take deeper dive into execution of plugins in synchronous pipeline. Fortunately out-of-box audit and some custom logging in plugins helped finding more than just a few unnecessary plugins execution. During the course of resolving these problems, i gathered following knowledge to share:

Scenario: Ownership being transferred to teams using plugins which at times took much longer to complete the assign request.

Observation: Assign request cascaded from primary to related entities. Thus every record of every related entity in “Assign: Cascade” behavior also received assign request.

Solution: This does not necessarily mean that relationship is configured incorrect, however it requires proper analysis & design of any custom business logic around. This includes plugins, workflows & dialogues, custom integration logic & batch processes etc.

Scenario: Plugins registered on update message steps executed on an entity being assigned to user/team.

Observation: Assign request triggered update message in the execution pipeline on the respective entity. This triggered all custom plugins registered on the message, and to add to our misery most of update plugin registrations were configured with “All Attributes” as “Filtering Attributes”. Thus unnecessarily stretching the request execution though non of the plugins really needed to execute on Assign message. (Combine this scenario with the previous one and imagine each and every update step execution on every instance of related entity with “Assign: Cascade” behavior.)

Solution: Correct use of “Filtering Attributes” provide the solution. As a best practice, always give good thought & design effort to “Filtering Attributes” during plugin registration on “Update” message. However still remember, it is not necessary that only changing the value of an attribute would trigger the step execution. Even if the value of an attribute remains unchanged during update request, however attribute is touched during request, update plugin step will execute if the specific attribute is one of “Filtering Attribute”. To avoid update step triggering as a result of an Assign request, I excluded those Filtering Attributes, which are TOUCHED during Assign request. So excluding the following list of attributes from filtering attributes fixed the problem.

      • modifiedby
      • modifiedonbehalfby
      • modifiedon
      • ownerid
      • owningbusinessunit
      • owningteam
      • owninguser
      • <<entityname>>id

 

filtering attributes to avoid update step trigger on assign

filtering attributes to avoid update step trigger on assign

Expected non-empty Guid Error when using OutArgument

Wonder why you have come across following exception in your custom activity execution.

Error:

Workflow paused due to error: Unhandled Exception: System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version=5.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxxxx]]: Expected non-empty Guid.Detail:
-2147220989
Expected non-empty Guid.
2012-07-28T23:38:16.1998292Z
-2147220970
System.ArgumentException: Expected non-empty Guid.
Parameter name: id
2012-07-28T23:38:16.1998292Z
[Microsoft.Xrm.Sdk.Workflow: Microsoft.Xrm.Sdk.Workflow.Activities.RetrieveEntity]
[RetrieveEntity]
Entered FindActiveSubscription.Execute(), Activity Instance Id: 75, Workflow Instance Id: 4824f47e-591a-4393-af02-e3cb9decc427
FindActiveSubscription.Execute(), Correlation Id: 914f2e28-b555-4812-b0f2-a5c515d26f0f, Initiating User: 5ee8d8e7-45b1-e111-9c7f-00155d013820
Exiting FindActiveSubscription.Execute(), Correlation Id: 914f2e28-b555-4812-b0f2-a5c515d26f0f

Synopsis:
While working with workflows, there are situations when one has to retrieve an entity using a custom criteria which is not possible to be built in out-of-box workflows user interface. For example in a most recent situation, i needed to fetch the latest active subscription of a contact based on its membership status. Cutting long story short i used a codeactivity to find the needed instance of subscription entity “new_subscription”, so i defined an OutputArgument

[Output("The related subscription")]
[ReferenceTarget(new_subscription.EntityLogicalName)]
public OutArgument<EntityReference> RelatedSubscription { get; set; }

Problem:
Then in the Execute method one would naturally only set RelatedSubscription if there could be found one such in CRM. However, workflow engine requires this to be set to a non-empty Guid value. Even with the latest rollup it is not able to gracefully handle a nullable situation.

Solution:
Set to a new Guid value as below:

IList<new_subscription> list = <<Whatever fetched from system>>;
if (list.Count > 0)
{
     RelatedSubscription.Set(executionContext, new EntityReference(new_subscription.EntityLogicalName, list[0].Id));
}
else
{
     RelatedSubscription.Set(executionContext, new EntityReference(new_subscription.EntityLogicalName, Guid.NewGuid()));
}

Then after execution of this activity in workflow, one just needs to enclose the dependent business logic steps in a check condition such as:

Note: Find Active Subscription is the name of step where custom activity is executed
so after that a check condition could look like:
"Find Active Subscription:Related Subscription" "Subscription" "Contains Data"

With this approach i managed to fix all such situations where a null result could be expected from custom code activity.

Hope this helps you too