FHIR Programming using .NET - Creating Your First FHIR Resource
Introduction
Welcome back to our series on FHIR Programming using .NET. In this article, we will delve into the process of creating your first FHIR resource. If you haven't set up your environment yet, make sure to check out the previous article in this series where we walked through setting up the necessary tools and connecting to a FHIR server.
Creating FHIR resources is a fundamental operation when working with FHIR-based applications. Whether you're building a patient management system, an electronic health record (EHR) system, or any other healthcare application, understanding how to create resources such as patients, observations, or conditions is essential. This tutorial will guide you through the steps required to create a FHIR resource using .NET and the FHIR .NET SDK, and show you how to interact with a FHIR server to store these resources.
Understanding FHIR Resource Structure
Before diving into code, it's essential to understand the hierarchical structure of FHIR resources and how they're organized.
Resource Hierarchy
All FHIR resources inherit from a common hierarchy:
- Base Resource - The root of all resources, containing the
Id,Meta,ImplicitRules, andLanguageelements. Meta includes version information, last updated timestamp, and profile declarations. - DomainResource - Extends Base and adds
Text(human-readable narrative),Contained(nested resources),Extension, andModifierExtension. Most clinical resources inherit from DomainResource. - Specific Resources - Concrete types like Patient, Observation, and Condition that add their specific elements.
Cardinality: Understanding Required vs Optional Fields
FHIR uses cardinality notation to specify how many times an element can appear:
- 0..1 - Optional, at most one (e.g., Patient.BirthDate)
- 0..* - Optional, unlimited (e.g., Patient.Name - a patient can have multiple names)
- 1..1 - Required, exactly one (e.g., Observation.Status)
- 1..* - Required, at least one (e.g., Bundle.Entry in a non-empty bundle)
Understanding cardinality helps you know which fields must be populated for a valid resource. Required fields (starting with 1) will cause validation errors if missing.
FHIR Data Types
FHIR defines two categories of data types:
Primitive Types - Simple values with a single element:
string- Unicode textboolean- true or falseinteger- Whole numbersdecimal- Rational numbersdate- Date only (YYYY, YYYY-MM, or YYYY-MM-DD)dateTime- Date and time with timezoneinstant- Precise timestamp (used for system times)uri- Uniform Resource Identifiercode- Constrained string from a value set
Complex Types - Structured types with multiple child elements:
HumanName- Structured name with Family, Given, Prefix, Suffix, PeriodAddress- Postal address with Line, City, State, PostalCode, CountryContactPoint- Phone, email, or other contact detailsIdentifier- Business identifier with System and ValueCodeableConcept- Coded value with text fallbackCoding- Reference to a code in a terminology systemQuantity- Measured amount with unitsResourceReference- Reference to another resourcePeriod- Time range with Start and End
Identifier Systems
Identifiers in FHIR use a system/value pair to ensure global uniqueness:
- System - A URI that defines the namespace for the identifier (e.g.,
http://hospital.example.org/patients) - Value - The actual identifier within that system (e.g.,
123456)
Common identifier system patterns include:
- OIDs -
urn:oid:2.16.840.1.113883.4.1(US Social Security Numbers) - URIs -
http://hl7.org/fhir/sid/us-ssn(more readable alternative) - Organization-specific -
http://hospital.example.org/mrn(Medical Record Numbers)
Resource References vs Contained Resources
When one resource needs to refer to another, you have two options:
References (Preferred) - Point to resources stored independently on the server:
// Reference to a separately stored Patient
observation.Subject = new ResourceReference("Patient/123");
Contained Resources - Embed the referenced resource within the parent:
// Contained resource - embedded within the parent
var inlinePatient = new Patient { Id = "pat1" };
observation.Contained.Add(inlinePatient);
observation.Subject = new ResourceReference("#pat1");
Use references when the resource has independent existence and may be shared. Use contained resources only when the referenced resource has no independent identity, cannot be accessed outside the parent, or when you need to include a snapshot of data at a point in time.
Resource Lifecycle
Understanding the lifecycle of FHIR resources helps in designing robust applications:
- Create - POST to
[base]/[ResourceType]creates a new resource; the server assigns an ID - Read - GET from
[base]/[ResourceType]/[id]retrieves a specific resource - Update - PUT to
[base]/[ResourceType]/[id]replaces the entire resource - Delete - DELETE to
[base]/[ResourceType]/[id]removes the resource - History - GET from
[base]/[ResourceType]/[id]/_historyretrieves all versions
Each modification creates a new version. The server tracks VersionId and LastUpdated in the resource's Meta element.
Bundles and Transactions
When creating multiple related resources, FHIR provides the Bundle resource for atomic operations:
var bundle = new Bundle { Type = Bundle.BundleType.Transaction };
// Add a Patient
bundle.Entry.Add(new Bundle.EntryComponent
{
Resource = patient,
Request = new Bundle.RequestComponent
{
Method = Bundle.HTTPVerb.POST,
Url = "Patient"
}
});
// Add an Observation referencing the patient
bundle.Entry.Add(new Bundle.EntryComponent
{
Resource = observation,
Request = new Bundle.RequestComponent
{
Method = Bundle.HTTPVerb.POST,
Url = "Observation"
}
});
// Execute as a transaction - all succeed or all fail
var response = await fhirClient.TransactionAsync(bundle);
Transaction bundles ensure atomicity: either all operations succeed, or none do. This is essential when creating resources that depend on each other.
Prerequisites
Before we get started, ensure you have the following installed:
- Download the .NET SDK from the official .NET website.
- Create an Azure Account if you don’t have one. You can create a free account at the Azure portal.
- Download and install the latest version of Visual Studio from the Visual Studio website.
- You can find all the code demonstrated in this tutorial on GitHub here
“Of all the great national heroes and statesmen of history Lincoln is the only real giant. Alexander, Frederick the Great, Caesar, Napoleon, Gladstone and even Washington stand in greatness of character, in depth of feeling and in a certain moral power far behind Lincoln. Lincoln was a man of whom a nation has a right to be proud; he was a Christ in miniature, a saint of humanity, whose name will live thousands of years in the legends of future generations. We are still too near to his greatness, and so can hardly appreciate his divine power; but after a few centuries more our posterity will find him considerably bigger than we do. His genius is still too strong and too powerful for the common understanding, just as the sun is too hot when its light beams directly on us” ~ Leo Tolstoy about Abraham Lincoln
Step 1 of 4: Understand FHIR Resources
Before we begin coding, it's important to understand what a FHIR resource is. A FHIR resource is the core building block of the FHIR standard. Each resource represents a specific piece of data related to healthcare, such as a patient, a medication, or a diagnosis. Resources are represented in either JSON or XML format, and each resource type has a predefined structure defined by the FHIR standard.
For this tutorial, we'll focus on creating a `Patient` resource. The `Patient` resource is one of the most commonly used resources in FHIR, representing an individual receiving care. It contains information such as the patient's name, gender, birth date, and contact information. Understanding the structure of the `Patient` resource will help you better interact with the FHIR API and effectively create and manage patient data in your healthcare application.
Step 2 of 4: Define a Patient Resource
To create a `Patient` resource in .NET, we first need to define the resource and populate it with data. Below is an example of how to define a `Patient` resource using the FHIR .NET SDK:
using Hl7.Fhir.Model;
using Hl7.Fhir.Rest;
class Program
{
static async Task Main(string[] args)
{
// Replace with your FHIR server base URL
string fhirServerUrl = "http://hapi.fhir.org/baseR4";
// Configure the FHIR client with proper settings
var settings = new FhirClientSettings
{
PreferredFormat = ResourceFormat.Json,
ReturnPreference = ReturnPreference.Representation
};
var fhirClient = new FhirClient(fhirServerUrl, settings);
// Create a new Patient resource
var patient = new Patient
{
Identifier = new List<Identifier>
{
new Identifier("http://hospital.smarthealthit.org", "123456")
},
Name = new List<HumanName>
{
new HumanName
{
Family = "Doe",
Given = new List<string> { "John", "A" }
}
},
Gender = AdministrativeGender.Male,
BirthDate = "1980-01-01",
Address = new List<Address>
{
new Address
{
Line = new List<string> { "123 Main St" },
City = "Metropolis",
State = "NY",
PostalCode = "10001",
Country = "USA"
}
}
};
// Output the patient details
Console.WriteLine("Patient Name: " + patient.Name[0].ToString());
Console.WriteLine("Patient Birthdate: " + patient.BirthDate);
// Interact with the FHIR server using async/await
var createdPatient = await fhirClient.CreateAsync(patient);
// Null-safe check for the created patient
if (createdPatient == null)
{
Console.WriteLine("Error: Failed to create patient on server.");
return;
}
Console.WriteLine("Patient created with ID: " + createdPatient.Id);
}
}
Step 3 of 4: Create the Patient Resource
The code above creates a `Patient` resource with basic demographic information. The `FhirClient.CreateAsync()` method sends a `POST` request to the FHIR server asynchronously to create the resource. The server then assigns an ID to the resource and stores it. The `createdPatient` object returned by the `CreateAsync()` method contains the resource as stored on the server, including the server-generated ID. Note that we use async/await patterns for better performance and null-safe checks to handle potential null responses gracefully.
By following this pattern, you can create other FHIR resources such as `Observation`, `Condition`, or `Medication`. Each resource type has its own properties and structure, but the process of creating them is similar to what we've done with the `Patient` resource.
Step 4 of 4: Verify the Creation
After creating the resource, you can verify its existence on the FHIR server. You can either use a FHIR API client like Postman to query the server. Alternatively, you can modify the above code to retrieve the created resource and print its details:
// Retrieve the created patient using async/await
var retrievedPatient = await fhirClient.ReadAsync<Patient>($"Patient/{createdPatient.Id}");
// Null-safe check for the retrieved patient
if (retrievedPatient == null)
{
Console.WriteLine("Error: Failed to retrieve patient from server.");
return;
}
Console.WriteLine("Retrieved Patient ID: " + retrievedPatient.Id);
// Null-safe access for patient name
if (retrievedPatient.Name?.Count > 0)
{
var name = retrievedPatient.Name[0];
Console.WriteLine($"Retrieved Patient Name: {name.Family}, {string.Join(" ", name.Given ?? new List<string>())}");
}
“There are perhaps no days of our childhood we lived so fully as those we spent with a favorite book.” ~ Marcel Proust
This code snippet retrieves the patient resource by its ID and prints out the details. This is a useful step to confirm that your resource was successfully created and is accessible via the FHIR server.
Conclusion
Congratulations! You've created your first FHIR resource using .NET. This foundational step is crucial as you move forward in developing healthcare applications that leverage the FHIR standard. In the next tutorial in this series, we will explore how to update FHIR resources, building upon what we've covered here.
Stay tuned for more in-depth tutorials as we continue our journey into FHIR programming with .NET.