Click here to Skip to main content
Click here to Skip to main content

Introduction to FHIR (Fast Healthcare Interoperability Resources)

, 1 Jun 2014
Rate this:
Please Sign up or sign in to vote.
This article is all about Introduction to FHIR specification. We will be seeing a real FHIR Client and Server Implementation.

 

Introduction

FHIR (Fast Healthcare Interoperability Resources) is a latest standard developed under the HL7 organization. It is pronounced as 'Fire', is a next generation healthcare interoperable standard which can be used in electronically exchanging patient health information between heterogeneous systems by combining the best features of HL7 Version 2 and Version 3.

Note – The FHIR specification, currently is in Draft Standard for Trial Use (DSTU) Release 1.1


FHIR at high level
 

  • FHIR specification was originally created by Grahame Grieve. Which is then later transferred to HL7 International.
  • The specification open and free with no restrictions.
  • Fast and easy to implement standard.
  • Very concise and easy to understand specification.
  • The FHIR makes use of the latest web standards – XML, JSON, HTTP, Atom, OAuth, etc.
  • HL7 Version 2 needs a transition path and HL7 V3 is too hard in implementing; HL7 V3 based data interchange requires a strong healthcare domain knowledge is required in implementing. Hence the FHIR has evolved.
  • The design of FHIR is based on RESTful service as opposed to SOAP based service which you can see in majority of IHE profiles. 
  • The FHIR data elements are designed with 80/20 rule. Only include 80% of data elements if the implementers of that artifact are using that data element. Hence allowing the remaining 20% of the data elements as extensions.

Background

Over the years, The healthcare records have been increasingly being digitized. The need to make patients medical records available, discoverable and exchanged between disparate systems have tremendously increased as the patients move around the healthcare eco-system.

HL7 is a standard which is being widely used in exchanging the patient healthcare information from more than 20 years. Mostly commonly used standards are HL7 V2 and V3. The FHIR specification was evolved by coming the best practices, lessons around the requirements, challenges gained through by implementing HL7 V2, V3, CDA etc. What you can do with FHIR is, use it as a standalone data exchange standard but you it doesn't limit, you could also combine with the existing standards too. 

 

Basic building blocks of FHIR


The basic building block of FHIR is Resource. In FHIR, all the exchangeable content is defined as a resource. 

A resource is an entity, instance of data which is either stored or exchanged. Every resource has the following.

Contains a human readable XHTML representation of the content of the resource.
Contains a set of structured data as defined by the resource definition. 
Has an identified version which gets changed as the resource content gets modified. 
Has a know identity a URL which can be used to address the resource. 

The resources can be represented in XML or JSON format according to the rules defined in the standard. 

Understanding the contents of the resource

All resources has the following optional or mandatory elements and properties.

A base set of defined data elements specific to the type
Extensions – These are additional data elements added by implementations.
A human-readable narrative description of the contents of the resource
Contained resources – These are additional resources that are part of the identification and transaction space of this resource.
Metadata – These are important information about the resource that is not part of the content model of the resource.
Tags – These are labels affixed to the resources which can be used to define additional operational behavior such as security, workflow etc.

Here's an example of a sample FHIR Resource

/FHIR-Resource

Extensions in Resource 

Let us see in brief about extensions

The extensions are fundamental part of the design of the FHIR specification which represents the additional information that are not a part of the basic definition of the resource. Though the implementer is allowed to define and use extensions, there is a set of requirements that must be met as part of their use and definition.

Here's how the extension element must be defined. 

<[name] xmlns="http://hl7.org/fhir" url="identifies the meaning of the extension (uri)"> doco
 <!-- from Element: extension -->
 <value[x]><!-- 0..1 * Value of extension --></value[x]>
</[name]>

The url is a mandatory attribute specifies an extension definition in a resource profile. 
The actual content of the extension can be a value or defined with child extensions. 

Note – When an extension is the target of an internal reference, the value of an extension will be the reference 

The value[x] can be either the following. Were the x is replaced by one the below mentioned types. Note – One has to define the contents as per the defined type.

  • integer
  • decimal
  • dateTime
  • date
  • instant
  • string
  • uri
  • boolean
  • code (only if the extension definition provides a fixed binding to a suitable set of codes)
  • base64Binary
  • Coding
  • CodeableConcept
  • Attachment
  • Identifier
  • Quantity
  • Range
  • Period
  • Ratio
  • HumanName
  • Address
  • Contact
  • Schedule
  • Resource - a reference to another resource

Here's an example for an extension, were you can see an extension is defined for trail status profile with the trial status code, date and who.

{
  "resource-type" : "Patient",
  "extension" : [
    {
      "url" : "http://acme.org/fhir/Profile/main#trial-status",
      "extension" : [
        {
          "url" : "http://acme.org/fhir/Profile/main#trial-status-code",
          "valueCode" : "unsure"
        }, 
        {
          "url" : "http://acme.org/fhir/Profile/main#trial-status-date",
          "valueDate" : "2014-05-26"
        }, 
        {
          "url" : "http://acme.org/fhir/Profile/main#trial-status-who",
          "valueResource" : {
            "reference" : "Practitioner/example"
          }
        }
     ]
   }
  ], 
  ... other data for patient... 
}

Human Readable Narrative

Now we will discuss on human readable narrative

The narrative element in a resource is used to represent a human readable content of the resource which is mainly used for a human to understand about the resource. Note – The narrative element in a resource is not mandatory. If you have one, it SHALL reflect all content needed for a human to understand the essential clinical and business information otherwise encoded within the resource

Note – It's a recommended practice to have narrative with in a resource to support human consumption as a fallback. However in a strictly managed trading environment, were all interacting systems share the common data model. In such cases, the narrative can be skipped. 

