Search Results for

    Show / Hide Table of Contents

    Plug-Ins

    Create Plug-In Project

    When creating a new plug-in project, you have two options to choose from on how to begin:

    1. Use Power Platform CLI to create the project for you (uses preview features at time of writing)
    2. Create plug-in project manually from Visual Studio

    Always follow the code structure guidelines when setting up your solutions.

    Using Power Platform CLI

    When using Power Platform CLI, it is recommended to create a new Visual Studio solution in advance.

    To create a new project:

    1. Install Power Platform CLI.
    2. Navigate to the src/{SolutionName} folder in a command line window.
    3. Use mkdir {SolutionName}.Dataverse.Plugins command to create plug-in folder.
    4. Navigate to the new folder with cd {SolutionName}.Dataverse.Plugins.
    5. Create the new project with pac plugin init command.
    6. Add the project to your existing Visual Studio solution.

    When creating a solution this way, the project will already be making use of the new Dependent Assembly plug-ins (preview) feature, which makes it possible to include other assemblies in your project, such as NuGet packages like Newtonsoft.Json.

    Using Visual Studio

    A plug-in solution can be created manually with Visual Studio as well, however in this case we will be using ILMerge to make it possible to include other assemblies with our project. Note that ILMerge is not officially supported by Microsoft and might cause deployment issues when merging with too large assemblies.

    To create a new project:

    1. Open the existing solution in Visual Studio, or create a new one.
    2. Select File/New/Project option.
    3. Select Class Library and select Next. Important: Do not select Class Library (.NET Framework). *
    4. Create the project with the name {SolutionName}.Dataverse.Plugins inside the src/{SolutionName}/ folder.
    5. Right click on the project in the Solution Explorer and select Properties.
    6. Under Build/Strong naming select the Sign the assembly option and provide a Strong name key file. You can create an snk file using the Developer Command Prompt for VS.

    * We are choosing the .NET Core based Class Library in favor of the legacy .NET Framework as it comes with the new csproj structure.

    As plug-in class libraries only support the legacy .NET Framework, we need to update the content of the csproj file's TargetFramework property (everything else can be left as is):

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>net462</TargetFramework>
      </PropertyGroup>
    
    </Project>
    

    Install the following NuGet packages to begin plug-in development:

    • Microsoft.CrmSdk.CoreAssemblies
    • ILMerge.Tools
    • ILmerge.MSBuild.Task (by Emerson Brito)

    Next Steps

    Once the plug-in project is ready - whether it was created using the Power Platform CLI or manually with Visual Studio -, the project must be configured:

    1. Create a new project in Visual Studio by following the steps in the previous step, naming the project {SolutionName}.Dataverse.PluginExtensions.
    2. Install the OSB.Dataverse.PluginExtensions NuGet package from our internal https://pkgs.dev.azure.com/osbgroup/_packaging/osb-d365-library/nuget/v3/index.json NuGet library.
    3. Once the classes of the library has been added to the project, you can remove the NuGet package.
    4. Reference the PluginExtensions project from the Plugins project.
    5. (Optional) Create the early bound entities project and reference it from the Plugins project as well.
    6. (Optional when using ILMerge) Create an ILMergeConfig.json file to control the behavior of DLL merging. Add the name of all assemblies to the InputAssemblies array which is referenced from the plugin project:
    {
      "General": {
        "InputAssemblies": [
          "$(TargetDir)\\{SolutionName}.Dataverse.PluginExtensions.dll",
          "$(TargetDir)\\{SolutionName}.Dataverse.DTO.dll"
        ],
        "OutputFile": "$(TargetDir)ILMerge/$(TargetFileName)"
      },
      "Advanced": {
        "DebugInfo": false
      }
    }
    

    Code Structure

    Code structure of your projects should always follow a specific pattern to make it easier for your fellow developers to find what they are looking for.

    All Power Platform code component projects should follow the folder structure as shown below:

    - docs/ # Optional: Any documents related to the project
    - pipelines/ # Pipeline definitions
    - solution/
      - ./{SolutionName}/
        - ./Unmanaged/ # Folder containing the unpacked unmanaged solution
        - ./Managed/ # Folder containing the unpacked managed solution
    - src/
      - ./{SolutionName}/
        - ./{SolutionName}.Dataverse.DTO/ # Early bound entities project
        - ./{SolutionName}.Dataverse.Plugins/ # Plugins projects
        - ./{SolutionName}.Dataverse.PluginExtensions/ # PluginBase project
        - ./{SolutionName}.sln
    - xrm-connections/
      - ./{project}.xrm-connections.xml # XrmToolbox connections file
    - README.md
    

    Create a plug-in

    All plug-ins must be added to the Plugins project of the solution. Plug-ins should be organized by the table which they are associated with. Each table with a plug-in should have its own folder created, named after its schema name.

    An example folder structure for the Plugins project:

     - account/
     - myproj_customtable/
     - myproj_othertable/
     - {SolutionName}.Dataverse.Plugins.csproj
     - ILMergeConfig.json
    

    All plug-ins must be derived from the PluginBase class (which is coming from the PluginExtensions project). The PluginBase class implements a method for each message that can be overridden to implement custom logic for the different messages.

    Naming of the plug-in should follow any of the following rules:

    1. Business logic summary: The name should be a short summary of the business logic within the file. E.g.: AddRelevantPartiesOnCreate.cs.
    2. Event Handler: The name reflects to an event triggering the plug-in. E.g.: HandleOwnerChange.cs.
    3. Registration definition: The name reflects to the registration definition of the plug-in. E.g.: PostMyTableUpdateAsync.

    A plug-in file should have a format as follows after it has been created:

    public class AddRelevantPartiesOnCreate : PluginBase
    {
        public override void HandlePostCreateMessage(PluginExecutionContext context)
        {
            // Add plug-in logic here
        }
    }
    

    Early Bound Entities

    XrmToolBox has many different tools, including the Early Bound Generator by Daryl LaBar helping us developers a lot.

    When generating your early bound entities for a project, always make sure that you:

    1. Generate early bound entities to a separate project, e.g.: YourProject.Dataverse.DTO.
    2. Save the settings XML file into the project folder, with a name referring to the project, e.g.: YourProject.EarlyBoundGenerator.Settings.xml.
    3. Commit updates made to the early bound entities/settings file in a separate commit to make these changes easier to track.
    4. Always generate the early bound entities from the development environment.

    There are some settings you need to make sure you configure properly when generating the early bound entities:

    • Entities
      • Entities Prefix Whitelist: Specify the prefix of the solution to include all tables that has been created for the soluiton.
      • Entities Whitelist: If for any reason you would need to include a table outside of the solution, add it to the whitelist.
      • Generate Entity Attribute Name Constants: Always set to true. This is important for making updates and changes for records using the IOrganizationService.

    Updating early bound records

    While working with early bound entities has its own benefits, there are some drawbacks as well. One of them is that you should not use the early bound classes to create or update a record.

    Instead, you should always instantiate a new Entity object and assign field values usng the Attribute Name Constants:

    var account = organizationService.Retrieve("account", "a709d67d-5603-4daf-a8da-4c7046ba4421").ToEntity<Account>();
    
    // Incorrect way
    account.emailaddress1 = "john.doe@contoso.com";
    organizationService.Update(account);
    
    // Correct way
    var updateRequest = new Entity(account.EntityLogicalName, account.Id);
    updateRequest[Account.Fields.emailaddress1] = "john.doe@contoso.com";
    
    organizationService.Update(updateRequest);
    

    Note: When updating an existing record it is always important to use a new instance of Entity class including only the attributes that has changed. Otherwise our update might trigger unwanted plug-in/workflow executions or any other business logic.

    Logging

    Easier debugging with frequent tracing

    To make debugging and tracing errors easier, it is recommended to add a Trace call at least at every if-condition junctions in your call.

    Example:

    if (!string.IsNullOrWhitespace(account.emailaddress1)) 
    {
      context.Trace("Email Address contains data. Beginning validation.");
    
      var isValid = emailValidator.Validate(account.emailaddress1);
      if (isValid) 
        context.Trace("Email Address is valid.");
      else 
        context.Trace("Email Address is invalid.");
    }
    else 
      context.Trace("Email Address does not contain data. Skipping execution.");
    

    When an error occurs, it will become easier to define the exact location of the error when using such methods for logging.

    Include the method name

    To improve maintainability of your code, it is recommended to organize and structure your code into separate classes and methods. However, this might make it more difficult to track the source of a trace message.

    To overcome this issue, you can either include the method name in each of your calls or use the following example method to always include it for you:

    public void DoSomeLogic() 
    {
      // Option 1
      conext.Trace($"[{nameof(DoSomeLogic)}] - Beginning code execution...");
      // Output: [DoSomeLogic] - Beginning code execution...
    
      // Option 2
      TraceMessage("Beginning code execution...");
      // Output: [DoSomeLogic] - Beginning code execution...
    }
    
    /// <summary>
    /// Trace a message with the caller method included.
    /// </summary>
    public void TraceMessage(string message, [CallerMemberName] string caller = null)
    {
      if (caller != null) {
        context.Trace($"[{caller}] - {message}");
      } else {
        context.Trace(message);
      }
    }
    
    • Improve this Doc
    In This Article
    Back to top Copyright 2023 One Step Beyond