Monday, June 9, 2014

Workflow solution creation , registration and usage in CRM2011

In this blog I would like to explain some practical steps about the Workflow lifecycle in CRM 2011.
The steps includes 
1. Creation of the workflow solution ( C# solution ) and generation of the assembly 

2. Registering the workflow assembly for CRM usage3. Using the workflow assembly in CRM 


1. Creation of the workflow solution and generation of the assembly 

The workflow can be defined as group of activities that performs the required process.+


a) Creating the activities of the Workflow steps in the C# solution  Workflow activities are defined in the System.Activities.dll of .NETFramework, so we need to add this dll to the workflow solution.


Define the Class : The workflow activities that are used in CRM are derived from the "CodeActivity" base class. so define the workflow activity class derived from the "CodeActivity" class.    

eg : public class Sampleworkflow : CodeActivity {} Override the abstract methods of the CodeActivity class :
Execute() Method: 
CodeActivity class declares the Execute()  method as the abstract method. All the derived classes should define the execute() method in the derived classes i.e in the workflow activity classes.    eg: protected override void Execute(CodeActivityContext executionContext)
Some common attributes and code useful in the execute() method are:
1. IWorkflowContext (defined in Microsoft.Xrm.Sdk.Workflow.dll)  which is derived from the IExecutionContext:  contains the details about the CRM attributes and parameters that can be used in the workflow execution. we can get the IWorkflowContext as follows    IWorkflowContext context = executionContext.GetExtension();
    Using the IWorkflowContext , we can obtains the CRM context values like  current records GUID, , Current User's Guid, OrganizationName, OrganizationId etc., as follows:     getting the current record Guid : WorkflowContext.PrimaryEntityId;    getting the current Usee Guid : WorkflowContext.UserId;   
2. Organization Service and Organization Service Factory:    To execute any CRM SDK API's, we need to have the organization service or OrganizationServiceProxy. These objects can be created after getting the IOrganizationServiceFactory  object from the CodeActivityContext  and then by creating the service/serviceproxy by sending the user Guid to the Service factor        IOrganizationServiceFactory serviceFactory = executionContext.GetExtension();                     IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
How to define and extract the Input and Output parameters required in the Workflow execution:  Defining the parameters: The input and output parameters for the workflow activity can be defined in the workflow activity class as follows:           a) String data type as input                  [Input("Account ID")]                  [Default("0")]                  public InArgument AccountName { get; set; }

Accessing the parameters : Input and output parameters can be obtained from the CodeActivityContext class passed to the execute() method.            string accountName = AccountName.Get(executionContext);           Where executionContext is the argument of type CodeActivityContext for the function execute()         b) Entity Reference type as input                    [Input("EntityReference input")]                    [ReferenceTarget()]                    [Default(, )]                    public InArgument  { get; set; }
        This data type can be accessed and assigned to the local variable of type Guid, within the execute() function as :                 Guid localGuid = (.Get(executionContext)).Id;
 After defining the execute() method with the required functionality for the workflow activity, compile and generate the workflow dll. 
This dll needs to be registered with the CRM. The steps that are to be followed for workflow registration into CRM 2011 will be available in my next blog on  Registering the workflow assembly for CRM usage

Sunday, June 8, 2014

Associate two records



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;


namespace AssociatedLeadEmail_WF
{
    public class AssociatedLeadEmail_WF : CodeActivity
    {
        #region Set the input mandatory parameters.
        //Input Value of Associated Lead Record
        [RequiredArgument]
        [Input("Email Send")]
        [ReferenceTarget("cdi_emailsend")]
        public InArgument<EntityReference> EmailSend { get; set; }

        [RequiredArgument]
        [Input("Lead")]
        [ReferenceTarget("lead")]
        public InArgument<EntityReference> Lead { get; set; }

        [Output("Success")]
        public OutArgument<bool> Success { get; set; }

        #endregion

