Slight change of pace with this week’s blog post, which will be a fairly condensed and self-indulgent affair – due to personal circumstances, I have been waylaid somewhat when it comes to producing content for the blog and I have also been unable to make any further progress with my new YouTube video series. Hoping that normal service will resume shortly, meaning additional videos and more content-rich blog posts, so stay tuned.

I’ve been running the CRM Chap blog for just over 2 years now. Over this time, I have been humbled and proud to have received numerous visitors to the site, some of whom have been kind enough to provide feedback or to share some of their Dynamics CRM/365 predicaments with me. Having reached such a landmark now seems to be good a time as any to take a look back on the posts that have received the most attention and to, potentially, give those who missed them the opportunity to read them. In descending order, here is the list of the most viewed posts to date on the crmchap.co.uk website:

  1. Utilising SQL Server Stored Procedures with Power BI
  2. Installing Dynamics CRM 2016 SP1 On-Premise
  3. Power BI Deep Dive: Using the Web API to Query Dynamics CRM/365 for Enterprise
  4. Utilising Pre/Post Entity Images in a Dynamics CRM Plugin
  5. Modifying System/Custom Views FetchXML Query in Dynamics CRM
  6. Grant Send on Behalf Permissions for Shared Mailbox (Exchange Online)
  7. Getting Started with Portal Theming (ADXStudio/CRM Portals)
  8. Microsoft Dynamics 365 Data Export Service Review
  9. What’s New in the Dynamics 365 Developer Toolkit
  10. Implementing Tracing in your CRM Plug-ins

I suppose it is a testament to the blog’s stated purpose that posts covering areas not exclusive to Dynamics CRM/365 rank so highly on the list and, indeed, represents how this application is so deeply intertwined with other technology areas within the Microsoft “stack”.

To all new and long-standing followers of the blog, thank you for your continued support and appreciation for the content ūüôā

As part of developing Dynamics CRM/Dynamics 365 Customer Engagement (CRM/D365CE) plug-ins day in, day out, you can often forget about the Execution Mode setting. This can be evidenced by the fact that I make no mention of it in my recent tutorial video on plug-in development. In a nutshell, this setting enables you to customise whether your plug-in executes in Synchronous or Asynchronous mode. Now, you may be asking Рjust what the hell does that mean?!? The best way of understanding is by rephrasing the terminology; it basically tells the system when you want your code to be executed. Synchronous plug-ins execute all of your business logic whilst the record is being saved by the user, with this action not being considered complete and committed to the backend database until the plug-in completes. By comparison, Asynchronous plug-ins are queued for execution after the record has been saved. A System Job record is created and queued alongside other jobs in the system via the Asynchronous Service. Another way of remembering the difference between each one is to think back to the options available to you as part of a Workflow. They can either be executed in real time (synchronously) or in the background (asynchronously). Plug-ins are no different and give you the flexibility to ensure your business logic is applied immediately or, if especially complex, queued so that the system has sufficient time to process in the background.

I came across a strange issue with an arguably even stranger Synchronous plug-in the other day, which started failing after taking an inordinately long time saving the record:

Unexpected exception from plug-in (Execute): MyPlugin.MyPluginClass: System.AggregateException: One or more errors occurred.

The “strange” plug-in was designed so that, on the Create action of an Entity record, it goes out and creates various related records within the application, based on a set of conditions. We originally had issues with the plug-in a few months back erroring, due to the execution time exceeding the 2 minute limit for sandbox plug-ins. A rather ingenious and much more accomplished developer colleague got around the issue by implementing a degree of asynchronous processing within the plug-in, achieved like so:

await Task.Factory.StartNew(() =>
{
    lock (service)
    {
        Stopwatch stopwatch = Stopwatch.StartNew();
        Guid record = service.Create(newRecord);
        tracing.Trace("Record with ID " + record.ToString() + " created successfully after: {0}ms.", stopwatch.ElapsedMilliseconds);
    }
});

I still don’t fully understand just exactly what this is doing, but I put this down to my novice level C# knowledge ūüôā The important thing was that the code worked…until some additional processing was added to the plug-in, leading to the error message above.

At this juncture, our only choice was to look at forcing the plug-in to execute in Asynchronous mode by modifying the appropriate setting on the plug-in step within the Plugin Registration Tool:

