Calling Server-Side Dataverse Logic from Power Pages Using a Custom Table Bridge

Author: Forrest Zhang

Power Pages is a good option for building external-facing Dataverse applications, but sometimes the portal needs to run server-side business logic that should not live in JavaScript. For example, the portal may need to check related records, apply duplicate-detection rules, update a status, or return a calculated result.

In a clean architecture, I usually prefer a Dataverse Custom API or Custom Action. It gives the solution a clear server-side contract. However, in some Power Pages projects, calling a Custom API directly from the portal may be difficult because of authentication, permissions, CORS, or portal limitations. In those cases, a custom table bridge can be a practical workaround.

What Is the Custom Table Bridge Pattern?

The custom table bridge pattern uses a Dataverse custom table as a controlled entry point from Power Pages to server-side plugin logic. The portal performs a normal FetchXML read against the custom table. A plugin registered on that table's RetrieveMultiple message intercepts the query, reads the requested action name and parameters, runs server-side logic, and returns a calculated response.

The table is not mainly used to store business records. It is used as a dispatch bridge between Power Pages and Dataverse plugin code.

Diagram

Power Pages
JavaScript
→
Web Template
Endpoint
→
FetchXML Read
new_portalaction
→
Plugin Step
RetrieveMultiple
→
Internal C#
Business Logic

The response travels back through the same path as a fake Dataverse query result.

Example Table Design

A generic bridge table could be called:

new_portalaction

Example columns:

  • new_name - action name, such as DuplicateApplicationCheck
  • new_parameters - serialized input values from the portal
  • new_response - calculated response returned by the plugin

High-Level Flow

  1. The portal JavaScript calls a Power Pages page or web template endpoint.
  2. The web template builds a parameter string from the current user and request values.
  3. The web template runs FetchXML against the custom bridge table.
  4. A plugin registered on RetrieveMultiple of that table fires.
  5. The plugin reads the action name and parameters from the query conditions.
  6. The dispatcher plugin calls the correct internal C# class or method.
  7. The internal logic performs Dataverse reads, validation, and business rules.
  8. The plugin adds a fake row to the output collection with the calculated response.
  9. The web template returns the response as JSON to the portal JavaScript.

Example FetchXML

<fetch top="1">
  <entity name="new_portalaction">
    <attribute name="new_portalactionid" />
    <attribute name="new_response" />
    <filter>
      <condition attribute="new_name" operator="eq" value="DuplicateApplicationCheck" />
      <condition attribute="new_parameters" operator="eq" value="contactId;applicationId;payload" />
    </filter>
  </entity>
</fetch>

Example Dispatcher Plugin

public class PortalActionRetrieveMultiple : Plugin
{
    public PortalActionRetrieveMultiple()
        : base(typeof(PortalActionRetrieveMultiple))
    {
        RegisteredEvents.Add(
            new Tuple<int, string, string, Action<LocalPluginContext>>(
                40,
                "RetrieveMultiple",
                "new_portalaction",
                ExecutePostRetrieveMultiple));
    }

    private void ExecutePostRetrieveMultiple(LocalPluginContext context)
    {
        string actionName = ReadConditionValue(context, "new_name");
        string parameters = ReadConditionValue(context, "new_parameters");

        string response = "";

        if (actionName == "DuplicateApplicationCheck")
        {
            response = DuplicateApplicationCheck.Execute(
                context.OrganizationServiceSystem,
                parameters,
                context.TracingService);
        }

        AddResponseRow(context, response);
    }
}

One Plugin Step or Two?

This pattern usually needs only one registered plugin step:

  • Registered plugin step: dispatcher plugin on RetrieveMultiple of the bridge table.
  • Internal action class: normal C# class called by the dispatcher.

The internal action class is not registered as another Dataverse plugin step. It is compiled into the same plugin assembly and called like normal code.

Custom API vs Custom Table Bridge

Option Best For Tradeoff
Custom API / Custom Action Clean new architecture with explicit request and response contracts. Power Pages calling path may require more setup.
Custom Table Bridge Existing portals where FetchXML reads are easy and a bridge pattern already exists. Less obvious because a table read is being used to trigger server logic.

Security Checklist

  • Validate the portal user inside the plugin.
  • Validate that submitted IDs belong to the current user or allowed context.
  • Use an allow-list of action names.
  • Do not expose broad table permissions.
  • Do not return sensitive data that the portal does not need.
  • Do not trust serialized parameters from the browser.
  • Log internal errors, but return safe messages to portal users.

When I Would Choose Each Option

For a new project, I would first look at Custom API or Custom Action. It is more explicit, easier to document, and easier for future developers to understand.

I would choose the custom table bridge when the project already uses it successfully, or when Power Pages cannot call the Custom API cleanly without extra risk. In that situation, the bridge can be a reasonable and controlled workaround.

Conclusion

The custom table bridge pattern is not the purest architecture, but it solves a real Power Pages problem. It gives the portal a controlled way to trigger server-side Dataverse logic while keeping sensitive business rules out of JavaScript.

My recommendation is simple: use Custom API or Custom Action when possible. Use the custom table bridge when portal limitations or existing project patterns make it the safer practical choice.

No comments:

Post a Comment