Clarifying how Business Rules work with Editable Grids

Let’s clarify how business rules work with editable grids once in for all. I keep seeing this discussed passionately and many people believe they don’t work together.

As a note the functionality we’re going to review today works the same for the UCI (Unified Client interface) or the classic editable grid. There currently is a bug with the unified interface version that I need to note here. I feel that this bug may be a cause for a lot of the debate. In writing this blog post it became very clear in the UCI when you switch from the read only grid to the editable grid on the home screen that business rules aren’t being applied. Once I see this issue resolved i’ll post an update to this.

Let’s create a simple business rule that says if the created on date contains data then lock the email field. I’ve tested the scope with all forms or entity and got the same result. In this scenario in order for this business rule to execute it must be able to validate the if condition; this means that the editable view must have the created on field in order to evaluate it. This seems to be something that alludes people frequently.

Checking this business rule against this simple view in the editable grid we see that the email is still editable.  As the view doesn’t contain the created on date field.

Let’s create a simpler business rule that checks to see if the email contains data and if it does then lock the email field. I’ve tested the scope with all forms or entity and got the same result. This covers a common scenario that wouldn’t allow the user to change a fields value once entered in the system on the form or editable grid.

Checking this business rule against this simple view in the editable grid we see that the email locked because it contains data. If we clicked on an account record that didn’t have an email value it would be editable until a value was saved in it.

I hope this brings some clarity to how business rules interact with editable grids.

The more you know!

Firing Business Rules when making JavaScript changes!

With the investment being made in Dynamics 365 around business rules I typically try and leverage them when possible. I’ve found at times it creates some challenges along the way. One specific challenge  a college and I’ve ran into was when making JavaScript modifications to fields the related business rules were’t executing.

Making the business rules fire took a bit of research but it paid off as we’re able to continue to use Business Rules with JavaScript. A co-worker of mine Mike Wright found the answer and documentation. Microsoft has the documentation for the fireOnChange event you need to use on the Attribute here.

When the fireOnChange event is called it will call your related javascript event for that attribute as well execute any business rules associated to it. This is a great piece of knowledge to know when working with business rules and javascript.
Xrm.Page.getAttribute(arg).fireOnChange()

The more you know!

Creating an action using a custom workflow activity

Hello;

Today we’re going to be starting a series that will lead us into PowerApps. This is part one and it’s walks through creating a custom workflow activity, then inserting into a custom action in a v9 environment.

The business requirement for this post is to have an action that is able to activate/deactivate a process.

Custom Workflow Activity

Microsoft provides a very extensively list of out of the box actions which gets updated frequently. It’s worth checking out this documentation from time to time to catch the latest and greatest https://msdn.microsoft.com/en-us/library/mt607829.aspx. Unfortunately there isn’t an action to activate/deactivate a process; this has to be handled through a custom workflow activity. The following code below is what’s required.

    public class SetProcessState : CodeActivity

    {

        /// <summary>
        /// This is the workflow that needs to be activated/deactivated.
        /// </summary>
        [Input("Workflow")]
        [ReferenceTarget("workflow")]
        public InArgument<EntityReference> Workflow { get; set; }
        
        /// <summary>
        /// This is the state of the workflow entity.
        /// 0 is draft and 1 is Activated
        /// </summary>
        [Input("Workflow State")]
        public InArgument<int> WorkflowState { get; set; }

        /// <summary>
        /// This is the status of the workflow entity.
        /// </summary>
        [Input("Workflow Status")]
        public InArgument<int> WorkflowStatus { get; set; }

        protected override void Execute(CodeActivityContext executionContext)
        {
            //Create the tracing service
            ITracingService tracingService = executionContext.GetExtension<ITracingService>();

            //Create the context
            IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
            IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            EntityReference workflow = Workflow.Get<EntityReference>(executionContext);
            int workflowState = WorkflowState.Get<int>(executionContext);
            int workflowStatus = WorkflowStatus.Get<int>(executionContext);

            if(workflow == null)
            {
                tracingService.Trace("The workflow entity reference past in is not valid");
                throw new Exception("The workflow entity reference past in is not valid");
            }

            tracingService.Trace("Activate/Deactivate Workflow:");

            var activateRequest = new SetStateRequest
            {
                EntityMoniker = new EntityReference
                    (workflow.LogicalName, workflow.Id),
                State = new OptionSetValue(workflowState),
                Status = new OptionSetValue(workflowStatus)
            };
            service.Execute(activateRequest);
            Console.WriteLine("and sent request to service.");

        }
    }

This custom workflow activity takes three parameters:

  1. Workflow – This is a entity reference to the actual process that’s going to be activated/deactivated.
  2. Workflow State – This is the state of the workflow entity
  3. Workflow Status – This is the status of the workflow entity.

With these three parameters we’re ready to create the action which will allow us to activate/deactivate a process.

Creating the Action

Creating an action is pretty straight forward; an action is a type of a process. The best part about actions is they become a message that’s callable through the web api directly. This alone makes them extremely powerful. It’s a great way to compartmentalize business logic or technical components.

Let’s navigate to a new solution I’ve setup where we are going to create an action.

This particular action is going to be a global action which means it can be called standalone as apposed to against a specific entity. I do this because the process entity isn’t in the list of entities. The first thing we’ll do once the action is created is to add the three actions we defined in the custom workflow activity.

Now we have the arguments that we can pass directly into the custom workflow activity allowing us to activate/deactivate any process. Let’s go ahead and add the call to our custom workflow activity and set the parameters of the step.

I wanted to stop and highlight when trying to set the Workflow value. All non entity reference arguments of the action are contained within the Local Values -> Arguments section shown above. The entity reference argument is shown differently as per the highlight item; it’s displayed under a section called Argument Entity. This is the one we’ll select to give the workflow a value. We can save and close.

We are now left with a completed action with the functionality to activate/deactivate processes. I hope you enjoyed the walk through on combining these two items together. Next post we’ll look at putting this action to good use through the API.