Debugging custom workflow assemblies

Plugin Registration tool can be used to debug not just plugin assemblies, but also custom workflow assemblies. I am not sure how well known this feature is, as I could not find any documentation about this in msdn.

Prerequisite

Use the correct version of Plugin Registration tool for your organisation. I could not use Plugin Registration tool that came along with CRM SDK 7.1.1, to debug a workflow assembly running in CRM 2015 Update 0.1 (7.0.1). I could however use Plugin Registration tool 8.0.0.7198 to debug workflow assembly running in CRMOnline 2016 (8.0.1.79). So, it seems the major version and minor version have to match to enable workflow assembly debugging.

After installing the profile, select the plugin profile node. You will now see a new button called “Profile Workflow

Workflow Profile Button

Next, choose the CRM workflow which contains the custom workflow assembly. If the workflow has multiple custom workflow assembly steps, you will see each of these in this screen. You can choose the assembly to profile. It is best to choose “Persist to Entity” option, as you won’t see the exception when it is thrown by the workflow.Profile Settings

 

Once you click OK, a clone of the workflow will be created. It will have “(Profiled)” in the end. Your original workflow will now be in “Draft” state.

Workflow Cloned

Now execute the profiled workflow manually. Confirm that the workflow has finished running.

Workflow Log

Once the workflow has completed running, you can now use the serialised profile to debug the workflow assembly. After you click the “Replay Plugin Execution” button you will see this screen, which will help you to select the correct profile record.

Debug Profile Selection

I ran the workflow manually only once and hence there is only one profile row. If the workflow ran multiple times, you will see multiple rows. After you click select, you’ll then have to choose the correct workflow assembly that matches this profile.

Start Debug

Now attach the Visual Studio Debugger to the Plugin Registration Tool. Now is also a good time to put couple of breakpoints in the workflow assembly code in Visual Studio. Once you click “Start Execution”, the control should now be transferred to Visual Studio to facilitate debugging.

Visual Studio Debug

The behaviour is exactly same as plugin debugging. You can step though the code, understand the root cause of any weird behaviour and resolve it. Once you complete the execution, you will see the trace logs in the profiler.

Debug Result

One more thing: You can use this exact same process to debug an Action which has a custom assembly. Here is how the profile looks when an action with custom workflow assembly is profiled.

Debug an action

Things to note:

  1. When a workflow step is profiled, the workflow assembly containing this activity is cloned and registered as a profiled workflow assembly. It is this assembly that is used in the workflow that has the name ending with “(Profiled)”
  2. Even though you can see the workflow being profiled, stopping the workflow profile using the Plugin Registration tool seems impossible. You can see what is being profiled.Profiled Workflow
  3. In order to stop profiling, you’ll have to
    • Unpublish and delete the workflow/action that ends with “(Profiled)
    • Delete the weirdly named assembly (all guid name) from the “Default Solution“. This will be under the “Plugin Assemblies” nodeCloned Workflow Assembly

I hope this technique will help you to debug custom workflow assemblies quite easily. Happy debugging.

EDIT (01/07/2016): Please refer Issues in debugging custom workflow assemblies where I address the issues in the comment.

Quicktip: Debugging merged plugin assembly

Plugin profiling and debugging is a very useful feature in the plugin registration tool. It helps you to debug and step through the plugin code in your local machine with Visual Studio, using the serialised plugin context. The serialised context can also be stored in CRM and debug can be initiated from this record.

Profile Screen

There is one small issue when you try to debug a IL Merged assembly. When you try to profile such a plugin assembly, you will get an error like this.

PluginRegTool Error

The error is saying that the plugin profiler cannot locate the assembly that was merged with the main plugin assembly.

Resolution:

Copy all the assemblies that were merged with the plugin assembly that you are trying to debug, into the same directory as the Plugin Registration tool executable.

I experienced this issue when I was trying to debug a plugin assembly merged using the CRM Solution Manager.

