Dynamics CRM: Proving Auto Number Data Type Is Legit!

I just saw Nishant Rana’s post about the auto-number data type that you can check here. And to be honest, I just knew about it and felt intrigued to test it. My expectation was pretty low about this data type (to handle lots of requests and result in duplicate numbers in the end). TL;DR (Too Long; Didn’t Read), IT’S NOT!

Continue reading “Dynamics CRM: Proving Auto Number Data Type Is Legit!”

Dynamics CRM: Effective Way Calling Custom Web API In Plugin Development

After I post several posts about Custom Web API (Dynamics CRM: Simplify Development with Custom API for Reusability and Get Environment Variable in a Flow using Custom Web API). You should be more clear about how to create the Custom Web API in your environment. Today, we will cover how to implement this feature effectively (real-world scenario) on the Plugin Development (backend).

For today’s demonstration, I already have a Custom API (all my custom API collection I put in here) in my environment with the name of “ins_getteamsemail”. This Custom API will return the array string of the email/userid and receives 2 parameters: boolean “IsEmail” and string “TeamName“. If we want to receive email, we can set IsEmail as true and TeamName we can put any string on it. If we want to call it in the normal way, we can use this code below:

using Microsoft.Xrm.Sdk;
using System;

namespace Blog.Plugin
{
    public abstract class PluginBase : IPlugin
    {
        protected string PluginClassName { get; }

        internal PluginBase(Type pluginClassName)
        {
            PluginClassName = pluginClassName.ToString();
        }

        public void Execute(IServiceProvider serviceProvider)
        {
            if (serviceProvider == null)
            {
                throw new InvalidPluginExecutionException("serviceProvider");
            }

            var localPluginContext = new LocalPluginContext(serviceProvider);

            localPluginContext.Trace($"Entered {PluginClassName}.Execute() " +
                $"Correlation Id: {localPluginContext.PluginExecutionContext.CorrelationId}, " +
                $"Initiating User: {localPluginContext.PluginExecutionContext.InitiatingUserId}");

            try
            {
                ExecuteCdsPlugin(localPluginContext);
                return;
            }
            catch (FaultException<OrganizationServiceFault> orgServiceFault)
            {
                localPluginContext.Trace($"Exception: {orgServiceFault.ToString()}");

                throw new InvalidPluginExecutionException($"OrganizationServiceFault: {orgServiceFault.Message}", orgServiceFault);
            }
            finally
            {
                localPluginContext.Trace($"Exiting {PluginClassName}.Execute()");
            }
        }

        protected virtual void ExecuteCdsPlugin(ILocalPluginContext localPluginContext)
        {
        }
    }

    public interface ILocalPluginContext
    {
        IOrganizationService CurrentUserService { get; }
        IOrganizationService SystemUserService { get; }
        IPluginExecutionContext PluginExecutionContext { get; }
        IServiceEndpointNotificationService NotificationService { get; }ITracingService TracingService { get; }
        void Trace(string message);
    }

    public class LocalPluginContext : ILocalPluginContext
    {
        public IOrganizationService CurrentUserService { get; }

        public IOrganizationService SystemUserService { get; }

        public IPluginExecutionContext PluginExecutionContext { get; }

        public IServiceEndpointNotificationService NotificationService { get; }

        public ITracingService TracingService { get; }

        public LocalPluginContext(IServiceProvider serviceProvider)
        {
            if (serviceProvider == null)
            {
                throw new InvalidPluginExecutionException("serviceProvider");
            }

            PluginExecutionContext = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

            TracingService = new LocalTracingService(serviceProvider);

            NotificationService = (IServiceEndpointNotificationService)serviceProvider.GetService(typeof(IServiceEndpointNotificationService));

            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

            CurrentUserService = factory.CreateOrganizationService(PluginExecutionContext.UserId);

            SystemUserService = factory.CreateOrganizationService(null);
        }

        public void Trace(string message)
        {
            if (string.IsNullOrWhiteSpace(message) || TracingService == null)
            {
                return;
            }

            TracingService.Trace(message);
        }
    }
    
    public class LocalTracingService : ITracingService
    {
        private readonly ITracingService _tracingService;

        private DateTime _previousTraceTime;

        public LocalTracingService(IServiceProvider serviceProvider)
        {
            DateTime utcNow = DateTime.UtcNow;

            var context = (IExecutionContext)serviceProvider.GetService(typeof(IExecutionContext));

            DateTime initialTimestamp = context.OperationCreatedOn;

            if (initialTimestamp > utcNow)
            {
                initialTimestamp = utcNow;
            }

            _tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

            _previousTraceTime = initialTimestamp;
        }