Here's an example of a Diagnostic report resource with a narrative text.

FHIR-Narrative

Here are some rules for defining contents with in the narrative.

1. The contents with in the narrative must be a XHTML fragment that SHALL contain only the basic html formatting elements.

2. The XHTML content SHALL not contain a head, a body element, external stylesheet references, deprecated elements, scripts, forms, base/link/xlink, frames, iframes, objects or event related attributes.
 

Contained Resources 

Now we shall take a look into the contained resources. 

The contained resources are part of the resources which cannot exist or identified independently. These situations typically arise when there are middle ware engines involved in assembling the data to make a single resource. Those resources which does not include record keys or absolute identifications will have a problem in assembling and even if an arbitrary identification was associated with the resource, the resource count never be the subject of a transaction outside of the context of the resource it refers to.

Here's an example. 

An interface engine creates a procedure record based on the HL7 V2 message and the only information about the primary provider which is available is his firstname and lastname. In the absence of the controlled practitioner directory, it's not possible to create an identified practitioner resource as there are changes of practitioners having same names. Below is an example of a contained resource.

{ "resourceType" : "Document",
  "extension" : { ... },
  "text" : { .. },
  "contained: [
    { "resourceType" : "Organization",
      "id" : "org1",
      .. whatever information is available ...
    }  ]
  "information: {
    ... other attributes ...
    "custodian" : {
      "reference" : "#org1"
    }
    ... other attributes ...
  }
}

FHIR Data types

Primitive Types

The primitive types are those per-defined types, those of which don't have sub-properties.

FHIR_PrimitiveDataType
 

Name

Schema Type

Description

boolean

xs:boolean

Values can be either true or false

integer

xs:int

A signed 32-bit integer

decimal

xs:decimal

A rational number. Decimals may not use exponents.

base64Binary

xs:base64Binary

A stream of bytes, base64 encoded

instant

xs:dateTime

An instant in time

string

xs:string

A sequence of Unicode characters. Not exceeding 1MB size.

uri

xs:anyURI

A Uniform Resource Identifier Reference. It can be either absolute or relative. May have an optional fragment identifier.

date

union of xs:date, xs:gYearMonth, xs:gYear

A date, or partial date (just year or year + month) as used in human communication. There is no time zone. Dates SHALL be valid dates.

dateTime

union of xs:dateTime, xs:date, xs:gYearMonth, xs:gYear

A date, date-time or partial date (just year or year + month) as used in human communication. If hours and minutes are specified, a time zone SHALL be populated. Seconds may be provided but may also be ignored. Dates SHALL be valid dates.

Complex Data Types 

The complex data types which are derived from an element type with the name as the element name as the type name and has child element say if you are going to define the complex types as XML element. Else if you are defining them as JSON , then you will have to define as an object and the corresponding properties of the objects represents child elements in for complex XML data type.

FHIR_ComplexDataTypes

Here's an example of a complex data type named Quantity defined with child elements as value, units, system and code.

Complex_DataType_Quantity


Conformance in FHIR


The conformance in an FHIR specifies the resource type interactions that are supported by the FHIR Server for a specific resource. 

The FHIR API defines the resources as a set of operation also know as interactions on the resources were individual resources are managed by their type. The FHIR server must always define the conformance for the resources by specifying the list of possible operations. 

The following are the list of operations at various levels.

Instance Level Interactions    

read    - Specifies the current state of the resource
vread    - Specifies the state of a specific version of the resource
update    - The update operation is used to update an existing resource by its id (or create it if it is new)
delete    - The delete operation is used to delete the resource
history    - The history operation is used to retrieve the update history of a particular resource

Type Level Interactions

create    - The create operation is used to create a new resource with a server assigned id
search    - The search operation is used to search the resource type based on some filter criteria
history     - The history operation is used to retrieve the update history for a particular resource type
validate – The validate operation is used to check whether the content would be acceptable as an update

Whole System Interactions

conformance – Used to get a conformance statement for the system
transaction – The transaction operation is used for update, create or delete a set of resources as a single transaction
history – The history operations is used to retrieve the update history for all resources
search – The search operation is used to search across all resource types based on some filter criteria

In addition to the above listed basic operations, there is a Mailbox and Document endpoints you can use them to specify conformance for a specific resource. 

Using the code


Let us build FHIR client and server application to understand the real implementation. We will be making use of Stefan Heesch's MSDN sample code , do little refactoring and enhance the existing code.

Here's the snapshot of FHIR Client.

FHIR_PatientGenerator

Below is the code snippet to insert or update FHIR Resource.

