Archive

Archive for March, 2019

[Code Snippet] Send Email To Bulk and Track

In this article, lets see how we can send email to bulk records by forming a query and Email template.

To understanding this better, lets send an email to all the ‘Active Contacts’ in the system.

Below are the required components to send the email to ‘Active Contacts’:

  • Query Expression of ‘Active Contacts’
    • You need to form your query as per your business requirement.
  • Email Template
    • As we are targeting ‘Active Contacts’, Email Template Type must be of ‘Contact’.

Bulk_5

  • Email From – Entity Reference of the ‘Sender’
  • Email Regarding – Record of which all the created Emails will get associated with.

Lets see the code.

Below is the main method which sends out emails.

public static Guid SendBulkEmail(EntityReference sender, EntityReference mailRegarding, Guid emailTemplateId, QueryExpression querySenders, IOrganizationService crmService){
// Set trackingId for bulk mail request.
var trackingId = Guid.NewGuid();
try{
var bulkMailRequest = new SendBulkMailRequest() {
Query = querySenders,
Sender = sender,
RegardingId = mailRegarding.Id,
RegardingType = mailRegarding.LogicalName,
TemplateId = emailTemplateId,
RequestId = trackingId
};

// Execute the async bulk email request
var resp = (SendBulkMailResponse)crmService.Execute(bulkMailRequest);
}
catch (Exception ex)
{
throw;
}

return trackingId;
}

Below are the helper methods which pass required inputs for the SendBulkEmail() method.

Get ‘Email Template ID’:

public static Guid GetEmailTemplateId(string emailTemplateName, IOrganizationService crmService)
{
Guid emailTemplateId = Guid.Empty;
var queryBuildInTemplates = new QueryExpression{
EntityName = “template”,
ColumnSet = new ColumnSet(“templateid”, “templatetypecode”),
Criteria = new FilterExpression(LogicalOperator.And)
};

queryBuildInTemplates.Criteria.AddCondition(“templatetypecode”, ConditionOperator.Equal, “contact”);
queryBuildInTemplates.Criteria.AddCondition(“title”, ConditionOperator.Equal, emailTemplateName);

var templateEntityCollection = crmService.RetrieveMultiple(queryBuildInTemplates);

if (templateEntityCollection.Entities.Count > 0){
emailTemplateId = (Guid)templateEntityCollection.Entities[0].Attributes[“templateid”];
}
else
{
throw new ArgumentException(“Standard Email Templates are missing”);
}

return emailTemplateId;
}

Get the ‘From’ and ‘Regarding’:

  • Note: For simplicity, I am using current user as ‘From’ and ‘Email Regarding’

public static EntityReference GetCurrentUserId(IOrganizationService crmService){
var systemUserRequest = new WhoAmIRequest();
var systemUserResponse = (WhoAmIResponse)crmService.Execute(systemUserRequest);
return new EntityReference(“systemuser”, systemUserResponse.UserId);
}

Get the Recipients (i.e., Active Contacts) ‘Query Expression’:

public static QueryExpression GetEmailRecipientsQuery(){
var queryContacts = new QueryExpression
{
EntityName = “contact”,
ColumnSet = new ColumnSet(“contactid”),
Criteria = new FilterExpression(LogicalOperator.And)
};

queryContacts.Criteria.AddCondition(“statecode”, ConditionOperator.Equal, 0);
//queryContacts.Criteria.AddCondition(“createdby”, ConditionOperator.EqualUserId);
return queryContacts;
}

That’s it, lets see how we do we call the main method

var currentUser = GetCurrentUserId(_service);

var trackingID = SendBulkEmail(currentUser, currentUser, GetEmailTemplateId(“Contact Welcome”, _service), GetEmailRecipientsQuery(), _service);

Execute above lines of code in console and you would get the response as below.

Bulk_6

What happens on the execution of the code?:

  • A new ‘Bulk Email’ system job gets created. Go to Settings -> System Jobs

Bulk_1

  • Once the ‘Status Reason’ turns in to ‘Succeeded’, Go to ‘Advance Find’ and query ‘Email Messages’.
  • You should see a new ‘Email’ records for each ‘Active Contact’, as the Query Expression we formed is for ‘Active Contacts’

