This document outlines best practices for integrating with Business Central and ensuring a consistent user experience through proven patterns and methodologies.
- Respect the standard Business Central user experience patterns
- Use standard controls and UI patterns
- Follow the Business Central action patterns
- Implement proper field validation
- Apply personalization capabilities where appropriate
- Consider multi-language support
- Implement proper dimension support
- Follow Business Central API design principles
Use event publishers and subscribers for loose coupling between modules. Follow standard event naming conventions and implement proper event handling.
// Event Publisher Pattern in a Management Codeunit
codeunit 50100 "ABC Customer Rating Mgt"
{
procedure ProcessCustomerRating(var CustomerRating: Record "ABC Customer Rating")
var
Handled: Boolean;
begin
OnBeforeProcessCustomerRating(CustomerRating, Handled);
DoProcessCustomerRating(CustomerRating, Handled);
OnAfterProcessCustomerRating(CustomerRating);
end;
local procedure DoProcessCustomerRating(var CustomerRating: Record "ABC Customer Rating"; var Handled: Boolean)
begin
if Handled then
exit;
// Core processing logic
CustomerRating.Validate("Processed Date", Today);
CustomerRating.Modify(true);
Handled := true;
end;
[BusinessEvent(false)]
local procedure OnBeforeProcessCustomerRating(var CustomerRating: Record "ABC Customer Rating"; var Handled: Boolean)
begin
// Allows other extensions to handle or modify the processing
end;
[BusinessEvent(false)]
local procedure OnAfterProcessCustomerRating(var CustomerRating: Record "ABC Customer Rating")
begin
// Allows other extensions to perform additional actions
end;
[IntegrationEvent(false, false)]
procedure OnCustomerRatingProcessed(CustomerRating: Record "ABC Customer Rating")
begin
// Integration event for external systems
end;
}
// Event Subscriber Pattern for Integration
codeunit 50101 "ABC Integration Subscribers"
{
[EventSubscriber(ObjectType::Codeunit, Codeunit::"ABC Customer Rating Mgt", 'OnAfterProcessCustomerRating', '', false, false)]
local procedure OnAfterProcessCustomerRating(var CustomerRating: Record "ABC Customer Rating")
begin
// Send notification to external CRM system
SendRatingToExternalCRM(CustomerRating);
// Update business intelligence data
UpdateAnalyticsData(CustomerRating);
end;
[EventSubscriber(ObjectType::Table, Database::Customer, 'OnAfterModifyEvent', '', false, false)]
local procedure OnAfterModifyCustomer(var Rec: Record Customer; var xRec: Record Customer)
begin
// Sync customer changes with external systems
if (Rec.Name <> xRec.Name) or (Rec."E-Mail" <> xRec."E-Mail") then
SyncCustomerToExternalSystems(Rec);
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", 'OnAfterSalesInvHeaderInsert', '', false, false)]
local procedure OnAfterSalesInvoicePost(var SalesInvHeader: Record "Sales Invoice Header")
begin
// Automatically create rating request after invoice posting
CreateRatingRequest(SalesInvHeader."Sell-to Customer No.");
end;
local procedure SendRatingToExternalCRM(CustomerRating: Record "ABC Customer Rating")
var
HttpClient: HttpClient;
HttpContent: HttpContent;
HttpResponseMessage: HttpResponseMessage;
JsonObject: JsonObject;
ApiUrl: Text;
JsonText: Text;
begin
// Prepare JSON payload
JsonObject.Add('customerNo', CustomerRating."Customer No.");
JsonObject.Add('ratingScore', CustomerRating."Rating Score");
JsonObject.Add('ratingDate', CustomerRating."Rating Date");
JsonObject.Add('category', Format(CustomerRating."Rating Category"));
JsonObject.WriteTo(JsonText);
// Configure HTTP request
HttpContent.WriteFrom(JsonText);
HttpContent.GetHeaders().Add('Content-Type', 'application/json');
ApiUrl := GetCRMApiUrl() + '/customer-ratings';
// Send with error handling
if HttpClient.Post(ApiUrl, HttpContent, HttpResponseMessage) then begin
if not HttpResponseMessage.IsSuccessStatusCode() then
LogIntegrationError('CRM Rating Upload', HttpResponseMessage.ReasonPhrase());
end else
LogIntegrationError('CRM Rating Upload', 'Failed to connect to CRM system');
end;
local procedure SyncCustomerToExternalSystems(Customer: Record Customer)
var
IntegrationQueue: Record "ABC Integration Queue";
begin
// Queue customer sync for async processing
IntegrationQueue.Init();
IntegrationQueue."Record ID" := Customer.RecordId;
IntegrationQueue."Integration Type" := IntegrationQueue."Integration Type"::"Customer Sync";
IntegrationQueue."Scheduled Date" := CurrentDateTime;
IntegrationQueue.Status := IntegrationQueue.Status::Pending;
IntegrationQueue.Insert();
end;
local procedure CreateRatingRequest(CustomerNo: Code[20])
var
RatingRequest: Record "ABC Rating Request";
begin
RatingRequest.Init();
RatingRequest."Customer No." := CustomerNo;
RatingRequest."Request Date" := Today;
RatingRequest."Due Date" := CalcDate('<+7D>', Today);
RatingRequest.Status := RatingRequest.Status::Pending;
RatingRequest.Insert();
end;
local procedure UpdateAnalyticsData(CustomerRating: Record "ABC Customer Rating")
var
AnalyticsData: Record "ABC Analytics Data";
begin
// Update analytics in background
if AnalyticsData.Get(CustomerRating."Customer No.") then begin
AnalyticsData."Last Rating Date" := CustomerRating."Rating Date";
AnalyticsData."Last Rating Score" := CustomerRating."Rating Score";
AnalyticsData.Modify();
end else begin
AnalyticsData.Init();
AnalyticsData."Customer No." := CustomerRating."Customer No.";
AnalyticsData."Last Rating Date" := CustomerRating."Rating Date";
AnalyticsData."Last Rating Score" := CustomerRating."Rating Score";
AnalyticsData.Insert();
end;
end;
local procedure GetCRMApiUrl(): Text
var
IntegrationSetup: Record "ABC Integration Setup";
begin
IntegrationSetup.Get();
IntegrationSetup.TestField("CRM API URL");
exit(IntegrationSetup."CRM API URL");
end;
local procedure LogIntegrationError(Operation: Text; ErrorMessage: Text)
var
IntegrationLog: Record "ABC Integration Log";
begin
IntegrationLog.Init();
IntegrationLog."Entry No." := GetNextLogEntryNo();
IntegrationLog."Date Time" := CurrentDateTime;
IntegrationLog.Operation := CopyStr(Operation, 1, MaxStrLen(IntegrationLog.Operation));
IntegrationLog."Error Message" := CopyStr(ErrorMessage, 1, MaxStrLen(IntegrationLog."Error Message"));
IntegrationLog."Entry Type" := IntegrationLog."Entry Type"::Error;
IntegrationLog.Insert();
end;
local procedure GetNextLogEntryNo(): Integer
var
IntegrationLog: Record "ABC Integration Log";
begin
if IntegrationLog.FindLast() then
exit(IntegrationLog."Entry No." + 1);
exit(1);
end;
}
## API Integration
### RESTful API Design and Implementation
Follow RESTful principles and implement proper authentication for Business Central API integrations.
### Complete API Page Examples
```al
// API Page for Customer Ratings
page 50200 "ABC Customer Ratings API"
{
APIVersion = 'v1.0';
APIPublisher = 'ABC Corp';
APIGroup = 'customerData';
Caption = 'Customer Ratings API';
DelayedInsert = true;
EntityName = 'customerRating';
EntitySetName = 'customerRatings';
PageType = API;
SourceTable = "ABC Customer Rating";
Extensible = false;
layout
{
area(Content)
{
repeater(Group)
{
field(id; Rec.SystemId)
{
Caption = 'Id';
Editable = false;
}
field(customerNumber; Rec."Customer No.")
{
Caption = 'Customer Number';
trigger OnValidate()
begin
ValidateCustomerExists();
end;
}
field(customerName; CustomerName)
{
Caption = 'Customer Name';
Editable = false;
}
field(ratingDate; Rec."Rating Date")
{
Caption = 'Rating Date';
}
field(ratingScore; Rec."Rating Score")
{
Caption = 'Rating Score';
trigger OnValidate()
begin
if (Rec."Rating Score" < 1) or (Rec."Rating Score" > 5) then
Error('Rating score must be between 1 and 5.');
end;
}
field(ratingCategory; Rec."Rating Category")
{
Caption = 'Rating Category';
}
field(comments; Rec.Comments)
{
Caption = 'Comments';
}
field(processedDate; Rec."Processed Date")
{
Caption = 'Processed Date';
Editable = false;
}
field(lastModifiedDateTime; Rec.SystemModifiedAt)
{
Caption = 'Last Modified Date Time';
Editable = false;
}
}
}
}
trigger OnAfterGetRecord()
var
Customer: Record Customer;
begin
if Customer.Get(Rec."Customer No.") then
CustomerName := Customer.Name
else
CustomerName := '';
end;
trigger OnInsertRecord(BelowxRec: Boolean): Boolean
var
CustomerRatingMgt: Codeunit "ABC Customer Rating Mgt";
begin
// Validate before insert
ValidateAPIData();
// Process through business logic
CustomerRatingMgt.ProcessCustomerRating(Rec);
exit(true);
end;
trigger OnModifyRecord(): Boolean
var
CustomerRatingMgt: Codeunit "ABC Customer Rating Mgt";
begin
ValidateAPIData();
CustomerRatingMgt.ProcessCustomerRating(Rec);
exit(true);
end;
trigger OnDeleteRecord(): Boolean
var
CustomerRatingMgt: Codeunit "ABC Customer Rating Mgt";
begin
exit(CustomerRatingMgt.DeleteCustomerRating(Rec));
end;
var
CustomerName: Text[100];
local procedure ValidateAPIData()
begin
Rec.TestField("Customer No.");
Rec.TestField("Rating Date");
if Rec."Rating Date" > Today then
Error('Rating date cannot be in the future.');
if (Rec."Rating Score" < 1) or (Rec."Rating Score" > 5) then
Error('Rating score must be between 1 and 5.');
end;
local procedure ValidateCustomerExists()
var
Customer: Record Customer;
begin
if Rec."Customer No." <> '' then
if not Customer.Get(Rec."Customer No.") then
Error('Customer %1 does not exist.', Rec."Customer No.");
end;
}
// API Codeunit for Complex Operations
codeunit 50202 "ABC Customer Rating API Mgt"
{
procedure CreateRatingFromWebhook(JsonData: Text): Boolean
var
CustomerRating: Record "ABC Customer Rating";
JsonObject: JsonObject;
JsonToken: JsonToken;
CustomerNo: Code[20];
RatingScore: Integer;
RatingCategory: Text;
Comments: Text;
begin
// Parse JSON webhook data
if not JsonObject.ReadFrom(JsonData) then
Error('Invalid JSON format in webhook data.');
// Extract fields with validation
if JsonObject.Get('customerNumber', JsonToken) then
CustomerNo := CopyStr(JsonToken.AsValue().AsText(), 1, 20)
else
Error('Customer number is required in webhook data.');
if JsonObject.Get('ratingScore', JsonToken) then
RatingScore := JsonToken.AsValue().AsInteger()
else
Error('Rating score is required in webhook data.');
if JsonObject.Get('ratingCategory', JsonToken) then
RatingCategory := JsonToken.AsValue().AsText();
if JsonObject.Get('comments', JsonToken) then
Comments := JsonToken.AsValue().AsText();
// Create rating through API
exit(CreateRatingFromAPI(CustomerNo, RatingScore, RatingCategory, Comments));
end;
procedure CreateRatingFromAPI(CustomerNo: Code[20]; RatingScore: Integer; RatingCategory: Text; Comments: Text): Boolean
var
CustomerRating: Record "ABC Customer Rating";
CustomerRatingMgt: Codeunit "ABC Customer Rating Mgt";
Customer: Record Customer;
RatingCategoryEnum: Enum "ABC Rating Category";
begin
// Validate customer exists
if not Customer.Get(CustomerNo) then
Error('Customer %1 does not exist.', CustomerNo);
// Validate rating score
if (RatingScore < 1) or (RatingScore > 5) then
Error('Rating score must be between 1 and 5.');
// Convert category text to enum
if RatingCategory <> '' then
if not Evaluate(RatingCategoryEnum, RatingCategory) then
Error('Invalid rating category: %1', RatingCategory);
// Create and populate rating
CustomerRating.Init();
CustomerRating."Customer No." := CustomerNo;
CustomerRating."Rating Date" := Today;
CustomerRating."Rating Score" := RatingScore;
if RatingCategory <> '' then
CustomerRating."Rating Category" := RatingCategoryEnum;
CustomerRating.Comments := CopyStr(Comments, 1, MaxStrLen(CustomerRating.Comments));
// Insert and process
CustomerRating.Insert(true);
CustomerRatingMgt.ProcessCustomerRating(CustomerRating);
exit(true);
end;
procedure GetCustomerRatingsAsJson(CustomerNo: Code[20]): Text
var
CustomerRating: Record "ABC Customer Rating";
JsonObject: JsonObject;
JsonArray: JsonArray;
RatingJson: JsonObject;
Customer: Record Customer;
begin
// Validate customer
if not Customer.Get(CustomerNo) then
Error('Customer %1 does not exist.', CustomerNo);
JsonObject.Add('customerNumber', CustomerNo);
JsonObject.Add('customerName', Customer.Name);
// Build ratings array
CustomerRating.SetRange("Customer No.", CustomerNo);
if CustomerRating.FindSet() then
repeat
Clear(RatingJson);
RatingJson.Add('id', CustomerRating.SystemId);
RatingJson.Add('ratingDate', CustomerRating."Rating Date");
RatingJson.Add('ratingScore', CustomerRating."Rating Score");
RatingJson.Add('ratingCategory', Format(CustomerRating."Rating Category"));
RatingJson.Add('comments', CustomerRating.Comments);
RatingJson.Add('processedDate', CustomerRating."Processed Date");
JsonArray.Add(RatingJson);
until CustomerRating.Next() = 0;
JsonObject.Add('ratings', JsonArray);
JsonObject.Add('totalRatings', CustomerRating.Count);
JsonObject.Add('retrievedAt', CurrentDateTime);
exit(Format(JsonObject));
end;
procedure GetRatingStatisticsAsJson(CustomerNo: Code[20]): Text
var
CustomerRatingMgt: Codeunit "ABC Customer Rating Mgt";
Customer: Record Customer;
JsonObject: JsonObject;
AverageRating: Decimal;
begin
// Validate customer
if not Customer.Get(CustomerNo) then
Error('Customer %1 does not exist.', CustomerNo);
AverageRating := CustomerRatingMgt.CalculateAverageRating(CustomerNo);
JsonObject.Add('customerNumber', CustomerNo);
JsonObject.Add('customerName', Customer.Name);
JsonObject.Add('averageRating', AverageRating);
JsonObject.Add('calculatedAt', CurrentDateTime);
exit(Format(JsonObject));
end;
procedure ProcessWebhookAuthentication(Request: HttpRequestMessage): Boolean
var
Headers: HttpHeaders;
AuthHeader: Text;
Token: Text;
IntegrationSetup: Record "ABC Integration Setup";
begin
// Get authorization header
Request.GetHeaders(Headers);
if not Headers.GetValues('Authorization', AuthHeader) then
exit(false);
// Extract token (assuming Bearer token)
if not AuthHeader.StartsWith('Bearer ') then
exit(false);
Token := CopyStr(AuthHeader, 8); // Remove 'Bearer ' prefix
// Validate against stored API key
IntegrationSetup.Get();
exit(Token = IntegrationSetup."Webhook API Key");
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"ABC Customer Rating Mgt", 'OnAfterProcessCustomerRating', '', false, false)]
local procedure OnAfterProcessCustomerRating(var CustomerRating: Record "ABC Customer Rating")
begin
// Send webhook notification to external systems
SendRatingWebhook(CustomerRating);
end;
local procedure SendRatingWebhook(CustomerRating: Record "ABC Customer Rating")
var
HttpClient: HttpClient;
HttpContent: HttpContent;
HttpResponseMessage: HttpResponseMessage;
IntegrationSetup: Record "ABC Integration Setup";
JsonObject: JsonObject;
JsonText: Text;
begin
IntegrationSetup.Get();
if IntegrationSetup."Outbound Webhook URL" = '' then
exit;
// Prepare webhook payload
JsonObject.Add('eventType', 'customerRatingProcessed');
JsonObject.Add('timestamp', CurrentDateTime);
JsonObject.Add('customerNumber', CustomerRating."Customer No.");
JsonObject.Add('ratingScore', CustomerRating."Rating Score");
JsonObject.Add('ratingCategory', Format(CustomerRating."Rating Category"));
JsonObject.Add('processedDate', CustomerRating."Processed Date");
JsonObject.WriteTo(JsonText);
// Configure and send request
HttpContent.WriteFrom(JsonText);
HttpContent.GetHeaders().Add('Content-Type', 'application/json');
HttpContent.GetHeaders().Add('Authorization', 'Bearer ' + IntegrationSetup."Webhook API Key");
if not HttpClient.Post(IntegrationSetup."Outbound Webhook URL", HttpContent, HttpResponseMessage) then
LogWebhookError('Failed to send rating webhook', JsonText);
end;
local procedure LogWebhookError(ErrorMessage: Text; Payload: Text)
var
IntegrationLog: Record "ABC Integration Log";
begin
IntegrationLog.Init();
IntegrationLog."Entry No." := GetNextLogEntryNo();
IntegrationLog."Date Time" := CurrentDateTime;
IntegrationLog.Operation := 'Outbound Webhook';
IntegrationLog."Error Message" := CopyStr(ErrorMessage, 1, MaxStrLen(IntegrationLog."Error Message"));
IntegrationLog."Entry Type" := IntegrationLog."Entry Type"::Error;
IntegrationLog."Payload Data" := CopyStr(Payload, 1, MaxStrLen(IntegrationLog."Payload Data"));
IntegrationLog.Insert();
end;
local procedure GetNextLogEntryNo(): Integer
var
IntegrationLog: Record "ABC Integration Log";
begin
if IntegrationLog.FindLast() then
exit(IntegrationLog."Entry No." + 1);
exit(1);
end;
}
## External System Integration
1. Use proper authentication mechanisms for external systems
2. Implement retry logic for external API calls
3. Handle timeouts and connection issues gracefully
4. Log all integration activities for troubleshooting
5. Implement proper error handling for external system failures
6. Consider using queues for asynchronous processing
7. Implement proper data validation before sending to external systems
## Integration Security
1. Never store credentials in code or configuration files
2. Use OAuth or other secure authentication methods
3. Implement proper error handling that doesn't expose sensitive information
4. Validate all input from external systems
5. Implement proper logging for security events
6. Follow the principle of least privilege for integration accounts
7. Regularly review and update integration security measures