1. Create a new FHIR Client with the uri (That's the FHIR server address)

2. Create a new Patient instance and set all the required properties. If we are going to update a resource, we need to create a ResourceIdentity instance and set the patient Id with the ResourceIdentity Id.

3. In the end, you will notice; we are using FHIR client instance and make a call to create or update with the resource which will either create a new resource or update existing one.

private void InsertOrUpdatePatient(bool insert = true)
{
            // Call into the model an pass the patient data
            ChangeBusyState(true);

            try
            {
                var uri = new Uri(_url);
                var client = new FhirClient(uri);

                var p = new Patient();
                p.Active = this.Active;

                if (!insert && _entry != null)
                {
                    var i = new ResourceIdentity(_entry.Id);
                    p.Id = i.Id;
                }

                String dob = BirthDate.Value.ToString("s");

                p.BirthDate = dob;
                p.Gender = new CodeableConcept("http://dummy.org/gender", this.Gender, this.Gender);
                p.MaritalStatus = new CodeableConcept("http://dummy.org/maritalstatus", this.MaritalState, this.MaritalState);
                
                var name = new HumanName();
                name.WithGiven(GivenName);
                name.AndFamily(Name);
                name.Text = GivenName + " " + Name;

                p.Name = new List<HumanName>();
                p.Name.Add(name);

                p.Photo = new List<Attachment>();
                var photo = new Attachment();
                photo.Title = "Potrait - " + GivenName + " " + Name;
                photo.ContentType = "image/jpeg";
                 
                if (SendImage && !string.IsNullOrEmpty(_photoFullPath))
                {
                    using (FileStream fileStream = File.OpenRead(_photoFullPath))
                    {
                        int len = (int)fileStream.Length;
                        photo.Size = len;
                        photo.Data = new byte[fileStream.Length];
                        fileStream.Read(photo.Data, 0, len);
                    }
                    p.Photo.Add(photo);
                }
                else
                {
                    if (insert == false && Photo != null)
                    {
                        byte[] data;
                        JpegBitmapEncoder encoder = new JpegBitmapEncoder();
                        encoder.Frames.Add(BitmapFrame.Create(Photo));
                        using (MemoryStream ms = new MemoryStream())
                        {
                            encoder.Save(ms);
                            data = ms.ToArray();
                        }
                        photo.Size = data.Length;
                        photo.Data = data;
                        p.Photo.Add(photo);
                    }
                }

                p.Telecom = new List<Contact>(3);

                p.Telecom.Add(new Contact()
                {
                    Value = Mobile,
                    System = Contact.ContactSystem.Phone,
                    Use = Contact.ContactUse.Mobile
                });

                p.Telecom.Add(new Contact()
                {
                    Value = Phone,
                    System = Contact.ContactSystem.Phone,
                    Use = Contact.ContactUse.Home
                });

                p.Telecom.Add(new Contact()
                {
                    Value = Email,
                    System = Contact.ContactSystem.Email,
                    Use = Contact.ContactUse.Home
                });

                p.Active = Active;
                p.MultipleBirth = new FhirBoolean(MultipleBirth);
                p.Deceased = new FhirBoolean(Deceased);

                p.Extension = new List<Extension>(1);
                p.Extension.Add(new Extension(new Uri("http://www.englishclub.com/vocabulary/world-countries-nationality.htm"), new FhirString(Nationality)));

                p.Address = new List<Address>(1);
                var a = new Address();
                a.Zip = Zip;
                a.City = City;
                a.State = State;
                a.Country = Country;
                var lines = new List<String>();
                if (!String.IsNullOrEmpty(Address1)) lines.Add(Address1);
                if (!String.IsNullOrEmpty(Address2)) lines.Add(Address2);
                a.Line = lines;
                p.Address.Add(a);
                
                if (insert)
                {
                    _entry = client.Create(p);
                }
                else
                {
                    _entry.Resource = p;
                    _entry = client.Update(_entry, true);
                }

                var identity = new ResourceIdentity(_entry.Id);
                PatientId = identity.Id;

                Status = "Created new patient: " + PatientId;
                Debug.WriteLine(Status);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.StackTrace);
                Status = e.Message;

            }
            finally
            {
                ChangeBusyState(false);
            }
}

Now let us see how to read or get existing resource from FHIR Server.  Here's the code snippet which does that.

1. Create a new instance of FHIR Client with the uri as the FHIR server address.
2. Using FHIR Client instance Read method, we will be reading the Resource. You will notice the resource location to read is specified as below.

String.Format("{0}/Patient/{1}", _url, id);

 