        protected override void Execute(CodeActivityContext context)
        {
            //Create the tracing service
            ITracingService tracingService = context.GetExtension<ITracingService>();
            //Create the context
            IWorkflowContext workFlowContext = context.GetExtension<IWorkflowContext>();
            IOrganizationServiceFactory serviceFactory = context.GetExtension<IOrganizationServiceFactory>();
            IOrganizationService service = serviceFactory.CreateOrganizationService(workFlowContext.UserId);


            string str_EmailSend = this.EmailSend.Get(context) == null ? Guid.Empty.ToString() : this.EmailSend.Get(context).Id.ToString();
            string str_lead = this.Lead.Get(context) == null ? Guid.Empty.ToString() : this.Lead.Get(context).Id.ToString();
            try
            {
                Microsoft.Xrm.Sdk.EntityReference Moniker2 = new Microsoft.Xrm.Sdk.EntityReference();
                Moniker2.Id = Lead.Get(context).Id;
                Moniker2.LogicalName = "lead";//Entity Name

                // Code Create Moniker for second Entity: New_CustomEntity
                Microsoft.Xrm.Sdk.EntityReference Moniker1 = new Microsoft.Xrm.Sdk.EntityReference();
                Moniker1.Id = EmailSend.Get(context).Id;
                Moniker1.LogicalName = "cdi_emailsend";
                tracingService.Trace("Started Assosiating");
                AssociateManyToManyEntityRecords(service, Moniker1, Moniker2, "cdi_emailsend_lead");


            }
            catch (Exception ex)
            {
                //throw new Exception(ex.Message.ToString());
                //tracingService.Trace("Exception" + ex.Message.ToString());
                this.Success.Set(context, false);
                //return;
            }
        }

        private bool AssociateManyToManyEntityRecords(IOrganizationService service, Microsoft.Xrm.Sdk.EntityReference moniker1, Microsoft.Xrm.Sdk.EntityReference moniker2, string strEntityRelationshipName)
        {
            try
            {
                AssociateRequest request = new AssociateRequest();
                request.Target = moniker1;
                request.RelatedEntities = new EntityReferenceCollection { moniker2 };
                request.Relationship = new Relationship(strEntityRelationshipName);

                // Execute the request.
                service.Execute(request);


                return true;
            }
            catch (Exception exp)
            {
                return false;
            }
        }

    }
}

Send Template Email

The Send Template Email custom workflow activity will search for Template Records where the Lead Source and Lead Sub-Source are match the Source and Sub-Source from the designated lead, or are null. Using the email template specified on the Template Record, the custom workflow activity will create an email from the template and send it to the lead

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;


namespace LeadTemplate_WF
{
    public class LeadTemplate_WF : CodeActivity
    {
        #region Set the input mandatory parameters.
        //Input Value of Template Record
        [RequiredArgument]
        [Input("new_leadorigin")]
        [ReferenceTarget("sbi_leadorigin")]
        public InArgument<EntityReference> LeadOrgin { get; set; }

        [RequiredArgument]
        [Input("new_leadsuborigin")]
        [ReferenceTarget("sbi_leadsuborigin")]
        public InArgument<EntityReference> LeadSubOrgin { get; set; }

        [Output("Notes")]
        public OutArgument<String> Notes { get; set; }

        [Output("Success")]
        public OutArgument<bool> Success { get; set; }

        [Output("From Address")]
        public OutArgument<String> FromAddress { get; set; }

        [Output("ClickDimensions Email Template")]
        [ReferenceTarget("cdi_emailtemplate")]
        public OutArgument<EntityReference> ClickDimensionsEmailTemplate { get; set; }

        #endregion

