How profile validation works
The Validator described in Validating data against profiles is a convenience wrapper over a lower-level engine: it compiles StructureDefinitions into schemas, runs them against an instance, and converts the result to an OperationOutcome. This page explains that engine and — more usefully — how to tune it, by configuring its pieces and handing them to the Validator.
Note
The engine’s types — the schema resolver, ISchemaBuilder, and the assertion types — are currently marked experimental in the SDK ([Experimental] on .NET 8+, [Obsolete] on older targets), so you may need to suppress that diagnostic to use them; the designation is under review. ValidationSettings and the Validator itself are not experimental.
The engine
A StructureDefinition is compiled into an ElementSchema — a tree of IAssertions, one per constraint (cardinality, binding, type, FhirPath invariant, slicing, and so on). Validation runs a schema against an instance to produce a ResultReport, which the Validator converts to a FHIR OperationOutcome. The Validator supplies the three things the engine needs: a schema resolver (which compiles and caches StructureDefinitions), a terminology service, and a ValidationSettings.
The engine lives in two assemblies: Firely.Fhir.Validation (the assertions, ElementSchema, and ResultReport) and Firely.Fhir.Validation.Compilation (compiling StructureDefinitions into schemas).
Schema compilation and caching
StructureDefinitionToElementSchemaResolver compiles StructureDefinitions — fetched from an IAsyncResourceResolver — into ElementSchemas (it implements IElementSchemaResolver). Compilation is expensive, so how you create the resolver matters:
CreatedCached(source)— caches compiled schemas; this is what theValidatorbuilds by default.CreatedCached(source, cache)— caches into aConcurrentDictionaryyou supply, so the cache can be shared across validators.Create(source)— no caching.
You can also extend compilation with your own rules by passing extra ISchemaBuilders, whose Build(...) method contributes additional IAssertions to the compiled schema.
Tuning the validator
You reach these knobs not by calling the engine yourself, but by passing a custom schema resolver and/or ValidationSettings to the Validator’s constructor:
public Validator(
IAsyncResourceResolver resourceResolver,
ICodeValidationTerminologyService terminologyService,
IExternalReferenceResolver? referenceResolver = null,
ValidationSettings? settings = null,
IElementSchemaResolver? schemaResolver = null) // inject a tuned resolver
For example, to supply a schema resolver (whose caching and custom ISchemaBuilders you control) and treat best-practice constraints as errors:
// The same resource resolver and terminology service as on the profile-validation page.
var resourceResolver = new CachedResolver(
FhirPackageSource.CreateCorePackageSource(ModelInfo.ModelInspector, FhirRelease.STU3, "https://packages.simplifier.net"));
var terminologyService = new LocalTerminologyService(resourceResolver);
// A schema resolver you construct yourself, so you control its caching and any custom ISchemaBuilders.
var schemaResolver = StructureDefinitionToElementSchemaResolver.CreatedCached(resourceResolver);
var settings = new ValidationSettings { ConstraintBestPractices = ValidateBestPracticesSeverity.Error };
var validator = new Validator(resourceResolver, terminologyService, settings: settings, schemaResolver: schemaResolver);
var patient = new Patient();
var outcome = validator.Validate(patient);
You still call the Validator’s POCO-based Validate (see Validating data against profiles); the customization lives in the resolver and settings you inject.
Note
There is currently no convenient public entry point to run a compiled schema directly against a POCO — the assertion-level Validate methods either take an ITypedElement or require an internal ValidationState. So the lower-level tuning is done through the Validator as shown above rather than by invoking the engine yourself; a convenient POCO entry point is tracked in firely-validator-api#669.
Why go lower-level
Caching — share a compiled-schema cache across validators, or disable caching, instead of accepting the default.
Custom compilation — add your own
ISchemaBuilders to contribute extra assertions to compiled schemas.Validation settings — configure the
ValidationSettingstheValidatorwould otherwise build with defaults (see Validating data against profiles).