private void ExecuteReadData(object obj)
{
      String id = PatientId;

      NewPatient();
      PatientId = "UNKNOWN";

      var uri = new Uri(_url);
      var client = new FhirClient(uri);

      var location = String.Format("{0}/Patient/{1}", _url, id);

      ResourceEntry entry = null;
      try
      {
           entry = client.Read(new Uri(location));
           if (entry == null) return;

           _entry = (ResourceEntry<Patient>)entry;

           var identity = new ResourceIdentity(_entry.Id);
           PatientId = identity.Id;

           var patient = _entry.Resource;

           Active = patient.Active ?? true;
           var d = patient.Deceased as Hl7.Fhir.Model.FhirBoolean;
           if (d != null)
           {
               Deceased = d.Value ?? false;
           }
           else
           {
              Deceased = false;
           }

           Name = patient.Name.First().Family.First();
           GivenName = patient.Name.First().Given.First();

           if (patient.BirthDate != null) BirthDate = DateTime.Parse(patient.BirthDate);

           var m = patient.MultipleBirth as FhirBoolean;
           if (m != null)
           {
               MultipleBirth = m.Value ?? false;
           }
           else
           {
              MultipleBirth = false;
           }

           Gender = patient.Gender.Text;
           MaritalState = patient.MaritalStatus.Text;
           Nationality = patient.Extension[0].Value.ToString();

           Address1 = patient.Address[0].LineElement[0].Value;
           Address2 = patient.Address[0].LineElement[1].Value;
           Zip = patient.Address[0].Zip.ToString();
           City = patient.Address[0].City.ToString();
           State = patient.Address[0].State.ToString();
           Country = patient.Address[0].Country.ToString();

           Mobile = patient.Telecom[0].Value.ToString();
           Phone = patient.Telecom[1].Value.ToString();
           Email = patient.Telecom[2].Value.ToString();

           if (patient.Photo != null)
               Photo = ConvertBytesToImage(patient.Photo[0].Data);
           else
               Photo = null;
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
}

Below are some of the helper method used to set default image or convert bytes to Image.

 private BitmapImage ConvertBytesToImage(byte[] data)
 {
      var bitmap = new BitmapImage();

      using (MemoryStream memoryStream = new MemoryStream(data))
      {
          bitmap.BeginInit();
          bitmap.StreamSource = memoryStream;
          bitmap.CacheOption = BitmapCacheOption.OnLoad;
          bitmap.EndInit();
      }

      return bitmap;
 }
        
 private BitmapImage SetDefaultImage(string fileName)
 {
      byte[] buffer = File.ReadAllBytes(fileName);
      var bitmap = new BitmapImage();

      using (MemoryStream memoryStream = new MemoryStream(buffer))
      {
           bitmap.BeginInit();
           bitmap.StreamSource = memoryStream;
           bitmap.CacheOption = BitmapCacheOption.OnLoad;
           bitmap.EndInit();
     }

     return bitmap;
 }

FHIR Server Implementation

Class Diagram

FHIRServer_ClassDiagram

Let us get started with building DataAccess layer. We are making use of Repository Pattern. 

public interface IRepository
{
    void CreatePatient(Patient patient);
    void UpdatePatient(Patient patient);
    bool DeletePatient(int patid);

    Patient ReadPatient(int id);

    List<Patient> SearchByName(string name);

    Patient ReadPatientHistory(int patid, int version);
    List<Patient> GetPatientHistory(int patid);
    List<Patient> GetPatientHistory(Patient patient);
}

public class Repository : IRepository
{
        const string CREATE = "CREATE";
        const string UPDATE = "UPDATE";
        const string DELETE = "DELETE";

        public void CreatePatient(Patient patient)
        {
            using (var ctx = new ResourceContext() )
            {
                var pat = new Patient(patient);
                patient.Action = CREATE;
                patient.Version = 1;

                ctx.Patients.Add(patient);
                ctx.SaveChanges();

                patient.PatientId = patient.RecordNo;
                ctx.SaveChanges();
            }
        }

        public bool DeletePatient(int id)
        {
            using (var ctx = new ResourceContext())
            {
                var history = ctx.Patients.Where(p => p.PatientId == id).ToList();
                if (history.Count == 0)
                {
                    return false;
                }

                try
                {
                    var patient = history.Single(p => p.Version == history.Max(h => h.Version));

                    var tmp = new Patient(patient);
                    tmp.Version = tmp.Version + 1;
                    tmp.IsDeleted = true;
                    tmp.Action = DELETE;

                    ctx.Patients.Add(tmp);
                    ctx.SaveChanges();
                }
                catch (Exception e)
                {
                    Debug.WriteLine(e.Message);
                    throw;
                }
            }
            return true;
        }
        
        public Patient ReadPatient(int id)
        {
            using (var ctx = new ResourceContext())
            {
                var history = ctx.Patients.Where( p => p.PatientId == id).ToList();
                if (history.Count == 0) return null;
                
                var patient = history.Single(p => p.Version == history.Max(h=>h.Version));
                if (patient.IsDeleted)
                {
                    return null;
                }
               return patient;
            }
        }

        public Patient ReadPatientHistory(int patid, int version)
        {
            using (var ctx = new ResourceContext())
            {
                var qry = from p in ctx.Patients
                    where p.PatientId == patid && p.Version == version
                    select p;

                return qry.SingleOrDefault();
            }
        }

        public List<Patient> GetPatientHistory(int patid)
        {
            using (var ctx = new ResourceContext())
            {
                return ctx.Patients.Where(h => h.PatientId == patid).ToList();
            }
        }

        public List<Patient> GetPatientHistory(Patient patient)
        {
            return GetPatientHistory(patient.PatientId);
        }

        public void UpdatePatient(Patient patient)
        {
            if (patient.PatientId == 0)
            {
                throw new ArgumentException("Cant find patient id for update");
            }

            using (var ctx = new ResourceContext())
            {
                int version = ctx.Patients.Where(p => p.PatientId == patient.PatientId).Max(h => h.Version);
                if (version == 0)
                {
                    throw new Exception("Patient not found");
                }
                
                var tmp = new Patient(patient);
                tmp.Version = version + 1;
                tmp.Action = UPDATE;
                ctx.Patients.Add(tmp);
                ctx.SaveChanges();
            }
        }

        public List<Patient> SearchByName(string name)
        {
            using (var ctx = new ResourceContext())
            {
                var pattern = name.ToLower();

                var qry = from p in ctx.Patients
                    where p.FirstName.ToLower().Contains(pattern) ||
                          p.LastName.ToLower().Contains(pattern)
                    select p;
                
                Debug.WriteLine("{0}", qry.ToString());

                return qry.ToList();
            }
        }
}

Here's the Patient data access context and the configuration for the model builder.

 public class ResourceContext : DbContext
 {
        public DbSet<Patient> Patients { set; get; }
     
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new PatientConfiguration());
        }
 }

 public class PatientConfiguration : EntityTypeConfiguration<Patient>
 { 
        public PatientConfiguration()
        {
            // Use Table "Patient"
            ToTable("Patient");

            // Primary key
            HasKey(p => p.RecordNo);
            
            // Do not allow to change the "RecordNo" but retrieve values only from database
            Property(p => p.RecordNo).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

            Property(p => p.PatientId)
                .HasColumnAnnotation("Index",
                new IndexAnnotation(new IndexAttribute("PatientIndex")
                {
                    IsUnique = true,
                    Order = 1
                }));

            Property(p => p.Version)
                .HasColumnAnnotation("Index",
                new IndexAnnotation(new IndexAttribute("PatientIndex")
                {
                    IsUnique = true,
                    Order = 2
                }));
        }
 }

We will be building a FHIR server by implementing resource handler pattern. Every resource handler in our FHIR server will be implementing IResourceHandler interface. 

public interface IResourceHandler
{
     String Type();
     Conformance.ConformanceRestResourceComponent Metadata();