Bulk_3

  • Open one of the Emails and you should see the email body copied from ‘Email Template’.

Bulk_4

  • We can also track the ‘Bulk Email’ job from the code, with below code snippet by passing the ‘Tracking ID’ (i.e., GUID) returned by SendBulkEmail() method.

private const int ARBITRARYMAXPOLLINGTIME = 60;

public static void TrackMailDelivery(Guid trackingId, IOrganizationService crmService){
var bulkQuery = new QueryByAttribute(){
EntityName = “asyncoperation”,
ColumnSet = new ColumnSet(new string[] { “requestid”, “statecode” }),
Attributes = { “requestid” },
Values = { trackingId }
};

// Retrieve the bulk email async operation.
var emailResponse = crmService.RetrieveMultiple(bulkQuery);Console.WriteLine(” Retrieved Bulk Email Async Operation.”);

// Monitor the async operation via polling.
int secondsTicker = ARBITRARYMAXPOLLINGTIME;

Entity createdBulkMailOperation = null;

Console.WriteLine(“Checking operation’s state for ” + ARBITRARYMAXPOLLINGTIME + ” seconds.”);
Console.WriteLine();

while (secondsTicker > 0){
// Make sure the async operation was retrieved.
if (emailResponse.Entities.Count > 0){
// Grab the one bulk operation that has been created.
createdBulkMailOperation = emailResponse.Entities[0];

// Check the operation’s state.
if (((OptionSetValue)createdBulkMailOperation[“statecode”]).Value != 3){
// The operation has not yet completed.
// Wait a second for the status to change.
System.Threading.Thread.Sleep(1000);
secondsTicker–;

// Retrieve a fresh version the bulk delete operation.
emailResponse = crmService.RetrieveMultiple(bulkQuery);
}
else{
// Stop polling because the operation’s state is now complete.
secondsTicker = 0;
}
}
else{
// Wait a second for the async operation to activate.
System.Threading.Thread.Sleep(1000);
secondsTicker–;

// Retrieve the entity again
emailResponse = crmService.RetrieveMultiple(bulkQuery);
}
}

// Validate async operation succeeded
if (((OptionSetValue)createdBulkMailOperation[“statecode”]).Value == 3){
Console.WriteLine(“Operation Completed.”);
}
else{
Console.WriteLine(“Operation not completed yet.”);
}
}

🙂

Categories: CRM

[Code Snippet] Custom Workflow Activity with Input and Output Params

Of late , I was getting questions around the usage of custom workflow activity and troubleshooting.

In this article, I am going to cover creation and the usage of custom workflow activity with both Input and Output arguemnts.

To simplify the understanding, lets take a simple scenario:

  • ‘Contact’ and ‘Account’ entities have 2 custom attributes (Company ID, Company Name)
  • Whenever a new ‘Contact’ gets created, check if there is an existing ‘Account’ with the ‘Contact.Company ID’
    • If no existing Account, create a new Account with Contact’s data.
    • If there is an existing Account, update Account with Contact’s data.

To achieve this,I am going create a new Custom workflow activity with below design:

  • Create a new ‘Custom Workflow Activity’ with the filter logic to check whether there is an existing ‘Account’ with ‘Company ID’ and returns the existing Account.
  • In Dynamics, Create a new Workflow on ‘Contact’ creation and trigger the newly created custom workflow activity by passing ‘Company Code’.

Lets go step by step.

Step 1: Create a new Custom Workflow Activity:

  • Open a new Class Library project in Visual Studio and download dynamics core library from Nuget .
  • The workflow will have 3 Arguments
    • Input Arguments
      • ContactCompanyID – ‘Company ID’ of Created Contact. Value will be passed from Dynamics Workflow.
    • Output Arguments
      • ExistingAccount – Entity reference of matching Account by ‘Company ID’. Value.
      • IsAccountExists – Boolean.
  • Complete Code:

using System;
using System.Activities;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Workflow;

