Validating POCOs
The SDK can validate a POCO in memory against the structural rules of FHIR: cardinalities, the allowed types of choice elements, the format of primitive values, coded values against required bindings, and a handful of invariants. These are the rules that can be expressed in the generated C# model — so you get them without needing a terminology server or the full profile validator.
Invoking validation
Add the using and call the Validate() extension on any POCO. It returns the validation problems as a collection — an empty result means the instance is valid. It does not throw:
using Hl7.Fhir.Validation;
var errors = patient.Validate();
if (errors.Count > 0)
{
// inspect the CodedValidationExceptions
foreach (var error in errors)
Console.WriteLine($"{error.ErrorCode}: {error.Message}");
}
By default this validates the whole object tree, uses the model for your project’s FHIR version, and validates narrative XHTML.
Note
The deserializers run this same validation automatically while parsing (depending on the mode). A POCO obtained from a deserializer that did not report issues is therefore already validated — there is no need to call Validate() on it again.
What is validated
Cardinality — repeating elements have an allowed number of items, and mandatory elements are present.
Choice types — a choice element (like
Observation.value[x]) holds one of its allowed types.Primitive formats — primitive values are well-formed (e.g. a valid
date,instant, or base64).Coded values — values bound to a required binding are valid members of the binding (these are generated as .NET enumerations).
Invariants — a few checks not bound to a single property, such as contained resources not themselves containing resources.
For how these checks are implemented, and how to customize or replace the validator, see How POCO validation works.
What is not covered
The in-memory validation only covers what the C# model can express. Notably it does not cover:
Terminology — it does not call a terminology service; it only checks required, explicit bindings.
Slices — not used by the core resources, and not readily expressible in a .NET class model.
FhirPath invariants — the additional
FhirPathconstraints in the FHIR definitions are not generated into the POCOs.
If you need these, use the profile validator.
Validation error codes
Every problem is reported as a CodedValidationException, which carries a human-readable message and a stable code (a PVAL… string). Each code has a matching …_CODE constant on CodedValidationException (for example CodedValidationException.CHOICE_TYPE_NOT_ALLOWED_CODE == "PVAL101") that you can switch on programmatically. The codes do not change between SDK versions.
Code |
Constant |
Checks that… |
|---|---|---|
|
|
a choice element’s value is one of the allowed types. |
|
|
an element has at least its minimum number of items. |
|
|
an element has at most its maximum number of items. |
|
|
a repeating element contains no null entries. |
|
|
a mandatory element is present. |
|
|
narrative is well-formed XML. |
|
|
narrative XML follows the FHIR narrative rules. |
|
|
a code is valid for its value set. |
|
|
contained resources do not themselves contain resources. |
|
|
a string is neither empty nor over the maximum length. |
|
|
a value is parseable as base64. |
|
|
a literal is the right kind for its primitive type. |
|
|
a literal is a valid value for its primitive type. |
|
|
a |
|
|
an |
|
|
a value’s type matches its element. |
|
|
an element is defined on the type. |
|
|
an element actually carries a value. |
|
|
a resource’s type is recognized. |
|
|
an element name uses the correct casing (FHIR names are case-sensitive). |
|
|
a resource type name uses the correct casing. |
Rather than depend on this list staying complete, the authoritative set is in the source.