     String Read(string patientId);
     String VRead(string patientId, string version);
     String List();

     HttpStatusCode Create(string format, string data);
     HttpStatusCode Update(string patientId, string format, string data);
     HttpStatusCode Delete(String patientId);
}

Here's the implementation of Patient resource handler. You will notice the CRUD operation implementation with the conformance stating the list of possible operation on the patient resource.

class PatientResourceHandler : IResourceHandler
{
        private const string PATIENT = "Patient";
        private readonly IRepository _repository;

        private String Serialize(Model.Patient patient)
        {
            var resource = PatientMapper.MapModel(patient);

            String payload = String.Empty;
            if (WebOperationContext.Current != null)
            {
                var response = WebOperationContext.Current.OutgoingResponse;
                
                response.LastModified = patient.Timestamp;
                string accept = WebOperationContext.Current.IncomingRequest.Accept;
                if (!String.IsNullOrEmpty(accept) && accept == "application/json")
                {
                    payload = FhirSerializer.SerializeResourceToJson(resource);
                    response.ContentType = "application/json+fhir";
                }
                else
                {
                    payload = FhirSerializer.SerializeResourceToXml(resource);
                    response.ContentType = "application/xml+fhir";
                }
            }
            return payload;
        }

        private Resource Parse(String format, String data)
        {
            Resource resource = null;
            if (format == "json")
            {
                resource = FhirParser.ParseResourceFromJson(data);
            }
            else if (format == "xml")
            {
                resource = FhirParser.ParseResourceFromXml(data);
            }

            if (resource == null)
            {
                throw new Exception(String.Format("HTTP content is invalid - does not correspond to content type {0}", format));
            }
            return resource;
        }
        

        public string Type()
        {
            return PATIENT;
        }

        public HttpStatusCode Create(string format, string data)
        {
            var resource = Parse(format, data);
            var patient = PatientMapper.MapResource(resource);

            // Create a new "Patient" resource in the repository
            _repository.CreatePatient( patient );

            var response = WebOperationContext.Current.OutgoingResponse;

            var uri = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri;
            string fhirbase = uri.AbsoluteUri;
            string host = uri.Host;

            response.LastModified = patient.Timestamp;
            response.StatusCode = HttpStatusCode.Created;
            response.Headers.Add( "Host", host);

            response.Location = String.Format("{0}/Patient/{1}/_history/{2}", fhirbase, patient.PatientId, patient.Version);
            return HttpStatusCode.OK;
        }

        public HttpStatusCode Update(string patientId, String format, String data)
        {
            int id = Int32.Parse(patientId);            
            
            var resource = Parse(format, data);
            var patient = PatientMapper.MapResource(resource);
            patient.PatientId = id;

            // Upate "Patient" resource in the repository
            _repository.UpdatePatient(patient);

            var response = WebOperationContext.Current.OutgoingResponse;

            var uri = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri;
            string fhirbase = uri.AbsoluteUri;
            string host = uri.Host;

            response.LastModified = patient.Timestamp;
            response.StatusCode = HttpStatusCode.Created;
            response.Headers.Add("Host", host);

            response.Location = String.Format("{0}/Patient/{1}/_history/{2}", fhirbase, patient.PatientId, patient.Version);
            return HttpStatusCode.OK;
        }

        public string Read(string patientId)
        {
            int id = Int32.Parse(patientId);

            // Get "Patient" resource by it's id from the repository
            var patient = _repository.ReadPatient(id);
            if (patient == null)
            {
                return String.Empty;
            }

            // Create response string
            return Serialize(patient);
        }

        public HttpStatusCode Delete(string patientId)
        {
            // We currently do not report any conflict that might occur during the deletion
            // Since at this point in time only the Patient resource is implemented, this
            // this is no issue - there can b no conflicts ;-)
            
            int id = Int32.Parse(patientId);
            bool result = _repository.DeletePatient(id);
            if (result == false) return HttpStatusCode.NotFound;
            return HttpStatusCode.NoContent;
        }

        public string List()
        {
            throw new NotImplementedException();
        }

        public string VRead(string patientId, string version)
        {
            int id = Int32.Parse(patientId);
            int vid = Int32.Parse(version);

            // Get historic "Patient" resource by it's id from the repository
            var history = _repository.ReadPatientHistory(id, vid);
            if (history == null)
            {
                return String.Empty;
            }

            // Create response string
            var fPatient = Serialize(history);
            return fPatient;
        }

        public PatientResourceHandler(IRepository repository)
        {
            _repository = repository;
        }

        public Conformance.ConformanceRestResourceComponent Metadata()
        {
            var patientConformance = new Conformance.ConformanceRestResourceComponent
            {
                Type = PATIENT,
                ReadHistory = false,
                Operation = new List<Conformance.ConformanceRestResourceOperationComponent>
                {
                    new Conformance.ConformanceRestResourceOperationComponent
                    {
                        Code = Conformance.RestfulOperationType.Create
                    },
                    new Conformance.ConformanceRestResourceOperationComponent
                    {
                        Code = Conformance.RestfulOperationType.Read
                    },
                    new Conformance.ConformanceRestResourceOperationComponent
                    {
                        Code = Conformance.RestfulOperationType.Update
                    },
                    new Conformance.ConformanceRestResourceOperationComponent
                    {
                        Code = Conformance.RestfulOperationType.Delete
                    },
                    new Conformance.ConformanceRestResourceOperationComponent
                    {
                        Code = Conformance.RestfulOperationType.SearchType
                    }
                }
            };

            return patientConformance;
        }
}

The next thing we will be doing is creating a Registry class which handles adding handlers , getting a handler based on the specified type. Also has an implementation to return conformance for the registered handlers. 