        public void Trace(string message, params object[] args)
        {
            var utcNow = DateTime.UtcNow;

            // The duration since the last trace.
            var deltaMilliseconds = utcNow.Subtract(_previousTraceTime).TotalMilliseconds;

            _tracingService.Trace($"[+{deltaMilliseconds:N0}ms)] - {message}");

            _previousTraceTime = utcNow;
        }
    }
    
    public class PreCacheOperation : PluginBase
    {
        public PreCacheOperation(string unsecureConfiguration, string secureConfiguration)
            : base(typeof(PreCacheOperation))
        {
        }

        protected override void ExecuteCdsPlugin(ILocalPluginContext localPluginContext)
        {
            if (localPluginContext == null)
            {
                throw new ArgumentNullException("localPluginContext");
            }

            IPluginExecutionContext context = localPluginContext.PluginExecutionContext;
         
            Entity entity = (Entity)context.InputParameters["Target"];
            var req = new OrganizationRequest("ins_getteamsemail")
            {
                ["IsEmail"] = true,
                ["TeamName"] = "Blog Team"
            };
            var team =
                localPluginContext.CurrentUserService.Execute(req);
            entity["ins_description"] = string.Join(",", team.Results["Result"] as string[]);
        }
    }
}

Here is the result from the screen:

System will set ins_description with all the email that is in Blog Team

The problem of the implementation above is about efficiency and reusability (same with the late-bound, we must remember the parameter name which is tedious to remember it). Let’s say we build a Custom Web API that is being used a lot in our project, we suppose have the collection of the Requests and Responses so the Developer will be easier to implement it in the code. Here is my attempt to enhance the above code:

using Microsoft.Xrm.Sdk;
using System;
using System.Runtime.Serialization;

namespace Blog.Plugin
{
    public static class OrganizationResponseExtensions
    {
        public static T Cast<T>(this Microsoft.Xrm.Sdk.OrganizationResponse response)
            where T : OrganizationResponse
        {
            var newClass = Activator.CreateInstance<T>();
            newClass.Results = response.Results;

            return newClass;
        }
    }

    [DataContract(Name = "OrganizationRequest",
        Namespace = "http://schemas.microsoft.com/xrm/2011/Contracts")]
    public sealed class GetTeamsEmailRequest : OrganizationRequest
    {
        public string TeamName
        {
            get
            {
                if (Parameters.Contains("TeamName"))
                {
                    return Parameters["TeamName"].ToString();
                }
                return "";
            }
            set { this["TeamName"] = value; }
        }

        public bool IsEmail
        {
            get
            {
                if (Parameters.Contains("IsEmail"))
                {
                    return bool.Parse(Parameters["IsEmail"].ToString());
                }
                return false;
            }
            set { this["IsEmail"] = value; }
        }

        public GetTeamsEmailRequest()
        {
            RequestName = "ins_getteamsemail";
        }
    }

    [DataContract(Name = "OrganizationResponse",
        Namespace = "http://schemas.microsoft.com/xrm/2011/Contracts")]
    public sealed class GetTeamsEmailResponse : Microsoft.Xrm.Sdk.OrganizationResponse
    {
        public string[] Emails => this["Result"] as string[];
    }
}

And here is the code when we want to implement it:

using Microsoft.Xrm.Sdk;
using System;
using System.Runtime.Serialization;

namespace Blog.Plugin
{
    public class PreCacheOperation : PluginBase
    {
        public PreCacheOperation(string unsecureConfiguration, string secureConfiguration)
            : base(typeof(PreCacheOperation))
        {
        }

        protected override void ExecuteCdsPlugin(ILocalPluginContext localPluginContext)
        {
            if (localPluginContext == null)
            {
                throw new ArgumentNullException("localPluginContext");
            }

            IPluginExecutionContext context = localPluginContext.PluginExecutionContext;

            Entity entity = (Entity) context.InputParameters["Target"];
            var req = new GetTeamsEmailRequest {IsEmail = true, TeamName = "Blog Team"};
            var team = localPluginContext.CurrentUserService.Execute(req).Cast<GetTeamsEmailResponse>();
            entity["ins_description"] = string.Join(",", team.Emails);
        }
    }
}

If you analyze it, we got GetTeamsEmailRequest as request (inherit from OrganizationRequest) class (mimicking those standard Request such as WhoAmIRequestUpdateRequestCreateRequest, etc). In this class, we defined the parameter that we need (DataType and Name) which is IsEmail and TeamName.

While for the response, we got GetTeamsEmailResponse as the response class (derived from OrganizationResponse) class. In this class, we just want to return an array of string attribute named Emails.

