When connecting Dynamics 365 Finance & Operations with third-party systems or middleware like Azure Logic Apps, Custom Services are often the cleanest approach. They allow us to expose custom X++ logic directly as REST/JSON endpoints with built-in OAuth support.
In this post, I will walk through a step-by-step implementation for building an inbound Custom Web Service to create Vendor Rebate Deals.
Component Breakdown
To build a robust web service in D365 FO, we need four core pieces:
Request Data Contract: Maps incoming JSON fields to X++ variables.
Response Data Contract: Returns execution status, messages, and record IDs back to the caller.
Service Class: Handles business logic, company switching (
changecompany), and transaction rollback.Service & Service Group: Exposes the X++ method in the AOT as an HTTP endpoint.
Step 1: Create Response Data Contract
First, define what the service returns. It is good practice to return a status indicator (Success or Error), any infolog error messages, and the primary key of the created record.
/// <summary>
/// Response contract for Vendor Rebate processing
/// </summary>
[DataContractAttribute]
public class GUPVendorRebateResponse
{
private str status;
private str message;
private TAMRebateDealId rebateDealId;
[DataMemberAttribute('Status')]
public str parmStatus(str _status = status)
{
status = _status;
return status;
}
[DataMemberAttribute('Message')]
public str parmMessage(str _message = message)
{
message = _message;
return message;
}
[DataMemberAttribute('RebateDealId')]
public TAMRebateDealId parmRebateDealId(TAMRebateDealId _rebateDealId = rebateDealId)
{
rebateDealId = _rebateDealId;
return rebateDealId;
}
public static GUPVendorRebateResponse newFromParameters(str _status, str _message, TAMRebateDealId _rebateDealId)
{
GUPVendorRebateResponse response = new GUPVendorRebateResponse();
response.parmStatus(_status);
response.parmMessage(_message);
response.parmRebateDealId(_rebateDealId);
return response;
}
}
Step 2: Create Request Data Contracts
Next, build the contracts to parse the inbound JSON payload. We will use a main request contract to capture the target DataAreaId (Legal Entity) and a child contract for deal details.
[DataContractAttribute]
public class GUPVendorRebateDealContract
{
private VendAccount vendAccount;
private TAMRebateCurrencyCode currencyCode;
private TAMRebateFromDate fromDate;
private TAMRebateToDate toDate;
[DataMemberAttribute('VendAccount')]
public VendAccount parmVendAccount(VendAccount _vendAccount = vendAccount)
{
vendAccount = _vendAccount;
return vendAccount;
}
[DataMemberAttribute('CurrencyCode')]
public TAMRebateCurrencyCode parmCurrencyCode(TAMRebateCurrencyCode _currencyCode = currencyCode)
{
currencyCode = _currencyCode;
return currencyCode;
}
[DataMemberAttribute('FromDate')]
public TAMRebateFromDate parmFromDate(TAMRebateFromDate _fromDate = fromDate)
{
fromDate = _fromDate;
return fromDate;
}
[DataMemberAttribute('ToDate')]
public TAMRebateToDate parmToDate(TAMRebateToDate _toDate = toDate)
{
toDate = _toDate;
return toDate;
}
}
Parent Request Contract
[DataContractAttribute]
public final class GUPVendorRebateRequest
{
private DataAreaId dataAreaId;
private GUPVendorRebateDealContract dealContract;
[DataMemberAttribute('DataAreaId')]
public DataAreaId parmDataAreaId(DataAreaId _dataAreaId = dataAreaId)
{
if (!prmIsDefault(_dataAreaId))
{
dataAreaId = _dataAreaId;
}
return dataAreaId;
}
[DataMemberAttribute('DealContract')]
public GUPVendorRebateDealContract parmDealContract(GUPVendorRebateDealContract _dealContract = dealContract)
{
if (!prmIsDefault(_dealContract))
{
dealContract = _dealContract;
}
return dealContract;
}
}
Step 3: Implement Service Class
Now create the class containing the business logic. Notice two critical features here:
changecompany()block: Ensures data is written to the legal entity requested in JSON, regardless of the integration user's default company.infolog.cut()helper: Captures internal infolog errors so callers get actionable error details if validation fails.
Step 4: Expose Service via AOT
Create Service Node:
Right-click your Visual Studio project > Add > New Item > Security & Services > Service.
Set Name to GUPVendorRebateService.
Set Class property to GUPVendorRebateService.
Under Service Operations, add a new operation and select the createVendorRebate method.
Create Service Group:
Right-click project > Add > New Item > Security & Services > Service Group.
Set Name to GUPVendorServices.
Drag your service node into this group.
Set AutoDeploy to Yes.
Create Service Node:
Right-click your Visual Studio project > Add > New Item > Security & Services > Service.
Set Name to
GUPVendorRebateService.Set Class property to
GUPVendorRebateService.Under Service Operations, add a new operation and select the
createVendorRebatemethod.
Create Service Group:
Right-click project > Add > New Item > Security & Services > Service Group.
Set Name to
GUPVendorServices.Drag your service node into this group.
Set AutoDeploy to
Yes.
Step 5: Testing with Postman
Once deployed, the endpoint is immediately available via OAuth 2.0 authentication.
URL Format:
https://<D365-URL>/api/services/GUPVendorServices/GUPVendorRebateService/createVendorRebateMethod:
POSTHeaders:
Authorization: Bearer <AccessToken>Content-Type: application/json
Comments
Post a Comment