Further reading:

Debug a plugin – https://msdn.microsoft.com/en-us/library/gg328574.aspx

Null Conditional Operator in C# 6

During my code session in the weekend, I discovered the Null Conditional operator in C# 6. In terms of CRM custom development, this operator reduces the risk of null reference exception. Let me give you an example.

Before

                var contactFetchQuery = @"
                <fetch version=""1.0"" output-format=""xml-platform"" mapping=""logical"" distinct=""true"">
                  <entity name=""contact"">
                    <attribute name=""contactid""/>
                    <attribute name=""birthdate""/>
                    <attribute name=""fullname""/>
                      <filter type=""and"">
		                <condition attribute=""statecode"" operator=""eq"" value=""0"" />
                      </filter>
                  </entity>
                </fetch>";
                var contactRecords = crmSvc.OrganizationServiceProxy.RetrieveMultiple(new FetchExpression(contactFetchQuery)).Entities;
                foreach (var contactRecord in contactRecords)
                {
                    var birthDate = contactRecord.GetAttributeValue<DateTime?>("birthdate");
                    if (birthDate.HasValue)
                    {
                        if (birthDate.Value.ToLocalTime().Day == DateTime.Today.Day && birthDate.Value.ToLocalTime().Month == DateTime.Today.Month)
                        {
                            Console.WriteLine("{0} was born today..", contactRecord.GetAttributeValue<string>("fullname"));
                        }
                    }
                }

After

                var contactFetchQuery = @"
                <fetch version=""1.0"" output-format=""xml-platform"" mapping=""logical"" distinct=""true"">
                  <entity name=""contact"">
                    <attribute name=""contactid""/>
                    <attribute name=""birthdate""/>
                    <attribute name=""fullname""/>
                      <filter type=""and"">
		                <condition attribute=""statecode"" operator=""eq"" value=""0"" />
                      </filter>
                  </entity>
                </fetch>";
                var contactRecords = crmSvc.OrganizationServiceProxy.RetrieveMultiple(new FetchExpression(contactFetchQuery)).Entities;
                foreach (var contactRecord in contactRecords)
                {
                    var birthDate = contactRecord.GetAttributeValue<DateTime?>("birthdate");
                    if (birthDate?.ToLocalTime().Day == DateTime.Today.Day && birthDate?.ToLocalTime().Month == DateTime.Today.Month)
                    {
                        Console.WriteLine("{0} was born today..", contactRecord.GetAttributeValue<string>("fullname"));
                    }
                }

This makes null checks almost redundant, as the part after the “?.” would not be even evaluated, if the part before “?.” is null. I compiled a project using .net 4.5.2 and VS2015 for the XrmToolBox project and I like using this new functionality. I will definitely use this in future applications.

Refer https://msdn.microsoft.com/en-us/library/dn986595.aspx for more information.

Publish a Duplicate Detection Rule: Behind the scenes

When you import a solution you get a nice progress bar that you can use to approximately estimate the finish time. But when you publish a duplicate detection rule, you get nothing. All you can basically do is hit the publish and check back 10-15 minutes later.

Sometimes, it might sit in the publishing state longer that you expect. It can be frustating to just wait, without actually knowing whether the process has frozen or it is actually doing something. The underlying entity DuplicateRule is not searcheable from advanced find, and so there is no other information available except the window saying “Publishing”.

I did some research into what actually happens under the covers when you hit publish. When you hit publish CRM has to:

  1. Create new matchcode tables for the base entity and the matching entity
  2. Populate the matchcode tables with the duplicate criteria specified in the rule

For some reason that I haven’t been able to figure out so far, it seems the matchcode table count is always actual entity count + 1. There is a matchcode record with ObjectId 00000000-0000-0000-0000-000000000000 in each matchcode table. I have no idea why this record exists, but my theory is that this is some sort of bit mask. I maybe totally wrong on this though and this could just be some sort of sequence.

