Validating POCOs with DataAnnotations
The POCOs generated in the Hl7.Fhir.Core
assembly are annotated with custom
ValidationAttributes based on the standard .NET
validation framework from the System.ComponentModel.DataAnnotations
namespace. These attributes validate primitive datatypes to make sure you use the
correct format for datetimes, adhere to the correct cardinalities for repeating elements etc.
For example the AllowedTypes
attribute below specifies that the allowed choices for ChargeItem.occurrence
are dateTime
and timing
. Since this attribute derives from ValidationAttribute
, it takes part in validation and will at runtime validate that the Occurrence
property is indeed assigned one of these types.
[FhirElement("occurrence", InSummary=true, Order=160, Choice=ChoiceType.DatatypeChoice)]
[AllowedTypes(typeof(Hl7.Fhir.Model.FhirDateTime),typeof(Hl7.Fhir.Model.Period),typeof(Hl7.Fhir.Model.Timing))]
public Hl7.Fhir.Model.DataType Occurrence
{
}
The following table lists the attributes used by the SDK:
Attribute |
Validation performed |
---|---|
AllowedTypesAttribute |
Applied to “choice” properties of type |
CardinalityAttribute |
Applied to a property of type |
CanonicalPatternAttribute |
Verifies whether the string property is a correctly formatted FHIR |
CodePatternAttribute |
Verifies whether the string property is a correctly formatted FHIR |
DatePatternAttribute |
Verifies whether the string property is a correctly formatted FHIR |
DateTimePatternAttribute |
Verifies whether the string property is a correctly formatted FHIR |
IdPatternAttribute |
Verifies whether the string property is a correctly formatted FHIR |
InstantPatternAttribute |
Verifies whether the string property is a correctly formatted FHIR |
NarrativeXhtmlPattern |
Verifies whether the string property (normally inside Div) in |
OidPatternAttribute |
Verifies whether the string property is a correctly formatted FHIR |
TimePatternAttribute |
Verifies whether the string property is a correctly formatted FHIR |
UriPatternAttribute |
Verifies whether the string property is a correctly formatted FHIR |
UuidPatternAttribute |
Verifies whether the string property is a correctly formatted FHIR |
In addition, the POCO classes implement IValidatableObject to perform additional validations not bound to a single property:
POCO |
Validation performed |
---|---|
All resources |
Validates that contained resources do not themselves have nested contained resources |
Code<T> |
Validates that the string in |
As is clear from the set of validations described above, the attribute-based validation does not cover all constraints that can be expressed using a StructureDefinition
, not even all those for the non-profiled FHIR core datatypes. Notable constrains not checked are:
Terminology - the attribute validator will not call upon a terminology service to validate the coded datatypes, it will however validate correct values for all required, explicit bindings, since these are generated as .NET enumerations.
Slices - these are not used in the definitions of the core resources from which the POCOs are generated. There is also no trivial method to express these constraints in a .NET class model, so attribute validation does not cover them.
FhirPath invariants - FHIR model definitions generally contain additional invariants specified using FhirPath. These are not generated into the POCO classes and require an external FhirPath engine.
If you need to include these aspects in your validation, you should use full (and slower) poco validation.
How to invoke attribute validation
Attribute can be invoked using the familiar validation methods from System.ComponentModel.DataAnnotations
, so it is possible to call Validator.ValidateObject()
or IValidatableObject.Validate()
directly. However, the attributes mentioned in this section use specific extensions on the ValidationContext
to work properly, so the SDK provides an extension method Validation.Validate(this Base, ...)
that can be used to ensure this context is properly instantiated:
using Hl7.Fhir.Validation;
var patient = new Patient() {...}
patient.Validate(recurse: true, narrativeValidation: NarrativeValidationKind.FhirXhtml);
When the instance is invalid, this method will throw an Exception detailing the validation problem. All SDK validation attributes will throw an Exception of type CodedValidationException
, which not only carries a message for the exception for human consumption, but also a pertinent code that can be used to recognize programmatically. These codes can be found as constants on CodedValidationException
. Examples are:
Constant |
Code |
Message |
---|---|---|
PVAL101 |
CHOICE_TYPE_NOT_ALLOWED_CODE |
“Value is of type ‘{0}’, which is not an allowed choice.” |
PVAL107 |
DATE_LITERAL_INVALID_CODE |
“’{0}’ is not a correct literal for a date.” |
PVAL117 |
CONTAINED_RESOURCES_CANNOT_BE_NESTED_CODE |
“It is not allowed for a resource to contain resources which themselves contain resources.” |
The full set of codes can be found in the source code
The new deserializer invokes the attribute-based validation described here while performing deserialization. This means that, after you have deserialized an object, there is no need to invoke validation yourself, and validation results should be consistent between deserialized POCOs and POCOs that are constructed in code.