Archive
Dataverse | Plugins | ILMerge Alternative | Shared Project
Role of ILMerge in Plugin Development:
If you are familiar with writing Plugins in Dataverse, chances are that you would have used ILMerge to merge the Assemblies.
In a typical Dynamics Plug-in development, we will have following .Net Class Library Projects.
- Plugins.csproj
- Plugins.Helper.csproj
When you compile above projects, you get two .dlls (i.e., Plugins.dll and Plugins.Helper.dll). As we can only deploy one .dll to Dataverse, we use ILMerge to merge both Plugins.dll and Plugins.Helper.dll in to one.
Is it recommended to use ILMerge in Plugin development? Answer is No. As per this Microsoft article ILMerge is not supported in Plugins development.
Now whats the alternative? Answer is Shared Projects.
Steps to use Shared Projects in Plug-in Development:
To explain the Shared Projects, I am going to build a simple Plugin project ‘Plugins.csproj’ with ‘PreAccountCreate’ class, which refers a ‘Shared’ Helper project ‘Plugins.Helper’.
- Create a new C# class library project and add ‘PreAccountCreate’ class as below.
- You can copy the code I’ve used from here.
- Now lets add a ‘Shared’ Helper project which our Plugin project would refer.
- Right click your Solution and click ‘New Project’.
- Select a Project template of type C# ‘Shared Project’.
- Give a Name to your ‘Shared Project’.
- I’ve named it as ‘Plugins.Helper’.
- ‘Plugins.Helper’ Shared Project, looks as below in the Solution Explorer.
- Now add a Class file ‘AccountHelper.cs’ to the ‘Shared Project’.
- I’ve added a simple function ‘GetAccountName()’ which returns ‘Microsoft India’.
- To use the ‘Shared Project’ in our Plug-in project, right click ‘Plugins’ project and add ‘Reference’.
- From ‘Shared Projects’, choose your project (i.e., Plugins.Helper in my case).
- Once you referred the ‘Shared Project’ in your Plugin project, it looks as below.
- Now its time to call ‘GetAccountName()’ from our ‘PreAccountCreate’ class.
- Sign the Assembly.
- Build the Plug-in project and you would get the ‘Plugins.dll’ in bin/Debug folder.
- Go ahead and deploy the ‘Plugins.dll’ to Dataverse and register step using Plugin Registration Tool.
đ
[Code Snippet] Set Business process flow (BPF) stage using C#
Assume you have a BPF with 3 Stages on an Entity ‘Employer’. When you create a new ‘Employer’ record, by default ‘Stage-1’ gets set.
What if you have to create the ‘Employer’ record with a different stage.
Lets see how to create a record and set the desired BPF stage with an example entity ‘Employer’.
I’ve an ‘Employer’ entity and a BPF name ‘Employer flow’ with 3 stages named ‘Basic Details’, ‘Address’ and ‘Experience’.

Key point to notice is, when ever you create a BPF a new entity gets created with the given BPF name.

Following are the steps to create a ‘Employer’ record and set the BPF stage to ‘Experience’.
- Query ‘Workflow’ entity to get the ‘BPF ID’ by passing the BPF entity schema name (i.e., crf10_employerflow).
var queryEmployerBPF = new QueryExpression
{
EntityName = "workflow",
ColumnSet = new ColumnSet(true),
Criteria = new FilterExpression
{
Conditions =
{
new ConditionExpression
{
AttributeName = "uniquename",
Operator = ConditionOperator.Equal,
Values = { "crf10_employerflow" }
}
}
}
};
var retrievedBPF = ConnectionManager.CrmService.RetrieveMultiple(queryEmployerBPF).Entities[0];
var _bpfId = retrievedBPF.Id;
- Query Process Stage by passing ‘BPF ID’ fetched in previous step.
var queryPS = new QueryExpression{
EntityName = "processstage",
ColumnSet = new ColumnSet(true),
Criteria = new FilterExpression{
Conditions ={
new ConditionExpression{
AttributeName = "processid",
Operator = ConditionOperator.Equal,
Values={ _bpfId }
}
}
}
};
- Copy the ‘Stage’ GUID’s which will be used in next steps.
- Create the ‘Employer’ record, which also creates record in ‘BPF entity’ (i.e., Employer Flow).
Entity entEmployer = new Entity("crf10_employer");
entEmployer["crf10_name"] = "BPF Test";
//entEmployer["processid"] = Guid.Empty;
var violationID = ConnectionManager.CrmService.Create(entEmployer);
- Fetch the ‘BPF entity’ (i.e., Employer Flow) record, which auto created in previous step, using ‘RetrieveProcessInstancesRequest’ request.
var procOpp2Req = new RetrieveProcessInstancesRequest
{
EntityId = violationID,
EntityLogicalName = "crf10_employer"
};
var procOpp2Resp = (RetrieveProcessInstancesResponse)ConnectionManager.CrmService.Execute(procOpp2Req);
- Update ‘activestageid’ field of the ‘BPF entity’ (i.e., Employer Flow) record fetched in previous step, with the desired stage GUID captured in Step #2.

// Declare variables to store values returned in response
int processCount = procOpp2Resp.Processes.Entities.Count;
var activeProcessInstance = procOpp2Resp.Processes.Entities[0]; // First record is the active process instance
var _processOpp2Id = activeProcessInstance.Id; // Id of the active process instance, which will be used
// Retrieve the process instance record to update its active stage
ColumnSet cols1 = new ColumnSet();
cols1.AddColumn("activestageid");
Entity retrievedProcessInstance = ConnectionManager.CrmService.Retrieve("crf10_employerflow", _processOpp2Id, cols1);
// Update the stage to 'Experience' by passing GUID (i.e.,"05aeaf03-e135-40ac-8ae7-cafc7d746a02")
retrievedProcessInstance["activestageid"] = new EntityReference("processstage", new Guid("05aeaf03-e135-40ac-8ae7-cafc7d746a02"));
ConnectionManager.CrmService.Update(retrievedProcessInstance);
- Open the record from the App and the stage should set to ‘Experience’.