public class CreateOrUpdateAccountActivity : CodeActivity{

// Input Parameter – Contact’s Company will be passed to this Parameter FROM Workflow
[RequiredArgument]
[Input(“Company ID”)]
public InArgument<string> ContactCompanyID { get; set; }

// Output Parameter – Matched Account will be passed back TO Workflow
[ReferenceTarget(“account”)]
[Output(“Existing Account”)]
public OutArgument<EntityReference> ExistingAccount { get; set; }

// Output Parameter – Boolean flag will be passed TO Workflow
[Output(“Account Exists?”)]
public OutArgument<bool> IsAccountExists { get; set; }

protected override void Execute(CodeActivityContext executionContext){
var tracingService = executionContext.GetExtension<ITracingService>();
var context = executionContext.GetExtension<IWorkflowContext>();
var serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
var service = serviceFactory.CreateOrganizationService(context.UserId);
try{
// Read the ‘Company ID’ from Workflow Input variable
var contactCompanyName = this.ContactCompanyID.Get<string>(executionContext);

// Check if Account exists already with the Contact’s Company ID
var matchedAccount = GetAccountByCompanyID(contactCompanyName, service);

if (matchedAccount == null){
// Set the boolean output param to false, as No matched Account
this.IsAccountExists.Set(executionContext, false);
}
else{
// Set the matched Account to Workflow Output param.
this.ExistingAccount.Set(executionContext, new EntityReference(matchedAccount.LogicalName, matchedAccount.Id));
// Set the boolean output param to true, as matched Account found
this.IsAccountExists.Set(executionContext, true);
}
}
catch (Exception ex){
tracingService.Trace(“Error in CreateOrUpdateAccountActivity – ” + ex.Message);
throw;
}
}

private static Entity GetAccountByCompanyID(string companyID, IOrganizationService service){
var queryAccounts = new QueryExpression(“account”){
ColumnSet = new ColumnSet(new string[] { “accountid” })
};
var filterAccount = new FilterExpression(LogicalOperator.And);
filterAccount.AddCondition(“new_companyid”, ConditionOperator.Equal, companyID);

queryAccounts.Criteria = filterAccount;
var contacts = service.RetrieveMultiple(queryAccounts);
if (contacts != null && contacts.Entities != null && contacts.Entities.Count > 0){
return contacts[0];
}

return null;
}
}

  • Sign the Assembly and build the code
  • Register the ‘Custom Workflow Activity’ using Plug-in Registration Tool.

WF_1

Step 2: Create Dynamics Workflow Process:

  • Create a new Workflow Process on ‘Contact’ creation.

WF_2

  • Under the Workflow steps, as a first step trigger the Custom Workflow Activity

WF_3

  • Click on ‘Set Properties’ and set the ‘Company ID’ argument with ‘Contact.Company ID’

WF_12

  • Next, add a ‘Check Condition’ step to check the ‘IsAccountExists’ value returned by custom workflow activity.

WF_5

  • Set the condition, If ‘Account Exists?’ (This is the display name of Input argument we set in custom workflow) is ‘False’

WF_14

  • As a child step of ‘Check Condition’, add a ‘Create Record’ step to create a new ‘Account’

WF_15

  • Next, add another ‘Check Condition’ step, to handle the ‘Else’ condition and update the ‘Existing Account’
  • Under the Else ‘Check Condition’, add an ‘Update Record’ step to update the ‘Account’ returned by ‘Custom Workflow Activity’

WF_16

  • Activate the workflow

WF_13

Test the flow:

  • Create a new ‘Contact’

WF_17

  • Check the workflow execution by clicking on ‘Process Sessions’ tab from the workflow. This helps us to troubleshoot, in case of any issues in our workflow steps.

WF_10

  • Since all the steps in above screen are Success, now Go to ‘Accounts’ and you should see a new ‘Account’ as there was no existing Account already.WF_11

Notes:

  • Compare to writing the whole logic server side, this approach gives us the flexibility to map the desired fields from ‘Contact’ to ‘Account’ as its a Workflow.

🙂

[Step by Step] Using ADX Auto Numbering Solution In Plug-ins

As we know with Dynamics 365 for CE 9.0 release, we can add an auto-number attribute for any entity programmatically.

Prior to Dynamics 9.0, ‘AdxstudioAutoNumberingWorkflowHelper’ solution is one of the options to implement auto numbering.

Lets see how to use this solution and generate auto number on Case entity.