After making this change and attempting to create the record again in the application, everything worked as expected. However, this did create a new problem for us to overcome – end users of the application were previously used to seeing the related records created by the plug-in within sub-grids on the Primary Entity form, which would then be accessed and worked through accordingly. As the very act of creating these records now took place within the background and took some time to complete, we needed to display an informative message to the user to advise them to refresh the form after a few minutes. You do have the ability within plug-ins to display a custom message back to the user, but this is only in situations where you are throwing an error message and it didn’t seem to be a particularly nice solution for this scenario.

In the end, the best way of achieving this requirement was to implement a JScript function on the form. This would trigger whenever the form is saved and displays a message box that the user has to click OK on before the save action is carried out:

function displaySaveMessage(context) {

    var eventArgs = context.getEventArgs();
    var saveMode = eventArgs.getSaveMode();

    if (saveMode == 70 || saveMode == 2 || saveMode == 1 || saveMode == 59) {
        var message = "Records will be populated in the background and you will need to refresh the form after a few minutes to see them on the Sub-Grid. Press OK to save the record."
        Xrm.Utility.alertDialog(message, function () {
            Xrm.Page.data.save().then(function () {
                Xrm.Page.data.refresh();
            })
        });
    }
}

By feeding through the execution context parameter, you are able to determine the type of save action that the alert will trigger on; in this case, Save, Save & Close, Save & New and Autosave. Just make sure you configure your script with the correct properties on the form, which are:

  • Using the¬†OnSave event handler
  • With the¬†Pass execution context as first parameter setting enabled

From the end-users perspective, they will see something similar to the below when the record is saved:

It’s a pity that we don’t have similar kind of functionality exposed via Business Rules that enable us to display OnSave alerts that are more in keeping with the applications look and feel. Nevertheless, the versatility of utilising JScript functions should be evident here and can often achieve these types of bespoke actions with a few lines of code.

When it comes to plug-in development, understanding the impact and processing time that your code has within the application is important for two reasons Рfirst, in ensuring that end users are not frustrated by long loading times and, secondly, in informing the choice of Execution Mode when it comes to deploying out a plug-in. Whilst Asynchronous plug-ins can help to mitigate any user woes and present a natural choice when working with bulk operations within the application, make sure you fully understand the impact that these have on the Asynchronous Service and avoid a scenario where the System Job entity is queued with more jobs then it can handle.

This is an accompanying blog post to my YouTube video Dynamics 365 Customer Engagement Deep Dive: Creating a Basic Plug-in, the second in a series aiming to provide tutorials on how to accomplish developer focused tasks within Dynamics 365 Customer Engagement. You can watch the video in full below:

Below you will find links to access some of the resources discussed as part of the video and to further reading topics:

PowerPoint Presentation (click here to download)

Full Code Sample

using System;
using System.Globalization;

using Microsoft.Xrm.Sdk;

namespace D365.SamplePlugin
{
    public class PreContactCreate_FormatNameValues : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            //Obtain the execution context from the service provider.

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

            //Extract the tracing service for use in debugging sandboxed plug-ins

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

            tracingService.Trace("Tracing implemented successfully!");

            if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)

            {
                Entity contact = (Entity)context.InputParameters["Target"];

                string firstName = contact.GetAttributeValue<string>("firstname");
                string lastName = contact.GetAttributeValue<string>("lastname");

                TextInfo culture = new CultureInfo("en-GB", false).TextInfo;

                if (firstName != null)
                {

                    tracingService.Trace("First Name Before Value = " + firstName);
                    contact["firstname"] = culture.ToTitleCase(firstName.ToLower());
                    tracingService.Trace("First Name After Value = " + contact.GetAttributeValue<string>("firstname"));

                }

                else

                {
                    tracingService.Trace("No value was provided for First Name field, skipping...");
                }

                if (lastName != null)

                {
                    tracingService.Trace("Last Name Before Value = " + lastName);
                    contact["lastname"] = culture.ToTitleCase(lastName.ToLower());
                    tracingService.Trace("Last Name After Value = " + contact.GetAttributeValue<string>("lastname"));
                }

                else

                {
                    tracingService.Trace("No value was provided for Last Name field, skipping...");
                }

                tracingService.Trace("PreContactCreate_FormatNameValues plugin execution complete.");

            }
        }
    }
}