Here's the code snippet.

public class Registry
{
        private readonly Dictionary<string, IResourceHandler> _servants;
        
        public Registry()
        {
            _servants = new Dictionary<string, IResourceHandler>();
        }

        public void AddHandler(IRepository repository, string type)
        {
            switch (type)
            {
                case "PatientResourceHandler":
                    if (!_servants.ContainsKey(type))
                    {
                        var patientHandler = new PatientResourceHandler(repository);
                        _servants.Add(patientHandler.Type(), patientHandler);
                    }
                    break;
            }
        }

        public IResourceHandler GetHandler(string type)
        {
            if (_servants.ContainsKey(type))
            {
                return _servants[type];
            }
            return null;
        }
        
        /// <summary>
        /// Gets the conformance reflecting the capabilities of all registred servants.
        /// </summary>
        /// <returns>Conformance.</returns>
        public Conformance Metadata()
        {
            var conformance = new Conformance
            {
                Description = "This is an example implementation of an FHIR server - for demonstration purpose only.",
                Date = DateTime.UtcNow.ToString("s"),
                Experimental = true,
                AcceptUnknown = false,
                FhirVersion = "DSTU 1.1",
                Name = "FHIR Server for Patient Resources",
                Publisher = "MSDN Code Gallery, Stefan Heesch",
                Telecom =
                    new List<Contact>
                    {
                        new Contact {System = Contact.ContactSystem.Url, Value = "https://twitter.com/hb9tws"}
                    },
                Rest = new List<Conformance.ConformanceRestComponent>()
            };

            var rc = new Conformance.ConformanceRestComponent
            {
                Mode = Conformance.RestfulConformanceMode.Server,
                Resource = new List<Conformance.ConformanceRestResourceComponent>()
            };

            foreach (var servant in _servants.Values)
            {
                rc.Resource.Add( servant.Metadata() );
            }
            conformance.Rest.Add(rc);

            return conformance;
        }
}

Let us now build a Rest Service. Here's the interface that we are making use of.

[ServiceContract]
public interface IRestService
{
        [OperationContract]
        [WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.WrappedRequest)]
        void AddRegistryHandler(Model.IRepository repository, string type);

        [OperationContract]
        [WebGet(UriTemplate = "/{type}/{id}")]
        Stream Read(string type, string id);

        [OperationContract]
        [WebGet(UriTemplate = "/{type}/{id}/_history/{vid}")]
        Stream Vread(string type, string id, string vid);

        [OperationContract]
        [WebGet(UriTemplate = "/metadata")]
        Stream Conformance();

        [OperationContract]
        [WebInvoke(Method = "PUT", UriTemplate = "/{type}/{id}?_format={format}", BodyStyle = WebMessageBodyStyle.Bare)]
        Stream Update(string type, string id, string format, Stream data);

        [OperationContract]
        [WebInvoke(Method = "POST", UriTemplate = "/{type}?_format={format}", BodyStyle = WebMessageBodyStyle.Bare)]
        Stream Create(string type, string format, Stream data);
 
        [OperationContract]
        [WebInvoke(Method = "POST", UriTemplate = "/")]
        Stream Transaction();

        [OperationContract]
        [WebInvoke(Method = "DELETE", UriTemplate = "/{type}/{id}")]
        Stream Delete(string type, string id);

        [OperationContract]
        [WebGet(UriTemplate = "/{type}?")]
        Stream Search(string type);

        [OperationContract]
        [WebGet(UriTemplate = "/")]
        Stream SearchAll();

        [OperationContract]
        [WebInvoke(Method = "POST", UriTemplate = "/{type}/_validate?_format={format}", BodyStyle = WebMessageBodyStyle.Bare)]
        Stream ValidateType(string type, string format, Stream data);

        [OperationContract]
        [WebInvoke(Method = "POST", UriTemplate = "/{type}/_validate({id}?_format={format}", BodyStyle = WebMessageBodyStyle.Bare)]
        Stream Validate(string type, string id, string format, Stream data);

        [OperationContract]
        [WebGet(UriTemplate = "/{type}/{id}/_history")]
        Stream History(string type, string id);

        [OperationContract]
        [WebGet(UriTemplate = "/{type}/_history")]
        Stream HistoryType(string type);

        [OperationContract]
        [WebGet(UriTemplate = "/_history")]
        Stream HistoryAll();
}