- Check the BPF records and you should see ‘Active Stage’ got set to ‘Experience’ (This is optional step and for your learning).

- Below is the complete snippet.
var queryEmployerBPF = new QueryExpression
{
EntityName = "workflow",
ColumnSet = new ColumnSet(true),
Criteria = new FilterExpression
{
Conditions =
{
new ConditionExpression
{
AttributeName = "uniquename",
Operator = ConditionOperator.Equal,
Values = { "crf10_employerflow" }
}
}
}
};
var retrievedBPF = ConnectionManager.CrmService.RetrieveMultiple(queryEmployerBPF).Entities[0];
var _bpfId = retrievedBPF.Id;
var queryPS = new QueryExpression
{
EntityName = "processstage",
ColumnSet = new ColumnSet(true),
Criteria = new FilterExpression
{
Conditions =
{
new ConditionExpression
{
AttributeName = "processid",
Operator = ConditionOperator.Equal,
Values={ _bpfId }
}
}
}
};
var retrievedPS = ConnectionManager.CrmService.RetrieveMultiple(queryPS);
// Copy the Stage GUID's using below loop.
foreach (var stage in retrievedPS.Entities)
{
Console.WriteLine($"Stage Name : {stage["stagename"]}");
Console.WriteLine($"Stage ID : {stage["processstageid"]}");
}
//Create 'Employer' record
var entEmployer = new Entity("crf10_employer");
entEmployer["crf10_name"] = "BPF Test";
//entEmployer["processid"] = Guid.Empty;
var violationID = ConnectionManager.CrmService.Create(entEmployer);
var procOpp2Req = new RetrieveProcessInstancesRequest
{
EntityId = violationID,
EntityLogicalName = "crf10_employer"
};
var procOpp2Resp = (RetrieveProcessInstancesResponse)ConnectionManager.CrmService.Execute(procOpp2Req);
// Declare variables to store values returned in response
int processCount = procOpp2Resp.Processes.Entities.Count;
var activeProcessInstance = procOpp2Resp.Processes.Entities[0]; // First record is the active process instance
var _processOpp2Id = activeProcessInstance.Id; // Id of the active process instance, which will be used
// Retrieve the process instance record to update its active stage
var cols1 = new ColumnSet();
cols1.AddColumn("activestageid");
var retrievedProcessInstance = ConnectionManager.CrmService.Retrieve("crf10_employerflow", _processOpp2Id, cols1);
// Update the stage to 'Experience' by passing GUID (i.e.,"05aeaf03-e135-40ac-8ae7-cafc7d746a02")
retrievedProcessInstance["activestageid"] = new EntityReference("processstage", new Guid("05aeaf03-e135-40ac-8ae7-cafc7d746a02"));
ConnectionManager.CrmService.Update(retrievedProcessInstance);
đ
Plug-in on related entities during âCascade Allâ actions
Assume the relationship behavior between âContactâ and âAppointmentâ is âCascade Allâ on âAssignâ action.
By virtue of that, if a Contact has 2 Appointments, if I change the owner of âContactâ the related 2 Appointment owner also changes.
In one of the requirement, we have to restrict the Cascade operation based on business logic on child record (i.e., Â Appointment)
So I registered a Plugin on âAssignâ of âAppointmentâ and want to handle the Cascade operation, but the Plugin never get executed.
Reason & Solution
- CRM treats Cascade Assign operation on Child records as an Update.
- Register the Plug-in on âUpdateâ instead of âAssignâ message.
- In the Update plug-in, the Target entity only contain âOwnerâ field
đ
UserId & InitiatingUserId properties in Plugin of CRM
In CRM plugin, âIExecutionContextâ contains 2 properties
- UserId
- Gets the GUID of the user for whom the plug-in invokes âon behalf ofâ.
- InitiatingUserId
- Gets the GUID of the user under which the current pipeline is executing.
Consider a scenario
- You have a user âRAJâ with âSales Personâ role with only âUser Levelâ âReadâ privilege on âContactâ
- You have a plugin on Post Deletion of âContactâ with name âPostContactDeleteâ
- Assume in one particular scenario user âRAJâ should be able to delete a âContactâ
- So you can run the âPostContactDeleteâ plugin in the user with âSystemAdministratorâ role
- (i.e., Set âRun in Userâs Contextâ to User with admin role; In sample screen shot below I chosen my admin user whose name is  âCRM WaSu1)
- When User âRAJâ logs in and try to delete âContactâ the plug-in âPostContactDeleteâ fires. When you debug
- IExecutionContext.UserId = GUID of  SystemAdministrator (i.e., OnBehalfOf User ‘RAJ’)
- IExecutionContext. InitiatingUserId =GUID of  RAJ  (i.e., Actual User)
đ
Creating a plug-in on SavedQuery entity using CRM developer toolkit
In one of my requirement I have to create a plugin on âSavedQueryâ entity to filter the views
I was using CRM developer toolkit and when I open the âCRM Explorerâ I did not find âSavedQueryâ entity
Later I came to know that âCRM Explorerâ show entities by âDisplay Nameâ and there was entity with name âViewâ for âSavedQueryâ
Thought of sharing this though its simple observation
đ