Download/Resource Links

Visual Studio 2017 Community Edition

Setup a free 30 day trial of Dynamics 365 Customer Engagement

C# Guide (Microsoft Docs)

Source Code Management Solutions

Further Reading

MSDN – Plug-in development

MSDN – Supported messages and entities for plug-ins

MSDN – Sample: Create a basic plug-in

MSDN – Debug a plug-in

I’ve written a number of blog posts around plug-ins previously, so here’s the obligatory plug section ūüôā :

Interested in learning more about JScript Form function development in Dynamics 365 Customer Engagement? Then check out my previous post for my video and notes on the subject. I hope you find these videos useful and do let me know if you have any comments or suggestions for future video content.

Did you know that you can write Plug-ins for Dynamics 365 Customer Engagement/Dynamics CRM (D365CE/CRM) using Visual Basic .NET (VB.NET)? You wouldn’t have thought so after a thorough look through the D365CE/CRM Software Development Kit (SDK). Whilst there is a plethora of code examples available for C# plug-ins, no examples are provided on how to write a basic plug-in for the application using VB.NET. This is to be expected, perhaps due to the common status that the language has when compared with C#. Whilst VB.NET knowledge is a great asset to have when extending Office applications via Visual Basic for Applications, you would struggle to find many at scale application systems that are written using VB.NET. C# is pretty much the¬†de facto language that you need to utilise when developing in .NET, and the commonly held view is that exclusive VB.NET experience is a detriment as opposed to an asset. With this in mind, it is somewhat understandable why the SDK does not have any in-depth VB.NET code examples.

Accepting the above, it is likely however that many long-standing developers will have knowledge of the BASIC language, thereby making VB.NET a natural choice when attempting to extend D365CE/CRM. I do not have extensive experience using the language, but I was curious to see how difficult it would be to implement a plug-in using it Рand to hopefully provide assistance to any lonely travellers out there who want to put their VB.NET expertise to the test. The best way to demonstrate this is to take an existing plug-in developed in C# and reverse engineer the code into VB.NET. We took a look at a fully implemented plug-in previously on the blog, that can be used to retrieve the name of the User who has created a Lead record. The entire class file for this is reproduced below:

using System;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;

namespace D365.BlogDemoAssets.Plugins
{
    public class PostLeadCreate_GetInitiatingUserExample : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            // Obtain the execution context from the service provider.

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

            // Obtain the organization service reference.
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            // The InputParameters collection contains all the data passed in the message request.
            if (context.InputParameters.Contains("Target") &&
                context.InputParameters["Target"] is Entity)

            {
                Entity lead = (Entity)context.InputParameters["Target"];

                //Use the Context to obtain the Guid of the user who triggered the plugin - this is the only piece of information exposed.
      
                Guid user = context.InitiatingUserId;

                //Then, use GetUserDisplayCustom method to retrieve the fullname attribute value for the record.

                string displayName = GetUserDisplayName(user, service);

                //Build out the note record with the required field values: Title, Regarding and Description field

                Entity note = new Entity("annotation");
                note["subject"] = "Test Note";
                note["objectid"] = new EntityReference("lead", lead.Id);
                note["notetext"] = @"This is a test note populated with the name of the user who triggered the Post Create plugin on the Lead entity:" + Environment.NewLine + Environment.NewLine + "Executing User: " + displayName;

                //Finally, create the record using the IOrganizationService reference

                service.Create(note);
            }
        }
    }
}

The code above encapsulates a number of common operations that a plug-in can seek to accomplish Рupdating a record, obtaining context specific values and performing a Retrieve operation against an entity Рthereby making it a good example for what will follow in this post.

With everything ready to go, it’s time for less talking and more coding ūüôā We’ll build out a VB.NET version of the above class file, covering some of the “gotchas” to expect on each step, before then bringing all of the code together in a finished state.

Importing References

As you would expect within C#, a class file requires references to the D365CE SDK DLL files. These should be imported into your project and then added to your class file using the Imports statement:

With these two lines of code, there are immediately two things which you may need to untrain yourself from doing if you have come from a C# background:

  1. Make sure not to add your semi-colons at the end of each line, as it is not required for VB.NET
  2. You may be tempted to use the Return key to auto-complete your syntax, which works fine in a C# project…but will instead skip you down to the next line in VB.NET. Instead, use the Tab key to autocomplete any IntelliSense prompts.

Adding a Namespace

By default, a VB.NET class project does not implement a Namespace for your class. This will need to be added next, underneath the project references like so:

Implementing the IPlugin Interface

So far so good…and things continue in the same vein when implementing the IPlugin interface. This is configured like so:

The only thing to remember here, if you are still in C# mode, is that your colon is replaced with the Implements statement and that this part of the code needs to be moved to the next line.

Putting together the Execute Method

The Execute method is the heart and soul of any plug-in, as this contains the code that will execute when the plug-in is triggered. In C#, this is implemented using a void method¬†(i.e. a block of code that does not return a specific value, object etc.). It’s equivalent within VB.Net is a¬†Sub – short for “Subroutine” – which needs to be additionally peppered with an¬†Implements¬†statement to the¬†IPlugin.Execute sub:

Implementing Variables

Here’s where things start to get different. Variables within C# are generally implemented using the following syntax:

<Type> <Variable Name> = <Variable Value>;

So to declare a string object called text, the following code should be used:

string text = “This is my string text”;

Variables in VB.NET, by contrast, are always declared as¬†Dim‘s, with the name and then the Type of the variable declared afterwards. Finally, a value can then be (optionally) provided. A complete example of this can be seen below in the implementing of the IPluginExecutionContext interface:

In this above example, we also see two additional differences that C# developers have to reconcile themselves with:

  • There is no need to specifically cast the value as an¬†IPluginExecutionContext object – a definite improvement over C# ūüôā
  • Rather than using the typeof¬†operator when obtaining the service, the VB.NET equivalent GetType should be used instead.

The process of creating variables can seem somewhat labourious when compared with C#, and there are a few other things to bear in mind with variables and this plug-in specifically. These will be covered shortly.

The If…Then Statement

Pretty much every programming language has an implementation of an If…Else construct to perform decisions based on conditions (answers in the comments if you have found a language that doesn’t!). VB.NET is no different, and we can see how this is implemented in the next bit of the plug-in code:

Compared with C#, you have to specifically remember to add a¬†Then statement after your conditional test and also to include an End If at the end of your code block. It’s also important to highlight the use of different operators as well – in this case, And should be used as opposed to &&.

Assigning Values to a CRM Entity Object

The assignment of entity attribute values differs only slight compared with C# – you just need to ensure that you surround your attribute Logical Name value behind brackets as opposed to square brackets:

String concatenates also work slightly differently. Be sure to use & as opposed to + to achieve the same purpose. For new line breaks, there is also an equivalent VB.NET snippet that can be used for this, vbCrLf.

Obtaining the Users Display Name Value

The final part of the class file is the retrieval of the Full Name value of the user. This has to be done via a Function as opposed to a Dim, as a specific value needs to be returned. Keep in mind the following as well:

  • Parameters that are fed to the¬†Function must always be prefaced with the¬†ByVal statement – again, another somewhat tedious thing to remember!
  • Note that for the GetAttributeVale method, we specify the attribute data type using the syntax¬†(Of String)¬†as opposed to¬†<string>

Other than that, syntax-wise, C# experienced developers should have little trouble re-coding this method into VB.NET. This is evidenced by the fact that the below code snippet is approximately 75% similar to how it needs to be in C#:

Bringing it all together

Having gone through the class file from start to bottom, the entire code for the plug-in is reproduced below:

Imports Microsoft.Xrm.Sdk
Imports Microsoft.Xrm.Sdk.Query