We are building a Rest service by implementing the above interface. Notice that the history , transaction and searchall is not being implemented in this sample.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class RestService : IRestService
{
        private static Registry _registry;

        public RestService()
        {
            _registry = new Registry();
        }

        public void AddRegistryHandler(IRepository repository, string type)
        {
            _registry.AddHandler(repository, type);
        }
        
        private Stream ErrorMessage(HttpStatusCode statusCode, string message)
        {
            if (WebOperationContext.Current != null)
            {
                WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain";
                WebOperationContext.Current.OutgoingResponse.StatusCode = statusCode;
            }
            return new MemoryStream(Encoding.UTF8.GetBytes(message));
        }

        private String GetContentType(string mime, string format = null )
        {
            string parameter = format;
            if (parameter == null) parameter = mime;

            string content = "xml";
            if (parameter == "xml" || parameter == "text/xml" || parameter == "application/xml" || 
                parameter == "application/xml+fhir")
            {
                content = "xml";
            }
            else if (parameter == "json" || parameter == "text/json" || parameter == "application/json" ||
                parameter == "application/json+fhir")
            {
                content = "json";
            }
            return content;
        }

        public Stream Conformance()
        {
            var conformance = _registry.Metadata();

            if (WebOperationContext.Current != null)
            {
                string accept = WebOperationContext.Current.IncomingRequest.Accept;
                string payload;
                if (!String.IsNullOrEmpty(accept) && accept == "application/json")
                {
                    payload = FhirSerializer.SerializeResourceToJson(conformance);
                }
                else
                {
                    payload = FhirSerializer.SerializeResourceToXml(conformance);
                }
                return new MemoryStream(Encoding.UTF8.GetBytes(payload));
            }
            return new MemoryStream();
        }

        public Stream Create(string type, string format, Stream content)
        {
            IResourceHandler handler = _registry.GetHandler(type);
            if (handler == null)
            {
                return ErrorMessage(HttpStatusCode.NotImplemented, String.Format("Create resource version of type {0} not implemented.", type));
            }

            try
            {
                var reader = new StreamReader(content);
                string data = reader.ReadToEnd();

                string contentType = GetContentType(WebOperationContext.Current.IncomingRequest.ContentType, format);

                WebOperationContext.Current.OutgoingResponse.StatusCode = handler.Create(contentType, data);
                return new MemoryStream(Encoding.UTF8.GetBytes(String.Empty));
            }
            catch (Exception e)
            {
                return ErrorMessage(HttpStatusCode.InternalServerError, e.Message);
            }
        }

        public Stream Update(string type, string id, string format, Stream content)
        {
            IResourceHandler handler = _registry.GetHandler(type);
            if (handler == null)
            {
                return ErrorMessage(HttpStatusCode.NotImplemented, String.Format("Create resource version of type {0} not implemented.", type));
            }

            try
            {
                var reader = new StreamReader(content);
                string data = reader.ReadToEnd();

                string contentType = GetContentType(WebOperationContext.Current.IncomingRequest.ContentType, format);

               WebOperationContext.Current.OutgoingResponse.StatusCode = handler.Update(id, contentType, data);
                return new MemoryStream(Encoding.UTF8.GetBytes(String.Empty));
            }
            catch (Exception e)
            {
                return ErrorMessage(HttpStatusCode.InternalServerError, e.Message);
            }
        }

        public Stream Read(string type, string id)
        {
            IResourceHandler handler = _registry.GetHandler(type);
            if (handler == null)
            {
                return ErrorMessage(HttpStatusCode.NotImplemented, String.Format("Get resource of type {0} not implemented.", type));
            }

            try
            {
                string payload = handler.Read(id);
                if (String.IsNullOrEmpty(payload))
                {
                    return ErrorMessage(HttpStatusCode.NotFound, String.Format("Patient resource {0} not found", id));
                }
                return new MemoryStream(Encoding.UTF8.GetBytes(payload));
            }
            catch (Exception e)
            {
                if (e.GetType() == typeof(NotImplementedException))
                {
                    return ErrorMessage(HttpStatusCode.NotImplemented, e.Message);
                }
                return ErrorMessage(HttpStatusCode.InternalServerError, e.Message);
            }
        }

        public Stream Vread(string type, string id, string vid)
        {
            IResourceHandler handler = _registry.GetHandler(type);
            if (handler == null)
            {
                return ErrorMessage(HttpStatusCode.NotImplemented, String.Format("Get resource version of type {0} not implemented.", type));
            }

            try
            {

                string payload = handler.VRead(id, vid);
                if (String.IsNullOrEmpty(payload))
                {
                    return ErrorMessage(HttpStatusCode.NotFound, String.Format("Patient resource {0} / version {1} not found", id, vid));
                }
                return new MemoryStream(Encoding.UTF8.GetBytes(payload));
            }
            catch (Exception e)
            {
                if (e.GetType() == typeof(NotImplementedException))
                {
                    return ErrorMessage(HttpStatusCode.NotImplemented, e.Message);
                }
                return ErrorMessage(HttpStatusCode.InternalServerError, e.Message);
            }
        }

        public Stream Delete(string type, string id)
        {
            IResourceHandler handler = _registry.GetHandler(type);
            if (handler == null)
            {
                return ErrorMessage(HttpStatusCode.NotImplemented, String.Format("Delete resource of type {0} not implemented.", type));
            }
            WebOperationContext.Current.OutgoingResponse.StatusCode = handler.Delete(id);
            return new MemoryStream(Encoding.UTF8.GetBytes(String.Empty));
        }

        public Stream ValidateType(string type, String format, Stream data)
        {
            IResourceHandler handler = _registry.GetHandler(type);
            if (handler == null)
            {
                return ErrorMessage(HttpStatusCode.NotImplemented, String.Format("Validate resource of type {0} not implemented.", type));
            }
            throw new NotImplementedException();
        }

        public Stream Validate(string type, string id, String format, Stream data)
        {
            IResourceHandler handler = _registry.GetHandler(type);
            if (handler == null)
            {
                return ErrorMessage(HttpStatusCode.NotImplemented, String.Format("Validate resource of type {0} not implemented.", type));
            }
            throw new NotImplementedException();
        }

        public Stream History(string type, string id)
        {
            IResourceHandler handler = _registry.GetHandler(type);
            if (handler == null)
            {
                return ErrorMessage(HttpStatusCode.NotImplemented, String.Format("Validate resource of type {0} not implemented.", type));
            }
            throw new NotImplementedException();
        }

        public Stream HistoryType(string type)
        {
            IResourceHandler handler = _registry.GetHandler(type);
            if (handler == null)
            {
                return ErrorMessage(HttpStatusCode.NotImplemented, String.Format("Validate resource of type {0} not implemented.", type));
            }
            throw new NotImplementedException();
        }

        public Stream HistoryAll()
        {
            throw new NotImplementedException();
        }

        public Stream Search(string type)
        {
            IResourceHandler handler = _registry.GetHandler(type);
            if (handler == null)
            {
                return ErrorMessage(HttpStatusCode.NotImplemented, String.Format("Search for resource of type {0} not implemented.", type));
            }
            throw new NotImplementedException();
        }

        public Stream SearchAll()
        {
            throw new NotImplementedException();
        }

        public Stream Transaction()
        {
            throw new NotImplementedException();
        }
}

