Custom APIs in Dynamics 365 let you extend the platform’s capabilities by defining custom business logic that can be called from XRM SDK, CRM WebAPI, Power Automate, Power Apps, or external systems. In this guide, we’ll walk through the process of creating and registering a Custom API in Dataverse.
What is a Custom API?
A Custom API in Dynamics 365 allows you to define your own API endpoints that execute custom business logic using plugins. These APIs provide a cleaner and more reusable alternative to custom workflow activities.
Step 1: Registering a Custom API in Dataverse
To create a Custom API, follow these steps:
- Navigate to the Dataverse
- Go to the “Custom API’s” table in Dataverse.
- Click New and define the API properties such as Name, Bound Entity (if applicable), and Allowed Custom Processing Steps.
- Add a New Plugin Class to Your Codebase
- Create a new plugin class in your solution.
- This class should implement the
IPlugininterface and contain the business logic you want to execute when the API is called. - Once you have finished writing your code, build your project. This will give you a NuGet package output. In the next step, we will use this package.
Step 2: Registering the Plugin Assembly
Once your plugin class is ready, you need to register the assembly in Dataverse:
- Update the Assembly Package
- Open the Plugin Registration Tool.
- Click View -> Display by Package.
- Select the package and find the NuGet package file for your assembly.
Step 3: Linking the Plugin to the Custom API
After registering the assembly, you need to associate your plugin with the Custom API:
- Link the Plugin Class to the Custom API Record
- In the Plugin Registration Tool, your plugin step should now appear under Plugin Steps in the Custom API record.
- Ensure the step is configured correctly to execute when the API is called.
- Go back to the Custom API record you created in Step 1 and now associate the plugin you saved with this record.
Step 4: Adding Request and Response Parameters
To make your Custom API functional, define the input (request) and output (response) parameters:
- Navigate to Your Solution in PowerApps
- Locate your Custom API record.
- Add Request Parameters (input data required for execution).
- Add Response Properties (output data returned to the caller).
- One alternative is to use the record you created in the Custom API table to add input and output parameters.
Step 5: Invoking the Custom API
Use case: The Custom API CopyUserRolesAPI is designed to copy security roles from one user to another within Microsoft Dataverse. It works by removing any roles from the target user that are not present on the source user and, conversely, associating roles that the source user has but the target user lacks.
First, I will show you the plugin I created for a custom API named CopyUserRoles, and then I will demonstrate how to call this plugin using .NET and JS.
How It Works
The Custom API logic (found in CopyUserRolesAPI.cs gist) performs the following steps:
- Parameter Validation:
- Reads sourceUserId and targetUserId from context input parameters.
- Throws an error if required parameters are missing.
- Role Copy Process:
- Invokes the CopyUserRolesService, which:
- Retrieves the roles assigned to both the source and target users.
- Uses LINQ to determine the difference between the role sets.
- Calls Disassociate and Associate methods to remove and add roles as needed.
- Invokes the CopyUserRolesService, which:
- Response Handling:
- Returns a response message in context.OutputParameters[“Response”] indicating the operation result.
Example: Invoking Custom APIs from the SDK for .NET
Input Parameters:
– sourceUserId : Guid
– targetUserId : Guid
Output Parameters:
– Response: string
using CrmMindsCore.Services;
using Microsoft.Xrm.Sdk;
using System;
namespace CrmMindsCore.CustomAPIs
{
/// <summary>
/// This Custom API copies user roles from one user to another.
/// It removes all roles from the target user and adds the roles from the source user.
/// If the source user does not have any roles, the target user will have no roles.
/// </summary>
public class CopyUserRolesAPI : PluginBase
{
public CopyUserRolesAPI(string unsecureConfiguration, string secureConfiguration)
: base(typeof(CopySolutionsAPI))
{
}
protected override void ExecuteDataversePlugin(ILocalPluginContext localPluginContext)
{
if (localPluginContext == null)
{
throw new ArgumentNullException(nameof(localPluginContext));
}
var context = localPluginContext.PluginExecutionContext;
var messageName = context.MessageName;
localPluginContext.Trace($"Executing {messageName} message");
var sourceUserId = context.InputParameters["sourceUserId"] as Guid?;
var targetUserId = context.InputParameters["targetUserId"] as Guid?;
if (sourceUserId == null || targetUserId == null)
{
throw new InvalidPluginExecutionException("Missing sourceUserId or targetUserId");
}
localPluginContext.Trace($"Copying Roles from User Id: {sourceUserId} to User Id: {targetUserId}");
var service = new CopyUserRolesService(localPluginContext.TracingService, localPluginContext.PluginUserService);
var response = service.CopyUserRoles(sourceUserId.Value, targetUserId.Value);
context.OutputParameters["Response"] = response.IsSuccess ? $"Success: {response.Data}" : $"Error: {response.ErrorMessage}";
}
}
}
using CrmMindsCore.Common;
using Microsoft.Xrm.Sdk;
using Plugins.Model;
using System;
using System.Linq;
namespace CrmMindsCore.Services
{
public class CopyUserRolesService : BaseService
{
private readonly ITracingService _tracingService;
private readonly IOrganizationService _service;
public CopyUserRolesService(ITracingService tracingService, IOrganizationService service)
{
_tracingService = tracingService;
_service = service;
}
public APIResponse<string> CopyUserRoles(Guid sourceUserId, Guid targetUserId)
{
using (var xrmContext = new OrgContext(_service))
{
var sourceUserRoles = (from sur in xrmContext.SystemUserRolesSet
where sur.SystemUserId == sourceUserId
select sur.RoleId.Value).ToList();
var targetUserRoles = (from sur in xrmContext.SystemUserRolesSet
where sur.SystemUserId == targetUserId
select sur.RoleId.Value).ToList();
// Remove all roles from the target user that are not in the source user
var rolesToRemove = targetUserRoles.Except(sourceUserRoles).ToList();
if (rolesToRemove.Any())
{
var entityReferencesToRemove = rolesToRemove.Select(roleId => new EntityReference("role", roleId)).ToList();
_service.Disassociate(
"systemuser",
targetUserId,
new Relationship("systemuserroles_association"),
new EntityReferenceCollection(entityReferencesToRemove)
);
}
// Add roles from the source user to the target user that are not already associated
var rolesToAdd = sourceUserRoles.Except(targetUserRoles).ToList();
if (rolesToAdd.Any())
{
var entityReferencesToAdd = rolesToAdd.Select(roleId => new EntityReference("role", roleId)).ToList();
_service.Associate(
"systemuser",
targetUserId,
new Relationship("systemuserroles_association"),
new EntityReferenceCollection(entityReferencesToAdd)
);
}
}
return CreateSuccessResponse<string>("User roles copied successfully");
}
}
}
Calling Custom API via C#
// Sample .NET code snippet to execute CopyUserRolesAPI via OrganizationService
public void CopyUserRoles(Guid sourceUserId, Guid targetUserId)
{
var requestParameters = new ParameterCollection
{
{ "sourceUserId", sourceUserId },
{ "targetUserId", targetUserId }
};
// Create an instance of OrganizationRequest with the custom API name
var request = new OrganizationRequest("cm_CopyUserRoles")
{
Parameters = requestParameters
};
var response = _service.Execute(request);
var result = response["Response"];
}
Calling Custom API via JavaScript
var CopySecurityRoles_Request = {
// Parameters
sourceUserId: { guid: selectedSystemUser.systemuserid }, // Edm.Guid
targetUserId: { guid: targetSystemUser.systemuserid }, // Edm.Guid
getMetadata: function () {
return {
boundParameter: null,
parameterTypes: {
sourceUserId: { typeName: "Edm.Guid", structuralProperty: 1 },
targetUserId: { typeName: "Edm.Guid", structuralProperty: 1 },
},
operationType: 0,
operationName: "cm_CopyUserRoles",
};
},
};
const response = await Xrm.WebApi.online.execute(CopySecurityRoles_Request);
I hope this content has been useful for you. For more information about Custom APIs, you can visit the Microsoft documentation.
Leave a comment