        protected override void Execute(CodeActivityContext context)
        {
            //Create the tracing service
            ITracingService tracingService = context.GetExtension<ITracingService>();
            //Create the context
            IWorkflowContext workFlowContext = context.GetExtension<IWorkflowContext>();
            IOrganizationServiceFactory serviceFactory = context.GetExtension<IOrganizationServiceFactory>();
            IOrganizationService service = serviceFactory.CreateOrganizationService(workFlowContext.UserId);


            string strLeadOrgin = this.LeadOrgin.Get(context) == null ? Guid.Empty.ToString() : this.LeadOrgin.Get(context).Id.ToString();
            string strLeadSubOrgin = this.LeadSubOrgin.Get(context) == null ? Guid.Empty.ToString() : this.LeadSubOrgin.Get(context).Id.ToString();

            const String FetchTemplateRecord = @"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
  <entity name='new_templaterecord'>
    <attribute name='new_templaterecordid' />
    <attribute name='new_name' />
    <attribute name='new_leadsuborigin' />
    <attribute name='new_leadorigin' />
    <attribute name='new_template' />
    <attribute name='new_templatesource' />
    <order attribute='new_leadorigin' descending='true' />
    <order attribute='new_leadsuborigin' descending='true' />
    <filter type='and'>
      <condition attribute='new_templatetype' operator='eq' value='100000000' />
      <condition attribute='new_template' operator='not-null' />
      <filter type='or'>
        <filter type='and'>
          <condition attribute='new_leadorigin' operator='eq' uitype='sbi_leadorigin' value='{0}' />
          <filter type='or'>
            <condition attribute='new_leadsuborigin' operator='eq' uitype='sbi_leadsuborigin' value='{1}' />
            <condition attribute='new_leadsuborigin' operator='null' />
          </filter>
        </filter>
        <condition attribute='new_leadorigin' operator='null' />
      </filter>
    </filter>
  </entity>
</fetch>";
            // Team Query 
            const String FetchTeam = @"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='true'>
                                                              <entity name='team'>
                                                                <attribute name='name' />
                                                                <attribute name='businessunitid' />
                                                                <attribute name='teamid' />
                                                                <attribute name='teamtype' />
                                                                <attribute name='queueid' />
                                                                 <attribute name='emailaddress' />
                                                                <order attribute='name' descending='false' />
                                                                <filter type='and'>
                                                                  <condition attribute='teamtype' operator='eq' value='0' />
                                                                </filter>
                                                                <link-entity name='businessunit' from='businessunitid' to='businessunitid' alias='ae'>
                                                                  <link-entity name='lead' from='owningbusinessunit' to='businessunitid' alias='af'>
                                                                    <filter type='and'>
                                                                      <condition attribute='leadid' operator='eq' uitype='lead' value='{0}' />
                                                                    </filter>
                                                                  </link-entity>
                                                                </link-entity>
                                                              </entity>
                                                            </fetch>";


            try
            {
                tracingService.Trace("Retrieving the Template Record");

                FetchExpression fetchQuerytoRetrieveTemplateRecord = new FetchExpression(String.Format(FetchTemplateRecord, strLeadOrgin, strLeadSubOrgin));
                //tracingService.Trace("Retrieving " + fetchQuerytoRetrieveTemplateRecord);

                // Retrive Business Unit’s Team 
                FetchExpression fetchQuerytoRetrieveTeam = new FetchExpression(String.Format(FetchTeam, strLeadOrgin));

                var aa = service.RetrieveMultiple(fetchQuerytoRetrieveTemplateRecord);

                var bb = service.RetrieveMultiple(fetchQuerytoRetrieveTeam);

                if (aa.Entities.Count > 0)
                {
                    // tracingService.Trace("Entity count " + aa.Entities.Count);
                    Entity TemplateRecordObj = aa.Entities[0];

                    string strtitle = TemplateRecordObj.Attributes.Contains("new_name") ? TemplateRecordObj.Attributes["new_name"].ToString() : "No Name found";
                    //tracingService.Trace("strtitle " + strtitle.ToString());
                    Guid TempalteID = Guid.Empty;

                    string TempID = TemplateRecordObj.Attributes.Contains("new_template") ? TemplateRecordObj.Attributes["new_template"].ToString() : "Test Guid";
                    //tracingService.Trace("strTempID " + TempID.ToString());


                    if (Guid.TryParse(TemplateRecordObj.Attributes["new_template"].ToString(), out TempalteID) && TemplateRecordObj.Contains("new_templatesource"))
                    {
                        switch (((OptionSetValue)TemplateRecordObj["new_templatesource"]).Value)
                        {
                            case 100000001://CRM Email Template
                                tracingService.Trace("Sending emails");
                                InstantiateTemplateRequest req = new InstantiateTemplateRequest();
                                req.ObjectId = workFlowContext.PrimaryEntityId;
                                req.ObjectType = workFlowContext.PrimaryEntityName;
                                req.TemplateId = TempalteID;

                                InstantiateTemplateResponse resp = (InstantiateTemplateResponse)service.Execute(req);

                                Entity tmpEmail = new Entity("email");

                                if (resp.EntityCollection != null && resp.EntityCollection.Entities.Count > 0)
                                {
                                    tmpEmail = resp.EntityCollection.Entities[0];
                                }
                                List<Entity> toParty = new List<Entity>();
                                Entity tempParty = new Entity("activityparty");
                                tempParty["partyid"] = new EntityReference(workFlowContext.PrimaryEntityName, workFlowContext.PrimaryEntityId);
                                toParty.Add(tempParty);
                                tmpEmail.Attributes["to"] = toParty.ToArray();

                                tmpEmail.Attributes["regardingobjectid"] = new EntityReference(workFlowContext.PrimaryEntityName, workFlowContext.PrimaryEntityId);

                                service.Create(tmpEmail);

                                SendEmailRequest sendReq = new SendEmailRequest();
                                sendReq.EmailId = tmpEmail.Id;
                                sendReq.TrackingToken = "";
                                sendReq.IssueSend = true;
                                SendEmailResponse res = (SendEmailResponse)service.Execute(sendReq);

                                // Set the Output Parameters
                                this.Success.Set(context, true);
                                this.Notes.Set(context, strtitle);
                                this.ClickDimensionsEmailTemplate.Set(context, null);
                                this.FromAddress.Set(context, null);
                                break;

                            case 100000000://ClickD Email Template
                                this.ClickDimensionsEmailTemplate.Set(context, new EntityReference("cdi_emailtemplate", TempalteID));
                                if (bb.Entities.Count > 0)
                                {
                                    Entity TeamObj = bb.Entities[0];
                                   // string str_queuename = TeamObj.Attributes.Contains("name") ? TeamObj.Attributes["name"].ToString() : "No Queue Name Found";
                                    string str_emailaddress = TeamObj.Attributes.Contains("emailaddress") ? TeamObj.Attributes["emailaddress"].ToString() : "No Email Address Found";
                                    this.FromAddress.Set(context, str_emailaddress);
                                }
                                break;
                        }

                    }

                }
            }
            catch (Exception ex)
            {
                //throw new Exception(ex.Message.ToString());
                //tracingService.Trace("Exception" + ex.Message.ToString());
                this.Notes.Set(context, ex.Message.ToString());
                //return;
            }
        }
    }

}

