DevOp.DK.Plus validates JSON request models before serialization and before any HTTP request is sent. Invalid requests throw DkPlusModelValidationException; the remote API is not called.
Data-annotation attributes such as Required, StringLength, and AllowedValues describe constraints, but a .NET class library does not execute those attributes automatically. The client therefore runs explicit recursive validation at its JSON request boundary, including nested objects and collection items.
try
{
await client.Customer.CreateAsync(
new CustomerCreateRequest { Number = "CUSTOMER-NUMBER-TOO-LONG" },
cancellationToken);
}
catch (DkPlusModelValidationException exception)
{
foreach (var (path, messages) in exception.Errors)
{
Console.Error.WriteLine($"{path}: {string.Join(", ", messages)}");
}
}
The exception contains property paths such as Number or Lines[0].ItemCode. Messages and logs never contain the rejected values.
Customer, Product, Project, Vendor, and the other types in DevOp.ERP.Abstractions are provider-neutral read models. DK Plus limits are not placed on those shared types because future Regla or Payday providers may define different limits, and they are never accepted as write input.
Writes use DK-specific request types (CustomerCreateRequest, CustomerUpdateRequest, VendorCreateRequest, ProductCreateRequest, ProjectUpdateRequest, WorkRequestCreateRequest, SalesOrderCreateRequest, SalesInvoiceCreateRequest, GeneralLedgerAccountCreateRequest, GeneralLedgerTaxGroupCreateRequest, and so on). The client maps a request to a DK Plus transport DTO, applies attributes derived from the DK Plus OpenAPI contract, recursively validates the transport graph, and only then serializes it. The request types also expose their applicable annotations directly (for example [Required] on Number and ItemCode, and the Swagger lengths on ProjectCreateRequest and WorkRequestCreateRequest). Sparse update requests omit null members after mapping; recursive validation still applies to every supplied nested object and collection item. Nested collection elements use request types too (ProductBarcodeRequest, ProductWarehouseSettingsRequest, ProductVariationRequest, ProductUnitConversionRequest, ProductVendorRequest, MemoPageRequest), which have no member for read-only data such as stock quantities, purchase history, or modification times, so that data cannot be sent.
Where a create request has a [Required] member whose wire DTO is shared with the sparse update (so the wire field cannot itself be required), the client also validates the request model before mapping. This covers SalesPersonCreateRequest.Number, SalesOrderCreateRequest.Customer, SalesQuoteCreateRequest.Customer, RecurringSaleCreateRequest.Customer, and ProjectPhaseCreateRequest.Number: a null, empty, or whitespace value fails before any I/O, even though the C# required keyword was satisfied.
Two further checks run before any I/O. An undefined enum value, such as (Gender)42, fails with a DkPlusModelValidationException keyed by the enum type name. Swagger-enumerated statuses are public enums on the request types, so only the Swagger value set can be sent: SalesDocumentStatus (v2 order and quote Status), SalesOrderDeliveryStatus (v2 order DeliveryStatus), and ProjectStatus (project JobStatus). A member that the target DK Plus endpoint cannot store, such as a customer ContactName on a v1 invoice or a Phone on a v2 document party, is rejected by path rather than silently dropped.
Examples currently enforced for implemented write operations include:
| Request field | Constraint |
|---|---|
Customer Number |
Required; 1–12 characters |
| Customer address lines | At most three lines for the v1 customer contract |
Customer contact (CustomerContactCreateRequest, CustomerContactUpdateRequest) address lines |
At most three lines (key Address.Lines); Swagger declares no other contact limits, and the customer number and contact id path arguments must be non-blank |
Customer item receiver (CustomerReceiverCreateRequest, CustomerReceiverUpdateRequest) address lines |
At most four lines (key Address.Lines); Swagger declares no other receiver limits, and the customer number, receiver id, and receiver number arguments must be non-blank |
Customer contact and receiver updates (CustomerContactUpdateRequest, CustomerReceiverUpdateRequest) |
An update that sets no member (including an empty Address or AdditionalData holding only nulls) throws ArgumentException before any request; nothing is sent |
| Vendor address lines | At most four lines |
| Sales-document party address lines (v2) | At most three lines |
| Invoice customer address lines | At most four lines; the receiver takes three |
Product ItemCode |
Required |
Product barcode (ProductBarcodeRequest) Barcode |
Required |
Product vendor (ProductVendorRequest) ItemCode and Vendor |
Required |
Project Number |
Required; 1–12 characters |
Project Name |
At most 60 characters |
Project Group |
At most 6 characters |
| Project customer numbers, founder, owner, supervisor, worker, and dimensions | At most 12 characters |
| Project billing-customer/contact names | At most 30 characters |
| Project phase and task identifiers | At most 10 characters |
| Project ledger account | At most 4 characters |
| Project foreign name | At most 60 characters |
Invoice (SalesInvoiceCreateRequest) customer Number |
Required; 1–12 characters |
Invoice line (SalesInvoiceLineRequest) ItemCode |
Required; 1–20 characters |
Invoice SalesPerson |
Required |
| Invoice contact number and variation code | Required |
Product-price ItemCode |
Required; 1–20 characters |
| Employee work-entry project | Required |
General-ledger account (GeneralLedgerAccountCreateRequest) Number |
Required (the request and the wire document are both validated before any I/O) |
General-ledger journal Code |
Required; at most 4 characters |
General-ledger journal Lines |
Required; at least one line |
Work request Number and TaskNumber |
Required; at most 10 characters |
Work request ProjectNumber, Supervisor, and Worker |
Required; at most 12 characters |
Work request PhaseNumber |
At most 10 characters |
Work request CustomerNumber |
1–12 characters when supplied |
Vendor Number (create) |
Required |
Sales person Number (create) |
Required (validated on the request before mapping) |
Sales order, quote, and recurring sale Customer (create) |
Required (validated on the request before mapping) |
Sales order and quote Status |
InProcess, Sent, PartHandled, Handled, OnHold, or Canceled (SalesDocumentStatus) |
Sales order DeliveryStatus |
InProcess, Ready, Hurry, Wait, or Request (SalesOrderDeliveryStatus) |
Project Status (wire JobStatus) |
NotStarted, InProgress, InWaiting, Finished, or Ready (ProjectStatus) |
Project phase Number (create) |
Required (validated on the request before mapping) |
Sales order, quote, and recurring sale Contact |
At most three address lines; address City is rejected because the v2 contact cannot store it |
Sales order, quote, and recurring sale Lines[i] (create and update) |
Each entry is required: a null line is rejected as Lines[i] before the head is sent, because the client sends every line as its own request after the head (see Sales documents) |
Sales quote lines (head Lines[i] and the single-line endpoints) |
QuantityDelivered, OrderedQuantity, DailyRate, and IndexRatio are rejected before I/O because the v2 quote line schema does not store them (orders and recurring sales accept them) |
Paged read (page x count) on a direct page method |
At most 50,000 records; a larger product throws ArgumentOutOfRangeException before any request. Narrow the query with a modified window or another filter and start again from page 1 |
Token report email To |
Required |
| My Pages invite customer and email | Required |
| My Pages role | User, PowerUser, Accountant, Admin, or Owner |
| Company action name and parameters | Required |
The checked-in sanitized contracts under openapi/ are the source for DK Plus-specific required fields, lengths, ranges, and enumerated values. When those snapshots are refreshed, validation constraints must be reviewed alongside transport DTO changes.
required versus runtime validationC# required members help callers during compilation, but they do not guarantee valid runtime values. A caller can still provide null, whitespace, an excessive string length, or construct data through deserialization or reflection. Data annotations plus the request-boundary validator provide the runtime protection.