The last one, we need to create Extensions to help us cast the result (OrganizationResponse) to the custom response class that we wanted. That is why we give criteria only if T : OrganizationResponse which means only if T is derived from OrganizationResponse. I not yet found any way that we can cast the response directly to the custom response that we want currently.

Summary

You can put those Request, Response, and The extension into separate C# project / SharedProject. Then you can reuse this collection in the projects that will use it. It will optimize how you work in Plugin Development because we already have the “shortcut” of the Custom API that we made!

What do you think?

Dynamics CRM 365: Build Automation for Create/Update RESX WebResource + Power Automate Desktop

In this post, we already learned how to make a plugin library for getting resx web-resource. Now we will build a simple automation program (a little bit DevOps) to read an Excel file and create/update resx web-resource with a single click. Also, we will use Power Automate Desktop which enables us to a more complex scenario in the future.

Continue reading “Dynamics CRM 365: Build Automation for Create/Update RESX WebResource + Power Automate Desktop”

Dynamics CRM Create Plugin Helper: Duplicate Xrm.Utility.getResourceString for Plugin Side

Have you ever tried Xrm.Utility.getResourceString?  Xrm.Utility.getResourceString is a function that we can use in the front-end to return the localized string for a given key associated with the specified web resource. For instance, we can use it for storing localized error messages (for validation purposes). But the problem is we only can use it in the front-end, which is why we will duplicate this feature in the back-end so we can use it also.

Continue reading “Dynamics CRM Create Plugin Helper: Duplicate Xrm.Utility.getResourceString for Plugin Side”

Dynamics CRM Model-Driven Apps: Implement Niam.Xrm.Client and Niam.Xrm.Client.Test

Now we go to the last part of the tutorial for Front-end Customization. We will implement Niam.Xrm.Client and Niam.Xrm.Client.Tests on the project we set up earlier. Niam.Xrm.Client is a framework wrapper for Xrm object that focuses on making customization easier, while Niam.Xrm.Client.Tests will mainly focus on Xrm.webApi mock objects and testing-related helpers. Both packages are in beta versions but are already sufficient for the scenario that we will cover in this post.

Continue reading “Dynamics CRM Model-Driven Apps: Implement Niam.Xrm.Client and Niam.Xrm.Client.Test”

Dynamics CRM Model-Driven Apps: Setup Testing Environment with chai, Mocha, xrm-mock, and SinonJS

As developers, we are supposed to adapt ourselves to a better working framework or mindset. Following a Test-Driven Development (TDD) mindset, for example, enables us to work more effectively in the long run, compared to not using it. Hence making unit tests has become a crucial part (at least for me), and this post will explain how to set up our project to enable unit testing in our Model-Driven-Apps front-end development. This post is a continuation of this blog post. If you have not yet checked that, you need to go there to get the context (also the zip file).

Continue reading “Dynamics CRM Model-Driven Apps: Setup Testing Environment with chai, Mocha, xrm-mock, and SinonJS”

Tips Model-Driven Apps Javascript: How to trigger Custom Method After Saving

Have you ever had a scenario that needs to disable all the controls in the UI after data is successfully saved? Let’s say after the document State is changed to Approved, then we need to disable some of the attributes in the form? If yes, then this post is about my simple tips to achieve that scenario.

Continue reading “Tips Model-Driven Apps Javascript: How to trigger Custom Method After Saving”

Dynamics CRM Model-Driven Apps: Developing Frontend Code with Typescript, @types/xrm and webpack

First question: Why TypeScript? We know what JavaScript programming does. The programming language sometimes leads us to confusion because it’s too dynamic. While TypeScript as JavaScript superset trying to fix the whole weakness that it can lead us into. TypeScript offers a more strict language (data type) and supports IntelliSense which is easier for a new developer to catch up and accustom to.

Continue reading “Dynamics CRM Model-Driven Apps: Developing Frontend Code with Typescript, @types/xrm and webpack”

Creating Better PCF Component – Part 3

With all the things settled, now we can continue our journey to make our PCF-Component better. The last commit that we submitted, there are few things that we can enhance:

  1. When you choose an image file, then click the Submit button. We can reset the input file.
  2. Hide the image-element if no image has been upload.
  3. Add image-resizer to make our image smaller in our database.
Continue reading “Creating Better PCF Component – Part 3”

Creating Better PCF Component – Part 2

When I was writing this post, I felt the environment that I set up did not enhance my ability to write code. The reason for this is that I use jsdom, which is a mock object for the HTML component. Because it is not a real object of HTML, my focus has been changed to the test code instead of writing the implementation code. 

Continue reading “Creating Better PCF Component – Part 2”