The web services series continues, in this episode, I handle the XML parsing of the return and reshape the app to be functional.
If you missed episode 1 and 2, you can find them here!
In this third installment of the Web Services in AL series, Erik moves beyond the basics of calling web services and parsing XML. This time, the focus is on handling return values from the US Postal Service address verification API, building a comparison UI, and creating an “Apply” function that writes verified address data back to Business Central records. The result is a solution that’s starting to look like a real, usable product.
Recap: Where We Left Off
In Part 2, we built the core functionality for calling the USPS web service: constructing the XML request using helper functions like AddField, sending the request via the CallWebService function, and receiving an XML response. At the end of that episode, we had a working call but weren’t doing much with the result.
Introducing the Comparison Table
The first major addition in this episode is a new table called US Postal Service Address Verified. This table serves as a middle layer between the source record (customer, vendor, etc.) and the web service response. The idea is straightforward:
- Populate the table with address information from the source record (e.g., a Customer)
- Call the web service and fill out the USPS-returned fields into the same record
- Display both sets of fields side by side so the user can compare them
The table’s primary key uses an enum called US Postal Service Verify Type, which currently supports Customer and Vendor but is designed to be extensible. You could easily add Resources, Employees, or even document shipping addresses — anywhere users manually enter address data.
The table structure is split into two logical sections:
- Input fields (low field numbers): Name, Address, Address 2, City, Postcode, County — these come from the Business Central record
- USPS result fields (field numbers starting at 100): The corresponding fields returned by the US Postal Service
Refactoring the Verify Customer Address Function
The VerifyCustomerAddress function from the previous episode was refactored into a cleaner, more modular structure. Instead of one monolithic function, the logic is now split:
- Find or create the comparison record — Check if a verification record already exists for this customer. If not, initialize a new one with the primary key fields and insert it (using
truefor the run trigger parameter, as a best practice for future-proofing). - Copy address fields — Take the address fields from the Customer record and populate the comparison record.
- Call PrepareXML — A new generic function that takes the comparison record and the setup record. This function contains the XML-building code from the last episode and calls the web service.
The key benefit of this refactoring is that PrepareXML is now a generic function. It doesn’t care whether the address came from a Customer, Vendor, or any other entity. Creating a VerifyVendorAddress function becomes trivial — it’s mostly a copy of the customer version with different field mappings.
Parsing the XML Response with XPath
Once the web service returns its XML response, we need to extract the values. Erik introduced a GetField function that uses XPath to navigate the XML document:
local procedure GetField(ResultDoc: XmlDocument; Path: Text): Text
var
N: XmlNode;
E: XmlElement;
begin
if ResultDoc.SelectSingleNode(Path, N) then begin
E := N.AsXmlElement();
exit(E.InnerText);
end;
end;
The function takes an XML document and an XPath expression, uses SelectSingleNode to locate the desired node, converts it to an element, and returns its inner text value.
Understanding the XPath
For a USPS response XML structure like this:
<AddressValidateResponse>
<Address>
<Address1>Value</Address1>
<Address2>Value</Address2>
<City>Value</City>
...
</Address>
</AddressValidateResponse>
The XPath to get the Address1 value would be: AddressValidateResponse/Address[1]/Address1. The [1] handles the case where there might be multiple address entities in the response.
A Performance Caveat
Erik noted an important caveat: using absolute XPath addressing for every individual field works fine for small XML structures like this one (only about 8 fields). However, for XML documents with hundreds or thousands of fields, this approach is not performance-friendly. Each SelectSingleNode call has overhead, and for large documents you’d want to parse the XML differently — perhaps by iterating through child nodes. Erik offered to cover that in a future video if there’s interest.
Handling Errors vs. Success
The USPS API can return two types of responses: a successful address verification or an error. The code handles both:
// Check for error first
ErrorDescription := GetField(ResultDoc, 'AddressValidateResponse/Address[1]/Error/Description');
if ErrorDescription <> '' then
Error(ErrorDescription);
// If no error, extract the verified address fields
CompareRec."US Postal Service Firm Name" := GetField(ResultDoc, '...');
CompareRec."US Postal Service Address 1" := GetField(ResultDoc, '...');
CompareRec."US Postal Service Address 2" := GetField(ResultDoc, '...');
CompareRec."US Postal Service City" := GetField(ResultDoc, '...');
CompareRec."US Postal Service Zip5" := GetField(ResultDoc, '...');
CompareRec."US Postal Service State" := GetField(ResultDoc, '...');
If an error description is found in the XML, the function immediately throws an error to the user. Otherwise, it populates the USPS fields in the comparison record, modifies the record, and commits the transaction so the comparison page can display the results.
Building the Comparison UI
A simple card page displays the comparison record with two groups:
- Business Central Fields — The address data as it exists in BC
- US Postal Service Fields — The verified address data returned by USPS
This allows users to visually compare what they have against what USPS considers the correct, standardized address.
Testing the Error Handling
To test error handling, Erik removed the state (Florida) and postal code from a test customer record, leaving an obviously incomplete address. When the verify action was triggered, USPS returned an “Invalid State Code” error, which was properly caught and displayed to the user. After restoring the state and zip code, the verification succeeded and the comparison page appeared with both sets of data.
Adding the Apply Address Action
The comparison page is useful, but users need a way to actually apply the verified address back to the source record. Erik added an “Apply US Postal Service Address” action to the comparison page:
actions
{
area(Processing)
{
action(Apply)
{
Caption = 'Apply US Postal Service Address';
Image = Apply;
ApplicationArea = All;
ToolTip = 'Apply the fields from US Postal Service to the Business Central entity.';
Promoted = true;
PromotedCategory = Process;
PromotedIsBig = true;
PromotedOnly = true;
trigger OnAction()
var
Management: Codeunit "US Postal Service Management";
begin
if Confirm('Update %1 %2 with US Postal Service address?', true, Rec.Type, Rec.Name) then begin
Management.ApplyAddress(Rec);
CurrPage.Close();
end;
end;
}
}
}
A few noteworthy details about the action:
- It uses
Confirmto ask the user before applying changes - After applying, it closes the comparison page since there’s no reason to keep it open
- The action is promoted directly to the action bar (
Promoted = true,PromotedOnly = true,PromotedIsBig = true) for better visibility — Erik pointed out that users often don’t realize that actions are hidden in the upper-left corner rather than the lower-right corner where they might expect buttons
The Apply Address Function
The ApplyAddress procedure in the management codeunit uses a case statement on the verify type to handle different entity types:
procedure ApplyAddress(var CompareRec: Record "US Postal Service Address Verified")
var
Customer: Record Customer;
begin
case CompareRec.Type of
CompareRec.Type::Customer:
begin
Customer.Get(CompareRec.Number);
Customer.Validate(Name, CompareRec."US Postal Service Firm Name");
Customer.Validate(Address, CompareRec."US Postal Service Address 1");
Customer.Validate("Address 2", CompareRec."US Postal Service Address 2");
Customer.Validate(City, CompareRec."US Postal Service City");
Customer.Validate("Post Code", CompareRec."US Postal Service Zip5");
Customer.Validate(County, CompareRec."US Postal Service State");
Customer.Modify(true);
end;
end;
end;
Key points about this implementation:
- Fields are applied using
Validaterather than direct assignment, ensuring any field-level validation triggers fire properly Modify(true)is used to ensure any table-level triggers run as well- The case structure makes it easy to add Vendor support (and other entity types) later
The Result
After deploying and testing, the full workflow works end to end:
- Open a customer card and click “Verify Address”
- The comparison page appears showing the BC address alongside the USPS-verified address
- Click “Apply US Postal Service Address”
- Confirm the update
- The customer record is updated with the standardized USPS address
Summary and What’s Next
In this episode, the solution evolved from a basic web service call into something that’s starting to resemble a real product. Here’s what was accomplished:
- Created a comparison table to hold both the original and verified address data
- Refactored the verify function into reusable, generic components
- Built an XPath-based XML parser to extract return values from the USPS response
- Added error handling for invalid API responses
- Created a comparison page with a promoted “Apply” action
- Implemented the apply function to write verified data back to the Customer record using proper validation
In the next episode, Erik plans to add Vendor support, clean up the UI (including fixing the field ordering for postcode and state), run the code analyzers to ensure the code meets best practices, and continue polishing the solution toward something potentially submittable to AppSource.