mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-10-05 14:46:55 +01:00
Merge main into integration
Resolve testing and web-services review routing conflicts by preserving the approved reliability guidance and incorporating current main coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
commit
008d458e75
70 changed files with 1406 additions and 9 deletions
|
|
@ -0,0 +1,27 @@
|
|||
page 50101 "Customer Info API"
|
||||
{
|
||||
PageType = API;
|
||||
APIPublisher = 'contoso';
|
||||
APIGroup = 'sales';
|
||||
APIVersion = 'v1.0';
|
||||
EntityName = 'customerInfo';
|
||||
EntitySetName = 'customerInfos';
|
||||
SourceTable = Customer;
|
||||
ODataKeyFields = "No.";
|
||||
InsertAllowed = true;
|
||||
|
||||
layout
|
||||
{
|
||||
area(content)
|
||||
{
|
||||
repeater(General)
|
||||
{
|
||||
field(customerNo; Rec."No.")
|
||||
{
|
||||
Editable = false; // consumer must supply "No." on POST — this rejects it
|
||||
}
|
||||
field(name; Rec.Name) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
page 50101 "Customer Info API"
|
||||
{
|
||||
PageType = API;
|
||||
APIPublisher = 'contoso';
|
||||
APIGroup = 'sales';
|
||||
APIVersion = 'v1.0';
|
||||
EntityName = 'customerInfo';
|
||||
EntitySetName = 'customerInfos';
|
||||
SourceTable = Customer;
|
||||
ODataKeyFields = "No.";
|
||||
InsertAllowed = true;
|
||||
DelayedInsert = true;
|
||||
|
||||
layout
|
||||
{
|
||||
area(content)
|
||||
{
|
||||
repeater(General)
|
||||
{
|
||||
field(customerNo; Rec."No.") { }
|
||||
field(name; Rec.Name) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: web-services
|
||||
keywords: [api-page, key-fields, editable, insert, odata]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Keep Consumer-Provided Key Fields Editable on API Pages
|
||||
|
||||
> Contributions welcome — open a PR to refine or extend this article.
|
||||
|
||||
## Description
|
||||
|
||||
A field listed in `ODataKeyFields` cannot have `Editable = false` when the API page allows inserts and the field's value must be supplied by the caller. Marking it read-only removes the field from the OData write schema, so a POST that includes it is rejected as an unknown property. This only applies to keys the consumer must supply — a system-generated key such as `SystemId` is a valid exception, since Business Central assigns its value automatically on insert.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Leave every consumer-supplied key field referenced in `ODataKeyFields` without `Editable = false` on pages where `InsertAllowed = true`, so the OData layer accepts it as a writable property on POST.
|
||||
|
||||
See sample: [`api-page-key-fields-must-be-editable-on-insert.good.al`](api-page-key-fields-must-be-editable-on-insert.good.al).
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Marking a consumer-provided key field `Editable = false`, out of habit or for perceived safety. This silently breaks create operations with a generic `BadRequest` instead of a clear validation error.
|
||||
|
||||
See sample: [`api-page-key-fields-must-be-editable-on-insert.bad.al`](api-page-key-fields-must-be-editable-on-insert.bad.al).
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
page 50102 "Project Task API"
|
||||
{
|
||||
PageType = API;
|
||||
APIPublisher = 'contoso';
|
||||
APIGroup = 'jobs';
|
||||
APIVersion = 'v1.0';
|
||||
EntityName = 'projectTask';
|
||||
EntitySetName = 'projectTasks';
|
||||
SourceTable = "Project Task";
|
||||
|
||||
layout
|
||||
{
|
||||
area(content)
|
||||
{
|
||||
repeater(General)
|
||||
{
|
||||
// "Remaining Hours" is a stored field, set inside the
|
||||
// OnValidate of "Budgeted Hours" — it goes stale whenever
|
||||
// "Hours Used" changes through any other path.
|
||||
field(remainingHours; Rec."Remaining Hours") { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
page 50102 "Project Task API"
|
||||
{
|
||||
PageType = API;
|
||||
APIPublisher = 'contoso';
|
||||
APIGroup = 'jobs';
|
||||
APIVersion = 'v1.0';
|
||||
EntityName = 'projectTask';
|
||||
EntitySetName = 'projectTasks';
|
||||
SourceTable = "Project Task";
|
||||
DelayedInsert = true;
|
||||
|
||||
layout
|
||||
{
|
||||
area(content)
|
||||
{
|
||||
repeater(General)
|
||||
{
|
||||
field(remainingHours; RemainingHoursCalc) { }
|
||||
field(budgetedHours; Rec."Budgeted Hours") { }
|
||||
field(hoursUsed; Rec."Hours Used") { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trigger OnAfterGetRecord()
|
||||
begin
|
||||
Rec.CalcFields("Hours Used");
|
||||
RemainingHoursCalc := Rec."Budgeted Hours" - Rec."Hours Used";
|
||||
end;
|
||||
|
||||
var
|
||||
RemainingHoursCalc: Decimal;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: web-services
|
||||
keywords: [api-page, derived-fields, exposure, odata]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Recalculate Stored Derived Fields Before Exposing Them on API Pages
|
||||
|
||||
> Contributions welcome — open a PR to refine or extend this article.
|
||||
|
||||
## Description
|
||||
|
||||
A stored field whose value is derived from other fields inside an `OnValidate` trigger only updates when that specific trigger fires. If the underlying source data changes through some other path, the stored value goes stale without raising any error. Exposing such a field directly on an API page hands external consumers a snapshot that may be significantly out of date.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Recalculate the derived value in `OnAfterGetRecord` from its authoritative source — typically a FlowField — using a page-level variable, and expose that recalculated value instead of the stale stored field. Whether to also expose the source fields is a separate design decision, not a requirement of this pattern; keep the API contract scoped to what consumers actually need. If letting the consumer verify the recalculation is itself a requirement, expose every field the calculation reads, not just one of them — a derived value with two inputs needs both exposed, or the "verification" is incomplete.
|
||||
|
||||
See sample: [`stored-derived-fields-must-not-be-exposed-directly.good.al`](stored-derived-fields-must-not-be-exposed-directly.good.al).
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Exposing the stored field directly via `Rec`, trusting that it was kept in sync by whichever trigger last touched it.
|
||||
|
||||
See sample: [`stored-derived-fields-must-not-be-exposed-directly.bad.al`](stored-derived-fields-must-not-be-exposed-directly.bad.al).
|
||||
Loading…
Add table
Add a link
Reference in a new issue