Hello! Recently, while working on a D365 Finance integration, I came across a requirement that looked simple on the surface but ended up becoming an interesting implementation of OData Actions and Deep Links.
The requirement was straightforward.
An external approval platform was responsible for reviewing Vendor Payment Journals. Once an approver made a decision, D365 Finance needed to know whether the journal was approved or rejected. Additionally, the approver wanted a direct link back to the exact journal inside D365 Finance instead of navigating through multiple screens and searching manually.
To solve this, I created a custom OData entity called:
VT_VendorPaymentJournalHeaderEntity
and exposed two unbound OData Actions:
- Update Vendor Payment Journal Status
- Generate Deep Link to Vendor Payment Journal
Let's walk through both.
Action 1 – Update Vendor Payment Journal Status
The first action is called by the external approval platform whenever an approver approves or rejects a payment journal.
[SysODataActionAttribute('vendPaymJourUpdateStatus', false)]
public static str vendPaymJourUpdateStatus(
DataAreaId _dataAreaId,
LedgerJournalId _journalNum,
boolean _status)
The action accepts:
- Legal Entity (DataAreaId)
- Journal Number
- Approval Status
Where:
- true = Approve
- false = Reject
Before updating anything, I perform a couple of validations.
First, I verify that the journal exists.
Second, I verify that the journal is actually a Vendor Payment Journal.
This validation is important because without it, another journal type could accidentally be passed into the API and updated incorrectly.
if (ledgerJournalTable &&
ledgerJournalTable.JournalType != LedgerJournalType::Payment)
{
throw error(
strFmt(
'The specified journal %1 is not of type Vendor Payment Journal.',
_journalNum));
}
Once validation succeeds, the action delegates the processing to a dedicated service class.
This keeps the OData action lightweight and focused purely on request handling while business logic remains reusable and easier to maintain.
The actual update is surprisingly simple:
if (_status)
{
ledgerJournalTable.markApproved();
}
else
{
ledgerJournalTable.markRejected();
}
These methods already handle the underlying framework logic correctly.
Action 2 – Generate Deep Link to Vendor Payment Journal
The second action focuses on user experience.
When an approval email is sent, we want the approver to click a link and immediately land on the relevant Vendor Payment Journal.
[SysODataActionAttribute('vendorPaymentJournalGenerateDeepLink', false)]
public static str vendorPaymentJournalGenerateDeepLink(
DataAreaId _dataAreaId,
LedgerJournalId _journalNum)
Before generating the URL, I validate:
- Legal Entity exists
- Journal exists
- Journal is a Vendor Payment Journal
Since journals are company-specific, I use:
changeCompany(_dataAreaId)
to ensure validation occurs in the correct legal entity.
Generating the Deep Link
One framework class that I believe deserves more attention within the D365 community is:
UrlHelper.UrlGenerator
This class provides a supported and secure mechanism for generating deep links.
Instead of manually building URLs, it automatically handles:
- Company context
- Query parameter encryption
- URL construction
- Navigation to forms
The setup looks like this:
|
IApplicationEnvironment env = EnvironmentFactory::GetApplicationEnvironment();
System.Uri currentHost = new System.Uri(env.Infrastructure.HostUrl);
UrlHelper.UrlGenerator generator = new UrlHelper.UrlGenerator();
generator.HostUrl = currentHost.GetLeftPart(System.UriPartial::Authority);
generator.Company = strUpr(_dataAreaId);
generator.MenuItemName = menuItemDisplayStr(LedgerJournalTable5);
generator.MenuItemType = MenuItemType::Display;
generator.Partition = getCurrentPartition();
generator.EncryptRequestQuery = true; The menu item points directly to the Vendor Payment Journals form. Next, we attach the journal number as a request parameter: queryParams.AddRequestQueryParameter( tableStr(LedgerJournalTable), fieldStr(LedgerJournalTable, JournalNum), _journalNum); Finally: System.Uri fullUri = generator.GenerateFullUrl();
return fullUri.AbsoluteUri;
|
The generated URL is fully encrypted and takes the user directly to the target journal
NOTE:
Before using UrlHelper.UrlGenerator and EnvironmentFactory, make sure the following namespaces are included in your class:
using
Microsoft.Dynamics.ApplicationPlatform.Environment;
using Microsoft.Dynamics.AX.Framework.Utilities;
These namespaces are required for:
- EnvironmentFactory::GetApplicationEnvironment()
- UrlHelper.UrlGenerator
Without them, the deep-link generation code will not compile.

Like
Report
*This post is locked for comments