I wrote the script below to confirm the duplicate rules are indeed doing something.

declare @duplicaterules table (rownum int identity(1,1), name varchar(200),matchcodetable varchar(200),entity varchar(200))
declare @i int,@matchcodetable varchar(200),@entity varchar(200),@name varchar(200),@max int
declare @result varchar(max),@query nvarchar(max),@matchcodecount int,@entityrowcount int
insert into @duplicaterules
select name,MatchingEntityMatchCodeTable,MatchingEntityName from DuplicateRule where statecode=0
select @max=@@ROWCOUNT,@i=1
while(@i<=@max)
begin
	select @matchcodetable=matchcodetable,@entity=entity,@name=name from @duplicaterules where rownum=@i
	set @query=N'select @matchcodecountOUT=count(*) from ' + @matchcodetable + ' where ObjectId<>''00000000-0000-0000-0000-000000000000'''
	exec sp_executesql @query,N'@matchcodecountOUT int OUTPUT',@matchcodecountOUT=@matchcodecount OUTPUT
	set @query=N'select @entityrowcountOUT=count(*) from ' + @entity
	exec sp_executesql @query,N'@entityrowcountOUT int OUTPUT',@entityrowcountOUT=@entityrowcount OUTPUT
	print 'Duplicate Rule '+@name+' is '+CAST(CAST(@matchcodecount*100.0/@entityrowcount as numeric(5,2)) as varchar(6))+'% complete: '
	print 'Entity: '+@entity+', Matchcode Table: '+@matchcodetable+', Matchcodes: '+CAST(@matchcodecount as varchar(20))+', Entity Rows: '+CAST(@entityrowcount as varchar(20))+CHAR(10)
	set @i=@i+1
end

When you run the script in SSMS on the MSCRM database, you will see a output that looks something like this.

DuplicateOutput

So now you can quickly check the status in the database, if you are little impatient. Once again, sorry CRMOnline, but I like having access to the actual database.

EDIT (22/09/15): Updated SQL Script slightly to produce the percentages correctly. The previous script was always displaying integer. I also exclude matchcode row with objectid 00000000-0000-0000-0000-000000000000

SQL Script: Entity row count

Run the script below on the MSCRM database to quickly get a count of records, for real and customisable entities (not logical).

