0
votes

I have recently started getting the CRM objects in the Execute method and performing the plugin/workflow business logic in a separate method.
Can this approach be improved (or discarded)?
Is it acceptable to get the target record of the workflow with LINQ?
It is more convenient than input parameters; also workflows are asynchronous and do not effect the user experience.

For Plugins:

    public class AddEmailAttachments : IPlugin
    {
        private void AddAttachments(Entity target, IOrganizationService service, Context linq)
        {
            // Business logic
        }
        /*
         * Get all the Objects that we need
         * and call AddAttachments
         * */
        public void Execute(IServiceProvider serviceProvider)
        {
            IPluginExecutionContext context = null;
            IOrganizationServiceFactory factory = null;
            IOrganizationService service = null;
            Entity target = null;
            Context linq = null;
            try // and get the services we need
            {
                context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
                factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
                service = factory.CreateOrganizationService(context.UserId);
                target = (Entity)context.InputParameters["Target"];
                linq = new Context(service);
                if (service != null && target != null && linq != null)
                    AddAttachments(target, service, linq);
            }
            catch (Exception) { }; // this is strict because if this plugin fails then the email will not be created in CRM and disrupt the business process

        }
    }

For Workflows:

    public class SendCaseNotifications : CodeActivity
    {
        /* Receives the necessary Objects and 
         * Sends the email
         * */
        private void SendNotifications(Incident incident, IOrganizationService service, Email email, Context linq)
        {
            // Perform business logic
        }
        /* Get the Objects that are required and call
         * the method
         * */
        protected override void Execute(CodeActivityContext context)
        {
            IWorkflowContext workflowContext = context.GetExtension<IWorkflowContext>();
            IOrganizationServiceFactory factory = context.GetExtension<IOrganizationServiceFactory>();
            IOrganizationService service = factory.CreateOrganizationService(workflowContext.InitiatingUserId);
            Entity target = workflowContext.InputParameters["Target"] as Entity;
            Incident incident = null;
            Email email = null;
            Context linq = new Context(service);
            IEnumerable<Incident> incidentQuery = from incidents in linq.IncidentSet where incidents.Id.Equals(target.Id) select incidents;
            if (incidentQuery.Any())
                incident = incidentQuery.First();
            if (incident == null)
                throw new InvalidPluginExecutionException("Unable to retrieve Case with id: " + target.Id.ToString() + ". Re-try the operation or contact the system administrator.");
            IEnumerable<Email> emailQuery = from emails in linq.EmailSet where emails.Id.Equals(incident.mda_originatingemail.Id) select emails;
            if (emailQuery.Any())
                email = emailQuery.First();
            if (email == null)
                throw new InvalidPluginExecutionException("Unable to retrieve Email with id: " + incident.mda_originatingemail.Id.ToString() + ". Re-try the operation or contact the system administrator.");
            SendNotifications(incident, service, email, linq);
        }
    }

I do as much exception handling as possible in Execute and then pass the objects to the method that performs the actual work.
I have recently learnt the hard way that a synchronous plugin can effect the business process if it throws an exception.

1

1 Answers

2
votes

1 => Can this approach be improved (or discarded)?

You can improve this in a couple ways. The biggest way you can improve this is create a base class for your Plugins and Workflow Activities that automatically do all of the necessary extracting of the contexts, orgservice, etc. You can even take this step a bit further and have base classes for Create/Update/Delete/SetState/etc plugins extract the input and output parameters appropriately.

Second, if you wanted to go full OO design you could put all your business logic in separate classes instead of functions. It would be less coupled, more organized for complicated logic, and testable if you are ever want to do unit testing.

2 => Is it acceptable to get the target record of the workflow with LINQ?

I think the question is it good practice to retrieve the current record of a workflow with LINQ? There isn't anything wrong with how you were doing it. You can probably just use the LINQ extension method FirstOrDefault() instead of Any() and First().

Also in general, it is good practice for Crm Retrieves to only return the columns that you need. While you might not notice this for retrieving one record, you should definitely do this if you are retrieving multiple records.