What does AdxstudioAutoNumberingWorkflowHelper solution contain?

  • AdxstudioAutoNumberingWorkflowHelper solution contain 2 key components
  • AN_8
    • ‘Auto Numbering Definition’ entity – Where you define auto numbering pattern.
    • ‘Adxstudio.Xrm.Workflow.AutoNumberHelper’ custom workflow – Which generates the Auto number as per the configurations made in ‘Auto Numbering Definition’ entity

Below are the high level steps:

  • Download and Install ADX solution and configure ‘Auto Number Definition’.
  • Create a new ‘Action’ which calls Adx Auto Number Workflow.
  • Trigger ‘Action’ from ‘Plugin’

Install ADX solution and configure Definition:

  • Download and install the solution.
  • Once the solution is installed you would get Sitemap node as below under ‘Settings’.

AN_4

  • Create a record in ‘Auto Numbering Definition’ entity and set below fields
  • AN_3
    • Name – Can be anything. ‘Name’ has to be passed as parameter to the ‘Adxstudio.Xrm.Workflow.AutoNumberHelper’ workflow
    • Format – Pattern of Auto number
    • Digits – Length of your auto number digits.

Create a new ‘Action’ which calls Adx Auto Number Workflow:

Best way to trigger workflow from Plug-in is by creating an ‘Action’

  • Create a new ‘Action’
  • Add 2 parameters
    • ‘NumberDefintion’ – Input : We use this to pass ‘Auto Numbering Definition’ Name
    • AutoNumber – Output : We use this to capture the generated  ‘Auto Number’
  • Add 2 steps to the ‘Action’
    • Step 1 – Trigger ‘Adxstudio Workflow Helpers:Get Auto-Number’  with below PropertiesAN_2
    • Step 2 – Capture the generated Auto Number using ‘Assign Value:’ step with below Properties

AN_5

  • Overall ‘Action’ looks as below

AN_6

  • Activate the ‘Action’

Trigger ‘Action’ from ‘Plugin’:

  • Below is the code to execute the ‘Action’ by passing Parameter and read the ‘Auto Number’

var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var svc = serviceFactory.CreateOrganizationService(context.UserId);

if (context.InputParameters.Contains(“Target”) && context.InputParameters[“Target”] is Entity) {
var entCase = context.InputParameters[“Target”] as Entity;

// Logic to trigger Custom Action which trigger ADX Auto Number WF
// Pass your Custom Action Name (i.e., raj_TriggerADXAutoNumber in my case)
var orgRequest = new OrganizationRequest(“raj_TriggerADXAutoNumber”);
// Pass ‘Auto Number Definition Name’ you configured in system
orgRequest[“NumberDefinition”] = “Case Auto Number”;

// Execute the request
OrganizationResponse response = svc.Execute(orgRequest);
string autoNumber = response.Results[“AutoNumber”].ToString();

// Set generated auto number to Case’s Title field
entCase[“title”] = autoNumber;
}

  • Register the Plug-in on ‘PreCaseCreate’
  • Create a Case and you would get the number set on ‘Title’ field

AN_7

🙂

 

 

 

 

Dynamics Portals – Display Plug-in error messages

You would have got “An unknown failure has occurred…” many times in your portal when there was exception thrown by your Dynamics CE plug-in code.

Now in Dynamics Portals, we can show the plug-in error in following portal screens by adding “Site/EnableCustomPluginError” entry in Dynamics CE Portals -> Site Settings

  • Entity list
    • Retrieval of records
  • Entity form
    • Retrieve
    • Create/Update and so on
  • Web forms
    • Retrieve
    • Create/Update and so on

Lets see the approach and steps.

  • I have created and registered a plug-in with logic to intentionally throw an exception on ‘Case’ Create.

Portal_Plugin_Error_4

  • Go to your Portal and try to create a Case and you would get below generic exception “An unknown failure…”

Portal_Plugin_Error_1

  • Now open your Dynamics CE application, navigate to Portals -> Site Settings and create a new Site Setting with Name “Site/EnableCustomPluginError” and Value true.

Portal_Plugin_Error_2

  • Go back to your Portal and try to create a Case and you would get actual Plug-in error.

Portal_Plugin_Error_3

🙂