declare @query nvarchar(max)
set @query=''
select @query=@query+'select count(*) Records,'''+Label+''' Entity, '+ cast(IsCustomEntity as varchar(1)) +' as IsCustomEntity from '+name+' union all ' from dbo.LocalizedLabelAsIfPublishedView a
inner join dbo.EntityAsIfPublishedLogicalView b on a.ObjectId=b.EntityId
where a.ObjectColumnName='LocalizedName'
and IsLogicalEntity=0 and IsCustomizable=1
set @query='select Entity,Records, case when IsCustomEntity=''0'' then ''No'' else ''Yes'' end as CustomEntity from ('+left(@query,len(@query)-10)+') s where s.Records>0 order by Entity'
exec sp_executesql @query

Quicktip: Always retrieve primary key with distinct=’true’ fetchxml

FetchXml is great when you quickly want to extract some data. Today, I found a weird behaviour, that is either a bug, or a documented “feature” that I am not aware of.

If you run this code below

            var fetch = @"
            <fetch version='1.0' output-format='xml-platform' mapping='logical' {0} count='1'>
	            <entity name='contact'>
		            <attribute name='fullname'/>
	            </entity>
            </fetch>";

            Console.WriteLine("With Distinct True And Without Id\n--------------------------------");
            var results = crmSvc.OrganizationServiceProxy.RetrieveMultiple(new FetchExpression(string.Format(fetch, "distinct='true'"))).Entities.ToList();
            results.ForEach(x => Console.WriteLine("Id: {0}",x.Id));

            Console.WriteLine("\nWith Distinct True And With Id Field\n--------------------------------");
            results = crmSvc.OrganizationServiceProxy.RetrieveMultiple(new FetchExpression(
            @"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='true' count='1'>
	            <entity name='contact'>
                    <attribute name='contactid'/>
		            <attribute name='fullname'/>
	            </entity>
            </fetch>")).Entities.ToList();
            results.ForEach(x => Console.WriteLine("Id: {0}", x.Id));

            Console.WriteLine("\nWith Distinct False\n--------------------------------");
            results = crmSvc.OrganizationServiceProxy.RetrieveMultiple(new FetchExpression(string.Format(fetch, "distinct='false'"))).Entities.ToList();
            results.ForEach(x => Console.WriteLine("Id: {0}", x.Id));

            Console.WriteLine("\nWithout Distinct\n--------------------------------");
            results = crmSvc.OrganizationServiceProxy.RetrieveMultiple(new FetchExpression(string.Format(fetch, string.Empty))).Entities.ToList();
            results.ForEach(x => Console.WriteLine("Id: {0}", x.Id));

You get this output,

Output

As you can see, if you put distinct=true in your fetchxml, without retrieving the primary key, and iterate through the RetrieveMultiple result, the Id property will be Guid.Empty for all the entities in the EntityCollection.

Leverage Actions to bundle and minify form scripts

EDIT (26/05/15): I forgot to disable the plugin on RetrieveMultiple of webresource, described in the previous post. I also forgot to minify the script that runs on Form Experiment entity. Strangely however, this has reduced the performance. I tested this in CRMOnline, and so I am not sure if this is due to the load on the server at this time of the day. I have modified the performance screenshot, to reflect this.

I tried another approach to dynamically minify and load CRM form script. This time I am utilising Actions. I have tried different ideas with Actions in the past and love how extensible and powerful they are. Of all the approaches I have tried so far, I like this a lot. As usual, if you would rather read code, head to https://github.com/rajyraman/DynamicScriptBundling and fork the code.

This is the basic flow of this design:

  1. Form script just has code to invoke the action using current entityname as input parameter
  2. Action uses the Form Load Sequence entity to collate a list of scripts that have to be minified and concatenated. The minified and concatenated script is stored in the Action’s output parameter
  3. The calling form script, evals the returned minified and concatenated script

Before you get defensive aggressive about using evals, please read http://www.nczonline.net/blog/2013/06/25/eval-isnt-evil-just-misunderstood/. IMHO, it is perfectly alright to use eval in this scenario. Here are the steps:

Create the Action

The action doesn’t contain much config, other than the input parameter and output parameter. The maximum length of the string, if you set this from the UI is 255, however, if you use a plugin to do the same, there is no limit. We are going to use this behaviour, to set the minifiedscripts with the bundled/concatenated scripts.

Create the Plugin that is triggered by the Action

The Action’s input argument is passed on to the Plugin’s InputParameter and this is how the plugin knows, which entity it is dealing with. It then proceeds to concatenate and minify the scripts.

Plugin Code

using DouglasCrockford.JsMin;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Linq;
using System.Xml.Linq;
using Contract = System.Diagnostics.Contracts.Contract;

namespace RYR.Experiments.Plugins
{
    public class PostGetScriptsForEntity : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            Contract.Assert(serviceProvider != null, "serviceProvider is null");
            var pluginExecutionContext =
                (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            Contract.Assert(tracingService != null, "TracingService is null");

            try
            {
                var factory =
                    (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

                var organizationService = factory.CreateOrganizationService(pluginExecutionContext.UserId);
                var entityName = pluginExecutionContext.InputParameters["entityname"];

                var scriptLoadOrderFetchXml = string.Format(@"
                    <fetch count='1' >
                      <entity name='ryr_formloadsequence' >
                        <attribute name='ryr_name' />
                        <attribute name='ryr_scripts' />
                        <filter>
                          <condition attribute='ryr_name' operator='eq' value='{0}' />
                        </filter>
                      </entity>
                    </fetch>", entityName);
                var concatenatedScripts = new StringBuilder();
                var loadSequenceResults =
                    organizationService.RetrieveMultiple(new FetchExpression(scriptLoadOrderFetchXml))
                        .Entities;

                if (!loadSequenceResults.Any()) return;

                var scriptsToMerge = loadSequenceResults[0].GetAttributeValue<string>("ryr_scripts").Split(',');
                var webresourceFetchXml = string.Format(@"
                        <fetch>
                          <entity name='webresource' >
                            <attribute name='content' />
                            <attribute name='name' />
                            <filter>
                              <condition attribute='webresourcetype' operator='eq' value='3' />
                              <condition attribute='name' operator='in' >
                                {0}
                              </condition>
                            </filter>
                          </entity>
                        </fetch>", string.Join(string.Empty,
                         scriptsToMerge.Select(x => string.Format("<value>{0}</value>", x))));
                var toBeMergedWebResources = organizationService.RetrieveMultiple(
                    new FetchExpression(webresourceFetchXml)).Entities;

                if (!toBeMergedWebResources.Any()) return;

                foreach (var s in scriptsToMerge)
                {
                    var matchedWebresource = toBeMergedWebResources
                        .FirstOrDefault(x => x.GetAttributeValue<string>("name") == s);
                    if (matchedWebresource != null)
                    {
                        concatenatedScripts.AppendLine(Encoding.UTF8.GetString(Convert.FromBase64String(
                            matchedWebresource.GetAttributeValue<string>("content"))));
                    }
                    else
                    {
                        concatenatedScripts.AppendLine(
                            string.Format(
                                "Xrm.Page.ui.setFormNotification('Unable to load {0}', 'ERROR', '{1}');", s,
                                Guid.NewGuid()));
                    }
                }
                pluginExecutionContext.OutputParameters["minifiedscripts"] = new JsMinifier().Minify(concatenatedScripts.ToString());
            }
            catch (Exception e)
            {
                tracingService.Trace(e.StackTrace);
                throw;
            }
        }
    }
}

Use Sdk.Soap.js to generate Javascript code for Action

Download Sdk.Soap.js  and run the Sdk.SoapActionMessageGenerator.exe executable. Once you choose the organisation details and enter the credentials, the Javascript code for each individual action will be generated.

Modify the generated code to auto-populate entity name

The code generated by the Sdk.SoapActionMessageGenerator is going to be used in the form. We want to simply add this script to any entity, without having to change a single line of code. However, the Action accepts an input parameter called entity, that will obviously be different from entity to entity. Hence we have to make a slight modification to ensure that entityname is set automatically using Client API.

Change

(function() {
this.ryr_GetScriptsForEntityRequest = function (
entityname
)
{
///
/// 
///
///
/// [Add Description]
///
if (!(this instanceof Sdk.ryr_GetScriptsForEntityRequest)) {
return new Sdk.ryr_GetScriptsForEntityRequest(entityname);
}

to

function () {
this.ryr_GetScriptsForEntityRequest = function ()
{
var entityname = Xrm.Page.data.entity.getEntityName(); 
///
/// 
///
///
/// [Add Description]
///
if (!(this instanceof Sdk.ryr_GetScriptsForEntityRequest)) {
return new Sdk.ryr_GetScriptsForEntityRequest(entityname);
}

I also had problem with strict mode and so I removed it from both Sdk.Soap.js and Sdk.ryr_GetScriptsForEntity.vsdoc.js. Now we merge both these scripts into one and add that as a javascript webresource. This webresource is then added to the form.

Here is the script coming through in the Action response, before getting evaled.

Performance

 291 ms (minified and added to the form)
vs
 440 ms (dynamically minified using Action)
Hope this gives you an idea on how you can use Action to solve the script load sequence issue in CRM2015.