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:
- Make sure not to add your semi-colons at the end of each line, as it is not required for VB.NET
- 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.