Next, we will be writing a code to host the  Rest service. Here's the code snippet which does that.

1. First we will be initializing the database. We are going to create a new database if not exist.
2. Setting up the url to host the Rest service.
3. Getting the FHIR Server name and assembly version and displaying the same in Console.
4. Create an instance of RestService, then add the Registry handlers with the repositories.
5. Create an instance of WebServiceHost with the Rest Service instance and the Uri.
6. Register Open Event of WebServiceHost instance.
7. Make a call to open method of WebServiceHost instance to listen for incoming requests. 

class Program
{

    static void Main(string[] args)
    {
            InitializeDatabase();
            
            string url = String.Format("http://localhost:8732/Design_Time_Addresses/fhir");

            var version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
            var name = Assembly.GetExecutingAssembly().GetName().Name;

            var header = String.Format("FHIR Server - {0} version {1}", name, version);
            var line = new string('-', header.Length);
            Console.WriteLine(header);
            Console.WriteLine(line);
            Console.WriteLine();
                    
            var service = new RestService();

            var patientRepository = new PatientRepository();
            service.AddRegistryHandler(patientRepository,typeof(PatientResourceHandler).ToString());

            var server = new WebServiceHost(service, new Uri(url));
            server.Opened += server_Opened;
            try
            {
                server.Open();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            Console.WriteLine();
            Console.WriteLine("Press ENTER to exit.");
            Console.ReadLine();
        }

        static void server_Opened(object sender, EventArgs e)
        {
            var server = (WebServiceHost) sender;
            Console.WriteLine("Listening on {0}", server.BaseAddresses[0].OriginalString);
        }

        static void InitializeDatabase()
        {
            Database.SetInitializer(new CreateDatabaseIfNotExists<ResourceContext>());
        }
    }
}

Here's what it looks like when you are running the service.

FHIRServer_ConsoleRunning

Resources

http://www.hl7.org/implement/standards/fhir/

https://github.com/ewoutkramer/fhir-net-api/tree/master/src

http://code.msdn.microsoft.com/windowsdesktop/FHIR-Server-Visual-Studio-2cf4f6ea

http://code.msdn.microsoft.com/windowsdesktop/Client-for-HL7-FHIR-server-0709be0b

Points of Interest

There's always something new coming up in Healthcare sector. The FHIR is really exciting and I believe it has a bright future ahead. Building a fast interoperable solution wan't really possible until with the introduction to FHIR. The previous implementations were not bad but they required a strong domain knowledge and it wasn't that easy enough to implement. 

History

Version 1.0 - Initial version of article with FHIR Client and Server Implementation - 06/01/2014.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ranjan.D
Web Developer
United States United States
Profile
 
Around 9 years of professional software development experience in analysis, design, development, testing and implementation of enterprise web applications for healthcare domain with good exposure to object-oriented design, software architectures, design patterns, test-driven development and agile practices.
 
In Brief
 
Analyse and create High Level , Detailed Design documents.
Use UML Modelling and create Use Cases , Class Diagram , Component Model , Deployment Diagram, Sequence Diagram in HLD.
 
Area of Working : Dedicated to Microsoft .NET Technologies
Experience with : C# , J2EE , J2ME, Windows Phone 8, Windows Store App
Proficient in: C# , XML , XHTML, XML, HTML5, Javascript, Jquery, CSS, SQL, LINQ, EF
 
Software Development
 
Database: Microsoft SQL Server, FoxPro
Development Frameworks: Microsoft .NET 1.1, 2.0, 3.5, 4.5
UI: Windows Forms, Windows Presentation Foundation, ASP.NET Web Forms and ASP.NET MVC3, MVC4
Coding: WinForm , Web Development, Windows Phone, WinRT Programming, WCF, WebAPI
 
Healthcare Domain Experience
 
CCD, CCR, QRDA, HIE, HL7 V3, Healthcare Interoperability
 
Others:
 
TTD, BDD
 
Education
 
B.E (Computer Science)
 
CodeProject Contest So Far:
 
1. Windows Azure Developer Contest - HealthReunion - A Windows Azure based healthcare product , link - http://www.codeproject.com/Articles/582535/HealthReunion-A-Windows-Azure-based-healthcare-pro
 
2. DnB Developer Contest - DNB Business Lookup and Analytics , link - http://www.codeproject.com/Articles/618344/DNB-Business-Lookup-and-Analytics
 
3. Intel Ultrabook Contest - Journey from development, code signing to publishing my App to Intel AppUp , link - http://www.codeproject.com/Articles/517482/Journey-from-development-code-signing-to-publishin
 
4. Intel App Innovation Contest 2013 - eHealthCare - http://www.codeproject.com/Articles/635815/eHealthCare
 
5. Grand Prize Winner of CodeProject HTML5 &CSS3 Article Content 2014

Comments and Discussions

 
QuestionExcellent article Pinmembereng_cs31-Jul-14 6:19 
GeneralFHIR Article PinmemberStefan Heesch2-Jun-14 11:05 
GeneralRe: FHIR Article PinmvpRanjan.D2-Jun-14 11:39 
QuestionMy Vote of 5 PinpremiumAbhishek Nandy1-Jun-14 21:17 
AnswerRe: My Vote of 5 PinmvpRanjan.D2-Jun-14 0:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140827.1 | Last Updated 1 Jun 2014
Article Copyright 2014 by Ranjan.D
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid