Custom API Message Configuration
Complete the full lesson to earn 25 points
Work through each section, then tap “Mark as Complete” on the last one.
✦ Skip the page breaks and see fewer ads — read each lesson on a single page with Pro
Mastering Custom API Message Configuration in Dataverse
Introduction: Why Custom API Messages Matter
In the ecosystem of Microsoft Dataverse, developers often find themselves needing to go beyond standard CRUD (Create, Read, Update, Delete) operations. While the platform provides a rich set of out-of-the-box messages, real-world business requirements frequently demand specialized logic that does not map cleanly to a single record update or creation. This is where Custom API messages come into play. A Custom API allows you to define your own operations—complete with specific inputs, outputs, and execution logic—that behave like native platform messages.
Understanding Custom API configuration is essential for building modular, maintainable, and scalable extensions. Instead of forcing complex business processes into standard entity-based plugins, you can create a dedicated "service" layer within your Dataverse environment. This approach improves security, provides better discoverability for client applications, and makes your integrations more resilient to changes in the underlying data schema. By mastering this, you transition from simply reacting to data changes to orchestrating sophisticated business processes that feel like native platform functionality.
Understanding the Architecture of a Custom API
At its core, a Custom API in Dataverse is a configuration-driven object that exposes a specific endpoint. Unlike traditional plugins that are bound to an entity and a specific stage (like Pre-Operation or Post-Operation), a Custom API is decoupled. It acts as a wrapper for your logic, allowing you to define exactly what parameters are required and what data is returned.
The configuration consists of several metadata records that inform the platform how to handle the request:
- Custom API Record: The primary definition, including the name, binding type, and whether the API is bound to an entity or global.
- Custom API Request Parameter: Defines the input arguments for your API, including data types and whether they are mandatory.
- Custom API Response Property: Defines the structure of the data returned to the caller.
- Plugin Assembly/Type: The actual C# code that executes the logic when the API is invoked.
Callout: Custom API vs. Classic Actions Many developers are familiar with "Classic Actions" created through the workflow designer. While they serve a similar purpose, Custom APIs are the modern successor. They offer better support for typed parameters, improved performance, and are designed to be managed via solution lifecycle management (ALM) rather than the UI-heavy workflow designer. Custom APIs are the preferred standard for new development.
Step-by-Step: Creating Your First Custom API
To build a functional Custom API, you must follow a structured process. This begins in the Power Apps Maker Portal or through the Dataverse API directly. For this walkthrough, we will assume you are configuring a custom API to calculate a complex discount for an order, which is a common business requirement.
Step 1: Define the Custom API Record
Navigate to your solution and create a new "Custom API" component. You will need to provide:
- Unique Name: Use a consistent naming convention, such as
new_CalculateOrderDiscount. - Binding Type: Choose "Global" if the API doesn't relate to a specific record, or "Entity" if it acts upon a specific table instance (e.g., the Order table).
- Allowed Custom Processing Step Type: Set this to "Async and Sync" to give you the most flexibility in how your code runs.
- Execute Privilege: If you want to restrict who can run this API, you can define a privilege here, though often "None" is used if you handle security within the plugin code.
Step 2: Define Request Parameters
Once the API record exists, you must define the inputs. For our discount calculator, we need two inputs: the OrderId (a Guid) and the DiscountPercentage (a Decimal).
- Create a new "Custom API Request Parameter" record.
- Set the "Logical Name" to match your code's expected input.
- Choose the "Type" (e.g., EntityReference, String, Decimal).
- Mark it as "Required" if the API cannot function without this specific piece of data.
Step 3: Define Response Properties
Next, define the output. Let's return a "DiscountedTotal" as a decimal.
- Create a "Custom API Response Property" record.
- Set the "Logical Name" to match the return variable in your plugin code.
- Ensure the type matches the C# data type you intend to return.
Step 4: Develop the Plugin Logic
This is the heart of your Custom API. You must write a standard C# plugin that implements the IPlugin interface. The unique aspect here is how you retrieve the parameters from the PluginExecutionContext.
using Microsoft.Xrm.Sdk;
using System;
namespace MyCompany.Plugins
{
public class CalculateDiscountPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
// Retrieve the input parameters defined in the Custom API configuration
if (context.InputParameters.Contains("OrderId") && context.InputParameters.Contains("DiscountPercentage"))
{
Guid orderId = (Guid)context.InputParameters["OrderId"];
decimal discount = (decimal)context.InputParameters["DiscountPercentage"];
// Perform business logic
Entity order = service.Retrieve("salesorder", orderId, new Microsoft.Xrm.Sdk.Query.ColumnSet("totalamount"));
decimal total = (decimal)order["totalamount"];
decimal newTotal = total - (total * (discount / 100));
// Set the output parameter
context.OutputParameters["DiscountedTotal"] = newTotal;
}
}
}
}
Advanced Configuration: Handling Errors and Security
A common pitfall in developing Custom APIs is failing to handle errors gracefully. When your plugin throws an unhandled exception, it propagates to the calling application, which might not be prepared to parse a standard Dataverse error object.
Implementing Robust Error Handling
Always wrap your core logic in a try-catch block. When an error occurs, use InvalidPluginExecutionException. This allows you to pass a user-friendly message back to the caller while stopping the execution.
Tip: Custom Exception Messages Instead of generic error messages, provide specific feedback to the API consumer. For example, instead of "An error occurred," use "The provided OrderId does not exist in the system." This makes debugging much easier for front-end developers consuming your API.
Security and Execution Context
Custom APIs run in the security context of the user who calls them. However, if your plugin needs to perform operations the user doesn't have permissions for, you can configure the plugin step to run as a specific user (the "Owner" of the plugin step). Be cautious with this "impersonation" capability, as it can inadvertently open security holes if not strictly governed.
| Feature | Custom API | Classic Action |
|---|---|---|
| Configuration | Metadata-driven | Workflow UI-driven |
| Typed Parameters | Yes (Strongly typed) | No (Loose) |
| Performance | High (Compiled) | Moderate (Interpreted) |
| ALM | Excellent (Solution based) | Poor (Harder to move) |
| Discoverability | High (Swagger/OData) | Low |
Best Practices for Custom API Design
When building Custom APIs, think of them as public-facing contracts. Once you publish an API, other teams or external systems will rely on it. Changing the signature later can break those integrations.
1. Consistent Naming Conventions
Always prefix your Custom APIs with a publisher prefix (e.g., contoso_). Avoid generic names like Calculate or Process. Use descriptive names like contoso_CalculateShippingCost or contoso_ValidateCustomerAddress.
2. Versioning Strategy
Dataverse does not have a native "versioning" feature for Custom APIs. If you need to make a breaking change, the best practice is to create a new Custom API (e.g., v2_CalculateShippingCost) and keep the old one active for a transition period. Deprecate the old one by updating its documentation or adding a warning in the plugin logic.
3. Keep Logic Lean
Do not put your entire application logic inside the plugin. Use the Custom API plugin as a "controller" that calls secondary classes or service layers. This makes your code unit-testable. If you put all your logic in the Execute method, you will find it nearly impossible to write effective unit tests without mocking the entire Dataverse service context.
4. Optimize Data Access
Avoid "N+1" query patterns inside your plugin. If your Custom API handles multiple records, fetch all necessary data in a single query using QueryExpression or FetchXML before entering your processing loop. Frequent calls to the database will significantly degrade the performance of your API.
Warning: Performance Bottlenecks Custom APIs are synchronous by default. If your API performs long-running operations—such as calling an external web service or processing thousands of records—you will hit timeout limits. In these cases, design your API to trigger an asynchronous process or use the
Asyncflag in the plugin registration.
Common Pitfalls and Troubleshooting
Even experienced developers encounter issues when implementing custom messages. Here are the most common challenges and how to resolve them.
Missing Parameter Mappings
The most frequent error is a mismatch between the InputParameter name in the code and the "Unique Name" in the Custom API Request Parameter record. These must match exactly, including casing. If you receive a KeyNotFoundException in your plugin, check your configuration records to ensure the parameter exists and the names match.
Type Mismatch
Ensure the data type of the parameter in the Custom API configuration matches the expected type in your C# code. For example, if you define a parameter as a Decimal in the configuration but try to cast it as a Double in C#, you will encounter runtime errors.
The "Plugin Not Triggering" Problem
If your API is called but the logic doesn't seem to run, verify that the Plugin Type is correctly associated with the Custom API record. In some cases, the plugin is registered in the assembly, but the link between the Custom API configuration and the plugin code is missing. Ensure your solution includes both the Custom API metadata and the Plugin Assembly.
Dealing with Nulls
Always validate input parameters. A caller might send an empty request or omit an optional parameter. Your code should check context.InputParameters.Contains("ParamName") before attempting to access the value. Relying on the presence of a key without checking will lead to intermittent crashes.
Testing Your Custom API
Testing is non-negotiable. Because Custom APIs are essentially endpoints, you should treat them with the same rigor as any web service.
Unit Testing
Use a framework like FakeXrmEasy to mock the IPluginExecutionContext and the IOrganizationService. This allows you to simulate the call to your Custom API, pass in mocked parameters, and verify that the OutputParameters are set correctly.
// Example of unit testing logic with FakeXrmEasy
[Fact]
public void TestDiscountCalculation()
{
var context = new XrmFakedContext();
var plugin = new CalculateDiscountPlugin();
// Setup inputs
var inputs = new ParameterCollection {
{ "OrderId", Guid.NewGuid() },
{ "DiscountPercentage", 10.0m }
};
// Execute
context.ExecutePluginWith<CalculateDiscountPlugin>(inputs);
// Assert
Assert.True(context.PluginContext.OutputParameters.Contains("DiscountedTotal"));
}
Integration Testing
Once unit tests pass, deploy to a development environment. Use tools like Postman or the "Dataverse REST Builder" to trigger your API. This validates that the metadata configuration is correct and that the API is reachable via the OData endpoint.
Real-World Scenario: The "Bulk Status Update" API
Imagine a requirement where a user needs to update the status of hundreds of "Project" records based on a specific business rule. Doing this one by one via the UI or standard API updates is slow and prone to partial failures.
A Custom API approach:
- Define
contoso_BulkUpdateProjectStatus: This API accepts a list of IDs and a new status code. - Plugin Logic: The plugin receives the list (as a serialized JSON string or a collection), iterates through the IDs, and performs the updates using a single
ExecuteMultipleRequestbatch. - Outcome: The caller receives a single confirmation response. If any part of the process fails, the plugin can roll back the entire transaction, ensuring data integrity.
This pattern is far superior to forcing the client to loop through updates, as it reduces network latency and ensures the operation is atomic.
Deep Dive: Managing Complexity with JSON Payloads
As your APIs grow in complexity, you might find that passing individual parameters (like String, Decimal, Guid) becomes cumbersome. A standard pattern in professional Dataverse development is to pass a single String parameter containing a serialized JSON object.
Inside your plugin, you can use Newtonsoft.Json (or the built-in System.Text.Json if using modern .NET environments) to deserialize the payload into a strongly-typed C# object.
Why use JSON payloads?
- Flexibility: You can add new fields to your JSON schema without needing to update the Dataverse metadata configuration.
- Complex Structures: You can pass arrays, nested objects, or hierarchies that a flat list of parameters cannot represent.
- Developer Experience: It is often easier for front-end developers to construct a single JSON object than to map dozens of individual parameters.
Implementation Example
// Inside your plugin
string jsonPayload = (string)context.InputParameters["JsonInput"];
var requestData = JsonConvert.DeserializeObject<MyCustomRequest>(jsonPayload);
// Use the object properties directly
if (requestData.Items.Count > 0) { ... }
Note: If you choose the JSON payload route, ensure you implement validation on the deserialized object. Since the platform won't be validating the "schema" of your JSON, your code must be prepared to handle missing fields or malformed data inside the payload.
Best Practices for Long-Term Maintenance
Once your Custom API is live, the focus shifts to maintenance. Here are industry-standard practices to ensure your API remains stable over years of use:
- Logging: Implement a logging mechanism inside your plugin. Even simple tracing using
ITracingServiceis vital. When a user reports an error, the trace logs in the Dataverse Plugin Trace Log are your first line of defense. - Documentation: Since Custom APIs are not automatically documented like standard Web APIs, create a simple Markdown file or internal wiki page. List the API name, the required parameters, the expected output, and a sample request payload.
- Avoid Over-Engineering: If a standard
UpdateorCreateoperation can satisfy the requirement, use that instead. Custom APIs are powerful, but they add complexity to your solution. Only build them when the business logic truly requires a custom interface. - Security Audits: Periodically review which users have access to your custom APIs. If an API performs high-privilege actions, ensure the security roles assigned to users are as restrictive as possible.
Summary: Key Takeaways for Success
Mastering Custom API configuration is a milestone in your journey as a Dataverse developer. It changes how you approach problem-solving, moving you away from rigid entity-based limitations and toward a flexible, service-oriented architecture.
- Decoupling Logic: Custom APIs allow you to build business processes that are independent of specific entity events, leading to cleaner and more maintainable code.
- Metadata-Driven: Use the platform’s configuration records to define your API’s contract (parameters and outputs), which ensures consistency and discoverability.
- Strong Typing: By leveraging typed parameters, you reduce runtime errors and make your API easier for other developers to integrate with.
- Error Handling: Always provide meaningful feedback to the caller via
InvalidPluginExecutionException. A well-crafted error message saves hours of debugging time. - Performance Awareness: Keep your plugin logic efficient. Use batch processing and avoid N+1 query patterns to ensure your API remains responsive under load.
- Testing is Mandatory: Use mocking frameworks like
FakeXrmEasyto unit test your logic before deployment. Integration testing with tools like Postman is the final step to guarantee a reliable contract. - Versioning: Plan for change. Use descriptive naming and versioning strategies to ensure that future updates do not break existing integrations.
By following these principles, you will create extensions that feel like natural parts of the Dataverse platform. Whether you are building complex discount engines, bulk data processors, or specialized integration endpoints, Custom APIs provide the structure you need to deliver high-quality, reliable solutions for your organization.
Continue the course
Enjoying the courses?
Everything stays free. Pro shows fewer ads, doubles your daily points limit so you progress twice as fast, and lets you read each lesson on one page.
- ✓ Fewer advertisements
- ✓ 2× daily points limit
- ✓ Distraction-free lessons