Ribbon and UI Customizations


Email Template Selector

On the form ribbon of the Template Record add a flyout to display a list of Email Templates for leads. On click of a button in the flyout, set the Template field to the id of the specified Email Template. The flyout should be enabled only for existing, editable records.


Code:
/************************************************
populateArticles - function to populate a button
on the ribbon with the available disclaimers
Policy is 1
************************************************/
function PopulateArticles1(CommandProperties) {

    var serverUrl = Xrm.Page.context.getClientUrl();
    var requestUrl = serverUrl + "/xrmservices/2011/OrganizationData.svc/cdi_emailtemplateSet?$select=cdi_emailtemplateId,cdi_name&$filter=statecode/Value eq 0";
    
    $.ajax({
        type: "GET",
        contentType: "application/json; charset=utf-8",
        datatype: "json",
        async: false,
        url: requestUrl,
        beforeSend: function (XMLHttpRequest) {
            //Specifying this header ensures that the results will be returned as JSON.             
            XMLHttpRequest.setRequestHeader("Accept", "application/json");
        },
        success: function (data, textStatus, XmlHttpRequest) {
            var engines = data.d["results"];

            var theXml = '<Menu Id=\"Columbus.new_templaterecord.ClickDimensionButton\"><MenuSection Id=\"Columbus.new_templaterecord.ClickDimensionButton.Section\" Sequence=\"10\"><Controls Id=\"Columbus.new_templaterecord.ClickDimensionButton.Controls\">';
            var i = 0;
            for (engineKey in engines) {
                theXml += '<Button Id=\"' + engines[engineKey].cdi_emailtemplateId + "||" + engines[engineKey].cdi_name + '\" Command=\"new.new_templaterecord.ClickDimensionEmailTemplate.Command\" Sequence=\"' + ((i + 1) * 10).toString() + '\" LabelText=\"' + engines[engineKey].cdi_name + '\" />';
                i++;
            }
            theXml += '</Controls></MenuSection></Menu>';
            //prompt("the xml", theXml);
            CommandProperties.PopulationXML = theXml;

        },
        error: function (XmlHttpRequest, textStatus, errorThrown) {
            alert(XmlHttpRequest.status + "\n" + textStatus + "\n" + errorThrown);
        }
    });
}
/***  end populateArticles    ***/


/************************************************
openKbArticle - function to append a specific
disclaimer to the field
************************************************/
function openKbArticle1(CommandProperties) {
    var controlId = CommandProperties.SourceControlId;
   // alert(controlId);
    Xrm.Utility.openEntityForm("kbarticle", controlId);
}


function MainAlert1(CommandProperties) {
    var controlId = CommandProperties.SourceControlId;
var Tem_Type='100000000';
if(controlId != null)
{
var str_array = controlId.split('||');
for (var i = 0; i < str_array.length; i++) {
            // Trim the excess whitespace.
            str_array[i] = str_array[i].replace(/^\s*/, "").replace(/\s*$/, "");
        }
controlId=str_array[0];
var name=str_array[1];
    Xrm.Page.getAttribute("new_template").setValue(controlId);
Xrm.Page.getAttribute("new_name").setValue(name);
Xrm.Page.getAttribute("new_templatesource").setValue(Tem_Type);
}
else
{
 alert("Please Select the Template!");
 return;
}
}
/***   end openKbArticle   ***/

Get files of last hour in Azure Data Factory

  Case I have a Data Factory pipeline that should run each hour and collect all new files added to the data lake since the last run. What is...