Namespace D365.BlogDemoAssets.VB

    Public Class PostLeadCreate_GetInitiatingUserExample
        Implements IPlugin

        Private Sub IPlugin_Execute(serviceProvider As IServiceProvider) Implements IPlugin.Execute

            'Obtain the execution context from the service provider.

            Dim context As IPluginExecutionContext = serviceProvider.GetService(GetType(IPluginExecutionContext))

            'Obtain the organization service reference.
            Dim serviceFactory As IOrganizationServiceFactory = serviceProvider.GetService(GetType(IOrganizationServiceFactory))
            Dim service As IOrganizationService = serviceFactory.CreateOrganizationService(context.UserId)

            'The InputParameters collection contains all the data passed in the message request.
            If (context.InputParameters.Contains("Target") And TypeOf context.InputParameters("Target") Is Entity) Then

                Dim lead As Entity = context.InputParameters("Target")

                'Use the Context to obtain the Guid of the user who triggered the plugin - this is the only piece of information exposed.

                Dim user As Guid = context.InitiatingUserId

                'Then, use GetUserDisplayCustom method to retrieve the fullname attribute value for the record.

                Dim displayName As String = GetUserDisplayName(user, service)

                'Build out the note record with the required field values: Title, Regarding and Description field

                Dim note As Entity = New Entity("annotation")
                note("subject") = "Test Note"
                note("objectid") = New EntityReference("lead", lead.Id)
                note("notetext") = "This is a test note populated with the name of the user who triggered the Post Create plugin on the Lead entity:" & vbCrLf & vbCrLf & "Executing User: " & displayName

                'Finally, create the record using the IOrganizationService reference

                service.Create(note)

            End If

        End Sub
        Private Function GetUserDisplayName(ByVal userID As Guid, ByVal service As IOrganizationService) As String

            Dim user As Entity = service.Retrieve("systemuser", userID, New ColumnSet("fullname"))
            Return user.GetAttributeValue(Of String)("fullname")

        End Function

    End Class

End Namespace

Conclusions or Wot I Think

C# is arguably the de facto choice when programming using D365CE/CRM, and more generally as well for .NET. All of the code examples, both within the SDK and online, will favour C# and I do very much hold the view that development in C# should always be preferred over VB.NET. Is there ever then a good business case for developing in VB.NET over C#? Clearly, if you have a developer available within your business who can do amazing things in VB.NET, it makes sense for this to be the language of choice to use for time-saving purposes. There may also be a case for developing in VB.NET from a proprietary standpoint. VB.NET is, as acknowledged, not as widely disseminated compared with C#. By developing custom plug-ins in VB.NET, that contain sensitive business information, you are arguably safeguarding the business by utilising a language that C# developers may have difficulty in initially deciphering.

All being said, C# should be preferred when developing plug-ins, custom workflow assemblies or custom applications involving D365CE/CRM. Where some work could be made is in ensuring that¬†all¬†supported programming languages are adequately provisioned for within the D365CE SDK moving forward. Because, let’s be honest – there is no point in supporting something if people have no idea how to use it in the first place.

In last week’s post, we took a look at how a custom Workflow activity can be implemented within Dynamics CRM/Dynamics 365 for Customer Engagement to obtain the name of the user who triggered the workflow. It may be useful to retrieve this information for a variety of different reasons, such as debugging, logging user activity or to automate the population of key record information. I mentioned in the post the “treasure trove” of information that the IWorkflowContext interface exposes to developers. Custom Workflow activities are not unique in having execution-specific information exposable, with an equivalent interface at our disposal when working with plug-ins. No prizes for guessing its name – the IPluginExecutionContext.

When comparing both interfaces, some comfort can be found in that they share almost identical properties, thereby allowing us to replicate the functionality demonstrated in last weeks post as Post-Execution Create step for the Lead entity. The order of work for this is virtually the same:

  1. Develop a plug-in C# class file that retrieves the User ID of the account that has triggered the plugin.
  2. Add supplementary logic to the above class file to retrieve the Display Name of the User.
  3. Deploy the compiled .dll file into the application via the Plug-in Registration Tool, adding on the appropriate execution step.

The emphasis on this approach, as will be demonstrated, is much more focused towards working outside of the application; something you may not necessarily be comfortable with. Nevertheless, I hope that the remaining sections will provide enough detail to enable you to replicate within your own environment.

Developing the Class File

As before, you’ll need to have ready access to a Visual Studio C# Class file project and the Dynamics 365 SDK. You’ll also need to ensure that your project has a Reference added to the Microsoft.Xrm.Sdk.dll. Create a new¬†Class¬†file and copy and paste the following code into the window:

using System;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;

namespace D365.BlogDemoAssets.Plugins
{
    public class PostLeadCreate_GetInitiatingUserExample : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            // Obtain the execution context from the service provider.

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

