Best practices

Although it is seemingly easy to invoke FhirPath, there are a few details that are easy to get wrong.

Start evaluation from the root

To make the resolve() function work well (e.g. to resolve to entries in a Bundle or to a contained resource), the FhirPath engine needs to have “seen” all the resources while navigating through the data, which means you need to evaluate Bundles from their roots.

Bundle b = new() {...}

// The engine has worked from the root of the bundle down, so it knows how to resolve to other entries
var active = b.Select("Bundle.entry.ofType(Patient).organization.resolve()");

// The engine was started from the nested Patient node, so does not know how to find other entries.
var org = Bundle.entry.OfType<Patient>[0];
var active2 = org.Select("organization.resolve()");

// This is fine too, since the context is transferred from call to call.
var org2 = b.Select("Bundle.entry.ofType(Patient)");
var active3 = org2.Select("organization.resolve()");

Use a context constructor which takes a resource to set %resource

Although not many FhirPath statements use the %resource and %rootResource environment variables, they do get used, and the default constructors will make it easy for you to not set them (blame us for that). To make sure these variables work well, you should pass a sensible EvaluationContext to the FhirPath functions, even though they are optional:

Patient p = new() {...}
var hasName = p.IsTrue("Patient.name.exists()", new FhirEvaluationContext(p.ToScopedNode()));

As you can see, we are passing in a new FhirEvaluationContext, constructed with a reference to the root of the object. Additionally, the FhirPath engine needs its data to be a ScopedNode. This is a wrapper for ITypedElement that keeps track of parent nodes, contained nodes an entry nodes in a Bundle, and does the heavy lifting for making resolve() work (see previous section).

Set the Resolver property in the FhirEvaluationContext

Finally, the engine needs you to supply a delegate when you want resolve() to be able to reach out to instances of Resources (via uri) that it cannot locate itself. The delegate you need to supply takes a single string parameter (the uri), and returns an ITypedElement. Just like in the previous section, it would be best if you call ToScopedNode() on it before you return the instance.

var ctx = new FhirEvaluationContext(p.ToScopedNode());
ctx.Resolver = myResolver;

ITypedElement myResolver(string uri)
{
     var resolved = ...;
     return resolved.ToScopedNode();
}

If you are thinking: couldn’t this be easier? Yes, we think so - but most of the solutions would be breaking changes. We are working on it ;-)

Set the TerminologyService in the FhirEvaluationContext

To utilize the FhirPath function memberOf(valueset), you must define the TerminologyService property in the FhirEvaluationContext. This is necessary to provide the FhirPath engine with a means to search for codes.

var ctx = FhirEvaluationContext.CreateDefault();
ctx.TerminologyService = new LocalTerminologyService(resolver: ZipSource.CreateValidationSource());

var result = new Code("male").Scalar("memberOf('http://hl7.org/fhir/ValueSet/administrative-gender')", context);