Using Extension Methods in Dynamics 365 CE Plugins

In my previous blog post, Extension Methods in the .NET Framework, I covered how to use extension methods in C# and VisualBasic to extend the functionality of base types. Using extension methods can also be useful when writing plugins for Dynamics 365 Customer Engagement (Dynamics CRM). I create my own library of extension methods that can be used as needed for any project I’m currently working on. The most common types of extensions for me involves the tracing service, the organization service, and the entity.

I have two types of tracing that I perform. The first is standard trace messages that are always logged to the plugin trace logs. The second is a debug-only trace message that only writes to the plugin trace log if the assembly is compiled in Debug as opposed to Release. As a side comment, it is always preferable to have your assemblies compiled in Release mode when deployed to a production ready environment, to improve plugin performance. This makes using the #if DEBUG preprocessing directive more effective.

Without an extension message, sending a message to the plugin tracing log is done by calling the Trace method of Microsoft.Xrm.Sdk.ITracingService.

using Microsoft.Xrm.Sdk;

public namespace Sample.Tracing
{
    public class PluginTest : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            tracingService.Trace("Entering plug-in execution.");
            tracingService.Trace("Some debug information is logged to the plugin trace log.");
            // Additional code goes here
        }
    }
}

The standard way of tracing is effective on its own but is still missing some elements that make it useful. As mentioned previously, you may only want certain tracing calls to be done if you are actively debugging your code. Prepending every call to the trace log with a time stamp is also beneficial when trying to determine where plug-in performance is being adversely impacted. You can also parse your error object to output more detailed error messages directly to the plugin trace logs. For the sample below, I’m just going to demonstrate how we can expand the tracing log to handle debug only messages and time-stamped messages.

using Microsoft.Xrm.Sdk;
using System;

namespace Sample.SdkExtensions
{
    public static class ExtITracingService
    {
        public static void DebugMessage(this ITracingService tracingService, string format, params object[] args)
#if DEBUG
            => tracingService.LogMessage(format, args);
#else
            { }
#endif
    }

    public static void LogMessage(this ITracingService tracingService, string format, params object[] args)
    {
        string message = (args == null || args.Length == 0)
            ? format
            : String.Format(format, args);
        tracingService.Trace("{0}: {1}", DateTime.Now.ToString("o"), message);
    }
}

The DebugMessage extension method shows how to use the preprocessing directive to only execute when running in Debug mode. If the code is running in Debug, then it will call the LogMessage extension method. If it isn’t, then the method doesn’t do anything at all. This will allow us to have more detailed messages when running the assembly in Debug, and better performance when running the assembly in Release. The LogMessage extension method will create the appropriate message string from the format and optional arguments and pass that directly over to the ITracingService, prepended with a date and time stamp.

Now, we can update the plugin code to use our new extension methods.

using Sample.SdkExtensions;
using Microsoft.Xrm.Sdk;

public namespace Sample.Tracing
{
    public class PluginTest : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            tracingService.LogMessage("Entering plug-in execution.");
            tracingService.DebugMessage("Some debug information is logged to the plugin trace log.");
            // Additional code goes here
        }
    }
}

As you can see, once we have our extension method in the project, we can reference the namespace for it in our using statements. At that point, our DebugMessage and LogMessage extension methods are able to be used. Now, debug messages will only be added to the plugin tracing log if the assembly is compiled as Debug, and both messages will be prepended with the appropriate time stamp. These extension methods increase the readability and usability of the code, giving consistent logging functionality no matter where the calls are made in the code.

Extending the entity is another area that it is useful to create extension methods. Getting and setting of attribute values often requires the same several lines of code to retrieve the appropriate information. Examine the code below using the standard SDK calls to retrieve the primary contact Id from one account, and set it on another. While it is preferable to retrieve the Entity Reference in this type of scenario, I am writing in this fashion to show the value in reducing code complexity.

Guid entityReferenceId = account1.Contains("primarycontact")
    ? ((EntityReference)account1["primarycontact"]).Id
    : Guid.Empty;

if (entityReferenceId == Guid.Empty)
{
    if (account2.Contains("primarycontact"))
    {
        account2["primarycontact"] = null;
    }
}
else
{
    if (account2.Contains("primarycontact"))
    {
        account2["primarycontact"] = new EntityReference("contact", entityReferenceId);
    }
    else
    {
        account2.Attributes.Add("primarycontact") = new EntityReference("contact", entityReferenceId);
    }
}

There are several lines of code required to perform a simple get and set operation. These operations can be created as extension methods on Microsoft.Xrm.Sdk.Entity as we did for the tracing service. I won’t be putting the extension methods here, instead, I will show how we can use two new extension methods GetEntityReferenceId and SetEntityReference to increase the readability of the plugin code.

Guid entityReferenceId = account1.GetEntityReferenceId("primarycontact");
account2.SetEntityReference("contact", entityReferenceId);

As you can see, the extension methods now make it easier to set and get values from the entity image. As mentioned previously, this increases readability and reusability of the code, making debugging easier. Just be aware that if you are setting default values (such as Guid.Empty for Guid values) you’ll want to have additional checks in your code to ensure that you are working with the proper data.

The final extension method I will touch on in this entry is related to Microsoft.Xrm.Sdk.IOrganizationService. In my previous blog post, I mentioned that it is strongly discouraged from overloading the methods of existing types. In the case of extending the organization service, I am intentionally ignoring that advice. In this case, my extension methods are simply shortcuts that are passed to the primary call, and even if Microsoft later adds their own overloads that mirror what I have done, it won’t break my existing functionality. This is an exception to the recommendation that still needs to be handled with care.

// Global Values
EntityReference entityReference = new EntityReference("account", Guid.Parse("608DA112-2681-4967-B30E-A4132321010A")); // Normally will be retrieved from an entity.
string[] columnNames= new string[] { ..list of fields.. };

// Default SDK Retrieve call.
service.Retrieve(entityReference.LogicalName, entityReference.Id, new ColumnSet(columnNames));

// Overloaded extension Retrieve call.
service.Retrieve(entityReference, columnNames);

// The overload in from the extension static class
public static Entity Retrieve(this IOrganizationService service, EntityReference entityReference, string[] columnNames)
    => service.Retrieve(entityReference.LogicalName, entityReference.Id, new ColumnSet(columnNames));

As you can see, the overload is simply calling the SDK Retrieve method with the appropriate parameter values. No additional validation is being done. It simply allows for a shorter method call to simplify the code.

As mentioned in the previous article, there is no consensus on how often to use extension methods. Microsoft does recommend using “sparingly and only when you have to.” It could be argued that the SDK methods are simple enough and don’t fall under “when you have to.” In my case, I prefer having multiple overloads, as I find that it makes debugging the code easier. My methods become shorter and easier to read. I have additional tracing methods, beyond what is shown in this blog post, to give more details in the plugin trace log. The ultimate goal for me is the maintainability, reusability, and readability of my code. Using extension methods with the SDK achieves that for me.