            // Obtain the organization service reference.
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            // The InputParameters collection contains all the data passed in the message request.
            if (context.InputParameters.Contains("Target") &&
                context.InputParameters["Target"] is Entity)

            {
                Entity lead = (Entity)context.InputParameters["Target"];

                //Use the Context to obtain the Guid of the user who triggered the plugin - this is the only piece of information exposed.
      
                Guid user = context.InitiatingUserId;

                //Then, use GetUserDisplayCustom method to retrieve the fullname attribute value for the record.

                string displayName = GetUserDisplayName(user, service);

                //Build out the note record with the required field values: Title, Regarding and Description field

                Entity note = new Entity("annotation");
                note["subject"] = "Test Note";
                note["objectid"] = new EntityReference("lead", lead.Id);
                note["notetext"] = @"This is a test note populated with the name of the user who triggered the Post Create plugin on the Lead entity:" + Environment.NewLine + Environment.NewLine + "Executing User: " + displayName;

                //Finally, create the record using the IOrganizationService reference

                service.Create(note);
            }
        }
    }
}

Note also that you will need to rename the namespace value to match against the name of your project.

To explain, the code replicates the same functionality developed as part of the Workflow on last week’s post – namely, create a Note¬†related to a newly created Lead record and populate it with the Display Name of the User who has triggered the plugin.

Retrieving the User’s Display Name

After copying the above code snippet into your project, you may notice a squiggly red line on the following method call:

The GetUserDisplayName is a custom method that needs to be added in manually and is the only way in which we can retrieve the Display Name of the user, which is not returned as part of the IPluginExecutionContext. We, therefore, need to query the User (systemuser) entity to return the Full Name (fullname) field, which we can then use to populate our newly create Note record. We use a custom method to return this value, which is provided below and should be placed after the last 2 curly braces after the Execute method, but before the final 2 closing braces:

private string GetUserDisplayName(Guid userID, IOrganizationService service)
    {
        Entity user = service.Retrieve("systemuser", userID, new ColumnSet("fullname"));
        return user.GetAttributeValue<string>("fullname");
    }

Deploy to the application using the Plug-in Registration Tool

The steps involved in this do not differ greatly from what was demonstrated in last week’s post, so I won’t repeat myself ūüôā The only thing you need to make sure you do after you have registered the plug-in is to configure the plug-in Step. Without this, your plug-in will not execute. Right-click your newly deployed plug-in on the main window of the Registration Tool and select¬†Register New Step:

On the form that appears, populate the fields/values indicated below:

  • Message:¬†Create
  • Primary Entity: Lead
  • Run in User’s Context: Calling User
  • Event Pipeline Stage of Execution: Post-Operation

The window should look similar to the below if populated correctly. If so, then you can click Register New Step to update the application:

All that remains is to perform a quick test within the application by creating a new Lead record. After saving, we can then verify that the plug-in has created the Note record as intended:

Having compared both solutions to achieve the same purpose, is there a recommended approach to take?

The examples shown in the past two blog posts indicate excellently how solutions to specific scenarios within the application can be achieved via differing ways. As clearly evidenced, one could argue that there is a code-heavy (plug-in) and a light-touch coding (custom Workflow assembly) option available, depending on how comfortable you are with working with the SDK. Plug-ins are a natural choice if you are confident working solely within Visual Studio or have a requirement to perform additional business logic as part of your requirements. This could range from complex record retrieval operations within the application or even an external integration piece involving specific and highly tailored code. The Workflow path clearly favours those of us who prefer to work within the application in a supported manner and, in this particular example, can make certain tasks easier to accomplish. As we have seen, the act of retrieving the Display Name of a user is greatly simplified when we go down the Workflow route. Custom Workflow assemblies also offer greater portability and reusability, meaning that you can tailor logic that can be applied to multiple different scenarios in the future. Code reusability is one of the key drivers in many organisations these days, and the use of custom Workflow assemblies neatly fits into this ethos.

These are perhaps a few considerations that you should make when choosing the option that fits the needs of your particular requirement, but it could be that the way you feel most comfortable with ultimately wins the day – so long as this does not compromise the organisation as a consequence, then this is an acceptable stance to take. Hopefully, this short series of posts have demonstrated the versatility of the application and the ability to approach challenges with equally acceptable pathways for resolution.